contain client fullscreen inside kick; decouple from the host window

a client's fullscreen request used to be mirrored onto the host window
via libdecor, but labwc 0.9.8 expires its 100 ms configure deadline under
load and then applies "move but keep old size" permanently -- the
enlarged image anchored at the old origin with black margins.  the mirror
is gone: a client's fullscreen now fills kick's output only, leaving the
host window alone.  fullscreening the host remains an explicit act
(prefix+F11 or the compositor), and the host-to-clients propagation is
unchanged.
This commit is contained in:
Andrew Hamilton 2026-08-26 10:11:18 +10:00
parent 79ae788bfc
commit aafd2de5b7
3 changed files with 32 additions and 24 deletions

View file

@ -268,16 +268,19 @@ browser's `F11`).
## Fullscreen ## Fullscreen
Fullscreen mirrors between the host window and the clients: ## Fullscreen
Fullscreening the kick window and fullscreening a client are independent:
- Fullscreen the kick window (your host compositor's fullscreen keybinding, - Fullscreen the kick window (your host compositor's fullscreen keybinding,
an app's own `F11`, or `Alt+F11`) and every window of every tab -- mains or `Alt+F11`) and every window of every tab -- mains and stacked
and stacked tab-group members alike -- is told it is fullscreen, so tab-group members alike -- is told it is fullscreen, so clients drop
clients drop their decorations and fill the screen. their decorations and fill the screen.
- A client that requests fullscreen itself (e.g. a video player) also - A client that requests fullscreen itself (e.g. a video player) fills the
fullscreens the kick host window. kick window's content only: the host window itself stays exactly where
- Leaving fullscreen returns every window to maximized (X11 tab-group and how it is. Use `Alt+F11` as well if you want the whole monitor.
members keep their own geometry). - Leaving host fullscreen returns every window to maximized (X11
tab-group members keep their own geometry).
## Standalone mode ## Standalone mode

15
kick.c
View file

@ -2642,12 +2642,13 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
if (!toplevel->base->initialized) return; if (!toplevel->base->initialized) return;
/* wlroots 0.19 emits request_fullscreen with NULL data; the direction /* wlroots 0.19 emits request_fullscreen with NULL data; the direction
lives in toplevel->requested.fullscreen. Honor the client's request lives in toplevel->requested.fullscreen. Honor the client's request
and mirror it onto the host window: a fullscreen client fills the within kick only: the window fills our output while the host window
host window, and an un-fullscreen client puts it back. */ itself is left alone. Fullscreening the host window is a separate,
explicit act (prefix+F11 or the compositor), which propagates back
down through clients_set_fullscreen(). */
bool fs = toplevel->requested.fullscreen; bool fs = toplevel->requested.fullscreen;
wlr_xdg_toplevel_set_fullscreen(toplevel, fs); wlr_xdg_toplevel_set_fullscreen(toplevel, fs);
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs); kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
host_queue_fullscreen(fs);
} }
/* Propagate the host window's fullscreen state onto every client -- each /* Propagate the host window's fullscreen state onto every client -- each
@ -3644,11 +3645,10 @@ static void handle_xs_request_fullscreen(struct wl_listener *listener,
struct kick_app *app = wl_container_of(listener, app, request_fullscreen); struct kick_app *app = wl_container_of(listener, app, request_fullscreen);
struct wlr_xwayland_surface *xs = app->xsurface; struct wlr_xwayland_surface *xs = app->xsurface;
if (!xs) return; if (!xs) return;
/* Mirror onto the host window like the xdg path. */ /* Same containment as the xdg path: fullscreen stays inside kick. */
bool fs = xs->fullscreen; bool fs = xs->fullscreen;
wlr_xwayland_surface_set_fullscreen(xs, fs); wlr_xwayland_surface_set_fullscreen(xs, fs);
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs); kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
host_queue_fullscreen(fs);
} }
static void handle_xs_request_configure(struct wl_listener *listener, static void handle_xs_request_configure(struct wl_listener *listener,
@ -5766,6 +5766,7 @@ static void host_teardown_window(void) {
static void host_frame_configure(struct libdecor_frame *frame, static void host_frame_configure(struct libdecor_frame *frame,
struct libdecor_configuration *config, struct libdecor_configuration *config,
void *data) void *data)
{
(void)data; (void)data;
int width = 0, height = 0; int width = 0, height = 0;
bool size_ok = bool size_ok =
@ -6361,8 +6362,8 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
/* Function keys are not grabbed (prefix+F11 above is the one exception): /* Function keys are not grabbed (prefix+F11 above is the one exception):
they pass through to the focused client, which may have its own they pass through to the focused client, which may have its own
bindings (e.g. a browser's F11 fullscreen request, which bindings (e.g. a browser's F11 fullscreen request, which
handle_request_fullscreen mirrors onto the host window). Frame dumps handle_request_fullscreen contains inside kick). Frame dumps are
are available via SIGUSR1 instead. */ available via SIGUSR1 instead. */
/* Alt+Up / Alt+Down cycle tabs. Grabbed so the focused client never /* Alt+Up / Alt+Down cycle tabs. Grabbed so the focused client never
sees them. The host sends a single physical press, so repeats are sees them. The host sends a single physical press, so repeats are
synthesized by kbd_repeat_start(); subsequent host presses of an synthesized by kbd_repeat_start(); subsequent host presses of an

View file

@ -1484,18 +1484,22 @@ def test_function_keys_pass_through(session):
assert session.proc.poll() is None assert session.proc.poll() is None
def test_client_fullscreen_round_trip(session): def test_client_fullscreen_stays_inside_kick(session):
"""A client's fullscreen request is honored and mirrored onto the host """A client's fullscreen request is honored within kick but never
window; the host configure then propagates fullscreen to every tab. mirrored onto the host window: wltest toggles xdg_toplevel fullscreen
wltest toggles xdg_toplevel fullscreen on the 'F' key, so tap it and on the 'F' key, so tap it and check only the client-side state moves."""
watch both directions of the mirror."""
session.wait_any_active() session.wait_any_active()
session.tap(F) session.tap(F)
session.wait_for("host fullscreen=1") session.wait_for("client fullscreen=1")
session.wait_for("clients fullscreen=1") time.sleep(0.6) # give a wrong mirror time to show up in the log
assert "host fullscreen=" not in session.read_log(), \
"client fullscreen leaked onto the host window"
assert "clients fullscreen=" not in session.read_log(), \
"client fullscreen propagated to other tabs"
session.tap(F) session.tap(F)
session.wait_for("host fullscreen=0") session.wait_for("client fullscreen=0")
session.wait_for("clients fullscreen=0") time.sleep(0.4)
assert "host fullscreen=" not in session.read_log()
assert session.proc.poll() is None assert session.proc.poll() is None