wayland: Phase 78 — keyboard event dispatch and WM shortcut handler
Add wWaylandKeyPress(keycode, modifiers) to event.c/h: scans wKeyBindings and dispatches a subset of WM shortcuts (miniaturize, hide, maximize, raise/lower, close, focus-next/prev, workspace switch) using raw XKB keycode + modifier values instead of an XEvent. In wl_backend.c, wire handle_keyboard_key and handle_keyboard_modifiers listeners on the headless keyboard device so that: - key events are checked against WM shortcuts first - non-shortcut keys are forwarded to the focused client via wlr_seat_keyboard_notify_key - modifier changes are always notified via wlr_seat_keyboard_notify_modifiers The headless backend doesn't generate real key events so the listener is a no-op in the smoke test, but the infrastructure is ready for a real DRM+libinput backend. Smoke test: check for "keyboard key+modifiers listeners wired" log. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b1e4a99c5d
commit
b576025f9b
|
|
@ -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
|
||||
|
|
|
|||
127
src/event.c
127
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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue