/* * wevent_wl.c — Wayland event backend for WINGs. * * Events arrive via wlroots input callbacks (keyboard, pointer, touch) in * wl_backend.c, which call W_WL_EnqueueEvent(). This backend dequeues them * in WMNextEvent / WMMaskEvent without touching any Xlib function. * * For modal loops (e.g. dialog.c) a dispatch callback is registered via * W_WL_SetLoopDispatch() so the compositor event loop can be ticked while * waiting for the next WINGs event. */ #include "wconfig.h" #include "WINGsP.h" #include /* ------------------------------------------------------------------ */ #define WL_EVENT_QUEUE_CAP 512 typedef struct { WMEvent ev; W_View *target; /* direct view routing; NULL = use ev.window */ } WLQEntry; typedef struct { WLQEntry buf[WL_EVENT_QUEUE_CAP]; int head; /* next read position */ int tail; /* next write position */ int loop_fd; /* wl_event_loop fd for W_HandleInputEvents */ void (*dispatch_fn)(int timeout_ms, void *ud); void *dispatch_ud; } WLEventCtx; /* ------------------------------------------------------------------ */ static void * wl_screen_init(W_Screen *scr) { (void)scr; WLEventCtx *ctx = wmalloc(sizeof(*ctx)); memset(ctx, 0, sizeof(*ctx)); ctx->loop_fd = -1; return ctx; } static void wl_screen_fini(void *ectx) { wfree(ectx); } static Bool wl_pending(void *ectx) { WLEventCtx *ctx = ectx; return ctx->head != ctx->tail; } static void wl_fetch(void *ectx, WMEvent *ev) { WLEventCtx *ctx = ectx; WLQEntry *e = &ctx->buf[ctx->head]; *ev = e->ev; ev->_view_target = e->target; if (e->target && !W_IsValidView(e->target)) ev->_view_target = NULL; if (e->target) { W_Screen *scr = e->target->screen; if (scr) scr->event_target = e->target; } ctx->head = (ctx->head + 1) % WL_EVENT_QUEUE_CAP; } /* Event mask table for matching — same values as wevent.c's wmeventMasks */ static unsigned long wme_mask(int type) { switch (type) { case WME_KEY_PRESS: return KeyPressMask; case WME_KEY_RELEASE: return KeyReleaseMask; case WME_BUTTON_PRESS: return ButtonPressMask; case WME_BUTTON_RELEASE: return ButtonReleaseMask; case WME_MOTION: return PointerMotionMask | ButtonMotionMask; case WME_ENTER: return EnterWindowMask; case WME_LEAVE: return LeaveWindowMask; case WME_EXPOSE: return ExposureMask; case WME_CONFIGURE: return StructureNotifyMask; case WME_MAP: return StructureNotifyMask; case WME_UNMAP: return StructureNotifyMask; case WME_DESTROY: return StructureNotifyMask; case WME_FOCUS_IN: case WME_FOCUS_OUT: return FocusChangeMask; case WME_PROPERTY: return PropertyChangeMask; case WME_COLORMAP: return ColormapChangeMask; case WME_VISIBILITY: return VisibilityChangeMask; default: return 0; } } static Bool wl_check_mask(void *ectx, long mask, WMEvent *ev) { WLEventCtx *ctx = ectx; int i = ctx->head; while (i != ctx->tail) { unsigned long emask = wme_mask(ctx->buf[i].ev.type); if (emask & (unsigned long)mask) { *ev = ctx->buf[i].ev; if (ev->_view_target && !W_IsValidView(ev->_view_target)) ev->_view_target = NULL; /* Remove entry by shifting */ int next = (i + 1) % WL_EVENT_QUEUE_CAP; while (next != ctx->tail) { ctx->buf[i] = ctx->buf[next]; i = next; next = (next + 1) % WL_EVENT_QUEUE_CAP; } ctx->tail = i; return True; } i = (i + 1) % WL_EVENT_QUEUE_CAP; } return False; } static Bool wl_check_typed_window(void *ectx, WNativeWindow w, int type, WMEvent *ev) { WLEventCtx *ctx = ectx; int i = ctx->head; while (i != ctx->tail) { if (ctx->buf[i].ev.type == type && ctx->buf[i].ev.window == w) { *ev = ctx->buf[i].ev; if (ev->_view_target && !W_IsValidView(ev->_view_target)) ev->_view_target = NULL; int next = (i + 1) % WL_EVENT_QUEUE_CAP; while (next != ctx->tail) { ctx->buf[i] = ctx->buf[next]; i = next; next = (next + 1) % WL_EVENT_QUEUE_CAP; } ctx->tail = i; return True; } i = (i + 1) % WL_EVENT_QUEUE_CAP; } return False; } static void wl_flush(void *ectx, Bool discard) { WLEventCtx *ctx = ectx; (void)discard; /* Pump the wlroots event loop so pointer/key events arrive in our queue. * This is called from waitForEvent/WMMaskEvent when the queue is empty. */ if (ctx->dispatch_fn) ctx->dispatch_fn(16, ctx->dispatch_ud); } static int wl_fd(void *ectx) { WLEventCtx *ctx = ectx; return ctx->loop_fd; } static void wl_put_back(void *ectx, WMEvent *ev) { WLEventCtx *ctx = ectx; int prev = (ctx->head - 1 + WL_EVENT_QUEUE_CAP) % WL_EVENT_QUEUE_CAP; if (prev == ctx->tail) return; /* queue full — drop */ ctx->buf[prev].ev = *ev; ctx->buf[prev].target = NULL; ctx->head = prev; } const W_EventBackend W_EventBackendWL = { .screen_init = wl_screen_init, .screen_fini = wl_screen_fini, .pending = wl_pending, .fetch = wl_fetch, .check_mask = wl_check_mask, .check_typed_window = wl_check_typed_window, .flush = wl_flush, .fd = wl_fd, .put_back = wl_put_back, .post_dispatch = NULL, }; /* ------------------------------------------------------------------ */ /* Called from wl_backend.c */ /* ------------------------------------------------------------------ */ void W_WL_EnqueueEvent(W_Screen *scr, const WMEvent *ev, W_View *target) { if (!scr || !scr->eventBackendCtx) return; WLEventCtx *ctx = scr->eventBackendCtx; int next_tail = (ctx->tail + 1) % WL_EVENT_QUEUE_CAP; if (next_tail == ctx->head) return; /* queue full — drop */ ctx->buf[ctx->tail].ev = *ev; ctx->buf[ctx->tail].target = target; ctx->tail = next_tail; } void W_WL_SetLoopDispatch(W_Screen *scr, int loop_fd, void (*dispatch_fn)(int timeout_ms, void *ud), void *userdata) { if (!scr || !scr->eventBackendCtx) return; WLEventCtx *ctx = scr->eventBackendCtx; ctx->loop_fd = loop_fd; ctx->dispatch_fn = dispatch_fn; ctx->dispatch_ud = userdata; } /* Tick the Wayland compositor event loop once (used by modal loops). */ void W_WL_InvalidateView(W_Screen *scr, W_View *target) { if (!scr || !scr->eventBackendCtx) return; WLEventCtx *ctx = scr->eventBackendCtx; int i = ctx->head; while (i != ctx->tail) { if (ctx->buf[i].target == target) { ctx->buf[i].target = NULL; } i = (i + 1) % WL_EVENT_QUEUE_CAP; } } void W_WL_DispatchLoop(W_Screen *scr, int timeout_ms) { if (!scr || !scr->eventBackendCtx) return; WLEventCtx *ctx = scr->eventBackendCtx; if (ctx->dispatch_fn) ctx->dispatch_fn(timeout_ms, ctx->dispatch_ud); }