diff options
| author | 2018-04-25 18:07:30 -0400 | |
|---|---|---|
| committer | 2018-04-25 18:07:30 -0400 | |
| commit | 9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch) | |
| tree | ce8840148d8445055ba9e4f12263b2208f234c16 /src/lib/base | |
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/base')
36 files changed, 6123 insertions, 0 deletions
diff --git a/src/lib/base/CMakeLists.txt b/src/lib/base/CMakeLists.txt new file mode 100644 index 0000000..66ba5a6 --- /dev/null +++ b/src/lib/base/CMakeLists.txt @@ -0,0 +1,28 @@ +# barrier -- mouse and keyboard sharing utility +# Copyright (C) 2012-2016 Symless Ltd. +# Copyright (C) 2009 Nick Bolton +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file LICENSE that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +file(GLOB headers "*.h") +file(GLOB sources "*.cpp") + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +add_library(base STATIC ${sources}) + +if (UNIX) + target_link_libraries(base common) +endif() diff --git a/src/lib/base/ELevel.h b/src/lib/base/ELevel.h new file mode 100644 index 0000000..ec0f94f --- /dev/null +++ b/src/lib/base/ELevel.h @@ -0,0 +1,38 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2011 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +//! Log levels +/*! +The logging priority levels in order of highest to lowest priority. +*/ +enum ELevel { + kPRINT = -1, //!< For print only (no file or time) + kFATAL, //!< For fatal errors + kERROR, //!< For serious errors + kWARNING, //!< For minor errors and warnings + kNOTE, //!< For messages about notable events + kINFO, //!< For informational messages + kDEBUG, //!< For important debugging messages + kDEBUG1, //!< For verbosity +1 debugging messages + kDEBUG2, //!< For verbosity +2 debugging messages + kDEBUG3, //!< For verbosity +3 debugging messages + kDEBUG4, //!< For verbosity +4 debugging messages + kDEBUG5 //!< For verbosity +5 debugging messages +}; diff --git a/src/lib/base/Event.cpp b/src/lib/base/Event.cpp new file mode 100644 index 0000000..f2c1a12 --- /dev/null +++ b/src/lib/base/Event.cpp @@ -0,0 +1,100 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/Event.h" +#include "base/EventQueue.h" + +// +// Event +// + +Event::Event() : + m_type(kUnknown), + m_target(NULL), + m_data(NULL), + m_flags(0), + m_dataObject(nullptr) +{ + // do nothing +} + +Event::Event(Type type, void* target, void* data, Flags flags) : + m_type(type), + m_target(target), + m_data(data), + m_flags(flags), + m_dataObject(nullptr) +{ + // do nothing +} + +Event::Type +Event::getType() const +{ + return m_type; +} + +void* +Event::getTarget() const +{ + return m_target; +} + +void* +Event::getData() const +{ + return m_data; +} + +EventData* +Event::getDataObject() const +{ + return m_dataObject; +} + +Event::Flags +Event::getFlags() const +{ + return m_flags; +} + +void +Event::deleteData(const Event& event) +{ + switch (event.getType()) { + case kUnknown: + case kQuit: + case kSystem: + case kTimer: + break; + + default: + if ((event.getFlags() & kDontFreeData) == 0) { + free(event.getData()); + delete event.getDataObject(); + } + break; + } +} + +void +Event::setDataObject(EventData* dataObject) +{ + assert(m_dataObject == nullptr); + m_dataObject = dataObject; +} diff --git a/src/lib/base/Event.h b/src/lib/base/Event.h new file mode 100644 index 0000000..2741813 --- /dev/null +++ b/src/lib/base/Event.h @@ -0,0 +1,126 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/basic_types.h" +#include "common/stdmap.h" + +class EventData { +public: + EventData() { } + virtual ~EventData() { } +}; + +//! Event +/*! +A \c Event holds an event type and a pointer to event data. +*/ +class Event { +public: + typedef UInt32 Type; + enum { + kUnknown, //!< The event type is unknown + kQuit, //!< The quit event + kSystem, //!< The data points to a system event type + kTimer, //!< The data points to timer info + kLast //!< Must be last + }; + + typedef UInt32 Flags; + enum { + kNone = 0x00, //!< No flags + kDeliverImmediately = 0x01, //!< Dispatch and free event immediately + kDontFreeData = 0x02 //!< Don't free data in deleteData + }; + + Event(); + + //! Create \c Event with data (POD) + /*! + The \p data must be POD (plain old data) allocated by malloc(), + which means it cannot have a constructor, destructor or be + composed of any types that do. For non-POD (normal C++ objects + use \c setDataObject(). + \p target is the intended recipient of the event. + \p flags is any combination of \c Flags. + */ + Event(Type type, void* target = NULL, void* data = NULL, + Flags flags = kNone); + + //! @name manipulators + //@{ + + //! Release event data + /*! + Deletes event data for the given event (using free()). + */ + static void deleteData(const Event&); + + //! Set data (non-POD) + /*! + Set non-POD (non plain old data), where delete is called when the event + is deleted, and the destructor is called. + */ + void setDataObject(EventData* dataObject); + + //@} + //! @name accessors + //@{ + + //! Get event type + /*! + Returns the event type. + */ + Type getType() const; + + //! Get the event target + /*! + Returns the event target. + */ + void* getTarget() const; + + //! Get the event data (POD). + /*! + Returns the event data (POD). + */ + void* getData() const; + + //! Get the event data (non-POD) + /*! + Returns the event data (non-POD). The difference between this and + \c getData() is that when delete is called on this data, so non-POD + (non plain old data) dtor is called. + */ + EventData* getDataObject() const; + + //! Get event flags + /*! + Returns the event flags. + */ + Flags getFlags() const; + + //@} + +private: + Type m_type; + void* m_target; + void* m_data; + Flags m_flags; + EventData* m_dataObject; +}; diff --git a/src/lib/base/EventQueue.cpp b/src/lib/base/EventQueue.cpp new file mode 100644 index 0000000..b17e35b --- /dev/null +++ b/src/lib/base/EventQueue.cpp @@ -0,0 +1,658 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/EventQueue.h" + +#include "mt/Mutex.h" +#include "mt/Lock.h" +#include "arch/Arch.h" +#include "base/SimpleEventQueueBuffer.h" +#include "base/Stopwatch.h" +#include "base/IEventJob.h" +#include "base/EventTypes.h" +#include "base/Log.h" +#include "base/XBase.h" +#include "../gui/src/ShutdownCh.h" + +EVENT_TYPE_ACCESSOR(Client) +EVENT_TYPE_ACCESSOR(IStream) +EVENT_TYPE_ACCESSOR(IpcClient) +EVENT_TYPE_ACCESSOR(IpcClientProxy) +EVENT_TYPE_ACCESSOR(IpcServer) +EVENT_TYPE_ACCESSOR(IpcServerProxy) +EVENT_TYPE_ACCESSOR(IDataSocket) +EVENT_TYPE_ACCESSOR(IListenSocket) +EVENT_TYPE_ACCESSOR(ISocket) +EVENT_TYPE_ACCESSOR(OSXScreen) +EVENT_TYPE_ACCESSOR(ClientListener) +EVENT_TYPE_ACCESSOR(ClientProxy) +EVENT_TYPE_ACCESSOR(ClientProxyUnknown) +EVENT_TYPE_ACCESSOR(Server) +EVENT_TYPE_ACCESSOR(ServerApp) +EVENT_TYPE_ACCESSOR(IKeyState) +EVENT_TYPE_ACCESSOR(IPrimaryScreen) +EVENT_TYPE_ACCESSOR(IScreen) +EVENT_TYPE_ACCESSOR(Clipboard) +EVENT_TYPE_ACCESSOR(File) + +// interrupt handler. this just adds a quit event to the queue. +static +void +interrupt(Arch::ESignal, void* data) +{ + EventQueue* events = static_cast<EventQueue*>(data); + events->addEvent(Event(Event::kQuit)); +} + + +// +// EventQueue +// + +EventQueue::EventQueue() : + m_systemTarget(0), + m_nextType(Event::kLast), + m_typesForClient(NULL), + m_typesForIStream(NULL), + m_typesForIpcClient(NULL), + m_typesForIpcClientProxy(NULL), + m_typesForIpcServer(NULL), + m_typesForIpcServerProxy(NULL), + m_typesForIDataSocket(NULL), + m_typesForIListenSocket(NULL), + m_typesForISocket(NULL), + m_typesForOSXScreen(NULL), + m_typesForClientListener(NULL), + m_typesForClientProxy(NULL), + m_typesForClientProxyUnknown(NULL), + m_typesForServer(NULL), + m_typesForServerApp(NULL), + m_typesForIKeyState(NULL), + m_typesForIPrimaryScreen(NULL), + m_typesForIScreen(NULL), + m_typesForClipboard(NULL), + m_typesForFile(NULL), + m_readyMutex(new Mutex), + m_readyCondVar(new CondVar<bool>(m_readyMutex, false)) +{ + m_mutex = ARCH->newMutex(); + ARCH->setSignalHandler(Arch::kINTERRUPT, &interrupt, this); + ARCH->setSignalHandler(Arch::kTERMINATE, &interrupt, this); + m_buffer = new SimpleEventQueueBuffer; +} + +EventQueue::~EventQueue() +{ + delete m_buffer; + delete m_readyCondVar; + delete m_readyMutex; + + ARCH->setSignalHandler(Arch::kINTERRUPT, NULL, NULL); + ARCH->setSignalHandler(Arch::kTERMINATE, NULL, NULL); + ARCH->closeMutex(m_mutex); +} + +void +EventQueue::loop() +{ + m_buffer->init(); + { + Lock lock(m_readyMutex); + *m_readyCondVar = true; + m_readyCondVar->signal(); + } + LOG((CLOG_DEBUG "event queue is ready")); + while (!m_pending.empty()) { + LOG((CLOG_DEBUG "add pending events to buffer")); + Event& event = m_pending.front(); + addEventToBuffer(event); + m_pending.pop(); + } + + Event event; + getEvent(event); + while (event.getType() != Event::kQuit) { + dispatchEvent(event); + Event::deleteData(event); + getEvent(event); + } +} + +Event::Type +EventQueue::registerTypeOnce(Event::Type& type, const char* name) +{ + ArchMutexLock lock(m_mutex); + if (type == Event::kUnknown) { + m_typeMap.insert(std::make_pair(m_nextType, name)); + m_nameMap.insert(std::make_pair(name, m_nextType)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + type = m_nextType++; + } + return type; +} + +const char* +EventQueue::getTypeName(Event::Type type) +{ + switch (type) { + case Event::kUnknown: + return "nil"; + + case Event::kQuit: + return "quit"; + + case Event::kSystem: + return "system"; + + case Event::kTimer: + return "timer"; + + default: + TypeMap::const_iterator i = m_typeMap.find(type); + if (i == m_typeMap.end()) { + return "<unknown>"; + } + else { + return i->second; + } + } +} + +void +EventQueue::adoptBuffer(IEventQueueBuffer* buffer) +{ + ArchMutexLock lock(m_mutex); + + LOG((CLOG_DEBUG "adopting new buffer")); + + if (m_events.size() != 0) { + // this can come as a nasty surprise to programmers expecting + // their events to be raised, only to have them deleted. + LOG((CLOG_DEBUG "discarding %d event(s)", m_events.size())); + } + + // discard old buffer and old events + delete m_buffer; + for (EventTable::iterator i = m_events.begin(); i != m_events.end(); ++i) { + Event::deleteData(i->second); + } + m_events.clear(); + m_oldEventIDs.clear(); + + // use new buffer + m_buffer = buffer; + if (m_buffer == NULL) { + m_buffer = new SimpleEventQueueBuffer; + } +} + +bool +EventQueue::parent_requests_shutdown() const +{ + char ch; + return m_parentStream.try_read_char(ch) && ch == ShutdownCh; +} + +bool +EventQueue::getEvent(Event& event, double timeout) +{ + Stopwatch timer(true); +retry: + // before handling any events make sure we don't need to shutdown + if (parent_requests_shutdown()) { + event = Event(Event::kQuit); + return false; + } + // if no events are waiting then handle timers and then wait + while (m_buffer->isEmpty()) { + // handle timers first + if (hasTimerExpired(event)) { + return true; + } + + // get time remaining in timeout + double timeLeft = timeout - timer.getTime(); + if (timeout >= 0.0 && timeLeft <= 0.0) { + return false; + } + + // get time until next timer expires. if there is a timer + // and it'll expire before the client's timeout then use + // that duration for our timeout instead. + double timerTimeout = getNextTimerTimeout(); + if (timeout < 0.0 || (timerTimeout >= 0.0 && timerTimeout < timeLeft)) { + timeLeft = timerTimeout; + } + + // wait for an event + m_buffer->waitForEvent(timeLeft); + } + + // get the event + UInt32 dataID; + IEventQueueBuffer::Type type = m_buffer->getEvent(event, dataID); + switch (type) { + case IEventQueueBuffer::kNone: + if (timeout < 0.0 || timeout <= timer.getTime()) { + // don't want to fail if client isn't expecting that + // so if getEvent() fails with an infinite timeout + // then just try getting another event. + goto retry; + } + return false; + + case IEventQueueBuffer::kSystem: + return true; + + case IEventQueueBuffer::kUser: + { + ArchMutexLock lock(m_mutex); + event = removeEvent(dataID); + return true; + } + + default: + assert(0 && "invalid event type"); + return false; + } +} + +bool +EventQueue::dispatchEvent(const Event& event) +{ + void* target = event.getTarget(); + IEventJob* job = getHandler(event.getType(), target); + if (job == NULL) { + job = getHandler(Event::kUnknown, target); + } + if (job != NULL) { + job->run(event); + return true; + } + return false; +} + +void +EventQueue::addEvent(const Event& event) +{ + // discard bogus event types + switch (event.getType()) { + case Event::kUnknown: + case Event::kSystem: + case Event::kTimer: + return; + + default: + break; + } + + if ((event.getFlags() & Event::kDeliverImmediately) != 0) { + dispatchEvent(event); + Event::deleteData(event); + } + else if (!(*m_readyCondVar)) { + m_pending.push(event); + } + else { + addEventToBuffer(event); + } +} + +void +EventQueue::addEventToBuffer(const Event& event) +{ + ArchMutexLock lock(m_mutex); + + // store the event's data locally + UInt32 eventID = saveEvent(event); + + // add it + if (!m_buffer->addEvent(eventID)) { + // failed to send event + removeEvent(eventID); + Event::deleteData(event); + } +} + +EventQueueTimer* +EventQueue::newTimer(double duration, void* target) +{ + assert(duration > 0.0); + + EventQueueTimer* timer = m_buffer->newTimer(duration, false); + if (target == NULL) { + target = timer; + } + ArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(Timer(timer, duration, + duration + m_time.getTime(), target, false)); + return timer; +} + +EventQueueTimer* +EventQueue::newOneShotTimer(double duration, void* target) +{ + assert(duration > 0.0); + + EventQueueTimer* timer = m_buffer->newTimer(duration, true); + if (target == NULL) { + target = timer; + } + ArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(Timer(timer, duration, + duration + m_time.getTime(), target, true)); + return timer; +} + +void +EventQueue::deleteTimer(EventQueueTimer* timer) +{ + ArchMutexLock lock(m_mutex); + for (TimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + if (index->getTimer() == timer) { + m_timerQueue.erase(index); + break; + } + } + Timers::iterator index = m_timers.find(timer); + if (index != m_timers.end()) { + m_timers.erase(index); + } + m_buffer->deleteTimer(timer); +} + +void +EventQueue::adoptHandler(Event::Type type, void* target, IEventJob* handler) +{ + ArchMutexLock lock(m_mutex); + IEventJob*& job = m_handlers[target][type]; + delete job; + job = handler; +} + +void +EventQueue::removeHandler(Event::Type type, void* target) +{ + IEventJob* handler = NULL; + { + ArchMutexLock lock(m_mutex); + HandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + TypeHandlerTable& typeHandlers = index->second; + TypeHandlerTable::iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + handler = index2->second; + typeHandlers.erase(index2); + } + } + } + delete handler; +} + +void +EventQueue::removeHandlers(void* target) +{ + std::vector<IEventJob*> handlers; + { + ArchMutexLock lock(m_mutex); + HandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + // copy to handlers array and clear table for target + TypeHandlerTable& typeHandlers = index->second; + for (TypeHandlerTable::iterator index2 = typeHandlers.begin(); + index2 != typeHandlers.end(); ++index2) { + handlers.push_back(index2->second); + } + typeHandlers.clear(); + } + } + + // delete handlers + for (std::vector<IEventJob*>::iterator index = handlers.begin(); + index != handlers.end(); ++index) { + delete *index; + } +} + +bool +EventQueue::isEmpty() const +{ + return (m_buffer->isEmpty() && getNextTimerTimeout() != 0.0); +} + +IEventJob* +EventQueue::getHandler(Event::Type type, void* target) const +{ + ArchMutexLock lock(m_mutex); + HandlerTable::const_iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + const TypeHandlerTable& typeHandlers = index->second; + TypeHandlerTable::const_iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + return index2->second; + } + } + return NULL; +} + +UInt32 +EventQueue::saveEvent(const Event& event) +{ + // choose id + UInt32 id; + if (!m_oldEventIDs.empty()) { + // reuse an id + id = m_oldEventIDs.back(); + m_oldEventIDs.pop_back(); + } + else { + // make a new id + id = static_cast<UInt32>(m_events.size()); + } + + // save data + m_events[id] = event; + return id; +} + +Event +EventQueue::removeEvent(UInt32 eventID) +{ + // look up id + EventTable::iterator index = m_events.find(eventID); + if (index == m_events.end()) { + return Event(); + } + + // get data + Event event = index->second; + m_events.erase(index); + + // save old id for reuse + m_oldEventIDs.push_back(eventID); + + return event; +} + +bool +EventQueue::hasTimerExpired(Event& event) +{ + // return true if there's a timer in the timer priority queue that + // has expired. if returning true then fill in event appropriately + // and reset and reinsert the timer. + if (m_timerQueue.empty()) { + return false; + } + + // get time elapsed since last check + const double time = m_time.getTime(); + m_time.reset(); + + // countdown elapsed time + for (TimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + (*index) -= time; + } + + // done if no timers are expired + if (m_timerQueue.top() > 0.0) { + return false; + } + + // remove timer from queue + Timer timer = m_timerQueue.top(); + m_timerQueue.pop(); + + // prepare event and reset the timer's clock + timer.fillEvent(m_timerEvent); + event = Event(Event::kTimer, timer.getTarget(), &m_timerEvent); + timer.reset(); + + // reinsert timer into queue if it's not a one-shot + if (!timer.isOneShot()) { + m_timerQueue.push(timer); + } + + return true; +} + +double +EventQueue::getNextTimerTimeout() const +{ + // return -1 if no timers, 0 if the top timer has expired, otherwise + // the time until the top timer in the timer priority queue will + // expire. + if (m_timerQueue.empty()) { + return -1.0; + } + if (m_timerQueue.top() <= 0.0) { + return 0.0; + } + return m_timerQueue.top(); +} + +Event::Type +EventQueue::getRegisteredType(const String& name) const +{ + NameMap::const_iterator found = m_nameMap.find(name); + if (found != m_nameMap.end()) + return found->second; + + return Event::kUnknown; +} + +void* +EventQueue::getSystemTarget() +{ + // any unique arbitrary pointer will do + return &m_systemTarget; +} + +void +EventQueue::waitForReady() const +{ + double timeout = ARCH->time() + 10; + Lock lock(m_readyMutex); + + while (!m_readyCondVar->wait()) { + if (ARCH->time() > timeout) { + throw std::runtime_error("event queue is not ready within 5 sec"); + } + } +} + +// +// EventQueue::Timer +// + +EventQueue::Timer::Timer(EventQueueTimer* timer, double timeout, + double initialTime, void* target, bool oneShot) : + m_timer(timer), + m_timeout(timeout), + m_target(target), + m_oneShot(oneShot), + m_time(initialTime) +{ + assert(m_timeout > 0.0); +} + +EventQueue::Timer::~Timer() +{ + // do nothing +} + +void +EventQueue::Timer::reset() +{ + m_time = m_timeout; +} + +EventQueue::Timer& +EventQueue::Timer::operator-=(double dt) +{ + m_time -= dt; + return *this; +} + +EventQueue::Timer::operator double() const +{ + return m_time; +} + +bool +EventQueue::Timer::isOneShot() const +{ + return m_oneShot; +} + +EventQueueTimer* +EventQueue::Timer::getTimer() const +{ + return m_timer; +} + +void* +EventQueue::Timer::getTarget() const +{ + return m_target; +} + +void +EventQueue::Timer::fillEvent(TimerEvent& event) const +{ + event.m_timer = m_timer; + event.m_count = 0; + if (m_time <= 0.0) { + event.m_count = static_cast<UInt32>((m_timeout - m_time) / m_timeout); + } +} + +bool +EventQueue::Timer::operator<(const Timer& t) const +{ + return m_time < t.m_time; +} diff --git a/src/lib/base/EventQueue.h b/src/lib/base/EventQueue.h new file mode 100644 index 0000000..97e7fba --- /dev/null +++ b/src/lib/base/EventQueue.h @@ -0,0 +1,200 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "mt/CondVar.h" +#include "arch/IArchMultithread.h" +#include "base/IEventQueue.h" +#include "base/Event.h" +#include "base/PriorityQueue.h" +#include "base/Stopwatch.h" +#include "common/stdmap.h" +#include "common/stdset.h" +#include "base/NonBlockingStream.h" + +#include <queue> + +class Mutex; + +//! Event queue +/*! +An event queue that implements the platform independent parts and +delegates the platform dependent parts to a subclass. +*/ +class EventQueue : public IEventQueue { +public: + EventQueue(); + virtual ~EventQueue(); + + // IEventQueue overrides + virtual void loop(); + virtual void adoptBuffer(IEventQueueBuffer*); + virtual bool getEvent(Event& event, double timeout = -1.0); + virtual bool dispatchEvent(const Event& event); + virtual void addEvent(const Event& event); + virtual EventQueueTimer* + newTimer(double duration, void* target); + virtual EventQueueTimer* + newOneShotTimer(double duration, void* target); + virtual void deleteTimer(EventQueueTimer*); + virtual void adoptHandler(Event::Type type, + void* target, IEventJob* handler); + virtual void removeHandler(Event::Type type, void* target); + virtual void removeHandlers(void* target); + virtual Event::Type + registerTypeOnce(Event::Type& type, const char* name); + virtual bool isEmpty() const; + virtual IEventJob* getHandler(Event::Type type, void* target) const; + virtual const char* getTypeName(Event::Type type); + virtual Event::Type + getRegisteredType(const String& name) const; + void* getSystemTarget(); + virtual void waitForReady() const; + +private: + UInt32 saveEvent(const Event& event); + Event removeEvent(UInt32 eventID); + bool hasTimerExpired(Event& event); + double getNextTimerTimeout() const; + void addEventToBuffer(const Event& event); + bool parent_requests_shutdown() const; + +private: + class Timer { + public: + Timer(EventQueueTimer*, double timeout, double initialTime, + void* target, bool oneShot); + ~Timer(); + + void reset(); + + Timer& operator-=(double); + + operator double() const; + + bool isOneShot() const; + EventQueueTimer* + getTimer() const; + void* getTarget() const; + void fillEvent(TimerEvent&) const; + + bool operator<(const Timer&) const; + + private: + EventQueueTimer* m_timer; + double m_timeout; + void* m_target; + bool m_oneShot; + double m_time; + }; + + typedef std::set<EventQueueTimer*> Timers; + typedef PriorityQueue<Timer> TimerQueue; + typedef std::map<UInt32, Event> EventTable; + typedef std::vector<UInt32> EventIDList; + typedef std::map<Event::Type, const char*> TypeMap; + typedef std::map<String, Event::Type> NameMap; + typedef std::map<Event::Type, IEventJob*> TypeHandlerTable; + typedef std::map<void*, TypeHandlerTable> HandlerTable; + + int m_systemTarget; + ArchMutex m_mutex; + + // registered events + Event::Type m_nextType; + TypeMap m_typeMap; + NameMap m_nameMap; + + // buffer of events + IEventQueueBuffer* m_buffer; + + // saved events + EventTable m_events; + EventIDList m_oldEventIDs; + + // timers + Stopwatch m_time; + Timers m_timers; + TimerQueue m_timerQueue; + TimerEvent m_timerEvent; + + // event handlers + HandlerTable m_handlers; + +public: + // + // Event type providers. + // + ClientEvents& forClient(); + IStreamEvents& forIStream(); + IpcClientEvents& forIpcClient(); + IpcClientProxyEvents& forIpcClientProxy(); + IpcServerEvents& forIpcServer(); + IpcServerProxyEvents& forIpcServerProxy(); + IDataSocketEvents& forIDataSocket(); + IListenSocketEvents& forIListenSocket(); + ISocketEvents& forISocket(); + OSXScreenEvents& forOSXScreen(); + ClientListenerEvents& forClientListener(); + ClientProxyEvents& forClientProxy(); + ClientProxyUnknownEvents& forClientProxyUnknown(); + ServerEvents& forServer(); + ServerAppEvents& forServerApp(); + IKeyStateEvents& forIKeyState(); + IPrimaryScreenEvents& forIPrimaryScreen(); + IScreenEvents& forIScreen(); + ClipboardEvents& forClipboard(); + FileEvents& forFile(); + +private: + ClientEvents* m_typesForClient; + IStreamEvents* m_typesForIStream; + IpcClientEvents* m_typesForIpcClient; + IpcClientProxyEvents* m_typesForIpcClientProxy; + IpcServerEvents* m_typesForIpcServer; + IpcServerProxyEvents* m_typesForIpcServerProxy; + IDataSocketEvents* m_typesForIDataSocket; + IListenSocketEvents* m_typesForIListenSocket; + ISocketEvents* m_typesForISocket; + OSXScreenEvents* m_typesForOSXScreen; + ClientListenerEvents* m_typesForClientListener; + ClientProxyEvents* m_typesForClientProxy; + ClientProxyUnknownEvents* m_typesForClientProxyUnknown; + ServerEvents* m_typesForServer; + ServerAppEvents* m_typesForServerApp; + IKeyStateEvents* m_typesForIKeyState; + IPrimaryScreenEvents* m_typesForIPrimaryScreen; + IScreenEvents* m_typesForIScreen; + ClipboardEvents* m_typesForClipboard; + FileEvents* m_typesForFile; + Mutex* m_readyMutex; + CondVar<bool>* m_readyCondVar; + std::queue<Event> m_pending; + NonBlockingStream m_parentStream; +}; + +#define EVENT_TYPE_ACCESSOR(type_) \ +type_##Events& \ +EventQueue::for##type_() { \ + if (m_typesFor##type_ == NULL) { \ + m_typesFor##type_ = new type_##Events(); \ + m_typesFor##type_->setEvents(dynamic_cast<IEventQueue*>(this)); \ + } \ + return *m_typesFor##type_; \ +} diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp new file mode 100644 index 0000000..9a3e46c --- /dev/null +++ b/src/lib/base/EventTypes.cpp @@ -0,0 +1,203 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/EventTypes.h" +#include "base/IEventQueue.h" + +#include <assert.h> +#include <stddef.h> + +EventTypes::EventTypes() : + m_events(NULL) +{ +} + +IEventQueue* +EventTypes::getEvents() const +{ + assert(m_events != NULL); + return m_events; +} + +void +EventTypes::setEvents(IEventQueue* events) +{ + m_events = events; +} + +// +// Client +// + +REGISTER_EVENT(Client, connected) +REGISTER_EVENT(Client, connectionFailed) +REGISTER_EVENT(Client, disconnected) + +// +// IStream +// + +REGISTER_EVENT(IStream, inputReady) +REGISTER_EVENT(IStream, outputFlushed) +REGISTER_EVENT(IStream, outputError) +REGISTER_EVENT(IStream, inputShutdown) +REGISTER_EVENT(IStream, outputShutdown) + +// +// IpcClient +// + +REGISTER_EVENT(IpcClient, connected) +REGISTER_EVENT(IpcClient, messageReceived) + +// +// IpcClientProxy +// + +REGISTER_EVENT(IpcClientProxy, messageReceived) +REGISTER_EVENT(IpcClientProxy, disconnected) + +// +// IpcServerProxy +// + +REGISTER_EVENT(IpcServerProxy, messageReceived) + +// +// IDataSocket +// + +REGISTER_EVENT(IDataSocket, connected) +REGISTER_EVENT(IDataSocket, secureConnected) +REGISTER_EVENT(IDataSocket, connectionFailed) + +// +// IListenSocket +// + +REGISTER_EVENT(IListenSocket, connecting) + +// +// ISocket +// + +REGISTER_EVENT(ISocket, disconnected) +REGISTER_EVENT(ISocket, stopRetry) + +// +// OSXScreen +// + +REGISTER_EVENT(OSXScreen, confirmSleep) + +// +// ClientListener +// + +REGISTER_EVENT(ClientListener, accepted) +REGISTER_EVENT(ClientListener, connected) + +// +// ClientProxy +// + +REGISTER_EVENT(ClientProxy, ready) +REGISTER_EVENT(ClientProxy, disconnected) + +// +// ClientProxyUnknown +// + +REGISTER_EVENT(ClientProxyUnknown, success) +REGISTER_EVENT(ClientProxyUnknown, failure) + +// +// Server +// + +REGISTER_EVENT(Server, error) +REGISTER_EVENT(Server, connected) +REGISTER_EVENT(Server, disconnected) +REGISTER_EVENT(Server, switchToScreen) +REGISTER_EVENT(Server, switchInDirection) +REGISTER_EVENT(Server, keyboardBroadcast) +REGISTER_EVENT(Server, lockCursorToScreen) +REGISTER_EVENT(Server, screenSwitched) + +// +// ServerApp +// + +REGISTER_EVENT(ServerApp, reloadConfig) +REGISTER_EVENT(ServerApp, forceReconnect) +REGISTER_EVENT(ServerApp, resetServer) + +// +// IKeyState +// + +REGISTER_EVENT(IKeyState, keyDown) +REGISTER_EVENT(IKeyState, keyUp) +REGISTER_EVENT(IKeyState, keyRepeat) + +// +// IPrimaryScreen +// + +REGISTER_EVENT(IPrimaryScreen, buttonDown) +REGISTER_EVENT(IPrimaryScreen, buttonUp) +REGISTER_EVENT(IPrimaryScreen, motionOnPrimary) +REGISTER_EVENT(IPrimaryScreen, motionOnSecondary) +REGISTER_EVENT(IPrimaryScreen, wheel) +REGISTER_EVENT(IPrimaryScreen, screensaverActivated) +REGISTER_EVENT(IPrimaryScreen, screensaverDeactivated) +REGISTER_EVENT(IPrimaryScreen, hotKeyDown) +REGISTER_EVENT(IPrimaryScreen, hotKeyUp) +REGISTER_EVENT(IPrimaryScreen, fakeInputBegin) +REGISTER_EVENT(IPrimaryScreen, fakeInputEnd) + +// +// IScreen +// + +REGISTER_EVENT(IScreen, error) +REGISTER_EVENT(IScreen, shapeChanged) +REGISTER_EVENT(IScreen, suspend) +REGISTER_EVENT(IScreen, resume) + +// +// IpcServer +// + +REGISTER_EVENT(IpcServer, clientConnected) +REGISTER_EVENT(IpcServer, messageReceived) + +// +// Clipboard +// + +REGISTER_EVENT(Clipboard, clipboardGrabbed) +REGISTER_EVENT(Clipboard, clipboardChanged) +REGISTER_EVENT(Clipboard, clipboardSending) + +// +// File +// + +REGISTER_EVENT(File, fileChunkSending) +REGISTER_EVENT(File, fileRecieveCompleted) +REGISTER_EVENT(File, keepAlive) diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h new file mode 100644 index 0000000..d044c86 --- /dev/null +++ b/src/lib/base/EventTypes.h @@ -0,0 +1,754 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/Event.h" + +class IEventQueue; + +class EventTypes { +public: + EventTypes(); + void setEvents(IEventQueue* events); + +protected: + IEventQueue* getEvents() const; + +private: + IEventQueue* m_events; +}; + +#define REGISTER_EVENT(type_, name_) \ +Event::Type \ +type_##Events::name_() \ +{ \ + return getEvents()->registerTypeOnce(m_##name_, __FUNCTION__); \ +} + +class ClientEvents : public EventTypes { +public: + ClientEvents() : + m_connected(Event::kUnknown), + m_connectionFailed(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the connected event type. This is sent when the client has + successfully connected to the server. + */ + Event::Type connected(); + + //! Get connection failed event type + /*! + Returns the connection failed event type. This is sent when the + server fails for some reason. The event data is a FailInfo*. + */ + Event::Type connectionFailed(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when the client + has disconnected from the server (and only after having successfully + connected). + */ + Event::Type disconnected(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_connectionFailed; + Event::Type m_disconnected; +}; + +class IStreamEvents : public EventTypes { +public: + IStreamEvents() : + m_inputReady(Event::kUnknown), + m_outputFlushed(Event::kUnknown), + m_outputError(Event::kUnknown), + m_inputShutdown(Event::kUnknown), + m_outputShutdown(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get input ready event type + /*! + Returns the input ready event type. A stream sends this event + when \c read() will return with data. + */ + Event::Type inputReady(); + + //! Get output flushed event type + /*! + Returns the output flushed event type. A stream sends this event + when the output buffer has been flushed. If there have been no + writes since the event was posted, calling \c shutdownOutput() or + \c close() will not discard any data and \c flush() will return + immediately. + */ + Event::Type outputFlushed(); + + //! Get output error event type + /*! + Returns the output error event type. A stream sends this event + when a write has failed. + */ + Event::Type outputError(); + + //! Get input shutdown event type + /*! + Returns the input shutdown event type. This is sent when the + input side of the stream has shutdown. When the input has + shutdown, no more data will ever be available to read. + */ + Event::Type inputShutdown(); + + //! Get output shutdown event type + /*! + Returns the output shutdown event type. This is sent when the + output side of the stream has shutdown. When the output has + shutdown, no more data can ever be written to the stream. Any + attempt to do so will generate a output error event. + */ + Event::Type outputShutdown(); + + //@} + +private: + Event::Type m_inputReady; + Event::Type m_outputFlushed; + Event::Type m_outputError; + Event::Type m_inputShutdown; + Event::Type m_outputShutdown; +}; + +class IpcClientEvents : public EventTypes { +public: + IpcClientEvents() : + m_connected(Event::kUnknown), + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the socket is connected. + Event::Type connected(); + + //! Raised when a message is received. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_messageReceived; +}; + +class IpcClientProxyEvents : public EventTypes { +public: + IpcClientProxyEvents() : + m_messageReceived(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the server receives a message from a client. + Event::Type messageReceived(); + + //! Raised when the client disconnects from the server. + Event::Type disconnected(); + + //@} + +private: + Event::Type m_messageReceived; + Event::Type m_disconnected; +}; + +class IpcServerEvents : public EventTypes { +public: + IpcServerEvents() : + m_clientConnected(Event::kUnknown), + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when we have created the client proxy. + Event::Type clientConnected(); + + //! Raised when a message is received through a client proxy. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_clientConnected; + Event::Type m_messageReceived; +}; + +class IpcServerProxyEvents : public EventTypes { +public: + IpcServerProxyEvents() : + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the client receives a message from the server. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_messageReceived; +}; + +class IDataSocketEvents : public EventTypes { +public: + IDataSocketEvents() : + m_connected(Event::kUnknown), + m_secureConnected(Event::kUnknown), + m_connectionFailed(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the socket connected event type. A socket sends this + event when a remote connection has been established. + */ + Event::Type connected(); + + //! Get secure connected event type + /*! + Returns the secure socket connected event type. A secure socket sends + this event when a remote connection has been established. + */ + Event::Type secureConnected(); + + //! Get connection failed event type + /*! + Returns the socket connection failed event type. A socket sends + this event when an attempt to connect to a remote port has failed. + The data is a pointer to a ConnectionFailedInfo. + */ + Event::Type connectionFailed(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_secureConnected; + Event::Type m_connectionFailed; +}; + +class IListenSocketEvents : public EventTypes { +public: + IListenSocketEvents() : + m_connecting(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connecting event type + /*! + Returns the socket connecting event type. A socket sends this + event when a remote connection is waiting to be accepted. + */ + Event::Type connecting(); + + //@} + +private: + Event::Type m_connecting; +}; + +class ISocketEvents : public EventTypes { +public: + ISocketEvents() : + m_disconnected(Event::kUnknown), + m_stopRetry(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get disconnected event type + /*! + Returns the socket disconnected event type. A socket sends this + event when the remote side of the socket has disconnected or + shutdown both input and output. + */ + Event::Type disconnected(); + + //! Get stop retry event type + /*! + Returns the stop retry event type. This is sent when the client + doesn't want to reconnect after it disconnects from the server. + */ + Event::Type stopRetry(); + + //@} + +private: + Event::Type m_disconnected; + Event::Type m_stopRetry; +}; + +class OSXScreenEvents : public EventTypes { +public: + OSXScreenEvents() : + m_confirmSleep(Event::kUnknown) { } + + //! @name accessors + //@{ + + Event::Type confirmSleep(); + + //@} + +private: + Event::Type m_confirmSleep; +}; + +class ClientListenerEvents : public EventTypes { +public: + ClientListenerEvents() : + m_accepted(Event::kUnknown), + m_connected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get accepted event type + /*! + Returns the accepted event type. This is sent whenever a server + accepts a client. + */ + Event::Type accepted(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent whenever a + a client connects. + */ + Event::Type connected(); + + //@} + +private: + Event::Type m_accepted; + Event::Type m_connected; +}; + +class ClientProxyEvents : public EventTypes { +public: + ClientProxyEvents() : + m_ready(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get ready event type + /*! + Returns the ready event type. This is sent when the client has + completed the initial handshake. Until it is sent, the client is + not fully connected. + */ + Event::Type ready(); + + //! Get disconnect event type + /*! + Returns the disconnect event type. This is sent when the client + disconnects or is disconnected. The target is getEventTarget(). + */ + Event::Type disconnected(); + + //@} + +private: + Event::Type m_ready; + Event::Type m_disconnected; +}; + +class ClientProxyUnknownEvents : public EventTypes { +public: + ClientProxyUnknownEvents() : + m_success(Event::kUnknown), + m_failure(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get success event type + /*! + Returns the success event type. This is sent when the client has + correctly responded to the hello message. The target is this. + */ + Event::Type success(); + + //! Get failure event type + /*! + Returns the failure event type. This is sent when a client fails + to correctly respond to the hello message. The target is this. + */ + Event::Type failure(); + + //@} + +private: + Event::Type m_success; + Event::Type m_failure; +}; + +class ServerEvents : public EventTypes { +public: + ServerEvents() : + m_error(Event::kUnknown), + m_connected(Event::kUnknown), + m_disconnected(Event::kUnknown), + m_switchToScreen(Event::kUnknown), + m_switchInDirection(Event::kUnknown), + m_keyboardBroadcast(Event::kUnknown), + m_lockCursorToScreen(Event::kUnknown), + m_screenSwitched(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get error event type + /*! + Returns the error event type. This is sent when the server fails + for some reason. + */ + Event::Type error(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent when a client screen + has connected. The event data is a \c ScreenConnectedInfo* that + indicates the connected screen. + */ + Event::Type connected(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when all the + clients have disconnected. + */ + Event::Type disconnected(); + + //! Get switch to screen event type + /*! + Returns the switch to screen event type. The server responds to this + by switching screens. The event data is a \c SwitchToScreenInfo* + that indicates the target screen. + */ + Event::Type switchToScreen(); + + //! Get switch in direction event type + /*! + Returns the switch in direction event type. The server responds to this + by switching screens. The event data is a \c SwitchInDirectionInfo* + that indicates the target direction. + */ + Event::Type switchInDirection(); + + //! Get keyboard broadcast event type + /*! + Returns the keyboard broadcast event type. The server responds + to this by turning on keyboard broadcasting or turning it off. The + event data is a \c KeyboardBroadcastInfo*. + */ + Event::Type keyboardBroadcast(); + + //! Get lock cursor event type + /*! + Returns the lock cursor event type. The server responds to this + by locking the cursor to the active screen or unlocking it. The + event data is a \c LockCursorToScreenInfo*. + */ + Event::Type lockCursorToScreen(); + + //! Get screen switched event type + /*! + Returns the screen switched event type. This is raised when the + screen has been switched to a client. + */ + Event::Type screenSwitched(); + + //@} + +private: + Event::Type m_error; + Event::Type m_connected; + Event::Type m_disconnected; + Event::Type m_switchToScreen; + Event::Type m_switchInDirection; + Event::Type m_keyboardBroadcast; + Event::Type m_lockCursorToScreen; + Event::Type m_screenSwitched; +}; + +class ServerAppEvents : public EventTypes { +public: + ServerAppEvents() : + m_reloadConfig(Event::kUnknown), + m_forceReconnect(Event::kUnknown), + m_resetServer(Event::kUnknown) { } + + //! @name accessors + //@{ + + Event::Type reloadConfig(); + Event::Type forceReconnect(); + Event::Type resetServer(); + + //@} + +private: + Event::Type m_reloadConfig; + Event::Type m_forceReconnect; + Event::Type m_resetServer; +}; + +class IKeyStateEvents : public EventTypes { +public: + IKeyStateEvents() : + m_keyDown(Event::kUnknown), + m_keyUp(Event::kUnknown), + m_keyRepeat(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get key down event type. Event data is KeyInfo*, count == 1. + Event::Type keyDown(); + + //! Get key up event type. Event data is KeyInfo*, count == 1. + Event::Type keyUp(); + + //! Get key repeat event type. Event data is KeyInfo*. + Event::Type keyRepeat(); + + //@} + +private: + Event::Type m_keyDown; + Event::Type m_keyUp; + Event::Type m_keyRepeat; +}; + +class IPrimaryScreenEvents : public EventTypes { +public: + IPrimaryScreenEvents() : + m_buttonDown(Event::kUnknown), + m_buttonUp(Event::kUnknown), + m_motionOnPrimary(Event::kUnknown), + m_motionOnSecondary(Event::kUnknown), + m_wheel(Event::kUnknown), + m_screensaverActivated(Event::kUnknown), + m_screensaverDeactivated(Event::kUnknown), + m_hotKeyDown(Event::kUnknown), + m_hotKeyUp(Event::kUnknown), + m_fakeInputBegin(Event::kUnknown), + m_fakeInputEnd(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! button down event type. Event data is ButtonInfo*. + Event::Type buttonDown(); + + //! button up event type. Event data is ButtonInfo*. + Event::Type buttonUp(); + + //! mouse motion on the primary screen event type + /*! + Event data is MotionInfo* and the values are an absolute position. + */ + Event::Type motionOnPrimary(); + + //! mouse motion on a secondary screen event type + /*! + Event data is MotionInfo* and the values are motion deltas not + absolute coordinates. + */ + Event::Type motionOnSecondary(); + + //! mouse wheel event type. Event data is WheelInfo*. + Event::Type wheel(); + + //! screensaver activated event type + Event::Type screensaverActivated(); + + //! screensaver deactivated event type + Event::Type screensaverDeactivated(); + + //! hot key down event type. Event data is HotKeyInfo*. + Event::Type hotKeyDown(); + + //! hot key up event type. Event data is HotKeyInfo*. + Event::Type hotKeyUp(); + + //! start of fake input event type + Event::Type fakeInputBegin(); + + //! end of fake input event type + Event::Type fakeInputEnd(); + + //@} + +private: + Event::Type m_buttonDown; + Event::Type m_buttonUp; + Event::Type m_motionOnPrimary; + Event::Type m_motionOnSecondary; + Event::Type m_wheel; + Event::Type m_screensaverActivated; + Event::Type m_screensaverDeactivated; + Event::Type m_hotKeyDown; + Event::Type m_hotKeyUp; + Event::Type m_fakeInputBegin; + Event::Type m_fakeInputEnd; +}; + +class IScreenEvents : public EventTypes { +public: + IScreenEvents() : + m_error(Event::kUnknown), + m_shapeChanged(Event::kUnknown), + m_suspend(Event::kUnknown), + m_resume(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get error event type + /*! + Returns the error event type. This is sent whenever the screen has + failed for some reason (e.g. the X Windows server died). + */ + Event::Type error(); + + //! Get shape changed event type + /*! + Returns the shape changed event type. This is sent whenever the + screen's shape changes. + */ + Event::Type shapeChanged(); + + //! Get suspend event type + /*! + Returns the suspend event type. This is sent whenever the system goes + to sleep or a user session is deactivated (fast user switching). + */ + Event::Type suspend(); + + //! Get resume event type + /*! + Returns the resume event type. This is sent whenever the system wakes + up or a user session is activated (fast user switching). + */ + Event::Type resume(); + + //@} + +private: + Event::Type m_error; + Event::Type m_shapeChanged; + Event::Type m_suspend; + Event::Type m_resume; +}; + +class ClipboardEvents : public EventTypes { +public: + ClipboardEvents() : + m_clipboardGrabbed(Event::kUnknown), + m_clipboardChanged(Event::kUnknown), + m_clipboardSending(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get clipboard grabbed event type + /*! + Returns the clipboard grabbed event type. This is sent whenever the + clipboard is grabbed by some other application so we don't own it + anymore. The data is a pointer to a ClipboardInfo. + */ + Event::Type clipboardGrabbed(); + + //! Get clipboard changed event type + /*! + Returns the clipboard changed event type. This is sent whenever the + contents of the clipboard has changed. The data is a pointer to a + IScreen::ClipboardInfo. + */ + Event::Type clipboardChanged(); + + //! Clipboard sending event type + /*! + Returns the clipboard sending event type. This is used to send + clipboard chunks. + */ + Event::Type clipboardSending(); + + //@} + +private: + Event::Type m_clipboardGrabbed; + Event::Type m_clipboardChanged; + Event::Type m_clipboardSending; +}; + +class FileEvents : public EventTypes { +public: + FileEvents() : + m_fileChunkSending(Event::kUnknown), + m_fileRecieveCompleted(Event::kUnknown), + m_keepAlive(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Sending a file chunk + Event::Type fileChunkSending(); + + //! Completed receiving a file + Event::Type fileRecieveCompleted(); + + //! Send a keep alive + Event::Type keepAlive(); + + //@} + +private: + Event::Type m_fileChunkSending; + Event::Type m_fileRecieveCompleted; + Event::Type m_keepAlive; +}; diff --git a/src/lib/base/FunctionEventJob.cpp b/src/lib/base/FunctionEventJob.cpp new file mode 100644 index 0000000..705e058 --- /dev/null +++ b/src/lib/base/FunctionEventJob.cpp @@ -0,0 +1,44 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/FunctionEventJob.h" + +// +// FunctionEventJob +// + +FunctionEventJob::FunctionEventJob( + void (*func)(const Event&, void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +FunctionEventJob::~FunctionEventJob() +{ + // do nothing +} + +void +FunctionEventJob::run(const Event& event) +{ + if (m_func != NULL) { + m_func(event, m_arg); + } +} diff --git a/src/lib/base/FunctionEventJob.h b/src/lib/base/FunctionEventJob.h new file mode 100644 index 0000000..4b2c2fc --- /dev/null +++ b/src/lib/base/FunctionEventJob.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/IEventJob.h" + +//! Use a function as an event job +/*! +An event job class that invokes a function. +*/ +class FunctionEventJob : public IEventJob { +public: + //! run() invokes \c func(arg) + FunctionEventJob(void (*func)(const Event&, void*), void* arg = NULL); + virtual ~FunctionEventJob(); + + // IEventJob overrides + virtual void run(const Event&); + +private: + void (*m_func)(const Event&, void*); + void* m_arg; +}; diff --git a/src/lib/base/FunctionJob.cpp b/src/lib/base/FunctionJob.cpp new file mode 100644 index 0000000..859010e --- /dev/null +++ b/src/lib/base/FunctionJob.cpp @@ -0,0 +1,43 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/FunctionJob.h" + +// +// FunctionJob +// + +FunctionJob::FunctionJob(void (*func)(void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +FunctionJob::~FunctionJob() +{ + // do nothing +} + +void +FunctionJob::run() +{ + if (m_func != NULL) { + m_func(m_arg); + } +} diff --git a/src/lib/base/FunctionJob.h b/src/lib/base/FunctionJob.h new file mode 100644 index 0000000..9cdfa9d --- /dev/null +++ b/src/lib/base/FunctionJob.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a function. +*/ +class FunctionJob : public IJob { +public: + //! run() invokes \c func(arg) + FunctionJob(void (*func)(void*), void* arg = NULL); + virtual ~FunctionJob(); + + // IJob overrides + virtual void run(); + +private: + void (*m_func)(void*); + void* m_arg; +}; diff --git a/src/lib/base/IEventJob.h b/src/lib/base/IEventJob.h new file mode 100644 index 0000000..3e4a420 --- /dev/null +++ b/src/lib/base/IEventJob.h @@ -0,0 +1,33 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/IInterface.h" + +class Event; + +//! Event handler interface +/*! +An event job is an interface for executing a event handler. +*/ +class IEventJob : public IInterface { +public: + //! Run the job + virtual void run(const Event&) = 0; +}; diff --git a/src/lib/base/IEventQueue.h b/src/lib/base/IEventQueue.h new file mode 100644 index 0000000..cd4f0b3 --- /dev/null +++ b/src/lib/base/IEventQueue.h @@ -0,0 +1,251 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/IInterface.h" +#include "base/Event.h" +#include "base/String.h" + +class IEventJob; +class IEventQueueBuffer; + +// Opaque type for timer info. This is defined by subclasses of +// IEventQueueBuffer. +class EventQueueTimer; + +// Event type registration classes. +class ClientEvents; +class IStreamEvents; +class IpcClientEvents; +class IpcClientProxyEvents; +class IpcServerEvents; +class IpcServerProxyEvents; +class IDataSocketEvents; +class IListenSocketEvents; +class ISocketEvents; +class OSXScreenEvents; +class ClientListenerEvents; +class ClientProxyEvents; +class ClientProxyUnknownEvents; +class ServerEvents; +class ServerAppEvents; +class IKeyStateEvents; +class IPrimaryScreenEvents; +class IScreenEvents; +class ClipboardEvents; +class FileEvents; + +//! Event queue interface +/*! +An event queue provides a queue of Events. Clients can block waiting +on any event becoming available at the head of the queue and can place +new events at the end of the queue. Clients can also add and remove +timers which generate events periodically. +*/ +class IEventQueue : public IInterface { +public: + class TimerEvent { + public: + EventQueueTimer* m_timer; //!< The timer + UInt32 m_count; //!< Number of repeats + }; + + //! @name manipulators + //@{ + + //! Loop the event queue until quit + /*! + Dequeues and dispatches events until the kQuit event is found. + */ + virtual void loop() = 0; + + //! Set the buffer + /*! + Replace the current event queue buffer. Any queued events are + discarded. The queue takes ownership of the buffer. + */ + virtual void adoptBuffer(IEventQueueBuffer*) = 0; + + //! Remove event from queue + /*! + Returns the next event on the queue into \p event. If no event is + available then blocks for up to \p timeout seconds, or forever if + \p timeout is negative. Returns true iff an event was available. + */ + virtual bool getEvent(Event& event, double timeout = -1.0) = 0; + + //! Dispatch an event + /*! + Looks up the dispatcher for the event's target and invokes it. + Returns true iff a dispatcher exists for the target. + */ + virtual bool dispatchEvent(const Event& event) = 0; + + //! Add event to queue + /*! + Adds \p event to the end of the queue. + */ + virtual void addEvent(const Event& event) = 0; + + //! Create a recurring timer + /*! + Creates and returns a timer. An event is returned after \p duration + seconds and the timer is reset to countdown again. When a timer event + is returned the data points to a \c TimerEvent. The client must pass + the returned timer to \c deleteTimer() (whether or not the timer has + expired) to release the timer. The returned timer event uses the + given \p target. If \p target is NULL it uses the returned timer as + the target. + + Events for a single timer don't accumulate in the queue, even if the + client reading events can't keep up. Instead, the \c m_count member + of the \c TimerEvent indicates how many events for the timer would + have been put on the queue since the last event for the timer was + removed (or since the timer was added). + */ + virtual EventQueueTimer* + newTimer(double duration, void* target) = 0; + + //! Create a one-shot timer + /*! + Creates and returns a one-shot timer. An event is returned when + the timer expires and the timer is removed from further handling. + When a timer event is returned the data points to a \c TimerEvent. + The c_count member of the \c TimerEvent is always 1. The client + must pass the returned timer to \c deleteTimer() (whether or not the + timer has expired) to release the timer. The returned timer event + uses the given \p target. If \p target is NULL it uses the returned + timer as the target. + */ + virtual EventQueueTimer* + newOneShotTimer(double duration, + void* target) = 0; + + //! Destroy a timer + /*! + Destroys a previously created timer. The timer is removed from the + queue and will not generate event, even if the timer has expired. + */ + virtual void deleteTimer(EventQueueTimer*) = 0; + + //! Register an event handler for an event type + /*! + Registers an event handler for \p type and \p target. The \p handler + is adopted. Any existing handler for the type,target pair is deleted. + \c dispatchEvent() will invoke \p handler for any event for \p target + of type \p type. If no such handler exists it will use the handler + for \p target and type \p kUnknown if it exists. + */ + virtual void adoptHandler(Event::Type type, + void* target, IEventJob* handler) = 0; + + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + deletes it. + */ + virtual void removeHandler(Event::Type type, void* target) = 0; + + //! Unregister all event handlers for an event target + /*! + Unregisters all event handlers for the \p target and deletes them. + */ + virtual void removeHandlers(void* target) = 0; + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + virtual Event::Type + registerTypeOnce(Event::Type& type, + const char* name) = 0; + + //! Wait for event queue to become ready + /*! + Blocks on the current thread until the event queue is ready for events to + be added. + */ + virtual void waitForReady() const = 0; + + //@} + //! @name accessors + //@{ + + //! Test if queue is empty + /*! + Returns true iff the queue has no events in it, including timer + events. + */ + virtual bool isEmpty() const = 0; + + //! Get an event handler + /*! + Finds and returns the event handler for the \p type, \p target pair + if it exists, otherwise it returns NULL. + */ + virtual IEventJob* getHandler(Event::Type type, void* target) const = 0; + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + virtual const char* getTypeName(Event::Type type) = 0; + + //! Get an event type by name + /*! + Returns the registered type for an event for a given name. + */ + virtual Event::Type getRegisteredType(const String& name) const = 0; + + //! Get the system event type target + /*! + Returns the target to use for dispatching \c Event::kSystem events. + */ + virtual void* getSystemTarget() = 0; + + //@} + + // + // Event type providers. + // + + virtual ClientEvents& forClient() = 0; + virtual IStreamEvents& forIStream() = 0; + virtual IpcClientEvents& forIpcClient() = 0; + virtual IpcClientProxyEvents& forIpcClientProxy() = 0; + virtual IpcServerEvents& forIpcServer() = 0; + virtual IpcServerProxyEvents& forIpcServerProxy() = 0; + virtual IDataSocketEvents& forIDataSocket() = 0; + virtual IListenSocketEvents& forIListenSocket() = 0; + virtual ISocketEvents& forISocket() = 0; + virtual OSXScreenEvents& forOSXScreen() = 0; + virtual ClientListenerEvents& forClientListener() = 0; + virtual ClientProxyEvents& forClientProxy() = 0; + virtual ClientProxyUnknownEvents& forClientProxyUnknown() = 0; + virtual ServerEvents& forServer() = 0; + virtual ServerAppEvents& forServerApp() = 0; + virtual IKeyStateEvents& forIKeyState() = 0; + virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0; + virtual IScreenEvents& forIScreen() = 0; + virtual ClipboardEvents& forClipboard() = 0; + virtual FileEvents& forFile() = 0; +}; diff --git a/src/lib/base/IEventQueueBuffer.h b/src/lib/base/IEventQueueBuffer.h new file mode 100644 index 0000000..b594436 --- /dev/null +++ b/src/lib/base/IEventQueueBuffer.h @@ -0,0 +1,101 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/IInterface.h" +#include "common/basic_types.h" + +class Event; +class EventQueueTimer; + +//! Event queue buffer interface +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class IEventQueueBuffer : public IInterface { +public: + enum Type { + kNone, //!< No event is available + kSystem, //!< Event is a system event + kUser //!< Event is a user event + }; + + //! @name manipulators + //@{ + + //! Initialize + /*! + Useful for platform-specific initialisation from a specific thread. + */ + virtual void init() = 0; + + //! Block waiting for an event + /*! + Wait for an event in the event queue buffer for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Get the next event from the buffer. Return kNone if no event is + available. If a system event is next, return kSystem and fill in + event. The event data in a system event can point to a static + buffer (because Event::deleteData() will not attempt to delete + data in a kSystem event). Otherwise, return kUser and fill in + \p dataID with the value passed to \c addEvent(). + */ + virtual Type getEvent(Event& event, UInt32& dataID) = 0; + + //! Post an event + /*! + Add the given event to the end of the queue buffer. This is a user + event and \c getEvent() must be able to identify it as such and + return \p dataID. This method must cause \c waitForEvent() to + return at some future time if it's blocked waiting on an event. + */ + virtual bool addEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if event queue buffer is empty + /*! + Return true iff the event queue buffer is empty. + */ + virtual bool isEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the buffer but it must be a valid object (i.e. + not NULL). + */ + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c newTimer(). + */ + virtual void deleteTimer(EventQueueTimer*) const = 0; + + //@} +}; diff --git a/src/lib/base/IJob.h b/src/lib/base/IJob.h new file mode 100644 index 0000000..f966ec0 --- /dev/null +++ b/src/lib/base/IJob.h @@ -0,0 +1,31 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/IInterface.h" + +//! Job interface +/*! +A job is an interface for executing some function. +*/ +class IJob : public IInterface { +public: + //! Run the job + virtual void run() = 0; +}; diff --git a/src/lib/base/ILogOutputter.h b/src/lib/base/ILogOutputter.h new file mode 100644 index 0000000..ab218fc --- /dev/null +++ b/src/lib/base/ILogOutputter.h @@ -0,0 +1,69 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/Log.h" +#include "base/ELevel.h" +#include "common/IInterface.h" + +//! Outputter interface +/*! +Type of outputter interface. The logger performs all output through +outputters. ILogOutputter overrides must not call any log functions +directly or indirectly. +*/ +class ILogOutputter : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the outputter + /*! + Opens the outputter for writing. Calling this method on an + already open outputter must have no effect. + */ + virtual void open(const char* title) = 0; + + //! Close the outputter + /*! + Close the outputter. Calling this method on an already closed + outputter must have no effect. + */ + virtual void close() = 0; + + //! Show the outputter + /*! + Causes the output to become visible. This generally only makes sense + for a logger in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void show(bool showIfEmpty) = 0; + + //! Write a message with level + /*! + Writes \c message, which has the given \c level, to a log. + If this method returns true then Log will stop passing the + message to all outputters in the outputter chain, otherwise + it continues. Most implementations should return true. + */ + virtual bool write(ELevel level, const char* message) = 0; + + //@} +}; diff --git a/src/lib/base/Log.cpp b/src/lib/base/Log.cpp new file mode 100644 index 0000000..823bf6d --- /dev/null +++ b/src/lib/base/Log.cpp @@ -0,0 +1,309 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/String.h" +#include "base/log_outputters.h" +#include "common/Version.h" + +#include <cstdio> +#include <cstring> +#include <iostream> +#include <ctime> + +// names of priorities +static const char* g_priority[] = { + "FATAL", + "ERROR", + "WARNING", + "NOTE", + "INFO", + "DEBUG", + "DEBUG1", + "DEBUG2", + "DEBUG3", + "DEBUG4", + "DEBUG5" +}; + +// number of priorities +static const int g_numPriority = (int)(sizeof(g_priority) / sizeof(g_priority[0])); + +// the default priority +#ifndef NDEBUG +static const int g_defaultMaxPriority = kDEBUG; +#else +static const int g_defaultMaxPriority = kINFO; +#endif + +// +// Log +// + +Log* Log::s_log = NULL; + +Log::Log() +{ + assert(s_log == NULL); + + // create mutex for multithread safe operation + m_mutex = ARCH->newMutex(); + + // other initalization + m_maxPriority = g_defaultMaxPriority; + m_maxNewlineLength = 0; + insert(new ConsoleLogOutputter); + + s_log = this; +} + +Log::Log(Log* src) +{ + s_log = src; +} + +Log::~Log() +{ + // clean up + for (OutputterList::iterator index = m_outputters.begin(); + index != m_outputters.end(); ++index) { + delete *index; + } + for (OutputterList::iterator index = m_alwaysOutputters.begin(); + index != m_alwaysOutputters.end(); ++index) { + delete *index; + } + ARCH->closeMutex(m_mutex); +} + +Log* +Log::getInstance() +{ + assert(s_log != NULL); + return s_log; +} + +const char* +Log::getFilterName() const +{ + return getFilterName(getFilter()); +} + +const char* +Log::getFilterName(int level) const +{ + if (level < 0) { + return "Message"; + } + return g_priority[level]; +} + +void +Log::print(const char* file, int line, const char* fmt, ...) +{ + // check if fmt begins with a priority argument + ELevel priority = kINFO; + if ((strlen(fmt) > 2) && (fmt[0] == '%' && fmt[1] == 'z')) { + + // 060 in octal is 0 (48 in decimal), so subtracting this converts ascii + // number it a true number. we could use atoi instead, but this is how + // it was done originally. + priority = (ELevel)(fmt[2] - '\060'); + + // move the pointer on past the debug priority char + fmt += 3; + } + + // done if below priority threshold + if (priority > getFilter()) { + return; + } + + // compute prefix padding length + char stack[1024]; + + // compute suffix padding length + int sPad = m_maxNewlineLength; + + // print to buffer, leaving space for a newline at the end and prefix + // at the beginning. + char* buffer = stack; + int len = (int)(sizeof(stack) / sizeof(stack[0])); + while (true) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len - sPad, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > (int)len) { + if (buffer != stack) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if the buffer was big enough then continue + else { + break; + } + } + + // print the prefix to the buffer. leave space for priority label. + // do not prefix time and file for kPRINT (CLOG_PRINT) + if (priority != kPRINT) { + + struct tm *tm; + char timestamp[50]; + time_t t; + time(&t); + tm = localtime(&t); + sprintf(timestamp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + // square brackets, spaces, comma and null terminator take about 10 + size_t size = 10; + size += strlen(timestamp); + size += strlen(g_priority[priority]); + size += strlen(buffer); +#ifndef NDEBUG + size += strlen(file); + // assume there is no file contains over 100k lines of code + size += 6; +#endif + char* message = new char[size]; + +#ifndef NDEBUG + sprintf(message, "[%s] %s: %s\n\t%s,%d", timestamp, g_priority[priority], buffer, file, line); +#else + sprintf(message, "[%s] %s: %s", timestamp, g_priority[priority], buffer); +#endif + + output(priority, message); + delete[] message; + } else { + output(priority, buffer); + } + + // clean up + if (buffer != stack) { + delete[] buffer; + } +} + +void +Log::insert(ILogOutputter* outputter, bool alwaysAtHead) +{ + assert(outputter != NULL); + + ArchMutexLock lock(m_mutex); + if (alwaysAtHead) { + m_alwaysOutputters.push_front(outputter); + } + else { + m_outputters.push_front(outputter); + } + + outputter->open(kAppVersion); + + // Issue 41 + // don't show log unless user requests it, as some users find this + // feature irritating (i.e. when they lose network connectivity). + // in windows the log window can be displayed by selecting "show log" + // from the barrier system tray icon. + // if this causes problems for other architectures, then a different + // work around should be attempted. + //outputter->show(false); +} + +void +Log::remove(ILogOutputter* outputter) +{ + ArchMutexLock lock(m_mutex); + m_outputters.remove(outputter); + m_alwaysOutputters.remove(outputter); +} + +void +Log::pop_front(bool alwaysAtHead) +{ + ArchMutexLock lock(m_mutex); + OutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; + if (!list->empty()) { + delete list->front(); + list->pop_front(); + } +} + +bool +Log::setFilter(const char* maxPriority) +{ + if (maxPriority != NULL) { + for (int i = 0; i < g_numPriority; ++i) { + if (strcmp(maxPriority, g_priority[i]) == 0) { + setFilter(i); + return true; + } + } + return false; + } + return true; +} + +void +Log::setFilter(int maxPriority) +{ + ArchMutexLock lock(m_mutex); + m_maxPriority = maxPriority; +} + +int +Log::getFilter() const +{ + ArchMutexLock lock(m_mutex); + return m_maxPriority; +} + +void +Log::output(ELevel priority, char* msg) +{ + assert(priority >= -1 && priority < g_numPriority); + assert(msg != NULL); + if (!msg) return; + + ArchMutexLock lock(m_mutex); + + OutputterList::const_iterator i; + + for (i = m_alwaysOutputters.begin(); i != m_alwaysOutputters.end(); ++i) { + + // write to outputter + (*i)->write(priority, msg); + } + + for (i = m_outputters.begin(); i != m_outputters.end(); ++i) { + + // write to outputter and break out of loop if it returns false + if (!(*i)->write(priority, msg)) { + break; + } + } +} diff --git a/src/lib/base/Log.h b/src/lib/base/Log.h new file mode 100644 index 0000000..1d09be2 --- /dev/null +++ b/src/lib/base/Log.h @@ -0,0 +1,211 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchMultithread.h" +#include "arch/Arch.h" +#include "common/common.h" +#include "common/stdlist.h" + +#include <stdarg.h> + +#define CLOG (Log::getInstance()) +#define BYE "\nTry `%s --help' for more information." + +class ILogOutputter; +class Thread; + +//! Logging facility +/*! +The logging class; all console output should go through this class. +It supports multithread safe operation, several message priority levels, +filtering by priority, and output redirection. The macros LOG() and +LOGC() provide convenient access. +*/ +class Log { +public: + Log(); + Log(Log* src); + ~Log(); + + //! @name manipulators + //@{ + + //! Add an outputter to the head of the list + /*! + Inserts an outputter to the head of the outputter list. When the + logger writes a message, it goes to the outputter at the head of + the outputter list. If that outputter's \c write() method returns + true then it also goes to the next outputter, as so on until an + outputter returns false or there are no more outputters. Outputters + still in the outputter list when the log is destroyed will be + deleted. If \c alwaysAtHead is true then the outputter is always + called before all outputters with \c alwaysAtHead false and the + return value of the outputter is ignored. + + By default, the logger has one outputter installed which writes to + the console. + */ + void insert(ILogOutputter* adopted, + bool alwaysAtHead = false); + + //! Remove an outputter from the list + /*! + Removes the first occurrence of the given outputter from the + outputter list. It does nothing if the outputter is not in the + list. The outputter is not deleted. + */ + void remove(ILogOutputter* orphaned); + + //! Remove the outputter from the head of the list + /*! + Removes and deletes the outputter at the head of the outputter list. + This does nothing if the outputter list is empty. Only removes + outputters that were inserted with the matching \c alwaysAtHead. + */ + void pop_front(bool alwaysAtHead = false); + + //! Set the minimum priority filter. + /*! + Set the filter. Messages below this priority are discarded. + The default priority is 4 (INFO) (unless built without NDEBUG + in which case it's 5 (DEBUG)). setFilter(const char*) returns + true if the priority \c name was recognized; if \c name is NULL + then it simply returns true. + */ + bool setFilter(const char* name); + + //! Set the minimum priority filter (by ordinal). + void setFilter(int); + + //@} + //! @name accessors + //@{ + + //! Print a log message + /*! + Print a log message using the printf-like \c format and arguments + preceded by the filename and line number. If \c file is NULL then + neither the file nor the line are printed. + */ + void print(const char* file, int line, + const char* format, ...); + + //! Get the minimum priority level. + int getFilter() const; + + //! Get the filter name of the current filter level. + const char* getFilterName() const; + + //! Get the filter name of a specified filter level. + const char* getFilterName(int level) const; + + //! Get the singleton instance of the log + static Log* getInstance(); + + //! Get the console filter level (messages above this are not sent to console). + int getConsoleMaxLevel() const { return kDEBUG2; } + + //@} + +private: + void output(ELevel priority, char* msg); + +private: + typedef std::list<ILogOutputter*> OutputterList; + + static Log* s_log; + + ArchMutex m_mutex; + OutputterList m_outputters; + OutputterList m_alwaysOutputters; + int m_maxNewlineLength; + int m_maxPriority; +}; + +/*! +\def LOG(arg) +Write to the log. Because macros cannot accept variable arguments, this +should be invoked like so: +\code +LOG((CLOG_XXX "%d and %d are %s", x, y, x == y ? "equal" : "not equal")); +\endcode +In particular, notice the double open and close parentheses. Also note +that there is no comma after the \c CLOG_XXX. The \c XXX should be +replaced by one of enumerants in \c Log::ELevel without the leading +\c k. For example, \c CLOG_INFO. The special \c CLOG_PRINT level will +not be filtered and is never prefixed by the filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is defined during the build then it expands to a +call to Log::print. Otherwise it expands to a call to Log::printt, +which includes the filename and line number. +*/ + +/*! +\def LOGC(expr, arg) +Write to the log if and only if expr is true. Because macros cannot accept +variable arguments, this should be invoked like so: +\code +LOGC(x == y, (CLOG_XXX "%d and %d are equal", x, y)); +\endcode +In particular, notice the parentheses around everything after the boolean +expression. Also note that there is no comma after the \c CLOG_XXX. +The \c XXX should be replaced by one of enumerants in \c Log::ELevel +without the leading \c k. For example, \c CLOG_INFO. The special +\c CLOG_PRINT level will not be filtered and is never prefixed by the +filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is not defined during the build then it expands +to a call to Log::print that prints the filename and line number, +otherwise it expands to a call that doesn't. +*/ + +#if defined(NOLOGGING) +#define LOG(_a1) +#define LOGC(_a1, _a2) +#define CLOG_TRACE +#elif defined(NDEBUG) +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE NULL, 0, +#else +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE __FILE__, __LINE__, +#endif + +// the CLOG_* defines are line and file plus %z and an octal number (060=0, +// 071=9), but the limitation is that once we run out of numbers at either +// end, then we resort to using non-numerical chars. this still works (since +// to deduce the number we subtract octal \060, so '/' is -1, and ':' is 10 + +#define CLOG_PRINT CLOG_TRACE "%z\057" // char is '/' +#define CLOG_CRIT CLOG_TRACE "%z\060" // char is '0' +#define CLOG_ERR CLOG_TRACE "%z\061" +#define CLOG_WARN CLOG_TRACE "%z\062" +#define CLOG_NOTE CLOG_TRACE "%z\063" +#define CLOG_INFO CLOG_TRACE "%z\064" +#define CLOG_DEBUG CLOG_TRACE "%z\065" +#define CLOG_DEBUG1 CLOG_TRACE "%z\066" +#define CLOG_DEBUG2 CLOG_TRACE "%z\067" +#define CLOG_DEBUG3 CLOG_TRACE "%z\070" +#define CLOG_DEBUG4 CLOG_TRACE "%z\071" // char is '9' +#define CLOG_DEBUG5 CLOG_TRACE "%z\072" // char is ':' diff --git a/src/lib/base/NonBlockingStream.cpp b/src/lib/base/NonBlockingStream.cpp new file mode 100644 index 0000000..d44add1 --- /dev/null +++ b/src/lib/base/NonBlockingStream.cpp @@ -0,0 +1,60 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2008 Debauchee Open Source Group + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if !defined(_WIN32) + +#include "base/NonBlockingStream.h" + +#include <unistd.h> // tcgetattr/tcsetattr, read +#include <termios.h> // tcgetattr/tcsetattr +#include <fcntl.h> +#include <errno.h> +#include <assert.h> + +NonBlockingStream::NonBlockingStream(int fd) : + _fd(fd) +{ + // disable ICANON & ECHO so we don't have to wait for a newline + // before we get data (and to keep it from being echoed back out) + termios ta; + tcgetattr(fd, &ta); + _p_ta_previous = new termios(ta); + ta.c_lflag &= ~(ICANON | ECHO); + tcsetattr(fd, TCSANOW, &ta); + + // prevent IO from blocking so we can poll (read()) + int _cntl_previous = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, _cntl_previous | O_NONBLOCK); +} + +NonBlockingStream::~NonBlockingStream() +{ + tcsetattr(_fd, TCSANOW, _p_ta_previous); + fcntl(_fd, F_SETFL, _cntl_previous); + delete _p_ta_previous; +} + +bool NonBlockingStream::try_read_char(char &ch) const +{ + int result = read(_fd, &ch, 1); + if (result == 1) + return true; + assert(result == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)); + return false; +} + +#endif // !defined(_WIN32) diff --git a/src/lib/base/NonBlockingStream.h b/src/lib/base/NonBlockingStream.h new file mode 100644 index 0000000..4c27762 --- /dev/null +++ b/src/lib/base/NonBlockingStream.h @@ -0,0 +1,49 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2008 Debauchee Open Source Group + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +// windows doesn't have a unistd.h so this class won't work as-written. +// at the moment barrier doesn't need this functionality on windows so +// it's left as a stub to be optimized out +#if defined(_WIN32) + +class NonBlockingStream +{ +public: + bool try_read_char(char &ch) const { return false; }; +}; + +#else // non-windows platforms + +struct termios; + +class NonBlockingStream +{ +public: + explicit NonBlockingStream(int fd = 0); + ~NonBlockingStream(); + + bool try_read_char(char &ch) const; + +private: + int _fd; + termios * _p_ta_previous; + int _cntl_previous; +}; + +#endif diff --git a/src/lib/base/PriorityQueue.h b/src/lib/base/PriorityQueue.h new file mode 100644 index 0000000..d2ca70e --- /dev/null +++ b/src/lib/base/PriorityQueue.h @@ -0,0 +1,138 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/stdvector.h" + +#include <algorithm> +#include <functional> + +//! A priority queue with an iterator +/*! +This priority queue is the same as a standard priority queue except: +it sorts by std::greater, it has a forward iterator through the elements +(which can appear in any order), and its contents can be swapped. +*/ +template <class T, class Container = std::vector<T>, +#if defined(_MSC_VER) + class Compare = std::greater<Container::value_type> > +#else + class Compare = std::greater<typename Container::value_type> > +#endif +class PriorityQueue { +public: + typedef typename Container::value_type value_type; + typedef typename Container::size_type size_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef Container container_type; + + PriorityQueue() { } + PriorityQueue(Container& swappedIn) { swap(swappedIn); } + ~PriorityQueue() { } + + //! @name manipulators + //@{ + + //! Add element + void push(const value_type& v) + { + c.push_back(v); + std::push_heap(c.begin(), c.end(), comp); + } + + //! Remove head element + void pop() + { + std::pop_heap(c.begin(), c.end(), comp); + c.pop_back(); + } + + //! Erase element + void erase(iterator i) + { + c.erase(i); + std::make_heap(c.begin(), c.end(), comp); + } + + //! Get start iterator + iterator begin() + { + return c.begin(); + } + + //! Get end iterator + iterator end() + { + return c.end(); + } + + //! Swap contents with another priority queue + void swap(PriorityQueue<T, Container, Compare>& q) + { + c.swap(q.c); + } + + //! Swap contents with another container + void swap(Container& c2) + { + c.swap(c2); + std::make_heap(c.begin(), c.end(), comp); + } + + //@} + //! @name accessors + //@{ + + //! Returns true if there are no elements + bool empty() const + { + return c.empty(); + } + + //! Returns the number of elements + size_type size() const + { + return c.size(); + } + + //! Returns the head element + const value_type& top() const + { + return c.front(); + } + + //! Get start iterator + const_iterator begin() const + { + return c.begin(); + } + + //! Get end iterator + const_iterator end() const + { + return c.end(); + } + + //@} + +private: + Container c; + Compare comp; +}; diff --git a/src/lib/base/SimpleEventQueueBuffer.cpp b/src/lib/base/SimpleEventQueueBuffer.cpp new file mode 100644 index 0000000..b55fe55 --- /dev/null +++ b/src/lib/base/SimpleEventQueueBuffer.cpp @@ -0,0 +1,101 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/SimpleEventQueueBuffer.h" +#include "base/Stopwatch.h" +#include "arch/Arch.h" + +class EventQueueTimer { }; + +// +// SimpleEventQueueBuffer +// + +SimpleEventQueueBuffer::SimpleEventQueueBuffer() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +SimpleEventQueueBuffer::~SimpleEventQueueBuffer() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +SimpleEventQueueBuffer::waitForEvent(double timeout) +{ + ArchMutexLock lock(m_queueMutex); + Stopwatch timer(true); + while (!m_queueReady) { + double timeLeft = timeout; + if (timeLeft >= 0.0) { + timeLeft -= timer.getTime(); + if (timeLeft < 0.0) { + return; + } + } + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, timeLeft); + } +} + +IEventQueueBuffer::Type +SimpleEventQueueBuffer::getEvent(Event&, UInt32& dataID) +{ + ArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return kNone; + } + dataID = m_queue.back(); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return kUser; +} + +bool +SimpleEventQueueBuffer::addEvent(UInt32 dataID) +{ + ArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +SimpleEventQueueBuffer::isEmpty() const +{ + ArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +EventQueueTimer* +SimpleEventQueueBuffer::newTimer(double, bool) const +{ + return new EventQueueTimer; +} + +void +SimpleEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const +{ + delete timer; +} diff --git a/src/lib/base/SimpleEventQueueBuffer.h b/src/lib/base/SimpleEventQueueBuffer.h new file mode 100644 index 0000000..4aa76d3 --- /dev/null +++ b/src/lib/base/SimpleEventQueueBuffer.h @@ -0,0 +1,52 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/IEventQueueBuffer.h" +#include "arch/IArchMultithread.h" +#include "common/stddeque.h" + +//! In-memory event queue buffer +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class SimpleEventQueueBuffer : public IEventQueueBuffer { +public: + SimpleEventQueueBuffer(); + ~SimpleEventQueueBuffer(); + + // IEventQueueBuffer overrides + void init() { } + virtual void waitForEvent(double timeout); + virtual Type getEvent(Event& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(EventQueueTimer*) const; + +private: + typedef std::deque<UInt32> EventDeque; + + ArchMutex m_queueMutex; + ArchCond m_queueReadyCond; + bool m_queueReady; + EventDeque m_queue; +}; + diff --git a/src/lib/base/Stopwatch.cpp b/src/lib/base/Stopwatch.cpp new file mode 100644 index 0000000..b9ceb85 --- /dev/null +++ b/src/lib/base/Stopwatch.cpp @@ -0,0 +1,130 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/Stopwatch.h" +#include "arch/Arch.h" + +// +// Stopwatch +// + +Stopwatch::Stopwatch(bool triggered) : + m_mark(0.0), + m_triggered(triggered), + m_stopped(triggered) +{ + if (!triggered) { + m_mark = ARCH->time(); + } +} + +Stopwatch::~Stopwatch() +{ + // do nothing +} + +double +Stopwatch::reset() +{ + if (m_stopped) { + const double dt = m_mark; + m_mark = 0.0; + return dt; + } + else { + const double t = ARCH->time(); + const double dt = t - m_mark; + m_mark = t; + return dt; + } +} + +void +Stopwatch::stop() +{ + if (m_stopped) { + return; + } + + // save the elapsed time + m_mark = ARCH->time() - m_mark; + m_stopped = true; +} + +void +Stopwatch::start() +{ + m_triggered = false; + if (!m_stopped) { + return; + } + + // set the mark such that it reports the time elapsed at stop() + m_mark = ARCH->time() - m_mark; + m_stopped = false; +} + +void +Stopwatch::setTrigger() +{ + stop(); + m_triggered = true; +} + +double +Stopwatch::getTime() +{ + if (m_triggered) { + const double dt = m_mark; + start(); + return dt; + } + else if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +Stopwatch::operator double() +{ + return getTime(); +} + +bool +Stopwatch::isStopped() const +{ + return m_stopped; +} + +double +Stopwatch::getTime() const +{ + if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +Stopwatch::operator double() const +{ + return getTime(); +} diff --git a/src/lib/base/Stopwatch.h b/src/lib/base/Stopwatch.h new file mode 100644 index 0000000..dda74ea --- /dev/null +++ b/src/lib/base/Stopwatch.h @@ -0,0 +1,109 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/common.h" + +//! A timer class +/*! +This class measures time intervals. All time interval measurement +should use this class. +*/ +class Stopwatch { +public: + /*! + The default constructor does an implicit reset() or setTrigger(). + If triggered == false then the clock starts ticking. + */ + Stopwatch(bool triggered = false); + ~Stopwatch(); + + //! @name manipulators + //@{ + + //! Reset the timer to zero + /*! + Set the start time to the current time, returning the time since + the last reset. This does not remove the trigger if it's set nor + does it start a stopped clock. If the clock is stopped then + subsequent reset()'s will return 0. + */ + double reset(); + + //! Stop the timer + /*! + Stop the stopwatch. The time interval while stopped is not + counted by the stopwatch. stop() does not remove the trigger. + Has no effect if already stopped. + */ + void stop(); + + //! Start the timer + /*! + Start the stopwatch. start() removes the trigger, even if the + stopwatch was already started. + */ + void start(); + + //! Stop the timer and set the trigger + /*! + setTrigger() stops the clock like stop() except there's an + implicit start() the next time (non-const) getTime() is called. + This is useful when you want the clock to start the first time + you check it. + */ + void setTrigger(); + + //! Get elapsed time + /*! + Returns the time since the last reset() (or calls reset() and + returns zero if the trigger is set). + */ + double getTime(); + //! Same as getTime() + operator double(); + //@} + //! @name accessors + //@{ + + //! Check if timer is stopped + /*! + Returns true if the stopwatch is stopped. + */ + bool isStopped() const; + + // return the time since the last reset(). + //! Get elapsed time + /*! + Returns the time since the last reset(). This cannot trigger the + stopwatch to start and will not clear the trigger. + */ + double getTime() const; + //! Same as getTime() const + operator double() const; + //@} + +private: + double getClock() const; + +private: + double m_mark; + bool m_triggered; + bool m_stopped; +}; diff --git a/src/lib/base/String.cpp b/src/lib/base/String.cpp new file mode 100644 index 0000000..97b8997 --- /dev/null +++ b/src/lib/base/String.cpp @@ -0,0 +1,295 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/Arch.h" +#include "base/String.h" +#include "common/common.h" +#include "common/stdvector.h" + +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <algorithm> +#include <stdio.h> +#include <cstdarg> +#include <sstream> +#include <iomanip> +#include <algorithm> +#include <cerrno> + +namespace barrier { +namespace string { + +String +format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + String result = vformat(fmt, args); + va_end(args); + return result; +} + +String +vformat(const char* fmt, va_list args) +{ + // find highest indexed substitution and the locations of substitutions + std::vector<size_t> pos; + std::vector<size_t> width; + std::vector<size_t> index; + size_t maxIndex = 0; + for (const char* scan = fmt; *scan != '\0'; ++scan) { + if (*scan == '%') { + ++scan; + if (*scan == '\0') { + break; + } + else if (*scan == '%') { + // literal + index.push_back(0); + pos.push_back(static_cast<size_t>((scan - 1) - fmt)); + width.push_back(2); + } + else if (*scan == '{') { + // get argument index + char* end; + errno = 0; + long i = strtol(scan + 1, &end, 10); + if (errno || (i < 0) || (*end != '}')) { + // invalid index -- ignore + scan = end - 1; // BUG if there are digits? + } + else { + index.push_back(i); + pos.push_back(static_cast<size_t>((scan - 1) - fmt)); + width.push_back(static_cast<size_t>((end - scan) + 2)); + if (i > maxIndex) { + maxIndex = i; + } + scan = end; + } + } + else { + // improper escape -- ignore + } + } + } + + // get args + std::vector<const char*> value; + std::vector<size_t> length; + value.push_back("%"); + length.push_back(1); + for (int i = 0; i < maxIndex; ++i) { + const char* arg = va_arg(args, const char*); + size_t len = strlen(arg); + value.push_back(arg); + length.push_back(len); + } + + // compute final length + size_t resultLength = strlen(fmt); + const int n = static_cast<int>(pos.size()); + for (int i = 0; i < n; ++i) { + resultLength -= width[i]; + resultLength += length[index[i]]; + } + + // substitute + String result; + result.reserve(resultLength); + size_t src = 0; + for (int i = 0; i < n; ++i) { + result.append(fmt + src, pos[i] - src); + result.append(value[index[i]]); + src = pos[i] + width[i]; + } + result.append(fmt + src); + + return result; +} + +String +sprintf(const char* fmt, ...) +{ + char tmp[1024]; + char* buffer = tmp; + int len = (int)(sizeof(tmp) / sizeof(tmp[0])); + String result; + while (buffer != NULL) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > len) { + if (buffer != tmp) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if it was big enough then save the string and don't try again + else { + result = buffer; + if (buffer != tmp) { + delete[] buffer; + } + buffer = NULL; + } + } + + return result; +} + +void +findReplaceAll( + String& subject, + const String& find, + const String& replace) +{ + size_t pos = 0; + while ((pos = subject.find(find, pos)) != String::npos) { + subject.replace(pos, find.length(), replace); + pos += replace.length(); + } +} + +String +removeFileExt(String filename) +{ + size_t dot = filename.find_last_of('.'); + + if (dot == String::npos) { + return filename; + } + + return filename.substr(0, dot); +} + +void +toHex(String& subject, int width, const char fill) +{ + std::stringstream ss; + ss << std::hex; + for (unsigned int i = 0; i < subject.length(); i++) { + ss << std::setw(width) << std::setfill(fill) << (int)(unsigned char)subject[i]; + } + + subject = ss.str(); +} + +void +uppercase(String& subject) +{ + std::transform(subject.begin(), subject.end(), subject.begin(), ::toupper); +} + +void +removeChar(String& subject, const char c) +{ + subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); +} + +String +sizeTypeToString(size_t n) +{ + std::stringstream ss; + ss << n; + return ss.str(); +} + +size_t +stringToSizeType(String string) +{ + std::istringstream iss(string); + size_t value; + iss >> value; + return value; +} + +std::vector<String> +splitString(String string, const char c) +{ + std::vector<String> results; + + size_t head = 0; + size_t separator = string.find(c); + while (separator != String::npos) { + if (head!=separator) { + results.push_back(string.substr(head, separator - head)); + } + head = separator + 1; + separator = string.find(c, head); + } + + if (head < string.size()) { + results.push_back(string.substr(head, string.size() - head)); + } + + return results; +} + +// +// CaselessCmp +// + +bool +CaselessCmp::cmpEqual( + const String::value_type& a, + const String::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) == tolower(b); +} + +bool +CaselessCmp::cmpLess( + const String::value_type& a, + const String::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) < tolower(b); +} + +bool +CaselessCmp::less(const String& a, const String& b) +{ + return std::lexicographical_compare( + a.begin(), a.end(), + b.begin(), b.end(), + &barrier::string::CaselessCmp::cmpLess); +} + +bool +CaselessCmp::equal(const String& a, const String& b) +{ + return !(less(a, b) || less(b, a)); +} + +bool +CaselessCmp::operator()(const String& a, const String& b) const +{ + return less(a, b); +} + +} +} diff --git a/src/lib/base/String.h b/src/lib/base/String.h new file mode 100644 index 0000000..3661461 --- /dev/null +++ b/src/lib/base/String.h @@ -0,0 +1,135 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/common.h" +#include "common/stdstring.h" + +#include <stdarg.h> +#include <vector> + +// use standard C++ string class for our string class +typedef std::string String; + +namespace barrier { + +//! String utilities +/*! +Provides functions for string manipulation. +*/ +namespace string { + +//! Format positional arguments +/*! +Format a string using positional arguments. fmt has literal +characters and conversion specifications introduced by `\%': +- \%\% -- literal `\%' +- \%{n} -- positional element n, n a positive integer, {} are literal + +All arguments in the variable list are const char*. Positional +elements are indexed from 1. +*/ +String format(const char* fmt, ...); + +//! Format positional arguments +/*! +Same as format() except takes va_list. +*/ +String vformat(const char* fmt, va_list); + +//! Print a string using sprintf-style formatting +/*! +Equivalent to sprintf() except the result is returned as a String. +*/ +String sprintf(const char* fmt, ...); + +//! Find and replace all +/*! +Finds \c find inside \c subject and replaces it with \c replace +*/ +void findReplaceAll(String& subject, const String& find, const String& replace); + +//! Remove file extension +/*! +Finds the last dot and remove all characters from the dot to the end +*/ +String removeFileExt(String filename); + +//! Convert into hexdecimal +/*! +Convert each character in \c subject into hexdecimal form with \c width +*/ +void toHex(String& subject, int width, const char fill = '0'); + +//! Convert to all uppercase +/*! +Convert each character in \c subject to uppercase +*/ +void uppercase(String& subject); + +//! Remove all specific char in suject +/*! +Remove all specific \c c in \c suject +*/ +void removeChar(String& subject, const char c); + +//! Convert a size type to a string +/*! +Convert an size type to a string +*/ +String sizeTypeToString(size_t n); + +//! Convert a string to a size type +/*! +Convert an a \c string to an size type +*/ +size_t stringToSizeType(String string); + +//! Split a string into substrings +/*! +Split a \c string that separated by a \c c into substrings +*/ +std::vector<String> splitString(String string, const char c); + +//! Case-insensitive comparisons +/*! +This class provides case-insensitve comparison functions. +*/ +class CaselessCmp { + public: + //! Same as less() + bool operator()(const String& a, const String& b) const; + + //! Returns true iff \c a is lexicographically less than \c b + static bool less(const String& a, const String& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool equal(const String& a, const String& b); + + //! Returns true iff \c a is lexicographically less than \c b + static bool cmpLess(const String::value_type& a, + const String::value_type& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool cmpEqual(const String::value_type& a, + const String::value_type& b); +}; + +} +} diff --git a/src/lib/base/TMethodEventJob.h b/src/lib/base/TMethodEventJob.h new file mode 100644 index 0000000..a65f8c9 --- /dev/null +++ b/src/lib/base/TMethodEventJob.h @@ -0,0 +1,71 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "IEventJob.h" + +//! Use a member function as an event job +/*! +An event job class that invokes a member function. +*/ +template <class T> +class TMethodEventJob : public IEventJob { +public: + //! run(event) invokes \c object->method(event, arg) + TMethodEventJob(T* object, + void (T::*method)(const Event&, void*), + void* arg = NULL); + virtual ~TMethodEventJob(); + + // IJob overrides + virtual void run(const Event&); + +private: + T* m_object; + void (T::*m_method)(const Event&, void*); + void* m_arg; +}; + +template <class T> +inline +TMethodEventJob<T>::TMethodEventJob(T* object, + void (T::*method)(const Event&, void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template <class T> +inline +TMethodEventJob<T>::~TMethodEventJob() +{ + // do nothing +} + +template <class T> +inline +void +TMethodEventJob<T>::run(const Event& event) +{ + if (m_object != NULL) { + (m_object->*m_method)(event, m_arg); + } +} diff --git a/src/lib/base/TMethodJob.h b/src/lib/base/TMethodJob.h new file mode 100644 index 0000000..ec88f05 --- /dev/null +++ b/src/lib/base/TMethodJob.h @@ -0,0 +1,68 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a member function. +*/ +template <class T> +class TMethodJob : public IJob { +public: + //! run() invokes \c object->method(arg) + TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL); + virtual ~TMethodJob(); + + // IJob overrides + virtual void run(); + +private: + T* m_object; + void (T::*m_method)(void*); + void* m_arg; +}; + +template <class T> +inline +TMethodJob<T>::TMethodJob(T* object, void (T::*method)(void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template <class T> +inline +TMethodJob<T>::~TMethodJob() +{ + // do nothing +} + +template <class T> +inline +void +TMethodJob<T>::run() +{ + if (m_object != NULL) { + (m_object->*m_method)(m_arg); + } +} diff --git a/src/lib/base/Unicode.cpp b/src/lib/base/Unicode.cpp new file mode 100644 index 0000000..6a077e7 --- /dev/null +++ b/src/lib/base/Unicode.cpp @@ -0,0 +1,784 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/Arch.h" +#include "base/Unicode.h" + +#include <cstring> + +// +// local utility functions +// + +inline +static +UInt16 +decode16(const UInt8* n, bool byteSwapped) +{ + union x16 { + UInt8 n8[2]; + UInt16 n16; + } c; + if (byteSwapped) { + c.n8[0] = n[1]; + c.n8[1] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + } + return c.n16; +} + +inline +static +UInt32 +decode32(const UInt8* n, bool byteSwapped) +{ + union x32 { + UInt8 n8[4]; + UInt32 n32; + } c; + if (byteSwapped) { + c.n8[0] = n[3]; + c.n8[1] = n[2]; + c.n8[2] = n[1]; + c.n8[3] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + c.n8[2] = n[2]; + c.n8[3] = n[3]; + } + return c.n32; +} + +inline +static +void +resetError(bool* errors) +{ + if (errors != NULL) { + *errors = false; + } +} + +inline +static +void +setError(bool* errors) +{ + if (errors != NULL) { + *errors = true; + } +} + + +// +// Unicode +// + +UInt32 Unicode::s_invalid = 0x0000ffff; +UInt32 Unicode::s_replacement = 0x0000fffd; + +bool +Unicode::isUTF8(const String& src) +{ + // convert and test each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + for (UInt32 n = (UInt32)src.size(); n > 0; ) { + if (fromUTF8(data, n) == s_invalid) { + return false; + } + } + return true; +} + +String +Unicode::UTF8ToUCS2(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00010000) { + setError(errors); + c = s_replacement; + } + UInt16 ucs2 = static_cast<UInt16>(c); + dst.append(reinterpret_cast<const char*>(&ucs2), 2); + } + + return dst; +} + +String +Unicode::UTF8ToUCS4(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + dst.append(reinterpret_cast<const char*>(&c), 4); + } + + return dst; +} + +String +Unicode::UTF8ToUTF16(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + if (c < 0x00010000) { + UInt16 ucs2 = static_cast<UInt16>(c); + dst.append(reinterpret_cast<const char*>(&ucs2), 2); + } + else { + c -= 0x00010000; + UInt16 utf16h = static_cast<UInt16>((c >> 10) + 0xd800); + UInt16 utf16l = static_cast<UInt16>((c & 0x03ff) + 0xdc00); + dst.append(reinterpret_cast<const char*>(&utf16h), 2); + dst.append(reinterpret_cast<const char*>(&utf16l), 2); + } + } + + return dst; +} + +String +Unicode::UTF8ToUTF32(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + dst.append(reinterpret_cast<const char*>(&c), 4); + } + + return dst; +} + +String +Unicode::UTF8ToText(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert to wide char + UInt32 size; + wchar_t* tmp = UTF8ToWideChar(src, size, errors); + + // convert string to multibyte + int len = ARCH->convStringWCToMB(NULL, tmp, size, errors); + char* mbs = new char[len + 1]; + ARCH->convStringWCToMB(mbs, tmp, size, errors); + String text(mbs, len); + + // clean up + delete[] mbs; + delete[] tmp; + + return text; +} + +String +Unicode::UCS2ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 1; + return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UCS4ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 2; + return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UTF16ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 1; + return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UTF32ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 2; + return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::textToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert string to wide characters + UInt32 n = (UInt32)src.size(); + int len = ARCH->convStringMBToWC(NULL, src.c_str(), n, errors); + wchar_t* wcs = new wchar_t[len + 1]; + ARCH->convStringMBToWC(wcs, src.c_str(), n, errors); + + // convert to UTF8 + String utf8 = wideCharToUTF8(wcs, len, errors); + + // clean up + delete[] wcs; + + return utf8; +} + +wchar_t* +Unicode::UTF8ToWideChar(const String& src, UInt32& size, bool* errors) +{ + // convert to platform's wide character encoding + String tmp; + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + tmp = UTF8ToUCS2(src, errors); + size = (UInt32)tmp.size() >> 1; + break; + + case IArchString::kUCS4: + tmp = UTF8ToUCS4(src, errors); + size = (UInt32)tmp.size() >> 2; + break; + + case IArchString::kUTF16: + tmp = UTF8ToUTF16(src, errors); + size = (UInt32)tmp.size() >> 1; + break; + + case IArchString::kUTF32: + tmp = UTF8ToUTF32(src, errors); + size = (UInt32)tmp.size() >> 2; + break; + + default: + assert(0 && "unknown wide character encoding"); + } + + // copy to a wchar_t array + wchar_t* dst = new wchar_t[size]; + ::memcpy(dst, tmp.data(), sizeof(wchar_t) * size); + return dst; +} + +String +Unicode::wideCharToUTF8(const wchar_t* src, UInt32 size, bool* errors) +{ + // convert from platform's wide character encoding. + // note -- this must include a wide nul character (independent of + // the String's nul character). + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUCS4: + return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUTF16: + return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUTF32: + return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + default: + assert(0 && "unknown wide character encoding"); + return String(); + } +} + +String +Unicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +String +Unicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +String +Unicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + if (c < 0x0000d800 || c > 0x0000dfff) { + toUTF8(dst, c, errors); + } + else if (n == 1) { + // error -- missing second word + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else if (c >= 0x0000d800 && c <= 0x0000dbff) { + UInt32 c2 = decode16(data, byteSwapped); + data += 2; + --n; + if (c2 < 0x0000dc00 || c2 > 0x0000dfff) { + // error -- [d800,dbff] not followed by [dc00,dfff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else { + c = (((c - 0x0000d800) << 10) | (c2 - 0x0000dc00)) + 0x00010000; + toUTF8(dst, c, errors); + } + } + else { + // error -- [dc00,dfff] without leading [d800,dbff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + } + + return dst; +} + +String +Unicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + toUTF8(dst, c, errors); + } + + return dst; +} + +UInt32 +Unicode::fromUTF8(const UInt8*& data, UInt32& n) +{ + assert(data != NULL); + assert(n != 0); + + // compute character encoding length, checking for overlong + // sequences (i.e. characters that don't use the shortest + // possible encoding). + UInt32 size; + if (data[0] < 0x80) { + // 0xxxxxxx + size = 1; + } + else if (data[0] < 0xc0) { + // 10xxxxxx -- in the middle of a multibyte character. counts + // as one invalid character. + --n; + ++data; + return s_invalid; + } + else if (data[0] < 0xe0) { + // 110xxxxx + size = 2; + } + else if (data[0] < 0xf0) { + // 1110xxxx + size = 3; + } + else if (data[0] < 0xf8) { + // 11110xxx + size = 4; + } + else if (data[0] < 0xfc) { + // 111110xx + size = 5; + } + else if (data[0] < 0xfe) { + // 1111110x + size = 6; + } + else { + // invalid sequence. dunno how many bytes to skip so skip one. + --n; + ++data; + return s_invalid; + } + + // make sure we have enough data + if (size > n) { + data += n; + n = 0; + return s_invalid; + } + + // extract character + UInt32 c; + switch (size) { + case 1: + c = static_cast<UInt32>(data[0]); + break; + + case 2: + c = ((static_cast<UInt32>(data[0]) & 0x1f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 3: + c = ((static_cast<UInt32>(data[0]) & 0x0f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[2]) & 0x3f) ); + break; + + case 4: + c = ((static_cast<UInt32>(data[0]) & 0x07) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 5: + c = ((static_cast<UInt32>(data[0]) & 0x03) << 24) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 6: + c = ((static_cast<UInt32>(data[0]) & 0x01) << 30) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 24) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + default: + assert(0 && "invalid size"); + return s_invalid; + } + + // check that all bytes after the first have the pattern 10xxxxxx. + // truncated sequences are treated as a single malformed character. + bool truncated = false; + switch (size) { + case 6: + if ((data[5] & 0xc0) != 0x80) { + truncated = true; + size = 5; + } + // fall through + + case 5: + if ((data[4] & 0xc0) != 0x80) { + truncated = true; + size = 4; + } + // fall through + + case 4: + if ((data[3] & 0xc0) != 0x80) { + truncated = true; + size = 3; + } + // fall through + + case 3: + if ((data[2] & 0xc0) != 0x80) { + truncated = true; + size = 2; + } + // fall through + + case 2: + if ((data[1] & 0xc0) != 0x80) { + truncated = true; + size = 1; + } + } + + // update parameters + data += size; + n -= size; + + // invalid if sequence was truncated + if (truncated) { + return s_invalid; + } + + // check for characters that didn't use the smallest possible encoding + static UInt32 s_minChar[] = { + 0, + 0x00000000, + 0x00000080, + 0x00000800, + 0x00010000, + 0x00200000, + 0x04000000 + }; + if (c < s_minChar[size]) { + return s_invalid; + } + + // check for characters not in ISO-10646 + if (c >= 0x0000d800 && c <= 0x0000dfff) { + return s_invalid; + } + if (c >= 0x0000fffe && c <= 0x0000ffff) { + return s_invalid; + } + + return c; +} + +void +Unicode::toUTF8(String& dst, UInt32 c, bool* errors) +{ + UInt8 data[6]; + + // handle characters outside the valid range + if ((c >= 0x0000d800 && c <= 0x0000dfff) || c >= 0x80000000) { + setError(errors); + c = s_replacement; + } + + // convert to UTF-8 + if (c < 0x00000080) { + data[0] = static_cast<UInt8>(c); + dst.append(reinterpret_cast<char*>(data), 1); + } + else if (c < 0x00000800) { + data[0] = static_cast<UInt8>(((c >> 6) & 0x0000001f) + 0xc0); + data[1] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 2); + } + else if (c < 0x00010000) { + data[0] = static_cast<UInt8>(((c >> 12) & 0x0000000f) + 0xe0); + data[1] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 3); + } + else if (c < 0x00200000) { + data[0] = static_cast<UInt8>(((c >> 18) & 0x00000007) + 0xf0); + data[1] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 4); + } + else if (c < 0x04000000) { + data[0] = static_cast<UInt8>(((c >> 24) & 0x00000003) + 0xf8); + data[1] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[4] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 5); + } + else if (c < 0x80000000) { + data[0] = static_cast<UInt8>(((c >> 30) & 0x00000001) + 0xfc); + data[1] = static_cast<UInt8>(((c >> 24) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[4] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[5] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 6); + } + else { + assert(0 && "character out of range"); + } +} diff --git a/src/lib/base/Unicode.h b/src/lib/base/Unicode.h new file mode 100644 index 0000000..1391c1e --- /dev/null +++ b/src/lib/base/Unicode.h @@ -0,0 +1,144 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/String.h" +#include "common/basic_types.h" + +//! Unicode utility functions +/*! +This class provides functions for converting between various Unicode +encodings and the current locale encoding. +*/ +class Unicode { +public: + //! @name accessors + //@{ + + //! Test UTF-8 string for validity + /*! + Returns true iff the string contains a valid sequence of UTF-8 + encoded characters. + */ + static bool isUTF8(const String&); + + //! Convert from UTF-8 to UCS-2 encoding + /*! + Convert from UTF-8 to UCS-2. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-2. + Decoding errors do not set *errors. + */ + static String UTF8ToUCS2(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UCS-4 encoding + /*! + Convert from UTF-8 to UCS-4. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-4. + Decoding errors do not set *errors. + */ + static String UTF8ToUCS4(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-16 encoding + /*! + Convert from UTF-8 to UTF-16. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-16. + Decoding errors do not set *errors. + */ + static String UTF8ToUTF16(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-32 encoding + /*! + Convert from UTF-8 to UTF-32. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-32. + Decoding errors do not set *errors. + */ + static String UTF8ToUTF32(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to the current locale encoding + /*! + Convert from UTF-8 to the current locale encoding. If errors is not + NULL then *errors is set to true iff any character could not be encoded. + Decoding errors do not set *errors. + */ + static String UTF8ToText(const String&, bool* errors = NULL); + + //! Convert from UCS-2 to UTF-8 + /*! + Convert from UCS-2 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UCS2ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UCS-4 to UTF-8 + /*! + Convert from UCS-4 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UCS4ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UTF-16 to UTF-8 + /*! + Convert from UTF-16 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UTF16ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UTF-32 to UTF-8 + /*! + Convert from UTF-32 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UTF32ToUTF8(const String&, bool* errors = NULL); + + //! Convert from the current locale encoding to UTF-8 + /*! + Convert from the current locale encoding to UTF-8. If errors is not + NULL then *errors is set to true iff any character could not be decoded. + */ + static String textToUTF8(const String&, bool* errors = NULL); + + //@} + +private: + // convert UTF8 to wchar_t string (using whatever encoding is native + // to the platform). caller must delete[] the returned string. the + // string is *not* nul terminated; the length (in characters) is + // returned in size. + static wchar_t* UTF8ToWideChar(const String&, + UInt32& size, bool* errors); + + // convert nul terminated wchar_t string (in platform's native + // encoding) to UTF8. + static String wideCharToUTF8(const wchar_t*, + UInt32 size, bool* errors); + + // internal conversion to UTF8 + static String doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors); + + // convert characters to/from UTF8 + static UInt32 fromUTF8(const UInt8*& src, UInt32& size); + static void toUTF8(String& dst, UInt32 c, bool* errors); + +private: + static UInt32 s_invalid; + static UInt32 s_replacement; +}; diff --git a/src/lib/base/XBase.cpp b/src/lib/base/XBase.cpp new file mode 100644 index 0000000..29ae927 --- /dev/null +++ b/src/lib/base/XBase.cpp @@ -0,0 +1,76 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/XBase.h" +#include "base/String.h" + +#include <cerrno> +#include <cstdarg> + +// +// XBase +// + +XBase::XBase() : + std::runtime_error("") +{ + // do nothing +} + +XBase::XBase(const String& msg) : + std::runtime_error(msg) +{ + // do nothing +} + +XBase::~XBase() _NOEXCEPT +{ + // do nothing +} + +const char* +XBase::what() const _NOEXCEPT +{ + const char* what = std::runtime_error::what(); + if (strlen(what) == 0) { + m_what = getWhat(); + return m_what.c_str(); + } + return what; +} + +String +XBase::format(const char* /*id*/, const char* fmt, ...) const throw() +{ + // FIXME -- lookup message string using id as an index. set + // fmt to that string if it exists. + + // format + String result; + va_list args; + va_start(args, fmt); + try { + result = barrier::string::vformat(fmt, args); + } + catch (...) { + // ignore + } + va_end(args); + + return result; +} diff --git a/src/lib/base/XBase.h b/src/lib/base/XBase.h new file mode 100644 index 0000000..3064b6c --- /dev/null +++ b/src/lib/base/XBase.h @@ -0,0 +1,125 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "base/String.h" +#include "common/stdexcept.h" + +//! Exception base class +/*! +This is the base class of most exception types. +*/ +class XBase : public std::runtime_error { +public: + //! Use getWhat() as the result of what() + XBase(); + //! Use \c msg as the result of what() + XBase(const String& msg); + virtual ~XBase() _NOEXCEPT; + + //! Reason for exception + virtual const char* what() const _NOEXCEPT; + +protected: + //! Get a human readable string describing the exception + virtual String getWhat() const throw() { return ""; } + + //! Format a string + /*! + Looks up a message format using \c id, using \c defaultFormat if + no format can be found, then replaces positional parameters in + the format string and returns the result. + */ + virtual String format(const char* id, + const char* defaultFormat, ...) const throw(); +private: + mutable String m_what; +}; + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. getWhat() is not +declared. +*/ +#define XBASE_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const String& msg) : super_(msg) { } \ + virtual ~name_() _NOEXCEPT { } \ +} + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. getWhat() must be +implemented. +*/ +#define XBASE_SUBCLASS_WHAT(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const String& msg) : super_(msg) { } \ + virtual ~name_() _NOEXCEPT { } \ + \ +protected: \ + virtual String getWhat() const throw(); \ +} + +/*! +\def XBASE_SUBCLASS_FORMAT +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. what() is overridden +to call getWhat() when first called; getWhat() can format the +error message and can call what() to get the message passed to the +c'tor. +*/ +#define XBASE_SUBCLASS_FORMAT(name_, super_) \ +class name_ : public super_ { \ +private: \ + enum EState { kFirst, kFormat, kDone }; \ + \ +public: \ + name_() : super_(), m_state(kDone) { } \ + name_(const String& msg) : super_(msg), m_state(kFirst) { } \ + virtual ~name_() _NOEXCEPT { } \ + \ + virtual const char* what() const _NOEXCEPT \ + { \ + if (m_state == kFirst) { \ + m_state = kFormat; \ + m_formatted = getWhat(); \ + m_state = kDone; \ + } \ + if (m_state == kDone) { \ + return m_formatted.c_str(); \ + } \ + else { \ + return super_::what(); \ + } \ + } \ + \ +protected: \ + virtual String getWhat() const throw(); \ + \ +private: \ + mutable EState m_state; \ + mutable std::string m_formatted; \ +} diff --git a/src/lib/base/log_outputters.cpp b/src/lib/base/log_outputters.cpp new file mode 100644 index 0000000..8e56c26 --- /dev/null +++ b/src/lib/base/log_outputters.cpp @@ -0,0 +1,337 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "base/log_outputters.h" +#include "base/TMethodJob.h" +#include "arch/Arch.h" + +#include <fstream> + +enum EFileLogOutputter { + kFileSizeLimit = 1024 // kb +}; + +// +// StopLogOutputter +// + +StopLogOutputter::StopLogOutputter() +{ + // do nothing +} + +StopLogOutputter::~StopLogOutputter() +{ + // do nothing +} + +void +StopLogOutputter::open(const char*) +{ + // do nothing +} + +void +StopLogOutputter::close() +{ + // do nothing +} + +void +StopLogOutputter::show(bool) +{ + // do nothing +} + +bool +StopLogOutputter::write(ELevel, const char*) +{ + return false; +} + + +// +// ConsoleLogOutputter +// + +ConsoleLogOutputter::ConsoleLogOutputter() +{ +} + +ConsoleLogOutputter::~ConsoleLogOutputter() +{ +} + +void +ConsoleLogOutputter::open(const char* title) +{ + ARCH->openConsole(title); +} + +void +ConsoleLogOutputter::close() +{ + ARCH->closeConsole(); +} + +void +ConsoleLogOutputter::show(bool showIfEmpty) +{ + ARCH->showConsole(showIfEmpty); +} + +bool +ConsoleLogOutputter::write(ELevel level, const char* msg) +{ + ARCH->writeConsole(level, msg); + return true; +} + +void +ConsoleLogOutputter::flush() +{ + +} + + +// +// SystemLogOutputter +// + +SystemLogOutputter::SystemLogOutputter() +{ + // do nothing +} + +SystemLogOutputter::~SystemLogOutputter() +{ + // do nothing +} + +void +SystemLogOutputter::open(const char* title) +{ + ARCH->openLog(title); +} + +void +SystemLogOutputter::close() +{ + ARCH->closeLog(); +} + +void +SystemLogOutputter::show(bool showIfEmpty) +{ + ARCH->showLog(showIfEmpty); +} + +bool +SystemLogOutputter::write(ELevel level, const char* msg) +{ + ARCH->writeLog(level, msg); + return true; +} + +// +// SystemLogger +// + +SystemLogger::SystemLogger(const char* title, bool blockConsole) : + m_stop(NULL) +{ + // redirect log messages + if (blockConsole) { + m_stop = new StopLogOutputter; + CLOG->insert(m_stop); + } + m_syslog = new SystemLogOutputter; + m_syslog->open(title); + CLOG->insert(m_syslog); +} + +SystemLogger::~SystemLogger() +{ + CLOG->remove(m_syslog); + delete m_syslog; + if (m_stop != NULL) { + CLOG->remove(m_stop); + delete m_stop; + } +} + + +// +// BufferedLogOutputter +// + +BufferedLogOutputter::BufferedLogOutputter(UInt32 maxBufferSize) : + m_maxBufferSize(maxBufferSize) +{ + // do nothing +} + +BufferedLogOutputter::~BufferedLogOutputter() +{ + // do nothing +} + +BufferedLogOutputter::const_iterator +BufferedLogOutputter::begin() const +{ + return m_buffer.begin(); +} + +BufferedLogOutputter::const_iterator +BufferedLogOutputter::end() const +{ + return m_buffer.end(); +} + +void +BufferedLogOutputter::open(const char*) +{ + // do nothing +} + +void +BufferedLogOutputter::close() +{ + // remove all elements from the buffer + m_buffer.clear(); +} + +void +BufferedLogOutputter::show(bool) +{ + // do nothing +} + +bool +BufferedLogOutputter::write(ELevel, const char* message) +{ + while (m_buffer.size() >= m_maxBufferSize) { + m_buffer.pop_front(); + } + m_buffer.push_back(String(message)); + return true; +} + + +// +// FileLogOutputter +// + +FileLogOutputter::FileLogOutputter(const char* logFile) +{ + setLogFilename(logFile); +} + +FileLogOutputter::~FileLogOutputter() +{ +} + +void +FileLogOutputter::setLogFilename(const char* logFile) +{ + assert(logFile != NULL); + m_fileName = logFile; +} + +bool +FileLogOutputter::write(ELevel level, const char *message) +{ + bool moveFile = false; + + std::ofstream m_handle; + m_handle.open(m_fileName.c_str(), std::fstream::app); + if (m_handle.is_open() && m_handle.fail() != true) { + m_handle << message << std::endl; + + // when file size exceeds limits, move to 'old log' filename. + size_t p = m_handle.tellp(); + if (p > (kFileSizeLimit * 1024)) { + moveFile = true; + } + } + m_handle.close(); + + if (moveFile) { + String oldLogFilename = barrier::string::sprintf("%s.1", m_fileName.c_str()); + remove(oldLogFilename.c_str()); + rename(m_fileName.c_str(), oldLogFilename.c_str()); + } + + return true; +} + +void +FileLogOutputter::open(const char *title) {} + +void +FileLogOutputter::close() {} + +void +FileLogOutputter::show(bool showIfEmpty) {} + +// +// MesssageBoxLogOutputter +// + +MesssageBoxLogOutputter::MesssageBoxLogOutputter() +{ + // do nothing +} + +MesssageBoxLogOutputter::~MesssageBoxLogOutputter() +{ + // do nothing +} + +void +MesssageBoxLogOutputter::open(const char* title) +{ + // do nothing +} + +void +MesssageBoxLogOutputter::close() +{ + // do nothing +} + +void +MesssageBoxLogOutputter::show(bool showIfEmpty) +{ + // do nothing +} + +bool +MesssageBoxLogOutputter::write(ELevel level, const char* msg) +{ + // don't spam user with messages. + if (level > kERROR) { + return true; + } + +#if SYSAPI_WIN32 + MessageBox(NULL, msg, CLOG->getFilterName(level), MB_OK); +#endif + + return true; +} diff --git a/src/lib/base/log_outputters.h b/src/lib/base/log_outputters.h new file mode 100644 index 0000000..c4940aa --- /dev/null +++ b/src/lib/base/log_outputters.h @@ -0,0 +1,172 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "mt/Thread.h" +#include "base/ILogOutputter.h" +#include "base/String.h" +#include "common/basic_types.h" +#include "common/stddeque.h" + +#include <list> +#include <fstream> + +//! Stop traversing log chain outputter +/*! +This outputter performs no output and returns false from \c write(), +causing the logger to stop traversing the outputter chain. Insert +this to prevent already inserted outputters from writing. +*/ +class StopLogOutputter : public ILogOutputter { +public: + StopLogOutputter(); + virtual ~StopLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; + +//! Write log to console +/*! +This outputter writes output to the console. The level for each +message is ignored. +*/ +class ConsoleLogOutputter : public ILogOutputter { +public: + ConsoleLogOutputter(); + virtual ~ConsoleLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual void flush(); +}; + +//! Write log to file +/*! +This outputter writes output to the file. The level for each +message is ignored. +*/ + +class FileLogOutputter : public ILogOutputter { +public: + FileLogOutputter(const char* logFile); + virtual ~FileLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + + void setLogFilename(const char* title); + +private: + std::string m_fileName; +}; + +//! Write log to system log +/*! +This outputter writes output to the system log. +*/ +class SystemLogOutputter : public ILogOutputter { +public: + SystemLogOutputter(); + virtual ~SystemLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; + +//! Write log to system log only +/*! +Creating an object of this type inserts a StopLogOutputter followed +by a SystemLogOutputter into Log. The destructor removes those +outputters. Add one of these to any scope that needs to write to +the system log (only) and restore the old outputters when exiting +the scope. +*/ +class SystemLogger { +public: + SystemLogger(const char* title, bool blockConsole); + ~SystemLogger(); + +private: + ILogOutputter* m_syslog; + ILogOutputter* m_stop; +}; + +//! Save log history +/*! +This outputter records the last N log messages. +*/ +class BufferedLogOutputter : public ILogOutputter { +private: + typedef std::deque<String> Buffer; + +public: + typedef Buffer::const_iterator const_iterator; + + BufferedLogOutputter(UInt32 maxBufferSize); + virtual ~BufferedLogOutputter(); + + //! @name accessors + //@{ + + //! Get start of buffer + const_iterator begin() const; + + //! Get end of buffer + const_iterator end() const; + + //@} + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +private: + UInt32 m_maxBufferSize; + Buffer m_buffer; +}; + +//! Write log to message box +/*! +The level for each message is ignored. +*/ +class MesssageBoxLogOutputter : public ILogOutputter { +public: + MesssageBoxLogOutputter(); + virtual ~MesssageBoxLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; |
