/* * 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 /* * 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 { view = NULL; } 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; }