diff options
Diffstat (limited to 'src/lib/base/EventQueue.cpp')
| -rw-r--r-- | src/lib/base/EventQueue.cpp | 658 |
1 files changed, 658 insertions, 0 deletions
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; +} |
