diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index d6db683..447a79a 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -95,6 +95,7 @@ #include "../../screen.h" #include "../../window.h" #include "../../xmodifier.h" /* Phase 77: wSetModifierMasks */ +#include "../../event.h" /* Phase 78: wWaylandKeyPress */ #include "../backend.h" #include "wl_backend.h" @@ -162,6 +163,8 @@ struct wl_compositor_state { unsigned long next_synthetic_id; /* >= 0x10000000 */ /* Phase 43: headless keyboard device for seat keyboard focus */ struct wlr_input_device *keyboard_dev; + struct wl_listener keyboard_key_listener; /* Phase 78 */ + struct wl_listener keyboard_modifiers_listener; /* Phase 78 */ /* Phase 53: headless pointer device so seat advertises pointer capability */ struct wlr_input_device *pointer_dev; /* Phase 54: virtual pointer position (screen coordinates) */ @@ -401,6 +404,55 @@ handle_xdg_new_surface(struct wl_listener *listener, void *data) wlr_xdg_surface_schedule_configure(xdg_surface); } +/* ------------------------------------------------------------------ */ +/* Phase 78: keyboard event handlers */ +/* ------------------------------------------------------------------ */ + +/* + * handle_keyboard_key — fires for every key press/release on the headless + * keyboard. On key-press, dispatches WM shortcuts via wWaylandKeyPress(); + * then notifies the focused Wayland client via wlr_seat_keyboard_notify_key. + * (The headless backend never actually fires these in practice, but the + * listener is wired so it works immediately when a real backend is used.) + */ +static void +handle_keyboard_key(struct wl_listener *listener, void *data) +{ + struct wlr_event_keyboard_key *event = data; + (void)listener; + + if (!wl_state.seat || !wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard) + return; + + struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard; + + /* Update modifier state */ + wlr_seat_keyboard_notify_modifiers(wl_state.seat, &kb->modifiers); + + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + uint32_t keycode = event->keycode + 8; /* evdev → XKB keycode */ + uint32_t modifiers = wlr_keyboard_get_modifiers(kb); + + /* Try WM shortcut dispatch first */ + if (wWaylandKeyPress((unsigned int)keycode, (unsigned int)modifiers)) + return; /* consumed — do not forward to client */ + } + + /* Forward non-shortcut key events to focused client */ + wlr_seat_keyboard_notify_key(wl_state.seat, + event->time_msec, event->keycode, event->state); +} + +static void +handle_keyboard_modifiers(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + if (!wl_state.seat || !wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard) + return; + struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard; + wlr_seat_keyboard_notify_modifiers(wl_state.seat, &kb->modifiers); +} + /* ------------------------------------------------------------------ */ /* Display lifecycle (Phase 32 / Phase 34) */ /* ------------------------------------------------------------------ */ @@ -523,6 +575,18 @@ wl_display_open(const char *name) } } wlr_seat_set_keyboard(wl_state.seat, wl_state.keyboard_dev); + + /* Phase 78: wire keyboard key + modifier listeners so WM shortcuts + * are dispatched and non-shortcut keys reach the focused client. */ + if (wl_state.keyboard_dev->keyboard) { + wl_state.keyboard_key_listener.notify = handle_keyboard_key; + wl_signal_add(&wl_state.keyboard_dev->keyboard->events.key, + &wl_state.keyboard_key_listener); + wl_state.keyboard_modifiers_listener.notify = handle_keyboard_modifiers; + wl_signal_add(&wl_state.keyboard_dev->keyboard->events.modifiers, + &wl_state.keyboard_modifiers_listener); + wwarning("wl_backend: keyboard key+modifiers listeners wired"); + } } /* Phase 53: create a headless pointer so the seat advertises pointer diff --git a/src/event.c b/src/event.c index 4d748f5..66dd341 100644 --- a/src/event.c +++ b/src/event.c @@ -1299,6 +1299,133 @@ static int CheckFullScreenWindowFocused(WScreen * scr) return 0; } +/* + * Phase 78: Wayland keyboard event dispatch. + * Called from wl_backend's keyboard key listener with the XKB keycode and + * modifier mask. Scans wKeyBindings and dispatches the matching WM action. + * Returns 1 if consumed (WM shortcut), 0 if the event should be forwarded. + */ +int wWaylandKeyPress(unsigned int keycode, unsigned int modifiers) +{ + WScreen *scr = wScreenWithNumber(0); + if (!scr) + return 0; + + WWindow *wwin = scr->focused_window; + int modifiers_filtered = (int)(modifiers & (unsigned int)w_global.shortcut.modifiers_mask); + int command = -1; + short i; + + for (i = 0; i < WKBD_LAST; i++) { + if (wKeyBindings[i].keycode == 0) + continue; + if ((unsigned int)wKeyBindings[i].keycode == keycode && + wKeyBindings[i].modifier == modifiers_filtered) { + command = i; + break; + } + } + + if (command < 0) + return 0; /* not a WM shortcut */ + +/* Reuse the ISMAPPED/ISFOCUSED macros defined inside handleKeyPress below. */ +#ifndef ISMAPPED +#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded)) +#define ISFOCUSED(w) ((w) && (w)->flags.focused) +#endif + + /* Dispatch WM shortcuts — identical to handleKeyPress switch but without + * XEvent arguments. Only workspace/window commands that don't need an + * XEvent are included; pointer-position shortcuts use the virtual pointer. */ + switch (command) { + case WKBD_MINIATURIZE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + CloseWindowMenu(scr); + wIconifyWindow(wwin); + } + break; + case WKBD_HIDE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + WApplication *wapp = wApplicationOf(wwin->main_window); + CloseWindowMenu(scr); + if (wapp && !WFLAGP(wapp->main_window_desc, no_appicon)) + wHideApplication(wapp); + } + break; + case WKBD_MAXIMIZE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + CloseWindowMenu(scr); + wMaximizeWindow(wwin, MAX_VERTICAL | MAX_HORIZONTAL, + wGetHeadForWindow(wwin)); + } + break; + case WKBD_VMAXIMIZE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + CloseWindowMenu(scr); + wMaximizeWindow(wwin, MAX_VERTICAL, + wGetHeadForWindow(wwin)); + } + break; + case WKBD_HMAXIMIZE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + CloseWindowMenu(scr); + wMaximizeWindow(wwin, MAX_HORIZONTAL, + wGetHeadForWindow(wwin)); + } + break; + case WKBD_RAISE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { + CloseWindowMenu(scr); + wRaiseFrame(wwin->frame->core); + } + break; + case WKBD_LOWER: + if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { + CloseWindowMenu(scr); + wLowerFrame(wwin->frame->core); + } + break; + case WKBD_CLOSE: + if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { + CloseWindowMenu(scr); + if (wwin->protocols.DELETE_WINDOW && !WFLAGP(wwin, kill_close)) + wClientSendProtocol(wwin, w_global.atom.wm.delete_window, + w_global.timestamp.last_event); + } + break; + case WKBD_FOCUSNEXT: + wWindowFocusNext(wwin, True); + break; + case WKBD_FOCUSPREV: + wWindowFocusPrev(wwin, True); + break; + case WKBD_WORKSPACE1: case WKBD_WORKSPACE2: case WKBD_WORKSPACE3: + case WKBD_WORKSPACE4: case WKBD_WORKSPACE5: case WKBD_WORKSPACE6: + case WKBD_WORKSPACE7: case WKBD_WORKSPACE8: case WKBD_WORKSPACE9: + case WKBD_WORKSPACE10: + { + int widx2 = command - WKBD_WORKSPACE1; + int ws = (scr->current_workspace / 10) * 10 + widx2; + if (wPreferences.ws_advance || ws < scr->workspace_count) + wWorkspaceChange(scr, ws); + } + break; + case WKBD_NEXTWORKSPACE: + wWorkspaceRelativeChange(scr, 1); + break; + case WKBD_PREVWORKSPACE: + wWorkspaceRelativeChange(scr, -1); + break; + default: + /* Complex shortcuts that need XEvent (pointer coords, menus) are + * not dispatched here; return 0 so the caller forwards the key. */ + return 0; + } + + return 1; /* consumed */ +} + static void handleKeyPress(XEvent * event) { WScreen *scr = wScreenForRootWindow(event->xkey.root); diff --git a/src/event.h b/src/event.h index fa504be..4bcaff7 100644 --- a/src/event.h +++ b/src/event.h @@ -40,4 +40,12 @@ Bool IsDoubleClick(WScreen *scr, WBackendEvent *event); /* called from the signal handler */ void NotifyDeadProcess(pid_t pid, unsigned char status); +/* Phase 78: Wayland keyboard event dispatch — called from wl_backend when a + * key-press event arrives. keycode is the XKB keycode (evdev+8, compatible + * with wKeyBindings); modifiers is the XKB modifier mask (compatible with the + * X11 ShiftMask/ControlMask/Mod1Mask bit layout). Returns 1 if the event was + * consumed as a WM shortcut, 0 if it should be forwarded to the focused client. + */ +int wWaylandKeyPress(unsigned int keycode, unsigned int modifiers); + #endif /* WMEVENT_H */ diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 020af90..8a59a6f 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -97,6 +97,13 @@ # XKeysymToKeycode(dpy,...) replaced by vtable in defaults.c/rootmenu.c/ # usermenu.c so keyboard shortcuts are correctly configured in Wayland mode. # Verified by "XKB keymap set on headless keyboard" log. +# 36. (Phase 78) wWaylandKeyPress() added to event.c: scans wKeyBindings +# and dispatches WM shortcuts (miniaturize, hide, maximize, raise/lower, +# close, focus, workspace switch) without needing an XEvent. +# Keyboard key+modifiers listeners wired in wl_backend so key events +# from the headless keyboard are dispatched through the WM shortcut +# handler before being forwarded to focused clients. +# Verified by "keyboard key+modifiers listeners wired" log. # # This test grows as wl_backend vtable slots are implemented. # @@ -217,6 +224,11 @@ grep -q 'XKB keymap set on headless keyboard' /tmp/wl-smoke-stderr \ || die "compositor did not log XKB keymap setup (wl_keysym_to_keycode will return 0)" echo " xkb check: XKB keymap set on headless keyboard" +# -- 4e. Phase 78: keyboard listeners must be wired ---------------------- +grep -q 'keyboard key+modifiers listeners wired' /tmp/wl-smoke-stderr \ + || die "compositor did not log keyboard listeners wired" +echo " kbd listener check: keyboard key+modifiers listeners wired" + # -- 5. Core Wayland globals must be advertised --------------------------- # # Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the