windowmaker-wl/WINGs/wevent.c

432 lines
12 KiB
C

/*
* wevent.c — WINGs event dispatch layer.
*
* All raw event queue operations route through scr->eventBackend.
* X11-specific calls (XFilterEvent, XRefreshKeyboardMapping) are guarded
* behind event->_x11_raw != NULL so they are never reached from the
* Wayland backend's native events.
*/
#include "wconfig.h"
#include "WINGsP.h"
#include <X11/Xlib.h>
/*
* Event mask table indexed by WMEventType enum values.
* Maps each event type to the X11-style event mask used by
* WMCreateEventHandler's mask parameter.
*/
static const unsigned long wmeventMasks[] = {
[WME_NONE] = 0,
[WME_KEY_PRESS] = KeyPressMask,
[WME_KEY_RELEASE] = KeyReleaseMask,
[WME_BUTTON_PRESS] = ButtonPressMask,
[WME_BUTTON_RELEASE] = ButtonReleaseMask,
[WME_MOTION] = PointerMotionMask | ButtonMotionMask
| Button1MotionMask | Button2MotionMask
| Button3MotionMask,
[WME_ENTER] = EnterWindowMask,
[WME_LEAVE] = LeaveWindowMask,
[WME_EXPOSE] = ExposureMask,
[WME_CONFIGURE] = StructureNotifyMask,
[WME_CONFIGURE_REQUEST] = SubstructureRedirectMask,
[WME_MAP] = StructureNotifyMask,
[WME_MAP_REQUEST] = SubstructureRedirectMask,
[WME_UNMAP] = StructureNotifyMask,
[WME_DESTROY] = StructureNotifyMask,
[WME_REPARENT] = StructureNotifyMask,
[WME_FOCUS_IN] = FocusChangeMask,
[WME_FOCUS_OUT] = FocusChangeMask,
[WME_PROPERTY] = PropertyChangeMask,
[WME_SELECTION] = 0,
[WME_SELECTION_REQUEST] = 0,
[WME_SELECTION_CLEAR] = 0,
[WME_CLIENT_MESSAGE] = 0,
[WME_COLORMAP] = ColormapChangeMask,
[WME_VISIBILITY] = VisibilityChangeMask,
[WME_MAPPING] = 0,
};
#define WME_MASK_TABLE_SIZE (sizeof(wmeventMasks) / sizeof(wmeventMasks[0]))
static inline unsigned long
wmeMaskForType(int type)
{
if (type >= 0 && (unsigned)type < WME_MASK_TABLE_SIZE)
return wmeventMasks[type];
return 0;
}
static WMEventHook *extraEventHandler = NULL;
void WMCreateEventHandler(WMView * view, unsigned long mask, WMEventProc * eventProc, void *clientData)
{
W_EventHandler *hPtr;
WMArrayIterator iter;
WM_ITERATE_ARRAY(view->eventHandlers, hPtr, iter) {
if (hPtr->clientData == clientData && hPtr->proc == eventProc) {
hPtr->eventMask |= mask;
return;
}
}
hPtr = wmalloc(sizeof(W_EventHandler));
hPtr->eventMask = mask;
hPtr->proc = eventProc;
hPtr->clientData = clientData;
WMAddToArray(view->eventHandlers, hPtr);
}
static int matchHandler(const void *item, const void *cdata)
{
const W_EventHandler *h1 = item;
const W_EventHandler *h2 = cdata;
return ((h1->eventMask == h2->eventMask) &&
(h1->proc == h2->proc) &&
(h1->clientData == h2->clientData));
}
void WMDeleteEventHandler(WMView * view, unsigned long mask, WMEventProc * eventProc, void *clientData)
{
W_EventHandler tmp;
tmp.eventMask = mask;
tmp.proc = eventProc;
tmp.clientData = clientData;
WMRemoveFromArrayMatching(view->eventHandlers, matchHandler, (void *)&tmp);
}
static Time getEventTime(WMScreen * screen, WMEvent * event)
{
switch (event->type) {
case WME_BUTTON_PRESS:
case WME_BUTTON_RELEASE:
return event->u.button.time;
case WME_KEY_PRESS:
case WME_KEY_RELEASE:
return event->u.key.time;
case WME_MOTION:
return event->u.motion.time;
case WME_ENTER:
case WME_LEAVE:
return event->u.crossing.time;
case WME_PROPERTY:
return event->u.property.time;
case WME_SELECTION_CLEAR:
case WME_SELECTION_REQUEST:
case WME_SELECTION:
return event->u.selection.time;
default:
return screen->lastEventTime;
}
}
void W_CallDestroyHandlers(W_View * view)
{
WMEvent event;
WMArrayIterator iter;
W_EventHandler *hPtr;
memset(&event, 0, sizeof(event));
event.type = WME_DESTROY;
event.window = (WNativeWindow)view->window;
event.u.lifecycle.window = (WNativeWindow)view->window;
event.u.lifecycle.event = (WNativeWindow)view->window;
WM_ITERATE_ARRAY(view->eventHandlers, hPtr, iter) {
if (hPtr->eventMask & StructureNotifyMask) {
(*hPtr->proc) (&event, hPtr->clientData);
}
}
}
void WMSetViewNextResponder(WMView * view, WMView * responder)
{
view->nextResponder = responder;
}
void WMRelayToNextResponder(WMView * view, WMEvent * event)
{
unsigned long mask = wmeMaskForType(event->type);
if (view->nextResponder) {
WMView *next = view->nextResponder;
W_EventHandler *hPtr;
WMArrayIterator iter;
WM_ITERATE_ARRAY(next->eventHandlers, hPtr, iter) {
if ((hPtr->eventMask & mask)) {
(*hPtr->proc) (event, hPtr->clientData);
}
}
}
}
int WMHandleEvent(WMEvent * event)
{
W_EventHandler *hPtr;
W_View *view, *toplevel;
unsigned long mask;
WNativeWindow window;
WMArrayIterator iter;
/* X11-only: refresh keyboard mapping on MappingNotify. */
if (event->type == WME_MAPPING) {
if (event->_x11_raw)
XRefreshKeyboardMapping(&((XEvent *)event->_x11_raw)->xmapping);
return True;
}
/* X11-only: input method event filter. */
if (event->_x11_raw && XFilterEvent((XEvent *)event->_x11_raw, None) == True)
return False;
mask = wmeMaskForType(event->type);
window = event->window;
/* differentiate SubstructureNotify with StructureNotify */
if (mask == StructureNotifyMask) {
if (event->type == WME_MAP || event->type == WME_UNMAP ||
event->type == WME_DESTROY || event->type == WME_CONFIGURE) {
WNativeWindow ev_win = WNATIVE_NONE;
if (event->type == WME_MAP || event->type == WME_UNMAP ||
event->type == WME_DESTROY)
ev_win = event->u.lifecycle.event;
else if (event->type == WME_CONFIGURE)
ev_win = event->u.configure.event;
if (ev_win != WNATIVE_NONE && ev_win != window) {
mask = SubstructureNotifyMask;
window = ev_win;
}
}
}
/* Look up the WINGs view for this event.
* X11 mode: by X window ID.
* Wayland mode: use event_target side-channel, or fall through. */
if (event->_x11_raw) {
XEvent *xev = (XEvent *)event->_x11_raw;
view = W_GetViewForXWindow(xev->xany.display, (Window)window);
} else {
extern W_View *W_GetViewForWindow(Window id);
view = W_GetViewForWindow((Window)window);
/* Route pointer events to the deepest child at click point */
if (view && (mask & (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask))) {
int ex = event->u.button.x;
int ey = event->u.button.y;
W_View *child = view->childrenList;
while (child) {
if (child->flags.mapped &&
ex >= child->pos.x && ex < child->pos.x + child->size.width &&
ey >= child->pos.y && ey < child->pos.y + child->size.height) {
ex -= child->pos.x;
ey -= child->pos.y;
view = child;
child = view->childrenList;
} else {
child = child->nextSister;
}
}
}
}
if (!view) {
if (extraEventHandler)
(extraEventHandler)(event);
return False;
}
view->screen->lastEventTime = getEventTime(view->screen, event);
toplevel = W_TopLevelOfView(view);
if (event->type == WME_SELECTION || event->type == WME_SELECTION_CLEAR ||
event->type == WME_SELECTION_REQUEST) {
/* Selection events still need XEvent for Xlib calls */
if (event->_x11_raw)
W_HandleSelectionEvent((XEvent *)event->_x11_raw);
}
if (mask & (KeyPressMask | KeyReleaseMask)) {
W_View *focused = W_FocusedViewOfToplevel(toplevel);
if (focused)
view = focused;
}
/* compress Motion events */
if (event->type == WME_MOTION && !view->flags.dontCompressMotion) {
const W_EventBackend *be = view->screen->eventBackend;
void *ectx = view->screen->eventBackendCtx;
while (be->pending(ectx)) {
WMEvent ev;
be->fetch(ectx, &ev);
if (ev.type == WME_MOTION
&& event->u.motion.window == ev.u.motion.window
&& event->u.motion.subwindow == ev.u.motion.subwindow) {
*event = ev;
} else {
be->put_back(ectx, &ev);
break;
}
}
}
/* compress Expose events */
if (event->type == WME_EXPOSE && !view->flags.dontCompressExpose) {
const W_EventBackend *be = view->screen->eventBackend;
void *ectx = view->screen->eventBackendCtx;
WMEvent tmp;
while (be->check_typed_window(ectx, (WNativeWindow)view->window, WME_EXPOSE, &tmp))
;
}
if (view->screen->modalLoop && toplevel != view->screen->modalView && !toplevel->flags.worksWhenModal) {
if (event->type == WME_KEY_PRESS || event->type == WME_KEY_RELEASE
|| event->type == WME_MOTION || event->type == WME_BUTTON_PRESS
|| event->type == WME_BUTTON_RELEASE || event->type == WME_FOCUS_IN
|| event->type == WME_FOCUS_OUT) {
return True;
}
}
if (event->type == WME_ENTER)
W_BalloonHandleEnterView(view);
else if (event->type == WME_LEAVE)
W_BalloonHandleLeaveView(view);
W_RetainView(toplevel);
WM_ITERATE_ARRAY(view->eventHandlers, hPtr, iter) {
if ((hPtr->eventMask & mask)) {
(*hPtr->proc) (event, hPtr->clientData);
}
}
if (view->screen->ignoreNextDoubleClick) {
view->screen->ignoreNextDoubleClick = 0;
} else {
if (event->type == WME_BUTTON_PRESS) {
view->screen->lastClickWindow = (Window)event->u.button.window;
view->screen->lastClickTime = event->u.button.time;
}
}
if (event->type == WME_CLIENT_MESSAGE) {
/* DND still needs XEvent for protocol handling */
if (event->_x11_raw)
W_HandleDNDClientMessage(toplevel, &((XEvent *)event->_x11_raw)->xclient);
}
W_ReleaseView(toplevel);
return True;
}
int WMIsDoubleClick(WMEvent * event)
{
W_View *view;
if (event->type != WME_BUTTON_PRESS)
return False;
/* Double-click detection requires view lookup — X11 only for now */
if (!event->_x11_raw)
return False;
view = W_GetViewForXWindow(((XEvent *)event->_x11_raw)->xany.display,
(Window)event->u.button.window);
if (!view)
return False;
if (view->screen->lastClickWindow != (Window)event->u.button.window)
return False;
if (event->u.button.time - view->screen->lastClickTime < WINGsConfiguration.doubleClickDelay) {
view->screen->lastClickTime = 0;
view->screen->lastClickWindow = None;
view->screen->ignoreNextDoubleClick = 1;
return True;
} else
return False;
}
/* ------------------------------------------------------------------ */
/* Internal: wait for events or timer expiry. */
/* ------------------------------------------------------------------ */
static Bool waitForEvent(WMScreen *scr, unsigned long xeventmask, Bool waitForInput)
{
const W_EventBackend *be = scr->eventBackend;
void *ectx = scr->eventBackendCtx;
be->flush(ectx, False);
if (xeventmask == 0) {
if (be->pending(ectx))
return True;
} else {
WMEvent ev;
if (be->check_mask(ectx, xeventmask, &ev)) {
be->put_back(ectx, &ev);
return True;
}
}
return W_HandleInputEvents(waitForInput, be->fd(ectx));
}
void WMNextEvent(WMScreen * scr, WMEvent * event)
{
const W_EventBackend *be = scr->eventBackend;
void *ectx = scr->eventBackendCtx;
W_CheckTimerHandlers();
while (!be->pending(ectx)) {
while (!waitForEvent(scr, 0, False) && W_CheckIdleHandlers())
W_CheckTimerHandlers();
waitForEvent(scr, 0, True);
W_CheckTimerHandlers();
}
be->fetch(ectx, event);
}
void WMMaskEvent(WMScreen * scr, long mask, WMEvent * event)
{
const W_EventBackend *be = scr->eventBackend;
void *ectx = scr->eventBackendCtx;
W_CheckTimerHandlers();
while (!be->check_mask(ectx, mask, event)) {
while (!waitForEvent(scr, mask, False) && W_CheckIdleHandlers())
W_CheckTimerHandlers();
if (be->check_mask(ectx, mask, event))
return;
waitForEvent(scr, mask, True);
W_CheckTimerHandlers();
}
}
Bool WMScreenPending(WMScreen * scr)
{
return scr->eventBackend->pending(scr->eventBackendCtx);
}
WMEventHook *WMHookEventHandler(WMEventHook * handler)
{
WMEventHook *oldHandler = extraEventHandler;
extraEventHandler = handler;
return oldHandler;
}