wayland: Phase 77 — XKB keymap + keysym_to_keycode + modifier masks

Set a default XKB keymap on the headless keyboard device in
wl_display_open() so the keyboard has a keycode↔keysym mapping
available from startup.

Implement wl_keysym_to_keycode() by scanning the XKB keymap for the
first keycode that produces the given keysym; XKB keycodes equal
evdev+8 == X11 keycodes, so wKeyBindings are directly compatible.

Implement wl_keysym_from_keycode() as the reverse lookup.

Call wSetModifierMasks(Mod1Mask, Mod1Mask, Mod4Mask, 0) from
wl_screen_create_internals() so AltMask/MetaMask/SuperMask are
non-zero in Wayland mode, making modifier parsing in wXModifierFromKey
produce correct values.

Add wSetModifierMasks() to xmodifier.c/h as the non-X11 entry point.

Replace the four XKeysymToKeycode(dpy, ksym) call sites in
defaults.c, rootmenu.c, and usermenu.c with
wm_backend->keysym_to_keycode() so they work when dpy==NULL.

Smoke test: check for "XKB keymap set on headless keyboard" log line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-28 20:32:07 +01:00
parent c7acf9d89d
commit b1e4a99c5d
7 changed files with 114 additions and 6 deletions

View File

@ -88,10 +88,13 @@
/* Generated xdg-shell protocol implementation (Wayland glue code) */
#include "protocols/xdg-shell-protocol.c"
#include <wlr/util/log.h>
/* Phase 77: XKB for keysym→keycode translation and modifier mask setup */
#include <xkbcommon/xkbcommon.h>
#include "../../WindowMaker.h"
#include "../../screen.h"
#include "../../window.h"
#include "../../xmodifier.h" /* Phase 77: wSetModifierMasks */
#include "../backend.h"
#include "wl_backend.h"
@ -500,8 +503,27 @@ wl_display_open(const char *name)
* wlr_seat_keyboard_notify_enter() can deliver keyboard focus. */
wl_state.keyboard_dev =
wlr_headless_add_input_device(wl_state.backend, WLR_INPUT_DEVICE_KEYBOARD);
if (wl_state.keyboard_dev)
if (wl_state.keyboard_dev) {
/* Phase 77: set a default XKB keymap on the headless keyboard so that
* wl_keysym_to_keycode() can populate wKeyBindings during StartUp().
* The xkb_keymap_new_from_names(NULL) produces the system default
* layout (usually "us"), giving us the same keycode space as X11. */
struct wlr_keyboard *kb = wl_state.keyboard_dev ? wl_state.keyboard_dev->keyboard : NULL;
if (kb) {
struct xkb_context *xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (xkb_ctx) {
struct xkb_keymap *keymap = xkb_keymap_new_from_names(
xkb_ctx, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (keymap) {
wlr_keyboard_set_keymap(kb, keymap);
xkb_keymap_unref(keymap);
wwarning("wl_backend: XKB keymap set on headless keyboard");
}
xkb_context_unref(xkb_ctx);
}
}
wlr_seat_set_keyboard(wl_state.seat, wl_state.keyboard_dev);
}
/* Phase 53: create a headless pointer so the seat advertises pointer
* capability to clients (required for clients that bind wl_pointer). */
@ -1299,6 +1321,11 @@ wl_screen_create_internals(WScreen *scr)
wPreferences.auto_focus = 1;
if (wl_state.seat)
wlr_seat_keyboard_clear_focus(wl_state.seat);
/* Phase 77: initialise modifier masks to standard XKB/X11 values so
* that wXModifierFromKey("ALT") etc. return non-zero values.
* Mod1Mask=8 (Alt), Mod4Mask=64 (Super/Win); Meta aliases Alt. */
wSetModifierMasks(Mod1Mask, Mod1Mask, Mod4Mask, 0);
}
static void wl_screen_create_pixmaps(WScreen *scr) { (void)scr; }
static void wl_screen_load_tech_font(WScreen *scr) { (void)scr; }
@ -1368,8 +1395,59 @@ wl_workspace_badge_update(WScreen *scr, WNativeWindow badge_win, RImage *img)
/* XKB */
/* ------------------------------------------------------------------ */
static WKeyCode wl_keysym_to_keycode(unsigned long keysym) { (void)keysym; return 0; }
static unsigned long wl_keysym_from_keycode(unsigned int keycode, unsigned int index) { (void)keycode; (void)index; return 0; }
static WKeyCode
wl_keysym_to_keycode(unsigned long keysym)
{
/* Phase 77: scan the XKB keymap on the headless keyboard to find the first
* keycode that produces the given keysym at any level/layout.
* The XKB keycode == evdev_scancode + 8, which equals the X11 keycode —
* so the values stored in wKeyBindings are directly compatible. */
if (!wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard)
return 0;
struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard;
if (!kb->keymap)
return 0;
xkb_keycode_t min_kc = xkb_keymap_min_keycode(kb->keymap);
xkb_keycode_t max_kc = xkb_keymap_max_keycode(kb->keymap);
for (xkb_keycode_t kc = min_kc; kc <= max_kc; kc++) {
xkb_layout_index_t nlayouts = xkb_keymap_num_layouts_for_key(kb->keymap, kc);
xkb_layout_index_t layout;
for (layout = 0; layout < nlayouts; layout++) {
xkb_level_index_t nlevels = xkb_keymap_num_levels_for_key(kb->keymap, kc, layout);
xkb_level_index_t level;
for (level = 0; level < nlevels; level++) {
const xkb_keysym_t *syms = NULL;
int n = xkb_keymap_key_get_syms_by_level(kb->keymap, kc, layout, level, &syms);
int si;
for (si = 0; si < n; si++) {
if (syms[si] == (xkb_keysym_t)keysym)
return (WKeyCode)kc;
}
}
}
}
return 0;
}
static unsigned long
wl_keysym_from_keycode(unsigned int keycode, unsigned int index)
{
/* Phase 77: reverse lookup — return the keysym at the given keycode/level. */
if (!wl_state.keyboard_dev || !wl_state.keyboard_dev->keyboard)
return 0;
struct wlr_keyboard *kb = wl_state.keyboard_dev->keyboard;
if (!kb->keymap)
return 0;
const xkb_keysym_t *syms = NULL;
int n = xkb_keymap_key_get_syms_by_level(kb->keymap, (xkb_keycode_t)keycode,
0, (xkb_level_index_t)index, &syms);
if (n > 0 && syms)
return (unsigned long)syms[0];
return 0;
}
static void wl_xkb_lock_group(int group) { (void)group; }
static int wl_xkb_get_group(void) { return 0; }

