Nested mode: re-create host seat keyboard/pointer on capabilities changes
labwc (wlroots) invalidates every object created from a wl_seat when it tears down its input backend across a VT round-trip; we fetched host.keyboard/host.pointer once at host_init and never re-created them, so the nested client kept running silently with no keyboard events ever arriving ([kbd] lines stopped, Alt bindings dead). Bind a wl_seat listener at registry time and move the one-time seats objects into a capabilities handler: drop the keyboard/pointer objects when the capability disappears, re-create (plus re-add listeners) when it returns. The compositor re-sends keymap/enter on the fresh wl_keyboard, and host_kbd_enter re-focuses our nested seat, restoring input. Also handle wl_seat global remove/re-add (some compositors recreate the global rather than toggling capabilities): null the seat objects and reset the clipboard bridge so a fresh seat re-runs bridge_init() -- which is now re-entrant. Added [seat]/[kbd] DEBUG lines for Pi triage. Tests: 32/32.
This commit is contained in:
parent
6ccb3e8d12
commit
361a0b23b1
1 changed files with 101 additions and 9 deletions
110
tabbed-wlroots.c
110
tabbed-wlroots.c
|
|
@ -208,6 +208,8 @@ struct tabbed_host {
|
|||
struct wl_seat *seat;
|
||||
struct wl_keyboard *keyboard;
|
||||
struct wl_pointer *pointer;
|
||||
uint32_t seat_name; /* registry name of the bound wl_seat global */
|
||||
bool registry_synced; /* initial registry scan completed */
|
||||
bool configured;
|
||||
bool fullscreen;
|
||||
int width, height;
|
||||
|
|
@ -2862,6 +2864,19 @@ static void bridge_init(void) {
|
|||
tabbed_log(TABBED_LOG_INFO, "clipboard bridge: no host seat; disabled\n");
|
||||
return;
|
||||
}
|
||||
/* Re-entrant: drop any objects bound to a previous host seat so a runtime
|
||||
seat re-add (registry churn, e.g. VT round-trip) re-binds cleanly. */
|
||||
if (bridge.dd)
|
||||
wl_data_device_release(bridge.dd);
|
||||
bridge.dd = NULL;
|
||||
if (bridge.dc_device)
|
||||
zwlr_data_control_device_v1_destroy(bridge.dc_device);
|
||||
bridge.dc_device = NULL;
|
||||
bridge.dd_offer = NULL;
|
||||
bridge.dc_offer = NULL;
|
||||
bridge.dd_echo_offer = NULL;
|
||||
bridge.dc_echo_offer = NULL;
|
||||
bridge.backend = BRIDGE_BACKEND_NONE;
|
||||
if (bridge.ddm) {
|
||||
bridge.dd = wl_data_device_manager_get_data_device(bridge.ddm, host.seat);
|
||||
wl_data_device_add_listener(bridge.dd, &bridge_dd_listener, NULL);
|
||||
|
|
@ -2918,6 +2933,18 @@ static void bridge_cleanup(void) {
|
|||
|
||||
/* ===================== Host-side listeners ===================== */
|
||||
|
||||
/* wl_seat capabilities handler: see host_seat_capabilities() below (defined
|
||||
after the host keyboard/pointer listeners it installs). */
|
||||
static void host_seat_capabilities(void *data, struct wl_seat *seat,
|
||||
uint32_t capabilities);
|
||||
static void host_seat_name(void *data, struct wl_seat *seat, const char *name) {
|
||||
(void)data; (void)seat; (void)name;
|
||||
}
|
||||
static const struct wl_seat_listener host_seat_listener = {
|
||||
.capabilities = host_seat_capabilities,
|
||||
.name = host_seat_name,
|
||||
};
|
||||
|
||||
static void host_registry_global(void *data, struct wl_registry *registry,
|
||||
uint32_t name, const char *interface,
|
||||
uint32_t version)
|
||||
|
|
@ -2933,8 +2960,17 @@ static void host_registry_global(void *data, struct wl_registry *registry,
|
|||
host.decoration = wl_registry_bind(registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
/* Bind the seat and rely on the capabilities event (which the
|
||||
compositor always sends after a bind) to (re)create the keyboard
|
||||
and pointer objects -- including after a VT round-trip, where the
|
||||
compositor invalidates seat objects and a re-bind or capability
|
||||
re-gain must be observed to keep receiving input. */
|
||||
host.seat = wl_registry_bind(registry, name,
|
||||
&wl_seat_interface, version < 5 ? version : 5);
|
||||
host.seat_name = name;
|
||||
wl_seat_add_listener(host.seat, &host_seat_listener, NULL);
|
||||
if (host.registry_synced)
|
||||
bridge_init(); /* runtime seat re-add: re-establish the bridge */
|
||||
} else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
|
||||
bridge.ddm = wl_registry_bind(registry, name,
|
||||
&wl_data_device_manager_interface, version < 3 ? version : 3);
|
||||
|
|
@ -2948,7 +2984,27 @@ static void host_registry_global(void *data, struct wl_registry *registry,
|
|||
static void host_registry_global_remove(void *data, struct wl_registry *registry,
|
||||
uint32_t name)
|
||||
{
|
||||
(void)data; (void)registry; (void)name;
|
||||
(void)data; (void)registry;
|
||||
/* Only the wl_seat global's removal matters to us: its children (keyboard,
|
||||
pointer, clipboard data devices) are invalidated with it and must be
|
||||
re-created when the compositor advertises a new seat. Other globals we
|
||||
bind (compositor, xdg_wm_base, ...) are process-lifetime. */
|
||||
if (host.seat_name && name == host.seat_name) {
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] global %u removed\n", name);
|
||||
host.seat_name = 0;
|
||||
host.seat = NULL;
|
||||
host.keyboard = NULL;
|
||||
host.pointer = NULL;
|
||||
/* Seat-owned bridge objects are invalidated too; null them so the
|
||||
next bridge_init() on a fresh seat re-binds cleanly. */
|
||||
bridge.dd = NULL;
|
||||
bridge.dc_device = NULL;
|
||||
bridge.dd_offer = NULL;
|
||||
bridge.dc_offer = NULL;
|
||||
bridge.dd_echo_offer = NULL;
|
||||
bridge.dc_echo_offer = NULL;
|
||||
bridge.backend = BRIDGE_BACKEND_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener host_registry_listener = {
|
||||
|
|
@ -3149,6 +3205,8 @@ static void host_kbd_enter(void *data, struct wl_keyboard *kbd, uint32_t serial,
|
|||
bridge.host_set_pending = false;
|
||||
bridge_host_set_selection(server.seat->selection_source);
|
||||
}
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[kbd] enter surface=%p serial=%u active=%p\n",
|
||||
(void *)surface, serial, (void *)server.active);
|
||||
if (server.active)
|
||||
wlr_seat_keyboard_notify_enter(server.seat,
|
||||
server.active->base->surface,
|
||||
|
|
@ -3160,6 +3218,8 @@ static void host_kbd_leave(void *data, struct wl_keyboard *kbd, uint32_t serial,
|
|||
struct wl_surface *surface)
|
||||
{
|
||||
(void)data; (void)kbd; (void)serial; (void)surface;
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[kbd] leave surface=%p serial=%u\n",
|
||||
(void *)surface, serial);
|
||||
wlr_seat_keyboard_notify_clear_focus(server.seat);
|
||||
}
|
||||
|
||||
|
|
@ -3967,6 +4027,43 @@ static const struct wl_pointer_listener host_ptr_listener = {
|
|||
.axis_discrete = host_ptr_axis_discrete,
|
||||
};
|
||||
|
||||
/* When the host compositor (labwc on a VT round-trip) removes a seat
|
||||
capability, every object created from that seat -- our wl_keyboard /
|
||||
wl_pointer -- is invalidated, yet the client keeps running silently with
|
||||
no input ever arriving again. Re-create the objects as capabilities are
|
||||
re-gained; the compositor re-sends keymap/modifiers/enter on the fresh
|
||||
wl_keyboard, and host_kbd_enter() re-focuses our nested seat. */
|
||||
static void host_seat_capabilities(void *data, struct wl_seat *seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
(void)data; (void)seat;
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] capabilities=0x%x\n", capabilities);
|
||||
const bool have_kbd = (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0;
|
||||
const bool have_ptr = (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0;
|
||||
if (have_kbd && !host.keyboard) {
|
||||
host.keyboard = wl_seat_get_keyboard(host.seat);
|
||||
if (host.keyboard) {
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] re-gained keyboard\n");
|
||||
wl_keyboard_add_listener(host.keyboard, &host_kbd_listener, NULL);
|
||||
}
|
||||
} else if (!have_kbd && host.keyboard) {
|
||||
wl_keyboard_release(host.keyboard);
|
||||
host.keyboard = NULL;
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] keyboard capability lost\n");
|
||||
}
|
||||
if (have_ptr && !host.pointer) {
|
||||
host.pointer = wl_seat_get_pointer(host.seat);
|
||||
if (host.pointer) {
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] re-gained pointer\n");
|
||||
wl_pointer_add_listener(host.pointer, &host_ptr_listener, NULL);
|
||||
}
|
||||
} else if (!have_ptr && host.pointer) {
|
||||
wl_pointer_release(host.pointer);
|
||||
host.pointer = NULL;
|
||||
tabbed_log(TABBED_LOG_DEBUG, "[seat] pointer capability lost\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== Host init / EGL ===================== */
|
||||
|
||||
static bool host_init(void) {
|
||||
|
|
@ -3984,6 +4081,7 @@ static bool host_init(void) {
|
|||
host.registry = wl_display_get_registry(host.display);
|
||||
wl_registry_add_listener(host.registry, &host_registry_listener, NULL);
|
||||
wl_display_roundtrip(host.display);
|
||||
host.registry_synced = true;
|
||||
|
||||
if (!host.compositor || !host.wm_base) {
|
||||
fprintf(stderr, "tabbed-wlroots: host lacks compositor/xdg_wm_base\n");
|
||||
|
|
@ -4011,14 +4109,8 @@ static bool host_init(void) {
|
|||
wl_surface_commit(host.surface);
|
||||
wl_display_roundtrip(host.display);
|
||||
|
||||
if (host.seat) {
|
||||
host.keyboard = wl_seat_get_keyboard(host.seat);
|
||||
host.pointer = wl_seat_get_pointer(host.seat);
|
||||
if (host.keyboard)
|
||||
wl_keyboard_add_listener(host.keyboard, &host_kbd_listener, NULL);
|
||||
if (host.pointer)
|
||||
wl_pointer_add_listener(host.pointer, &host_ptr_listener, NULL);
|
||||
}
|
||||
/* Host keyboard/pointer are created by the seat capabilities handler
|
||||
during the registry round-trip above; nothing more to fetch here. */
|
||||
bridge_init();
|
||||
|
||||
/* Try EGL on the host wayland platform */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue