wings(wayland-client): wl_pointer + wl_keyboard input dispatch

Task 5.

wclient_wl.c subscribes to wl_seat.capabilities.  On POINTER capability
it binds wl_pointer with a listener that translates enter/leave/motion/
button events into WMEvent (WME_ENTER, WME_LEAVE, WME_MOTION,
WME_BUTTON_PRESS/RELEASE).  Linux evdev button codes (BTN_LEFT, etc.)
map to X-style numbers (1, 2, 3, 8, 9).  Pointer focus is tracked per
wl_surface and resolved to a top-level W_View via wlc_top_for_surface.

On KEYBOARD capability it binds wl_keyboard.  The keymap event mmaps
the fd, compiles via xkb_keymap_new_from_string, and stashes the result
in WLClientCtx (xkb_keymap + xkb_state).  Subsequent key events use
xkb_state_key_get_one_sym for the keysym and xkb_state_key_get_utf8 for
text — these populate WMEKey.{keycode, keysym, utf8, utf8_len}.
Modifiers update xkb_state and a cached X-style mod mask
(ShiftMask, ControlMask, Mod1Mask, Mod4Mask) used in WMEKey.state and
WMEButton.state.

Focus enter/leave on the keyboard side dispatches WME_FOCUS_IN /
WME_FOCUS_OUT so WINGs widgets get focus tracking.

Events flow via W_WL_EnqueueEvent, the same ring buffer wevent_wl.c
already drains for the compositor side — no separate event-pump
required.  Axis (scroll) events are stubbed out for now and will be
synthesized as button 4/5 in a follow-up.
This commit is contained in:
Window Maker 2026-05-24 10:27:56 +02:00
parent 97b2d03a34
commit da3bf4c096
1 changed files with 356 additions and 2 deletions

View File

