#include "EventDispatcher.h"
#include "BitmapManager.h"
#include "Desktop.h"
#include "EventStream.h"
#include "HWInterface.h"
#include "InputManager.h"
#include "ServerBitmap.h"
#include <MessagePrivate.h>
#include <MessengerPrivate.h>
#include <ServerProtocol.h>
#include <TokenSpace.h>
#include <Autolock.h>
#include <ToolTipManager.h>
#include <View.h>
#include <new>
#include <stdio.h>
#include <string.h>
#ifdef TRACE_EVENTS
# define ETRACE(x) printf x
#else
# define ETRACE(x) ;
#endif
struct event_listener {
int32 token;
uint32 event_mask;
uint32 options;
uint32 temporary_event_mask;
uint32 temporary_options;
uint32 EffectiveEventMask() const { return event_mask | temporary_event_mask; }
uint32 EffectiveOptions() const { return options | temporary_options; }
};
static const char* kTokenName = "_token";
static const uint32 kFakeMouseMoved = 'fake';
static const float kMouseMovedImportance = 0.1f;
static const float kMouseTransitImportance = 1.0f;
static const float kStandardImportance = 0.9f;
static const float kListenerImportance = 0.8f;
EventTarget::EventTarget()
:
fListeners(2)
{
}
EventTarget::~EventTarget()
{
}
void
EventTarget::SetTo(const BMessenger& messenger)
{
fMessenger = messenger;
}
event_listener*
EventTarget::FindListener(int32 token, int32* _index)
{
for (int32 i = fListeners.CountItems(); i-- > 0;) {
event_listener* listener = fListeners.ItemAt(i);
if (listener->token == token) {
if (_index)
*_index = i;
return listener;
}
}
return NULL;
}
bool
EventTarget::_RemoveTemporaryListener(event_listener* listener, int32 index)
{
if (listener->event_mask == 0) {
ETRACE(("events: remove temp. listener: token %ld, eventMask = %ld, options = %ld\n",
listener->token, listener->temporary_event_mask, listener->temporary_options));
fListeners.RemoveItemAt(index);
delete listener;
return true;
}
if (listener->temporary_event_mask != 0) {
ETRACE(("events: clear temp. listener: token %ld, eventMask = %ld, "
"options = %ld\n",
listener->token, listener->temporary_event_mask,
listener->temporary_options));
listener->temporary_event_mask = 0;
listener->temporary_options = 0;
}
return false;
}
void
EventTarget::RemoveTemporaryListeners()
{
for (int32 index = CountListeners(); index-- > 0;) {
event_listener* listener = ListenerAt(index);
_RemoveTemporaryListener(listener, index);
}
}
bool
EventTarget::RemoveTemporaryListener(int32 token)
{
int32 index;
event_listener* listener = FindListener(token, &index);
if (listener == NULL)
return false;
return _RemoveTemporaryListener(listener, index);
}
bool
EventTarget::RemoveListener(int32 token)
{
int32 index;
event_listener* listener = FindListener(token, &index);
if (listener == NULL)
return false;
if (listener->temporary_event_mask != 0) {
listener->event_mask = 0;
listener->options = 0;
return false;
}
fListeners.RemoveItemAt(index);
delete listener;
return true;
}
bool
EventTarget::AddListener(int32 token, uint32 eventMask,
uint32 options, bool temporary)
{
event_listener* listener = new (std::nothrow) event_listener;
if (listener == NULL)
return false;
listener->token = token;
if (temporary) {
listener->event_mask = 0;
listener->options = 0;
listener->temporary_event_mask = eventMask;
listener->temporary_options = options;
} else {
listener->event_mask = eventMask;
listener->options = options;
listener->temporary_event_mask = 0;
listener->temporary_options = 0;
}
bool success = fListeners.AddItem(listener);
if (!success)
delete listener;
return success;
}
void
EventFilter::RemoveTarget(EventTarget* target)
{
}
EventDispatcher::EventDispatcher()
:
BLocker("event dispatcher"),
fStream(NULL),
fThread(-1),
fCursorThread(-1),
fPreviousMouseTarget(NULL),
fFocus(NULL),
fSuspendFocus(false),
fMouseFilter(NULL),
fKeyboardFilter(NULL),
fTargets(10),
fNextLatestMouseMoved(NULL),
fLastButtons(0),
fLastUpdate(system_time()),
fDraggingMessage(false),
fCursorLock("cursor loop lock"),
fHWInterface(NULL),
fDesktop(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();
status_t status;
wait_for_thread(fThread, &status);
wait_for_thread(fCursorThread, &status);
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::RemoveTarget(EventTarget& target)
{
BAutolock _(this);
if (fFocus == &target)
fFocus = NULL;
if (fPreviousMouseTarget == &target)
fPreviousMouseTarget = NULL;
if (fKeyboardFilter.IsSet())
fKeyboardFilter->RemoveTarget(&target);
if (fMouseFilter.IsSet())
fMouseFilter->RemoveTarget(&target);
fTargets.RemoveItem(&target);
}
bool
EventDispatcher::_AddListener(EventTarget& target, int32 token,
uint32 eventMask, uint32 options, bool temporary)
{
BAutolock _(this);
if (temporary && fLastButtons == 0) {
return false;
}
if (!fTargets.HasItem(&target))
fTargets.AddItem(&target);
event_listener* listener = target.FindListener(token);
if (listener != NULL) {
if (temporary) {
if (eventMask != 0)
listener->temporary_event_mask = eventMask;
listener->temporary_options = options;
} else {
if (eventMask != 0)
listener->event_mask = eventMask;
listener->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"));
bool success = target.AddListener(token, eventMask, options, temporary);
if (!success) {
if (target.IsEmpty())
fTargets.RemoveItem(&target);
} else {
if (options & B_SUSPEND_VIEW_FOCUS)
fSuspendFocus = true;
}
return success;
}
void
EventDispatcher::_RemoveTemporaryListeners()
{
for (int32 i = fTargets.CountItems(); i-- > 0;) {
EventTarget* target = fTargets.ItemAt(i);
target->RemoveTemporaryListeners();
}
}
bool
EventDispatcher::AddListener(EventTarget& target, int32 token,
uint32 eventMask, uint32 options)
{
options &= B_NO_POINTER_HISTORY;
return _AddListener(target, token, eventMask, options, false);
}
bool
EventDispatcher::AddTemporaryListener(EventTarget& target,
int32 token, uint32 eventMask, uint32 options)
{
return _AddListener(target, token, eventMask, options, true);
}
void
EventDispatcher::RemoveListener(EventTarget& target, int32 token)
{
BAutolock _(this);
ETRACE(("events: remove listener token %ld\n", token));
if (target.RemoveListener(token) && target.IsEmpty())
fTargets.RemoveItem(&target);
}
void
EventDispatcher::RemoveTemporaryListener(EventTarget& target, int32 token)
{
BAutolock _(this);
ETRACE(("events: remove temporary listener token %ld\n", token));
if (target.RemoveTemporaryListener(token) && target.IsEmpty())
fTargets.RemoveItem(&target);
}
void
EventDispatcher::SetMouseFilter(EventFilter* filter)
{
BAutolock _(this);
if (fMouseFilter.Get() == filter)
return;
fMouseFilter.SetTo(filter);
}
void
EventDispatcher::SetKeyboardFilter(EventFilter* filter)
{
BAutolock _(this);
if (fKeyboardFilter.Get() == filter)
return;
fKeyboardFilter.SetTo(filter);
}
void
EventDispatcher::GetMouse(BPoint& where, int32& buttons)
{
BAutolock _(this);
where = fLastCursorPosition;
buttons = fLastButtons;
}
void
EventDispatcher::SendFakeMouseMoved(EventTarget& target, int32 viewToken)
{
if (fStream == NULL)
return;
BMessage* fakeMove = new BMessage(kFakeMouseMoved);
if (fakeMove == NULL)
return;
fakeMove->AddMessenger("target", target.Messenger());
fakeMove->AddInt32("view_token", viewToken);
fStream->InsertEvent(fakeMove);
}
void
EventDispatcher::_SendFakeMouseMoved(BMessage* message)
{
BMessenger target;
int32 viewToken;
if (message->FindInt32("view_token", &viewToken) != B_OK
|| message->FindMessenger("target", &target) != B_OK)
return;
if (fDesktop == NULL)
return;
::EventTarget* eventTarget = NULL;
fDesktop->LockSingleWindow();
if (target.IsValid())
eventTarget = fDesktop->FindTarget(target);
fDesktop->UnlockSingleWindow();
if (eventTarget == NULL)
return;
BMessage moved(B_MOUSE_MOVED);
moved.AddPoint("screen_where", fLastCursorPosition);
moved.AddInt32("buttons", fLastButtons);
if (fDraggingMessage)
moved.AddMessage("be:drag_message", &fDragMessage);
if (fPreviousMouseTarget != NULL
&& fPreviousMouseTarget->Messenger() != target) {
_AddTokens(&moved, fPreviousMouseTarget, B_POINTER_EVENTS);
_SendMessage(fPreviousMouseTarget->Messenger(), &moved,
kMouseTransitImportance);
_RemoveTokens(&moved);
}
moved.AddInt32("_view_token", viewToken);
moved.AddBool("be:transit_only", true);
_SendMessage(target, &moved, kMouseTransitImportance);
fPreviousMouseTarget = eventTarget;
}
bigtime_t
EventDispatcher::IdleTime()
{
BAutolock _(this);
return system_time() - fLastUpdate;
}
bool
EventDispatcher::HasCursorThread()
{
return fCursorThread >= B_OK;
}
void
EventDispatcher::SetHWInterface(HWInterface* interface)
{
BAutolock _(fCursorLock);
fHWInterface = interface;
if (interface != NULL)
fLastCursorPosition = interface->CursorPosition();
}
void
EventDispatcher::SetDragMessage(BMessage& message,
ServerBitmap* bitmap, const BPoint& offsetFromCursor)
{
ETRACE(("EventDispatcher::SetDragMessage()\n"));
BAutolock _(this);
if (fLastButtons == 0) {
return;
}
fHWInterface->SetDragBitmap(bitmap, offsetFromCursor);
fDragMessage = message;
fDraggingMessage = true;
fDragOffset = offsetFromCursor;
}
void
EventDispatcher::SetDesktop(Desktop* desktop)
{
fDesktop = desktop;
}
bool
EventDispatcher::_SendMessage(BMessenger& messenger, BMessage* message,
float importance)
{
status_t status = messenger.SendMessage(message, (BHandler*)NULL, 0);
if (status != B_OK) {
printf("EventDispatcher: failed to send message '%.4s' to target: %s\n",
(char*)&message->what, strerror(status));
}
if (status == B_BAD_PORT_ID) {
return false;
}
return true;
}
bool
EventDispatcher::_AddTokens(BMessage* message, EventTarget* target,
uint32 eventMask, BMessage* nextMouseMoved, int32* _viewToken)
{
_RemoveTokens(message);
int32 count = target->CountListeners();
int32 added = 0;
for (int32 i = 0; i < count; i++) {
event_listener* listener = target->ListenerAt(i);
if ((listener->EffectiveEventMask() & eventMask) == 0)
continue;
if (nextMouseMoved != NULL && message->what == B_MOUSE_MOVED
&& (listener->EffectiveOptions() & B_NO_POINTER_HISTORY) != 0
&& message != nextMouseMoved
&& _viewToken != NULL) {
if (listener->token == *_viewToken) {
*_viewToken = B_NULL_TOKEN;
}
continue;
}
ETRACE((" add token %ld\n", listener->token));
if (message->AddInt32(kTokenName, listener->token) == B_OK)
added++;
}
return added != 0;
}
void
EventDispatcher::_RemoveTokens(BMessage* message)
{
message->RemoveName(kTokenName);
}
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::_DeliverDragMessage()
{
ETRACE(("EventDispatcher::_DeliverDragMessage()\n"));
if (fDraggingMessage && fPreviousMouseTarget != NULL) {
BMessage::Private(fDragMessage).SetWasDropped(true);
fDragMessage.RemoveName("_original_what");
fDragMessage.AddInt32("_original_what", fDragMessage.what);
fDragMessage.AddPoint("_drop_point_", fLastCursorPosition);
fDragMessage.AddPoint("_drop_offset_", fDragOffset);
fDragMessage.what = _MESSAGE_DROPPED_;
_SendMessage(fPreviousMouseTarget->Messenger(),
&fDragMessage, 100.0);
}
fDragMessage.MakeEmpty();
fDragMessage.what = 0;
fDraggingMessage = false;
fHWInterface->SetDragBitmap(NULL, B_ORIGIN);
}
void
EventDispatcher::_EventLoop()
{
BMessage* event;
while (fStream->GetNextEvent(&event)) {
BAutolock _(this);
fLastUpdate = system_time();
EventTarget* current = NULL;
EventTarget* previous = NULL;
bool pointerEvent = false;
bool keyboardEvent = false;
bool addedTokens = false;
switch (event->what) {
case kFakeMouseMoved:
_SendFakeMouseMoved(event);
break;
case B_MOUSE_MOVED:
{
BPoint where;
if (event->FindPoint("where", &where) == B_OK)
fLastCursorPosition = where;
if (fDraggingMessage)
event->AddMessage("be:drag_message", &fDragMessage);
if (!HasCursorThread()) {
BAutolock _(fCursorLock);
if (fHWInterface != NULL) {
fHWInterface->MoveCursorTo(fLastCursorPosition.x,
fLastCursorPosition.y);
}
}
if (fNextLatestMouseMoved == NULL)
fNextLatestMouseMoved = fStream->PeekLatestMouseMoved();
else if (fNextLatestMouseMoved != event) {
bigtime_t eventTime;
if (event->FindInt64("when", &eventTime) == B_OK) {
if (system_time() - eventTime > 100000)
break;
}
}
}
case B_MOUSE_DOWN:
case B_MOUSE_UP:
case B_MOUSE_IDLE:
{
#ifdef TRACE_EVENTS
if (event->what != B_MOUSE_MOVED)
printf("mouse up/down event, previous target = %p\n", fPreviousMouseTarget);
#endif
pointerEvent = true;
if (!fMouseFilter.IsSet())
break;
EventTarget* mouseTarget = fPreviousMouseTarget;
int32 viewToken = B_NULL_TOKEN;
if (fMouseFilter->Filter(event, &mouseTarget, &viewToken,
fNextLatestMouseMoved) == B_SKIP_MESSAGE) {
if (event->what == B_MOUSE_UP
&& event->FindInt32("buttons") == 0) {
fSuspendFocus = false;
_RemoveTemporaryListeners();
}
break;
}
int32 buttons;
if (event->FindInt32("buttons", &buttons) == B_OK)
fLastButtons = buttons;
else
fLastButtons = 0;
event->RemoveName("where");
event->AddPoint("screen_where", fLastCursorPosition);
if (event->what == B_MOUSE_MOVED
&& fPreviousMouseTarget != NULL
&& mouseTarget != fPreviousMouseTarget) {
addedTokens = _AddTokens(event, fPreviousMouseTarget,
B_POINTER_EVENTS);
if (addedTokens)
_SetFeedFocus(event);
_SendMessage(fPreviousMouseTarget->Messenger(), event,
kMouseTransitImportance);
previous = fPreviousMouseTarget;
}
current = fPreviousMouseTarget = mouseTarget;
if (current != NULL) {
int32 focusView = viewToken;
addedTokens |= _AddTokens(event, current, B_POINTER_EVENTS,
fNextLatestMouseMoved, &focusView);
bool noPointerHistoryFocus = focusView != viewToken;
if (viewToken != B_NULL_TOKEN)
event->AddInt32("_view_token", viewToken);
if (addedTokens && !noPointerHistoryFocus)
_SetFeedFocus(event);
else if (noPointerHistoryFocus) {
break;
}
_SendMessage(current->Messenger(), 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:
case B_INPUT_METHOD_EVENT:
ETRACE(("key event, focus = %p\n", fFocus));
if (fKeyboardFilter.IsSet()
&& fKeyboardFilter->Filter(event, &fFocus)
== B_SKIP_MESSAGE) {
break;
}
keyboardEvent = true;
if (fFocus != NULL && _AddTokens(event, fFocus,
B_KEYBOARD_EVENTS)) {
addedTokens = true;
if (!fSuspendFocus)
_SetFeedFocus(event);
}
default:
if (event->what == B_MOUSE_WHEEL_CHANGED)
current = fPreviousMouseTarget;
else
current = fFocus;
if (current != NULL && (!fSuspendFocus || addedTokens)) {
_SendMessage(current->Messenger(), event,
kStandardImportance);
}
break;
}
if (keyboardEvent || pointerEvent) {
if (addedTokens) {
_RemoveTokens(event);
_UnsetFeedFocus(event);
}
if (pointerEvent) {
event->RemoveName("_view_token");
}
for (int32 i = fTargets.CountItems(); i-- > 0;) {
EventTarget* target = fTargets.ItemAt(i);
if (current == target || previous == target)
continue;
if (!_AddTokens(event, target,
keyboardEvent ? B_KEYBOARD_EVENTS : B_POINTER_EVENTS,
event->what == B_MOUSE_MOVED
? fNextLatestMouseMoved : NULL))
continue;
if (!_SendMessage(target->Messenger(), event,
event->what == B_MOUSE_MOVED
? kMouseMovedImportance : kListenerImportance)) {
fTargets.RemoveItemAt(i);
}
}
if (event->what == B_MOUSE_UP && fLastButtons == 0) {
fSuspendFocus = false;
_RemoveTemporaryListeners();
if (fDraggingMessage)
_DeliverDragMessage();
}
}
if (fNextLatestMouseMoved == event)
fNextLatestMouseMoved = NULL;
delete event;
}
fThread = -1;
_Unset();
if (fDesktop)
fDesktop->PostMessage(AS_EVENT_STREAM_CLOSED);
}
void
EventDispatcher::_CursorLoop()
{
BPoint where;
const bigtime_t toolTipDelay = BToolTipManager::Manager()->ShowDelay();
bool mouseIdleSent = true;
status_t status = B_OK;
while (status != B_ERROR) {
const bigtime_t timeout = mouseIdleSent ?
B_INFINITE_TIMEOUT : toolTipDelay;
status = fStream->GetNextCursorPosition(where, timeout);
if (status == B_OK) {
mouseIdleSent = false;
BAutolock _(fCursorLock);
if (fHWInterface != NULL)
fHWInterface->MoveCursorTo(where.x, where.y);
} else if (status == B_TIMED_OUT) {
mouseIdleSent = true;
BMessage* mouseIdle = new BMessage(B_MOUSE_IDLE);
fStream->InsertEvent(mouseIdle);
}
}
fCursorThread = -1;
}
status_t
EventDispatcher::_event_looper(void* _dispatcher)
{
EventDispatcher* dispatcher = (EventDispatcher*)_dispatcher;
ETRACE(("Start event loop\n"));
dispatcher->_EventLoop();
return B_OK;
}
status_t
EventDispatcher::_cursor_looper(void* _dispatcher)
{
EventDispatcher* dispatcher = (EventDispatcher*)_dispatcher;
ETRACE(("Start cursor loop\n"));
dispatcher->_CursorLoop();
return B_OK;
}