View File

@ -2225,7 +2225,8 @@ static int getKeybind(WScreen * scr, WDefaultEntry * entry, WMPropList * value,
return False;
}
shortcut.keycode = XKeysymToKeycode(dpy, ksym);
/* Phase 77: use vtable so keysym lookup works for both X11 and Wayland. */
shortcut.keycode = wm_backend->keysym_to_keycode((unsigned long)ksym);
if (shortcut.keycode == 0) {
wwarning(_("%s:invalid key in shortcut \"%s\""), entry->key, val);
return False;

View File

@ -433,7 +433,8 @@ static Bool addShortcut(const char *file, const char *shortcutDefinition, WMenu
return False;
}
ptr->keycode = XKeysymToKeycode(dpy, ksym);
/* Phase 77: use vtable so keysym lookup works for both X11 and Wayland. */
ptr->keycode = wm_backend->keysym_to_keycode((unsigned long)ksym);
if (ptr->keycode == 0) {
wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
shortcutDefinition, entry->text);

View File

@ -173,7 +173,8 @@ static WUserMenuData *convertShortcuts(WScreen *scr, WMPropList *shortcut)
if (ksym == NoSymbol)
continue;
data->key[j].keycode = XKeysymToKeycode(dpy, ksym);
/* Phase 77: use vtable so keysym lookup works for both X11 and Wayland. */
data->key[j].keycode = wm_backend->keysym_to_keycode((unsigned long)ksym);
if (data->key[j].keycode)
j++;
}

View File

@ -328,6 +328,17 @@ int wXModifierFromKey(const char *key)
return -1;
}
/* Phase 77: allow non-X11 backends to set standard modifier masks directly.
* Called from wl_screen_create_internals with XKB-standard values:
* AltMask=Mod1Mask, MetaMask=Mod1Mask, SuperMask=Mod4Mask, HyperMask=0. */
void wSetModifierMasks(int alt, int meta, int super, int hyper)
{
AltMask = alt;
MetaMask = meta;
SuperMask = super;
HyperMask = hyper;
}
/* Wrapper so that we may fit the WM naming conventions, yet leave the
original XEmacs function name in place. */
void wXModifierInitialize(void)

View File

@ -23,5 +23,7 @@
void wXModifierInitialize(void);
int wXModifierFromKey(const char *key);
const char *wXModifierToShortcutLabel(int mask);
/* Phase 77: set modifier masks for non-X11 backends (Wayland). */
void wSetModifierMasks(int alt, int meta, int super, int hyper);
#endif /* _XMODIFIER_H_INCLUDED */

View File

@ -91,6 +91,12 @@
# 34. (Phase 76) switchpanel.c: Alt-Tab panel title falls back to
# wwin->wm_class when frame is NULL so Wayland windows appear in the
# switcher without crashing (build-only check).
# 35. (Phase 77) XKB keymap set on headless keyboard so wl_keysym_to_keycode
# produces real keycodes; modifier masks (Alt=Mod1Mask, Super=Mod4Mask)
# set via wSetModifierMasks so wKeyBindings are populated correctly;
# 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.
#
# This test grows as wl_backend vtable slots are implemented.
#
@ -203,6 +209,14 @@ grep -q 'xdg-decoration-manager created' /tmp/wl-smoke-stderr \
|| die "compositor did not log xdg-decoration-manager creation"
echo " decoration check: xdg-decoration-manager created"
# -- 4d. Phase 77: XKB keymap must be set on headless keyboard ------------
#
# wl_display_open() logs "XKB keymap set on headless keyboard" after
# Phase 77 changes; verify the log line is present.
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"
# -- 5. Core Wayland globals must be advertised ---------------------------
#
# Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the