wayland: Phase 43 — focus_set delivers keyboard focus via wlr_seat

Create a headless keyboard input device during wl_display_open and
attach it to wlr_seat so wlr_seat_keyboard_notify_enter() can deliver
keyboard focus to XDG toplevel surfaces.

Implement wl_focus_set: look up the view by synthetic ID, call
wlr_seat_keyboard_notify_enter with the xdg_surface->surface.
Call wl_focus_set from handle_toplevel_map immediately after
wManageWindow so newly mapped toplevels receive keyboard focus.

Smoke test: add Phase 43 check — grep "focus_set id=" in stderr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-28 06:40:16 +01:00
parent d9a70e9177
commit 0fe2960995
2 changed files with 41 additions and 2 deletions

View File

@ -21,6 +21,8 @@
* Phase 41: prop_get_wm_class and window_fetch_name return XDG app_id/title.
* Phase 42: window_get_client_attrs returns WCLIENT_VIEWABLE + surface
* geometry for mapped XDG toplevels.
* Phase 43: focus_set delivers keyboard focus via wlr_seat; headless keyboard
* device created and attached to seat; mapped toplevels auto-focused.
*/
#ifdef HAVE_CONFIG_H
@ -42,6 +44,7 @@
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/box.h>
/* Generated xdg-shell protocol implementation (Wayland glue code) */
@ -107,6 +110,8 @@ struct wl_compositor_state {
struct wl_list toplevel_list; /* wl_toplevel_view.link */
struct wl_list ctx_lists[3]; /* one per WContextKey */
unsigned long next_synthetic_id; /* >= 0x10000000 */
/* Phase 43: headless keyboard device for seat keyboard focus */
struct wlr_input_device *keyboard_dev;
char socket_name[64]; /* Phase 34: e.g. "wayland-0" */
int started; /* non-zero after successful display_open */
};
@ -135,6 +140,9 @@ frame_timer_cb(void *data)
#define VIEW_FROM_LISTENER(ptr, field) \
((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field)))
/* Phase 43: forward declaration — defined in the focus section below. */
static void wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp);
/* Phase 41: look up the view for a given synthetic WNativeWindow ID. */
static struct wl_toplevel_view *
wl_find_view_by_id(WNativeWindow id)
@ -166,13 +174,16 @@ handle_toplevel_map(struct wl_listener *listener, void *data)
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
if (wwin)
if (wwin) {
wwarning("wl_backend: wManageWindow succeeded id=0x%lx "
"class=%s name=%s geo=%dx%d",
view->id,
wwin->wm_class ? wwin->wm_class : "(none)",
wwin->wm_instance ? wwin->wm_instance : "(none)",
wwin->client.width, wwin->client.height);
/* Phase 43: focus the newly mapped toplevel. */
wl_focus_set(view->id, 0, 0);
}
}
}
}
@ -342,6 +353,15 @@ wl_display_open(const char *name)
if (wl_state.output)
wlr_output_create_global(wl_state.output);
/* Phase 43: create a headless keyboard and attach it to the seat so
* 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) {
wlr_seat_set_keyboard(wl_state.seat, wl_state.keyboard_dev);
wlr_seat_set_capabilities(wl_state.seat, WL_SEAT_CAPABILITY_KEYBOARD);
}
/* Phase 38: initialise per-toplevel tracking and context store. */
wl_list_init(&wl_state.toplevel_list);
{
@ -533,7 +553,19 @@ wl_context_delete(WNativeWindow win, WContextKey key)
static void
wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp)
{ (void)win; (void)revert_to; (void)timestamp; }
{
/* Phase 43: deliver keyboard focus to the XDG toplevel surface. */
(void)revert_to; (void)timestamp;
if (!wl_state.seat || !wl_state.keyboard_dev)
return;
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || !v->xdg_surface->surface)
return;
wlr_seat_keyboard_notify_enter(wl_state.seat,
v->xdg_surface->surface,
NULL, 0, NULL);
wwarning("wl_backend: focus_set id=0x%lx", win);
}
/* ------------------------------------------------------------------ */
/* Key / button grabs */

View File

@ -26,6 +26,8 @@
# succeeded" log includes class=org.freedesktop.weston.simple-shm
# 12. (Phase 42) window_get_client_attrs returns WCLIENT_VIEWABLE + geometry:
# "wManageWindow succeeded" log includes geo= with non-zero size
# 13. (Phase 43) focus_set delivers keyboard focus via wlr_seat: compositor
# logs "focus_set id=" after wManageWindow succeeds
#
# This test grows as wl_backend vtable slots are implemented.
#
@ -177,6 +179,11 @@ if command -v weston-simple-shm >/dev/null 2>&1; then
GEO=$(grep -o 'geo=[0-9]*x[0-9]*' /tmp/wl-smoke-stderr | head -1)
[ -n "$GEO" ] || die "wManageWindow did not log geo= geometry"
echo " geometry check: client geometry logged as $GEO"
# -- 13. Phase 43: keyboard focus delivered to toplevel --------------
grep -q 'focus_set id=' /tmp/wl-smoke-stderr \
|| die "compositor did not log focus_set (keyboard focus not delivered)"
echo " focus check: keyboard focus delivered to mapped toplevel"
else
echo " client check: skipped (weston-simple-shm not installed)"
fi