/* * Copyright 2005, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de */ #include "EventDispatcher.h" #include "EventStream.h" #include "HWInterface.h" #include "InputManager.h" #include #include #include #include #include //#define TRACE_EVENTS #ifdef TRACE_EVENTS # define ETRACE(x) printf x #else # define ETRACE(x) ; #endif /*! The differentiation between messenger and token looks odd, but it really has a reason as well: All events are sent to the window only - it may then use the token or token list to identify the specific target view(s). */ struct EventDispatcher::event_target { BMessenger messenger; int32 token; uint32 event_mask; uint32 options; uint32 temporary_event_mask; uint32 temporary_options; }; static const char* kTokenName = "_token"; static const float kMouseMovedImportance = 0.1f; static const float kMouseTransitImportance = 1.0f; static const float kStandardImportance = 0.9f; static const float kListenerImportance = 0.8f; EventDispatcher::EventDispatcher() : BLocker("event dispatcher"), fStream(NULL), fThread(-1), fCursorThread(-1), fHasFocus(false), fHasLastFocus(false), fTransit(false), fSuspendFocus(false), fMouseFilter(NULL), fKeyboardFilter(NULL), fListeners(10, true), // the list owns its items fCursorLock("cursor loop lock"), fHWInterface(NULL) { } EventDispatcher::~EventDispatcher() { _Unset(); } status_t EventDispatcher::SetTo(EventStream* stream) { ETRACE(("event dispatcher: stream = %p\n", stream)); _Unset(); if (stream == NULL) return B_OK; fStream = stream; return _Run(); } status_t EventDispatcher::InitCheck() { if (fStream != NULL) { if (fThread < B_OK) return fThread; return B_OK; } return B_NO_INIT; } void EventDispatcher::_Unset() { if (fStream == NULL) return; fStream->SendQuit(); wait_for_thread(fThread, NULL); wait_for_thread(fCursorThread, NULL); fThread = fCursorThread = -1; gInputManager->PutStream(fStream); fStream = NULL; } status_t EventDispatcher::_Run() { fThread = spawn_thread(_event_looper, "event loop", B_REAL_TIME_DISPLAY_PRIORITY - 10, this); if (fThread < B_OK) return fThread; if (fStream->SupportsCursorThread()) { ETRACE(("event stream supports cursor thread!\n")); fCursorThread = spawn_thread(_cursor_looper, "cursor loop", B_REAL_TIME_DISPLAY_PRIORITY - 5, this); if (resume_thread(fCursorThread) != B_OK) { kill_thread(fCursorThread); fCursorThread = -1; } } return resume_thread(fThread); } void EventDispatcher::SetFocus(const BMessenger* messenger) { BAutolock _(this); if ((messenger == NULL && !fHasFocus) || (messenger != NULL && fFocus == *messenger)) return; fHasLastFocus = fHasFocus; fLastFocusTokens.MakeEmpty(); if (fHasFocus) { fLastFocus = fFocus; fLastFocusTokens.AddList(&fFocusTokens); } fHasFocus = messenger != NULL; fFocusTokens.MakeEmpty(); if (fHasFocus) { fFocus = *messenger; // add all tokens that target this messenger for (int32 i = fListeners.CountItems(); i-- > 0;) { event_target* target = fListeners.ItemAt(i); if (target->messenger == fFocus) fFocusTokens.AddItem((void *)target->token); } } fTransit = true; } EventDispatcher::event_target* EventDispatcher::_FindListener(const BMessenger& messenger, int32 token, int32* _index) { for (int32 i = fListeners.CountItems(); i-- > 0;) { event_target* target = fListeners.ItemAt(i); if (target->token == token && target->messenger == messenger) { if (_index) *_index = i; return target; } } return NULL; } /*! \brief Adds the specified listener or updates its event mask and options if already added. It follows the BView semantics in that specifiying an event mask of zero leaves the event mask untouched and just updates the options. */ bool EventDispatcher::_AddListener(const BMessenger& messenger, int32 token, uint32 eventMask, uint32 options, bool temporary) { BAutolock _(this); event_target* target = _FindListener(messenger, token); if (target != NULL) { // we already have this target, update its event mask if (temporary) { if (eventMask != 0) target->temporary_event_mask = eventMask; target->temporary_options = options; } else { if (eventMask != 0) target->event_mask = eventMask; target->options = options; } return true; } if (eventMask == 0) return false; ETRACE(("events: add listener: token %ld, eventMask = %ld, options = %ld, %s\n", token, eventMask, options, temporary ? "temporary" : "permanent")); // we need a new target target = new (std::nothrow) event_target; if (target == NULL) return false; target->messenger = messenger; target->token = token; if (temporary) { target->event_mask = 0; target->options = 0; target->temporary_event_mask = eventMask; target->temporary_options = options; if (options & B_SUSPEND_VIEW_FOCUS) fSuspendFocus = true; } else { target->event_mask = eventMask; target->options = options; target->temporary_event_mask = 0; target->temporary_options = 0; } bool success = fListeners.AddItem(target); if (success) { if (fHasFocus && fFocus == messenger) { // add this token to the current token list ETRACE((" add token %ld to focus list\n", token)); fFocusTokens.AddItem((void *)token); } } else delete target; return success; } void EventDispatcher::_RemoveTemporaryListener(event_target* target, int32 index) { if (target->event_mask == 0) { // this is only a temporary target fListeners.RemoveItemAt(index); ETRACE(("events: remove temp. listener: token %ld, eventMask = %ld, options = %ld\n", target->token, target->temporary_event_mask, target->temporary_options)); if (fHasFocus && fFocus == target->messenger) { // remove this token from the current token list ETRACE((" remove token %ld from focus list\n", target->token)); fFocusTokens.RemoveItem((void *)target->token); } delete target; } else if (target->temporary_event_mask != 0) { ETRACE(("events: clear temp. listener: token %ld, eventMask = %ld, options = %ld\n", target->token, target->temporary_event_mask, target->temporary_options)); target->temporary_event_mask = 0; target->temporary_options = 0; } } void EventDispatcher::_RemoveTemporaryListeners() { for (int32 i = fListeners.CountItems(); i-- > 0;) { event_target* target = fListeners.ItemAt(i); _RemoveTemporaryListener(target, i); } } bool EventDispatcher::AddListener(const BMessenger& messenger, int32 token, uint32 eventMask, uint32 options) { options &= B_NO_POINTER_HISTORY; // that's currently the only allowed option return _AddListener(messenger, token, eventMask, options, false); } bool EventDispatcher::AddTemporaryListener(const BMessenger& messenger, int32 token, uint32 eventMask, uint32 options) { return _AddListener(messenger, token, eventMask, options, true); } void EventDispatcher::RemoveListener(const BMessenger& messenger, int32 token) { BAutolock _(this); ETRACE(("events: remove listener token %ld\n", token)); int32 index; event_target* target = _FindListener(messenger, token, &index); if (target == NULL) return; if (target->temporary_event_mask != 0) { // we still need this event target->event_mask = 0; target->options = 0; return; } if (fHasFocus && fFocus == target->messenger) { // remove this token from the current token list ETRACE((" remove token %ld from focus list\n", target->token)); fFocusTokens.RemoveItem((void *)target->token); } fListeners.RemoveItemAt(index); delete target; } void EventDispatcher::RemoveTemporaryListener(const BMessenger& messenger, int32 token) { BAutolock _(this); int32 index; event_target* target = _FindListener(messenger, token, &index); if (target == NULL) return; _RemoveTemporaryListener(target, index); } void EventDispatcher::SetMouseFilter(BMessageFilter* filter) { BAutolock _(this); if (fMouseFilter == filter) return; delete fMouseFilter; fMouseFilter = filter; } void EventDispatcher::SetKeyboardFilter(BMessageFilter* filter) { BAutolock _(this); if (fKeyboardFilter == filter) return; delete fKeyboardFilter; fKeyboardFilter = filter; } void EventDispatcher::GetMouse(BPoint& where, int32& buttons) { BAutolock _(this); where = fLastCursorPosition; buttons = fLastButtons; } bool EventDispatcher::HasCursorThread() { return fCursorThread >= B_OK; } /*! \brief Sets the HWInterface to use when moving the mouse cursor. \a interface is allowed to be NULL. */ void EventDispatcher::SetHWInterface(HWInterface* interface) { BAutolock _(fCursorLock); fHWInterface = interface; } /*! \brief Sends \a message to the provided \a messenger. TODO: the following feature is not yet implemented: If the message could not be delivered immediately, it is included in a waiting message queue with a fixed length - the least important messages are removed first when that gets full. Returns "false" if the target port does not exist anymore, "true" if it doesn't. */ bool EventDispatcher::_SendMessage(BMessenger& messenger, BMessage* message, float importance) { // TODO: add failed messages to a queue, and start dropping them by importance status_t status = messenger.SendMessage(message, (BHandler*)NULL, 100000); if (status != B_OK) printf("failed to send message to target: %lx\n", message->what); if (status == B_BAD_PORT_ID) { // the target port is gone return false; } return true; } bool EventDispatcher::_AddTokens(BMessage* message, BList& tokens) { _RemoveTokens(message); int32 count = tokens.CountItems(); for (int32 i = count; i-- > 0;) { int32 token = (int32)tokens.ItemAt(i); ETRACE((" add token %ld\n", token)); if (message->AddInt32(kTokenName, token) != B_OK) count--; } return count != 0; } void EventDispatcher::_RemoveTokens(BMessage* message) { message->RemoveName(kTokenName); } void EventDispatcher::_SetToken(BMessage* message, int32 token) { if (message->ReplaceInt32(kTokenName, token) != B_OK) message->AddInt32(kTokenName, token); } void EventDispatcher::_SetFeedFocus(BMessage* message) { if (message->ReplaceBool("_feed_focus", true) != B_OK) message->AddBool("_feed_focus", true); } void EventDispatcher::_UnsetFeedFocus(BMessage* message) { message->RemoveName("_feed_focus"); } void EventDispatcher::_EventLoop() { BMessage* event; while (fStream->GetNextEvent(&event)) { if (event == NULL) { // may happen in out of memory situations or junk at the port // we can't do anything about those yet continue; } BAutolock _(this); bool sendToLastFocus = false; bool pointerEvent = false; bool keyboardEvent = false; bool addedTransit = false; bool addedTokens = false; switch (event->what) { case B_MOUSE_MOVED: { if (!HasCursorThread()) { // there is no cursor thread, we need to move the cursor ourselves BAutolock _(fCursorLock); BPoint where; if (fHWInterface != NULL && event->FindPoint("where", &where) == B_OK) fHWInterface->MoveCursorTo(where.x, where.y); } if (fTransit) { // target has changed, we need to add the be:transit field // to the message if (fHasLastFocus) { addedTokens = _AddTokens(event, fLastFocusTokens); _SendMessage(fLastFocus, event, kMouseTransitImportance); sendToLastFocus = true; // we no longer need the last focus messenger fHasLastFocus = false; fLastFocusTokens.MakeEmpty(); } fTransit = false; addedTransit = true; } BPoint where; if (event->FindPoint("where", &where) == B_OK) fLastCursorPosition = where; // supposed to fall through } case B_MOUSE_DOWN: case B_MOUSE_UP: if (fMouseFilter != NULL && fMouseFilter->Filter(event, NULL) == B_SKIP_MESSAGE) { // this is a work-around if the wrong B_MOUSE_UP // event is filtered out if (event->what == B_MOUSE_UP) { fSuspendFocus = false; _RemoveTemporaryListeners(); } break; } int32 buttons; if (event->FindInt32("buttons", &buttons) == B_OK) fLastButtons = buttons; else fLastButtons = 0; // the "where" field will be filled in by the receiver // (it's supposed to be expressed in local window coordinates) event->RemoveName("where"); event->AddPoint("screen_where", fLastCursorPosition); pointerEvent = true; if (fHasFocus) { addedTokens |= _AddTokens(event, fFocusTokens); if (addedTokens) _SetFeedFocus(event); _SendMessage(fFocus, event, event->what == B_MOUSE_MOVED ? kMouseMovedImportance : kStandardImportance); } break; case B_KEY_DOWN: case B_KEY_UP: case B_UNMAPPED_KEY_DOWN: case B_UNMAPPED_KEY_UP: case B_MODIFIERS_CHANGED: if (fKeyboardFilter != NULL && fKeyboardFilter->Filter(event, NULL) == B_SKIP_MESSAGE) break; keyboardEvent = true; if (fHasFocus && _AddTokens(event, fFocusTokens)) { // if tokens were added, we need to explicetly suspend // focus in the event - if not, the event is simply not // forwarded to the target addedTokens = true; if (!fSuspendFocus) _SetFeedFocus(event); } // supposed to fall through default: if (fHasFocus && (!fSuspendFocus || addedTokens)) _SendMessage(fFocus, event, kStandardImportance); break; } if (keyboardEvent || pointerEvent) { // send the event to the additional listeners if (addedTokens) { _RemoveTokens(event); _UnsetFeedFocus(event); } if (pointerEvent) { // this is added in the RootLayer mouse processing // but it's only intended for the focus view event->RemoveName("_view_token"); } for (int32 i = fListeners.CountItems(); i-- > 0;) { event_target* target = fListeners.ItemAt(i); // we already sent the event to the all focus and last focus tokens if ((fHasFocus && target->messenger == fFocus) || (sendToLastFocus && target->messenger == fLastFocus)) continue; uint32 effectiveEvents = target->event_mask | target->temporary_event_mask; // make sure only those interested listeners get the message if ((keyboardEvent && (effectiveEvents & B_KEYBOARD_EVENTS) == 0) || (pointerEvent && (effectiveEvents & B_POINTER_EVENTS) == 0)) continue; _SetToken(event, target->token); if (!_SendMessage(target->messenger, event, event->what == B_MOUSE_MOVED ? kMouseMovedImportance : kListenerImportance)) { // the target doesn't seem to exist anymore, let's remove this target fListeners.RemoveItemAt(i); delete target; } } if (event->what == B_MOUSE_UP) { fSuspendFocus = false; _RemoveTemporaryListeners(); } } delete event; } } void EventDispatcher::_CursorLoop() { BPoint where; while (fStream->GetNextCursorPosition(where)) { BAutolock _(fCursorLock); if (fHWInterface != NULL) fHWInterface->MoveCursorTo(where.x, where.y); } fCursorThread = -1; } /*static*/ status_t EventDispatcher::_event_looper(void* _dispatcher) { EventDispatcher* dispatcher = (EventDispatcher*)_dispatcher; ETRACE(("Start event loop\n")); dispatcher->_EventLoop(); return B_OK; } /*static*/ status_t EventDispatcher::_cursor_looper(void* _dispatcher) { EventDispatcher* dispatcher = (EventDispatcher*)_dispatcher; ETRACE(("Start cursor loop\n")); dispatcher->_CursorLoop(); return B_OK; }