#define WRASTER_NO_XLIB /* Wayland-only file: no libX11 deps */ /* * 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 #include #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 WM_EVMASK_KEY_PRESS; case WME_KEY_RELEASE: return WM_EVMASK_KEY_RELEASE; case WME_BUTTON_PRESS: return WM_EVMASK_BUTTON_PRESS; case WME_BUTTON_RELEASE: return WM_EVMASK_BUTTON_RELEASE; case WME_MOTION: return WM_EVMASK_POINTER_MOTION | WM_EVMASK_BUTTON_MOTION; case WME_ENTER: return WM_EVMASK_ENTER_WINDOW; case WME_LEAVE: return WM_EVMASK_LEAVE_WINDOW; case WME_EXPOSE: return WM_EVMASK_EXPOSURE; case WME_CONFIGURE: return WM_EVMASK_STRUCTURE_NOTIFY; case WME_MAP: return WM_EVMASK_STRUCTURE_NOTIFY; case WME_UNMAP: return WM_EVMASK_STRUCTURE_NOTIFY; case WME_DESTROY: return WM_EVMASK_STRUCTURE_NOTIFY; case WME_FOCUS_IN: case WME_FOCUS_OUT: return WM_EVMASK_FOCUS_CHANGE; case WME_PROPERTY: return WM_EVMASK_PROPERTY_CHANGE; case WME_COLORMAP: return WM_EVMASK_COLORMAP_CHANGE; case WME_VISIBILITY: return WM_EVMASK_VISIBILITY_CHANGE; 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; } extern void W_WLClientCommitAllToplevels(void); static void wl_flush(void *ectx, Bool discard) { WLEventCtx *ctx = ectx; (void)discard; /* Commit any toplevel surfaces that may have been painted into * since the last flush (direct fill_rect, draw_line, etc. don't * trigger individual commits). */ W_WLClientCommitAllToplevels(); /* 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); if (ctx->head != ctx->tail) wwarning("wl_flush: after dispatch, queue has events (head=%d tail=%d)", ctx->head, ctx->tail); } } 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; } /* ------------------------------------------------------------------ */ /* Stubs for new vtable methods (Wayland: not yet implemented) */ /* ------------------------------------------------------------------ */ static Bool wl_grab_keyboard(void *ectx, WNativeWindow w) { (void)ectx; (void)w; return False; } static void wl_ungrab_keyboard(void *ectx) { (void)ectx; } static void wl_allow_events(void *ectx) { (void)ectx; } static Bool wl_grab_pointer(void *ectx, WNativeWindow w, WMNativePixmap c) { (void)ectx; (void)w; (void)c; return False; } static void wl_ungrab_pointer(void *ectx) { (void)ectx; } static WNativeWindow wl_query_pointer(void *ectx, WNativeWindow w, int *rx, int *ry, int *wx, int *wy, unsigned int *m) { (void)ectx; (void)w; if (rx) *rx = 0; if (ry) *ry = 0; if (wx) *wx = 0; if (wy) *wy = 0; if (m) *m = 0; return 0; } static Bool wl_get_window_geometry(void *ectx, WNativeWindow w, int *x, int *y, unsigned int *width, unsigned int *height) { (void)ectx; extern struct W_View *W_GetViewForWindow(WNativeWindow id); struct W_View *v = W_GetViewForWindow(w); if (!v) { if (x) *x = 0; if (y) *y = 0; if (width) *width = 0; if (height) *height = 0; return False; } if (x) *x = v->pos.x; if (y) *y = v->pos.y; if (width) *width = v->size.width; if (height) *height = v->size.height; return True; } extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y); static void wl_move_window(void *ectx, WNativeWindow w, int x, int y) { (void)ectx; W_WLClientPopupSetPosition(w, x, y); } /* Implemented in wclient_wl.c — calls xdg_toplevel_move on the client * connection so the compositor handles the drag. */ extern Bool W_WLClientBeginMove(WNativeWindow window); static Bool wl_window_begin_move(void *ectx, WNativeWindow window) { (void)ectx; return W_WLClientBeginMove(window); } extern struct W_View *W_GetViewForWindow(WNativeWindow id); static WNativeWindow wl_translate_coordinates(void *ectx, WNativeWindow src, WNativeWindow dst, int sx, int sy, int *dx, int *dy) { (void)ectx; (void)dst; /* Translate (sx, sy) from src view's coord space to the toplevel's * coord space (the closest thing to "screen" coords on Wayland — * actual screen position is unknown to clients). Walks up from src, * accumulating pos.x/y. */ int ox = 0, oy = 0; struct W_View *v = W_GetViewForWindow(src); while (v) { ox += v->pos.x; oy += v->pos.y; if (v->flags.topLevel) break; v = v->parent; } if (dx) *dx = sx + ox; if (dy) *dy = sy + oy; return src; } static WNativeWindow wl_get_root_window(void *ectx) { (void)ectx; return 0; } static Bool wl_query_tree(void *ectx, WNativeWindow w, WNativeWindow **children, unsigned int *nc) { (void)ectx; (void)w; if (children) *children = NULL; if (nc) *nc = 0; return False; } static void wl_get_pointer_control(void *ectx, int *an, int *ad, int *t) { (void)ectx; if (an) *an = 2; if (ad) *ad = 1; if (t) *t = 4; } static void wl_set_pointer_control(void *ectx, int an, int ad, int t) { (void)ectx; (void)an; (void)ad; (void)t; } static int wl_modifier_from_key(void *ectx, const char *key) { (void)ectx; /* Sensible defaults matching common Wayland compositors */ if (strcasecmp(key, "SHIFT") == 0) return 0; if (strcasecmp(key, "CONTROL") == 0) return 2; if (strcasecmp(key, "ALT") == 0) return 3; if (strcasecmp(key, "META") == 0) return 3; if (strcasecmp(key, "SUPER") == 0) return 6; if (strcasecmp(key, "HYPER") == 0) return 0; if (strcasecmp(key, "MOD1") == 0) return 3; if (strcasecmp(key, "MOD2") == 0) return 4; if (strcasecmp(key, "MOD3") == 0) return 5; if (strcasecmp(key, "MOD4") == 0) return 6; if (strcasecmp(key, "MOD5") == 0) return 7; return -1; } static const char *wl_keysym_to_string(void *ectx, WMNativeKeysym ks) { (void)ectx; static char buf[64]; xkb_keysym_get_name((xkb_keysym_t)ks, buf, sizeof(buf)); return buf; } static int wl_get_modifier_keysyms(void *ectx, WMNativeKeysym *out, int max_per_mod) { (void)ectx; memset(out, 0, 8 * max_per_mod * sizeof(WMNativeKeysym)); /* Shift=0, Lock=1, Control=2, Mod1=3, Mod2=4, Mod3=5, Mod4=6, Mod5=7 */ out[0 * max_per_mod] = XKB_KEY_Shift_L; out[1 * max_per_mod] = XKB_KEY_Caps_Lock; out[2 * max_per_mod] = XKB_KEY_Control_L; out[3 * max_per_mod] = XKB_KEY_Alt_L; out[4 * max_per_mod] = XKB_KEY_Num_Lock; out[5 * max_per_mod] = XKB_KEY_Scroll_Lock; out[6 * max_per_mod] = XKB_KEY_Super_L; out[7 * max_per_mod] = XKB_KEY_Hyper_L; return 8; } static unsigned int wl_keysym_to_keycode(void *ectx, WMNativeKeysym ks) { (void)ectx; extern struct xkb_keymap *W_WLClientGetKeymap(void); struct xkb_keymap *km = W_WLClientGetKeymap(); if (!km) return 0; xkb_keycode_t min = xkb_keymap_min_keycode(km); xkb_keycode_t max = xkb_keymap_max_keycode(km); for (xkb_keycode_t kc = min; kc <= max; kc++) { const xkb_keysym_t *syms; int num_levels = xkb_keymap_num_levels_for_key(km, kc, 0); for (int level = 0; level < num_levels; level++) { int nsyms = xkb_keymap_key_get_syms_by_level(km, kc, 0, level, &syms); for (int i = 0; i < nsyms; i++) { if (syms[i] == (xkb_keysym_t)ks) return (unsigned int)kc; } } } return 0; } static WMNativeKeysym wl_keycode_to_keysym(void *ectx, unsigned int kc, int idx) { (void)ectx; extern struct xkb_keymap *W_WLClientGetKeymap(void); struct xkb_keymap *km = W_WLClientGetKeymap(); if (!km) return 0; const xkb_keysym_t *syms; int nsyms = xkb_keymap_key_get_syms_by_level(km, (xkb_keycode_t)kc, 0, idx, &syms); return (nsyms > 0) ? (WMNativeKeysym)syms[0] : 0; } static void wl_keysym_convert_case(void *ectx, WMNativeKeysym sym, WMNativeKeysym *lower, WMNativeKeysym *upper) { (void)ectx; if (lower) *lower = (WMNativeKeysym)xkb_keysym_to_lower((xkb_keysym_t)sym); if (upper) *upper = (WMNativeKeysym)xkb_keysym_to_upper((xkb_keysym_t)sym); } static void wl_send_wm_command(void *ectx, const char *cmd) { (void)ectx; (void)cmd; /* Wayland backend relies on inotify to detect defaults file changes. * No explicit signal needed — wmaker's event loop polls inotify. */ } static void wl_set_window_hints(void *ectx, WNativeWindow w, int minw, int minh, int maxw, int maxh, int bw, int bh) { (void)ectx; (void)w; (void)minw; (void)minh; (void)maxw; (void)maxh; (void)bw; (void)bh; } static void wl_set_window_property_string(void *ectx, WNativeWindow w, const char *atom, const char *val) { (void)ectx; (void)w; (void)atom; (void)val; } static void wl_set_window_property_data(void *ectx, WNativeWindow w, WMNativeAtom prop, WMNativeAtom type, int fmt, const void *data, int nel) { (void)ectx; (void)w; (void)prop; (void)type; (void)fmt; (void)data; (void)nel; } static void wl_recolor_cursor(void *ectx, WMNativePixmap c, unsigned short fr, unsigned short fg, unsigned short fb, unsigned short br, unsigned short bg, unsigned short bb) { (void)ectx; (void)c; (void)fr; (void)fg; (void)fb; (void)br; (void)bg; (void)bb; } static void wl_set_error_handler(void *ectx, void *h) { (void)ectx; (void)h; } static void wl_restore_error_handler(void *ectx) { (void)ectx; } 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, .grab_keyboard = wl_grab_keyboard, .ungrab_keyboard = wl_ungrab_keyboard, .allow_events = wl_allow_events, .grab_pointer = wl_grab_pointer, .ungrab_pointer = wl_ungrab_pointer, .query_pointer = wl_query_pointer, .get_window_geometry = wl_get_window_geometry, .move_window = wl_move_window, .window_begin_move = wl_window_begin_move, .translate_coordinates = wl_translate_coordinates, .get_root_window = wl_get_root_window, .query_tree = wl_query_tree, .get_pointer_control = wl_get_pointer_control, .set_pointer_control = wl_set_pointer_control, .modifier_from_key = wl_modifier_from_key, .keysym_to_string = wl_keysym_to_string, .keysym_to_keycode = wl_keysym_to_keycode, .keycode_to_keysym = wl_keycode_to_keysym, .keysym_convert_case = wl_keysym_convert_case, .get_modifier_keysyms = wl_get_modifier_keysyms, .send_wm_command = wl_send_wm_command, .set_window_hints = wl_set_window_hints, .set_window_property_string = wl_set_window_property_string, .set_window_property_data = wl_set_window_property_data, .recolor_cursor = wl_recolor_cursor, .set_error_handler = wl_set_error_handler, .restore_error_handler = wl_restore_error_handler, }; /* ------------------------------------------------------------------ */ /* 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); }