diff --git a/waybox/seat.cpp b/waybox/seat.cpp index 3178eec..6513980 100644 --- a/waybox/seat.cpp +++ b/waybox/seat.cpp @@ -665,6 +665,19 @@ static void handle_new_pointer(struct wb_server *server, struct wlr_input_device wlr_cursor_attach_input_device(server->cursor->cursor, device); } +/* Advertise the seat's input capabilities to clients. waybox always owns a + * cursor, so POINTER is always advertised; KEYBOARD is advertised once at least + * one keyboard exists. This must run whenever an input device is added through + * ANY path — including the virtual-pointer/virtual-keyboard protocols, which + * bypass new_input_notify — otherwise clients never receive input from a + * virtual device (e.g. under the headless backend used by the tests). */ +static void update_seat_capabilities(struct wb_server *server) { + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&server->seat->keyboards)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(server->seat->seat, caps); +} + static void new_input_notify(struct wb_server *server, void *data) { struct wlr_input_device *device = static_cast(data); switch (device->type) { @@ -681,11 +694,7 @@ static void new_input_notify(struct wb_server *server, void *data) { break; } - uint32_t caps = WL_SEAT_CAPABILITY_POINTER; - if (!wl_list_empty(&server->seat->keyboards)) { - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - } - wlr_seat_set_capabilities(server->seat->seat, caps); + update_seat_capabilities(server); } void seat_focus_surface(struct wb_seat *seat, struct wlr_surface *surface) { @@ -731,6 +740,7 @@ struct wb_seat *wb_seat_create(struct wb_server *server) { auto *virtual_keyboard = static_cast(data); handle_new_keyboard(server, &virtual_keyboard->keyboard.base); + update_seat_capabilities(server); }); server->virtual_pointer_manager = wlr_virtual_pointer_manager_v1_create(server->wl_display); @@ -740,6 +750,7 @@ struct wb_seat *wb_seat_create(struct wb_server *server) { auto *event = static_cast(data); handle_new_pointer(server, &event->new_pointer->pointer.base); + update_seat_capabilities(server); }); seat->seat = wlr_seat_create(server->wl_display, "seat0"); diff --git a/waybox/xdg_shell.cpp b/waybox/xdg_shell.cpp index 7de2278..7b68b43 100644 --- a/waybox/xdg_shell.cpp +++ b/waybox/xdg_shell.cpp @@ -751,6 +751,12 @@ void set_toplevel_maximized(struct wb_toplevel *toplevel, bool horz, bool vert) void set_toplevel_fullscreen(struct wb_toplevel *toplevel, bool fullscreen) { if (!toplevel->xdg_toplevel->base->initialized) return; + /* No-op if the state is already what was asked for. Clients (notably GTK) + * send redundant unset_fullscreen requests while managing their state; + * acting on them would corrupt geometry (restoring an unset restore rect) + * or needlessly re-fullscreen. */ + if (fullscreen == toplevel->xdg_toplevel->current.fullscreen) + return; if (fullscreen) { toplevel->restore_fullscreen = toplevel->geometry; @@ -788,22 +794,30 @@ void set_toplevel_fullscreen(struct wb_toplevel *toplevel, bool fullscreen) { static void xdg_toplevel_request_fullscreen( struct wb_toplevel *toplevel, void *data) { - /* Toggle fullscreen on the client's request (e.g. a video player). A - * request before the first commit would assert in wlroots, so it is - * ignored until the surface is initialized; the client can re-request. */ + /* Apply the state the client actually asked for. wlroots raises this event + * for both set_fullscreen and unset_fullscreen, with the desired value in + * requested.fullscreen — toggling instead would turn a client's redundant + * "unset fullscreen" (which GTK sends while managing its window state) into + * an unexpected fullscreen, blowing the window up to full screen. A request + * before the first commit would assert in wlroots, so ignore it until the + * surface is initialized; the client can re-request. */ if (!toplevel->xdg_toplevel->base->initialized) return; set_toplevel_fullscreen(toplevel, - !toplevel->xdg_toplevel->current.fullscreen); + toplevel->xdg_toplevel->requested.fullscreen); } static void xdg_toplevel_request_maximize(struct wb_toplevel *toplevel, void *data) { - /* The client asked to (un)maximize, e.g. via its CSD maximize button. - * Toggle full maximize. Ignored until initialized (see above). */ + /* Apply the maximize state the client asked for (requested.maximized), for + * the same reason as fullscreen above: a redundant unset_maximized must not + * be toggled into a maximize. Ignored until initialized. */ if (!toplevel->xdg_toplevel->base->initialized) return; - const bool full = toplevel->max_horz && toplevel->max_vert; - set_toplevel_maximized(toplevel, !full, !full); + bool maximize = toplevel->xdg_toplevel->requested.maximized; + bool currently_full = toplevel->max_horz && toplevel->max_vert; + if (maximize == currently_full) + return; /* redundant request (e.g. GTK's unset while not maximized) */ + set_toplevel_maximized(toplevel, maximize, maximize); } static void xdg_toplevel_request_minimize(struct wb_toplevel *toplevel, void *data) {