Per-device standalone pointer listeners so VT switch survives pointer teardown

This commit is contained in:
Andrew Hamilton 2026-08-16 08:10:30 +10:00
parent 131950d6e0
commit 872356eaa5

View file

@ -164,6 +164,9 @@ struct tabbed_server {
/* Standalone native keyboard devices (one entry per libinput keyboard;
see standalone_kbdev below). */
struct wl_list kbdevs;
/* Standalone native pointer devices (one entry per libinput pointer;
see standalone_ptrdev below). */
struct wl_list ptrdevs;
};
/* ===================== Host-side (nested) state ===================== */
@ -420,6 +423,22 @@ struct standalone_kbdev {
struct wl_list link;
};
/* Standalone native pointer: one struct per libinput pointer device. Each
device owns its listeners so a device going away (libinput suspends every
device when the session loses its VT) can detach them before wlroots
asserts the event lists are empty, and so all pointers -- not just the
last-attached one -- reach the seat. */
struct standalone_ptrdev {
struct wlr_pointer *pointer;
struct wl_listener motion;
struct wl_listener motion_absolute;
struct wl_listener button;
struct wl_listener axis;
struct wl_listener frame;
struct wl_listener destroy;
struct wl_list link;
};
/* Grow server.apps to hold at least n entries. Returns 0 on success, -1 on
allocation failure. The buffer is freed in shutdown_cleanup(). */
static int apps_ensure_cap(int n) {
@ -465,6 +484,20 @@ static void shutdown_cleanup(void) {
}
wl_list_init(&server.kbdevs);
/* Detach + free the standalone native pointer devices. */
struct standalone_ptrdev *pd, *pd_tmp;
wl_list_for_each_safe(pd, pd_tmp, &server.ptrdevs, link) {
wl_list_remove(&pd->link);
wl_list_remove(&pd->motion.link);
wl_list_remove(&pd->motion_absolute.link);
wl_list_remove(&pd->button.link);
wl_list_remove(&pd->axis.link);
wl_list_remove(&pd->frame.link);
wl_list_remove(&pd->destroy.link);
free(pd);
}
wl_list_init(&server.ptrdevs);
if (server.overlay_atlas_wlr) {
wlr_texture_destroy(server.overlay_atlas_wlr);
server.overlay_atlas_wlr = NULL;
@ -3449,12 +3482,6 @@ static void standalone_kbd_destroy(struct wl_listener *listener, void *data) {
free(kd);
}
static struct wl_listener standalone_ptr_motion;
static struct wl_listener standalone_ptr_abs;
static struct wl_listener standalone_ptr_button;
static struct wl_listener standalone_ptr_axis;
static struct wl_listener standalone_ptr_frame;
static void standalone_ptr_motion_event(struct wl_listener *listener, void *data) {
(void)listener;
struct wlr_pointer_motion_event *ev = data;
@ -3500,6 +3527,22 @@ static void standalone_ptr_frame_event(struct wl_listener *listener, void *data)
(void)listener; (void)data;
}
/* The device is going away (libinput suspends all devices when the session
loses its VT). Drop our listeners so wlr_pointer_finish's assert that
every event list is empty holds, then free the struct. */
static void standalone_ptr_destroy(struct wl_listener *listener, void *data) {
(void)data;
struct standalone_ptrdev *pd = wl_container_of(listener, pd, destroy);
wl_list_remove(&pd->destroy.link);
wl_list_remove(&pd->motion.link);
wl_list_remove(&pd->motion_absolute.link);
wl_list_remove(&pd->button.link);
wl_list_remove(&pd->axis.link);
wl_list_remove(&pd->frame.link);
wl_list_remove(&pd->link);
free(pd);
}
static void handle_new_input(struct wl_listener *listener, void *data) {
(void)listener;
struct wlr_input_device *device = data;
@ -3526,16 +3569,22 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
}
case WLR_INPUT_DEVICE_POINTER: {
struct wlr_pointer *ptr = wlr_pointer_from_input_device(device);
standalone_ptr_motion.notify = standalone_ptr_motion_event;
wl_signal_add(&ptr->events.motion, &standalone_ptr_motion);
standalone_ptr_abs.notify = standalone_ptr_abs_event;
wl_signal_add(&ptr->events.motion_absolute, &standalone_ptr_abs);
standalone_ptr_button.notify = standalone_ptr_button_event;
wl_signal_add(&ptr->events.button, &standalone_ptr_button);
standalone_ptr_axis.notify = standalone_ptr_axis_event;
wl_signal_add(&ptr->events.axis, &standalone_ptr_axis);
standalone_ptr_frame.notify = standalone_ptr_frame_event;
wl_signal_add(&ptr->events.frame, &standalone_ptr_frame);
struct standalone_ptrdev *pd = calloc(1, sizeof(*pd));
if (!pd) break;
pd->pointer = ptr;
pd->motion.notify = standalone_ptr_motion_event;
wl_signal_add(&ptr->events.motion, &pd->motion);
pd->motion_absolute.notify = standalone_ptr_abs_event;
wl_signal_add(&ptr->events.motion_absolute, &pd->motion_absolute);
pd->button.notify = standalone_ptr_button_event;
wl_signal_add(&ptr->events.button, &pd->button);
pd->axis.notify = standalone_ptr_axis_event;
wl_signal_add(&ptr->events.axis, &pd->axis);
pd->frame.notify = standalone_ptr_frame_event;
wl_signal_add(&ptr->events.frame, &pd->frame);
pd->destroy.notify = standalone_ptr_destroy;
wl_signal_add(&ptr->base.events.destroy, &pd->destroy);
wl_list_insert(server.ptrdevs.prev, &pd->link);
tabbed_log(TABBED_LOG_INFO, "standalone: pointer '%s'\n", device->name);
break;
}
@ -4440,6 +4489,7 @@ int main(int argc, char *argv[]) {
}
server.loop = wl_display_get_event_loop(server.wl_display);
wl_list_init(&server.kbdevs);
wl_list_init(&server.ptrdevs);
/* Route SIGTERM/SIGINT through the event loop (signalfd) so shutdown runs
in normal context; see close_signal_cb for why a raw handler is unsafe. */