@ -472,6 +472,361 @@ wl_client_toplevel_destroy(struct W_View *view)
wl_client_view_destroy_backing((unsigned long)view->window);
}
/* ------------------------------------------------------------------ */
/* Input: wl_pointer / wl_keyboard listeners */
/* ------------------------------------------------------------------ */
#include "WMEvent.h"
/* Map an X11-style state mask from xkbcommon modifier state. */
static unsigned int
wlc_xkb_mod_mask(struct xkb_state *state)
{
if (!state) return 0;
unsigned int mask = 0;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= ShiftMask;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= ControlMask;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= Mod1Mask;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= Mod4Mask;
return mask;
}
/* Pointer state */
static double wlc_ptr_x, wlc_ptr_y;
static uint32_t wlc_ptr_focus_serial;
static WLClientToplevel *wlc_ptr_focused;
static unsigned int wlc_ptr_button_state;
static unsigned int wlc_ptr_mods;
static WLClientToplevel *
wlc_top_for_surface(struct wl_surface *surf)
{
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
if (wlc_toptable[i].id && wlc_toptable[i].top &&
wlc_toptable[i].top->surface == surf)
return wlc_toptable[i].top;
}
return NULL;
}
static void
pointer_handle_enter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy)
{
(void)p;
WLClientCtx *ctx = data;
wlc_ptr_focus_serial = serial;
wlc_ptr_focused = wlc_top_for_surface(surface);
wlc_ptr_x = wl_fixed_to_double(sx);
wlc_ptr_y = wl_fixed_to_double(sy);
if (!wlc_ptr_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_ENTER;
ev.window = wlc_ptr_focused->view->window;
ev.u.crossing.window = ev.window;
ev.u.crossing.x = (int)wlc_ptr_x;
ev.u.crossing.y = (int)wlc_ptr_y;
ev.u.crossing.x_root = (int)wlc_ptr_x;
ev.u.crossing.y_root = (int)wlc_ptr_y;
ev.u.crossing.mode = NotifyNormal;
ev.u.crossing.detail = NotifyAncestor;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
pointer_handle_leave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface)
{
(void)p; (void)serial; (void)surface;
WLClientCtx *ctx = data;
if (!wlc_ptr_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_LEAVE;
ev.window = wlc_ptr_focused->view->window;
ev.u.crossing.window = ev.window;
ev.u.crossing.mode = NotifyNormal;
ev.u.crossing.detail = NotifyAncestor;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
wlc_ptr_focused = NULL;
}
static void
pointer_handle_motion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t sx, wl_fixed_t sy)
{
(void)p;
WLClientCtx *ctx = data;
wlc_ptr_x = wl_fixed_to_double(sx);
wlc_ptr_y = wl_fixed_to_double(sy);
if (!wlc_ptr_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_MOTION;
ev.window = wlc_ptr_focused->view->window;
ev.u.motion.window = ev.window;
ev.u.motion.time = time;
ev.u.motion.x = (int)wlc_ptr_x;
ev.u.motion.y = (int)wlc_ptr_y;
ev.u.motion.x_root = (int)wlc_ptr_x;
ev.u.motion.y_root = (int)wlc_ptr_y;
ev.u.motion.state = wlc_ptr_button_state | wlc_ptr_mods;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
pointer_handle_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
(void)p; (void)serial;
WLClientCtx *ctx = data;
if (!wlc_ptr_focused) return;
/* Linux input event codes -> X-style button numbers. */
unsigned int wmb;
switch (button) {
case 0x110 /*BTN_LEFT*/: wmb = 1; break;
case 0x112 /*BTN_MIDDLE*/: wmb = 2; break;
case 0x111 /*BTN_RIGHT*/: wmb = 3; break;
case 0x113 /*BTN_SIDE*/: wmb = 8; break;
case 0x114 /*BTN_EXTRA*/: wmb = 9; break;
default: wmb = 0; break;
}
if (!wmb) return;
unsigned int mask = (wmb == 1) ? Button1Mask :
(wmb == 2) ? Button2Mask :
(wmb == 3) ? Button3Mask : 0;
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
wlc_ptr_button_state |= mask;
else
wlc_ptr_button_state &= ~mask;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = (state == WL_POINTER_BUTTON_STATE_PRESSED)
? WME_BUTTON_PRESS : WME_BUTTON_RELEASE;
ev.window = wlc_ptr_focused->view->window;
ev.u.button.window = ev.window;
ev.u.button.button = wmb;
ev.u.button.state = wlc_ptr_button_state | wlc_ptr_mods;
ev.u.button.time = time;
ev.u.button.x = (int)wlc_ptr_x;
ev.u.button.y = (int)wlc_ptr_y;
ev.u.button.x_root = (int)wlc_ptr_x;
ev.u.button.y_root = (int)wlc_ptr_y;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
pointer_handle_axis(void *data, struct wl_pointer *p, uint32_t time,
uint32_t axis, wl_fixed_t value)
{
(void)data; (void)p; (void)time; (void)axis; (void)value;
/* TODO: synthesize button 4/5 events for vertical scroll once we
* verify the basic flow. For now scroll is dropped. */
}
static void pointer_handle_frame(void *d, struct wl_pointer *p) { (void)d; (void)p; }
static void pointer_handle_axis_source(void *d, struct wl_pointer *p, uint32_t s) { (void)d; (void)p; (void)s; }
static void pointer_handle_axis_stop(void *d, struct wl_pointer *p, uint32_t t, uint32_t a) { (void)d; (void)p; (void)t; (void)a; }
static void pointer_handle_axis_discrete(void *d, struct wl_pointer *p, uint32_t a, int32_t v) { (void)d; (void)p; (void)a; (void)v; }
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_handle_enter,
.leave = pointer_handle_leave,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = pointer_handle_axis,
.frame = pointer_handle_frame,
.axis_source = pointer_handle_axis_source,
.axis_stop = pointer_handle_axis_stop,
.axis_discrete = pointer_handle_axis_discrete,
};
/* ------------------------------------------------------------------ */
/* Keyboard */
/* ------------------------------------------------------------------ */
static WLClientToplevel *wlc_kbd_focused;
static void
keyboard_handle_keymap(void *data, struct wl_keyboard *k,
uint32_t format, int fd, uint32_t size)
{
(void)k;
WLClientCtx *ctx = data;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (map == MAP_FAILED) return;
struct xkb_keymap *km = xkb_keymap_new_from_string(ctx->xkb_ctx, map,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
if (!km) return;
if (ctx->xkb_state) xkb_state_unref(ctx->xkb_state);
if (ctx->xkb_keymap) xkb_keymap_unref(ctx->xkb_keymap);
ctx->xkb_keymap = km;
ctx->xkb_state = xkb_state_new(km);
}
static void
keyboard_handle_enter(void *data, struct wl_keyboard *k, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys)
{
(void)k; (void)serial; (void)keys;
WLClientCtx *ctx = data;
wlc_kbd_focused = wlc_top_for_surface(surface);
if (!wlc_kbd_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_FOCUS_IN;
ev.window = wlc_kbd_focused->view->window;
ev.u.focus.window = ev.window;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
keyboard_handle_leave(void *data, struct wl_keyboard *k, uint32_t serial,
struct wl_surface *surface)
{
(void)k; (void)serial; (void)surface;
WLClientCtx *ctx = data;
if (!wlc_kbd_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_FOCUS_OUT;
ev.window = wlc_kbd_focused->view->window;
ev.u.focus.window = ev.window;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
wlc_kbd_focused = NULL;
}
static void
keyboard_handle_key(void *data, struct wl_keyboard *k, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state)
{
(void)k; (void)serial;
WLClientCtx *ctx = data;
if (!wlc_kbd_focused || !ctx->xkb_state) return;
/* wl_keyboard.key gives a Linux evdev keycode; xkbcommon expects +8. */
uint32_t xkb_keycode = key + 8;
xkb_keysym_t sym = xkb_state_key_get_one_sym(ctx->xkb_state, xkb_keycode);
char utf8[8] = {0};
int utf8_len = 0;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
utf8_len = xkb_state_key_get_utf8(ctx->xkb_state, xkb_keycode,
utf8, sizeof(utf8));
if (utf8_len < 0) utf8_len = 0;
if (utf8_len > 7) utf8_len = 7;
}
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = (state == WL_KEYBOARD_KEY_STATE_PRESSED)
? WME_KEY_PRESS : WME_KEY_RELEASE;
ev.window = wlc_kbd_focused->view->window;
ev.u.key.window = ev.window;
ev.u.key.keycode = xkb_keycode;
ev.u.key.state = wlc_ptr_button_state | wlc_ptr_mods;
ev.u.key.time = time;
ev.u.key.keysym = sym;
if (utf8_len > 0) {
memcpy(ev.u.key.utf8, utf8, utf8_len);
ev.u.key.utf8_len = utf8_len;
}
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
keyboard_handle_modifiers(void *data, struct wl_keyboard *k, uint32_t serial,
uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group)
{
(void)k; (void)serial;
WLClientCtx *ctx = data;
if (ctx->xkb_state) {
xkb_state_update_mask(ctx->xkb_state,
mods_depressed, mods_latched, mods_locked,
0, 0, group);
wlc_ptr_mods = wlc_xkb_mod_mask(ctx->xkb_state);
}
}
static void
keyboard_handle_repeat_info(void *data, struct wl_keyboard *k,
int32_t rate, int32_t delay)
{
(void)data; (void)k; (void)rate; (void)delay;
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
.repeat_info = keyboard_handle_repeat_info,
};
/* ------------------------------------------------------------------ */
/* Seat capabilities */
/* ------------------------------------------------------------------ */
static void
seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps)
{
WLClientCtx *ctx = data;
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ctx->pointer) {
ctx->pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(ctx->pointer, &pointer_listener, ctx);
} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ctx->pointer) {
wl_pointer_release(ctx->pointer);
ctx->pointer = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ctx->keyboard) {
ctx->keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(ctx->keyboard, &keyboard_listener, ctx);
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ctx->keyboard) {
wl_keyboard_release(ctx->keyboard);
ctx->keyboard = NULL;
}
}
static void
seat_handle_name(void *data, struct wl_seat *seat, const char *name)
{
(void)data; (void)seat; (void)name;
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};
/* ------------------------------------------------------------------ */
/* xdg_wm_base ping */
/* ------------------------------------------------------------------ */
@ -507,8 +862,7 @@ registry_handle_global(void *data, struct wl_registry *registry,
uint32_t v = version < 5 ? version : 5;
ctx->seat = wl_registry_bind(registry, name,
&wl_seat_interface, v);
/* Pointer/keyboard probes happen lazily on the first capabilities event;
* we'll add a wl_seat listener below in W_WLClientInit. */
wl_seat_add_listener(ctx->seat, &seat_listener, ctx);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
uint32_t v = version < 3 ? version : 3;
ctx->wm_base = wl_registry_bind(registry, name,