feat: per-entry fullscreen, host chrome follows shown clients
This commit is contained in:
parent
7494fafb89
commit
e7df8d5396
3 changed files with 352 additions and 86 deletions
17
README.md
17
README.md
|
|
@ -289,14 +289,19 @@ browser's `F11`).
|
|||
|
||||
## Fullscreen
|
||||
|
||||
Fullscreen mirrors between the host window and the clients:
|
||||
Fullscreen is per overlay entry:
|
||||
|
||||
- Fullscreen the kick window (your host compositor's fullscreen keybinding,
|
||||
or `Alt+F11`) and every window of every tab -- mains and stacked
|
||||
tab-group members alike -- is told it is fullscreen, so clients drop
|
||||
their decorations and fill the screen.
|
||||
- A client that requests fullscreen itself (e.g. a video player) also
|
||||
fullscreens the kick host window: one action fills the whole monitor.
|
||||
or `Alt+F11`) and only the focused overlay entry is told it is
|
||||
fullscreen, so it drops decorations and fills the screen. Unfocused
|
||||
stacked members, other tabs and new arrivals stay maximized and grow to
|
||||
the fullscreen output size without gaining fullscreen state. A black tab
|
||||
(client not yet mapped) picks fullscreen up when it maps.
|
||||
- A shown client that requests fullscreen itself (e.g. Firefox F11) takes
|
||||
the host window with it: one action fills the whole monitor, still for
|
||||
that entry only. Exiting (second F11) drops the host chrome again. A
|
||||
hidden tab's request is remembered via the protocol and live when you
|
||||
cycle to it, without touching the host.
|
||||
- Leaving fullscreen returns every window to maximized (X11 tab-group
|
||||
members keep their own geometry).
|
||||
|
||||
|
|
|
|||
271
kick.c
271
kick.c
|
|
@ -255,6 +255,12 @@ struct kick_window {
|
|||
of being configured to the output size. */
|
||||
bool transient;
|
||||
|
||||
/* Fullscreen state is NOT stored here: it lives in the protocol objects
|
||||
(xdg toplevel->scheduled.fullscreen, X11 xsurface->fullscreen) and is
|
||||
read via window_is_fullscreen. server.clients_fullscreen remains the
|
||||
aggregate (host chrome OR any live window) driving panel visibility
|
||||
and the fullscreen layout. */
|
||||
|
||||
struct wl_listener destroy;
|
||||
struct wl_listener map;
|
||||
struct wl_listener unmap;
|
||||
|
|
@ -666,8 +672,10 @@ struct kick_server {
|
|||
per --modifier). */
|
||||
xkb_mod_index_t xkb_prefix_mod;
|
||||
|
||||
/* A client's fullscreen request is honored and mirrored onto the host
|
||||
window, and vice versa. */
|
||||
/* Aggregate fullscreen state: host chrome fullscreen OR any live
|
||||
window fullscreen (read from the protocol objects via
|
||||
window_is_fullscreen, never stored). Drives panel visibility and
|
||||
the fullscreen layout. */
|
||||
bool clients_fullscreen;
|
||||
|
||||
/* Standalone (real output) mode: the output is driven directly by a
|
||||
|
|
@ -976,6 +984,11 @@ static void layer_apply_layout(void);
|
|||
static void layer_raise_above_windows(void);
|
||||
static void layer_update_visibility(void);
|
||||
static void clients_set_fullscreen(bool fs);
|
||||
static bool window_is_fullscreen(struct kick_window *w);
|
||||
static bool any_window_fullscreen(void);
|
||||
static void fullscreen_sync_aggregate(void);
|
||||
static void window_apply_fullscreen(struct kick_window *w, bool fs);
|
||||
static bool window_is_shown(struct kick_window *w);
|
||||
|
||||
/* Request a redraw from a non-client source (kick's own UI: tab switches,
|
||||
reorder, resize, overlay, boot). Marks the frame dirty and wakes the
|
||||
|
|
@ -1351,6 +1364,21 @@ static void window_set_fullscreen(struct kick_window *w, bool on) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/* Live fullscreen state, read from the protocol objects (never stored):
|
||||
xdg uses scheduled (what we've told the client; current lags until the
|
||||
client acks, requested is the client's wish). Uninitialized windows read
|
||||
as not fullscreen. */
|
||||
static bool window_is_fullscreen(struct kick_window *w) {
|
||||
if (!w) return false;
|
||||
if (w->toplevel)
|
||||
return w->toplevel->base->initialized && w->toplevel->scheduled.fullscreen;
|
||||
#if HAVE_XWAYLAND
|
||||
if (w->is_x11)
|
||||
return w->xsurface && w->xsurface->fullscreen;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Keep a mapped window at the maximized output size. XDG clients are told
|
||||
via set_size; X11 clients get a configure to the full output at 0,0. */
|
||||
static void window_set_size(struct kick_window *w, int width, int height) {
|
||||
|
|
@ -3246,6 +3274,7 @@ static void window_destroy_common(struct kick_window *w) {
|
|||
w->close_timer = NULL;
|
||||
}
|
||||
bool had_focus = w->focused;
|
||||
bool was_fs = window_is_fullscreen(w);
|
||||
/* Tear down the taskbar handles so taskbars drop the entry now. */
|
||||
foreign_handle_destroy(w);
|
||||
free(w->tag);
|
||||
|
|
@ -3257,10 +3286,29 @@ static void window_destroy_common(struct kick_window *w) {
|
|||
free(w);
|
||||
if (wl_list_empty(&tab->windows)) {
|
||||
drop_tab(tab);
|
||||
/* Host chrome still fullscreen but its client is gone: hand
|
||||
fullscreen to the successor so host and client stay in sync. */
|
||||
if (host.fullscreen && server.active_tab) {
|
||||
struct kick_window *nw = tab_current_window(server.active_tab);
|
||||
if (nw && !window_is_fullscreen(nw) && window_initialized(nw))
|
||||
window_set_fullscreen(nw, true);
|
||||
}
|
||||
(void)was_fs;
|
||||
fullscreen_sync_aggregate();
|
||||
return;
|
||||
}
|
||||
if (had_focus)
|
||||
refocus_tab(tab);
|
||||
if (was_fs) {
|
||||
/* Fullscreen window closed itself: if the host chrome is still up,
|
||||
keep a client fullscreen on this tab; otherwise just restore. */
|
||||
if (host.fullscreen && tab == server.active_tab) {
|
||||
struct kick_window *nw = tab_current_window(tab);
|
||||
if (nw && !window_is_fullscreen(nw) && window_initialized(nw))
|
||||
window_set_fullscreen(nw, true);
|
||||
}
|
||||
fullscreen_sync_aggregate();
|
||||
}
|
||||
/* The topmost window changed (a stacked member left): re-resolve the row
|
||||
title now that focus has settled, so the fallback is log-observable
|
||||
without an overlay draw. */
|
||||
|
|
@ -3445,37 +3493,105 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
|
|||
struct wlr_xdg_toplevel *toplevel = w->toplevel;
|
||||
if (!toplevel->base->initialized) return;
|
||||
/* wlroots 0.19 emits request_fullscreen with NULL data; the direction
|
||||
lives in toplevel->requested.fullscreen. Honor the client's request
|
||||
and mirror it onto the host window: one action fullscreens the whole
|
||||
monitor. */
|
||||
lives in toplevel->requested.fullscreen. Per-tab: only the
|
||||
requesting overlay entry is told fullscreen. A shown entry also
|
||||
takes the host chrome with it (nested); a hidden entry is just
|
||||
marked via the protocol and stays hidden. Exiting always drops the
|
||||
host chrome again (simple mirror, no conditions). */
|
||||
bool fs = toplevel->requested.fullscreen;
|
||||
wlr_xdg_toplevel_set_fullscreen(toplevel, fs);
|
||||
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
|
||||
host_apply_fullscreen(fs);
|
||||
/* Standalone has no host window to round-trip through: propagate
|
||||
directly so panels hide and every tab follows, like nested does via
|
||||
the host configure. */
|
||||
if (server.standalone)
|
||||
clients_set_fullscreen(fs);
|
||||
window_apply_fullscreen(w, fs);
|
||||
if (server.standalone) return;
|
||||
if (fs) {
|
||||
if (window_is_shown(w))
|
||||
host_apply_fullscreen(true);
|
||||
} else {
|
||||
host_apply_fullscreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Propagate the host window's fullscreen state onto every client window.
|
||||
Called from host_xdg_toplevel_configure when that state actually changes.
|
||||
X11 windows keep their own geometry (only into a tab by an explicit
|
||||
--tab-groups group attachment), so on the way out only xdg windows and
|
||||
tab-owned x11 windows get the re-maximize/resize; windows arriving later
|
||||
pick the flag up at their first commit. */
|
||||
static void clients_set_fullscreen(bool fs) {
|
||||
server.clients_fullscreen = fs;
|
||||
kick_log(KICK_LOG_INFO, "clients fullscreen=%d\n", fs);
|
||||
kick_log(KICK_LOG_INFO, "members fullscreen=%d\n", fs);
|
||||
/* Whether any live window holds fullscreen state (visible or hidden). */
|
||||
static bool any_window_fullscreen(void) {
|
||||
struct kick_tab *t;
|
||||
wl_list_for_each(t, &server.tabs, link) {
|
||||
struct kick_window *w;
|
||||
wl_list_for_each(w, &t->windows, link) {
|
||||
if (!window_initialized(w)) continue;
|
||||
window_set_fullscreen(w, fs);
|
||||
if (!fs && server.output &&
|
||||
if (window_is_fullscreen(w)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Recompute the aggregate fullscreen state (host chrome OR any live
|
||||
window), driving panel visibility and the fullscreen layout. All tabs
|
||||
are sized to the full area while aggregated, so non-fullscreen tabs grow
|
||||
bigger without gaining fullscreen state. */
|
||||
static void fullscreen_sync_aggregate(void) {
|
||||
bool agg = host.fullscreen || any_window_fullscreen();
|
||||
if (agg == server.clients_fullscreen) return;
|
||||
server.clients_fullscreen = agg;
|
||||
kick_log(KICK_LOG_INFO, "clients fullscreen=%d\n", agg);
|
||||
kick_log(KICK_LOG_INFO, "members fullscreen=%d\n", agg);
|
||||
layer_update_visibility();
|
||||
layer_apply_layout();
|
||||
}
|
||||
|
||||
/* Whether a window is the currently shown overlay entry. */
|
||||
static bool window_is_shown(struct kick_window *w) {
|
||||
return w && w->tab == server.active_tab &&
|
||||
w == tab_current_window(server.active_tab);
|
||||
}
|
||||
|
||||
/* Apply a per-window fullscreen change and sync the aggregate. Hidden
|
||||
windows are still told (protocol ack) so the state is live on return. */
|
||||
static void window_apply_fullscreen(struct kick_window *w, bool fs) {
|
||||
if (!w) {
|
||||
fullscreen_sync_aggregate();
|
||||
return;
|
||||
}
|
||||
if (!window_initialized(w)) {
|
||||
fullscreen_sync_aggregate();
|
||||
return;
|
||||
}
|
||||
bool cur = window_is_fullscreen(w);
|
||||
window_set_fullscreen(w, fs);
|
||||
if (cur != fs)
|
||||
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
|
||||
fullscreen_sync_aggregate();
|
||||
}
|
||||
|
||||
/* Propagate a host-window fullscreen transition onto clients per-tab.
|
||||
Enter: fullscreen the focused overlay entry only -- every other tab, and
|
||||
every unfocused stacked member, stays maximized and merely grows to the
|
||||
fullscreen output size. A black (pending) active tab keeps no protocol
|
||||
state to set; it picks fullscreen up at map via host.fullscreen (see the
|
||||
initial-commit and map paths). Exit: clear every live window and
|
||||
re-maximize. */
|
||||
static void clients_set_fullscreen(bool fs) {
|
||||
if (fs) {
|
||||
if (!any_window_fullscreen()) {
|
||||
struct kick_tab *at = server.active_tab;
|
||||
struct kick_window *w = at ? tab_current_window(at) : NULL;
|
||||
if (w && window_initialized(w)) {
|
||||
window_set_fullscreen(w, true);
|
||||
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", true);
|
||||
}
|
||||
}
|
||||
if (!server.clients_fullscreen) {
|
||||
server.clients_fullscreen = true;
|
||||
kick_log(KICK_LOG_INFO, "clients fullscreen=%d\n", true);
|
||||
kick_log(KICK_LOG_INFO, "members fullscreen=%d\n", true);
|
||||
}
|
||||
layer_update_visibility();
|
||||
layer_apply_layout();
|
||||
return;
|
||||
}
|
||||
struct kick_tab *t;
|
||||
wl_list_for_each(t, &server.tabs, link) {
|
||||
struct kick_window *w;
|
||||
wl_list_for_each(w, &t->windows, link) {
|
||||
if (window_is_fullscreen(w) && window_initialized(w))
|
||||
window_set_fullscreen(w, false);
|
||||
if (server.output &&
|
||||
(w->toplevel || w->origin != ORIGIN_GROUP ||
|
||||
w->tab != w->parent_tab)) {
|
||||
window_set_maximized(w, true);
|
||||
|
|
@ -3483,26 +3599,24 @@ static void clients_set_fullscreen(bool fs) {
|
|||
}
|
||||
}
|
||||
}
|
||||
/* A fullscreen tab takes the whole output: hide the panel/layer trees
|
||||
so a bar (e.g. waybar) does not paint over it, and re-run the layout
|
||||
pass so tabs grow back to the full area (restored to the bar-reserved
|
||||
usable box on the way out). */
|
||||
layer_update_visibility();
|
||||
layer_apply_layout();
|
||||
fullscreen_sync_aggregate();
|
||||
}
|
||||
|
||||
/* Toggle fullscreen. Nested flips the host window (see
|
||||
host_apply_fullscreen); the actual flip happens asynchronously in
|
||||
host_toplevel_configure, where the resulting configure updates
|
||||
host.fullscreen and propagates to every client. Standalone has no host
|
||||
window to round-trip through, so flip the clients directly -- otherwise a
|
||||
client-initiated fullscreen (e.g. firefox) outlives its tab with no way
|
||||
back once that tab closes. */
|
||||
/* Toggle fullscreen. Nested flips the host window chrome (see
|
||||
host_apply_fullscreen); the resulting configure fullscreens the focused
|
||||
overlay entry via clients_set_fullscreen. Standalone has no host window,
|
||||
so flip the focused entry directly. */
|
||||
static void host_toggle_fullscreen(void) {
|
||||
if (server.standalone)
|
||||
clients_set_fullscreen(!server.clients_fullscreen);
|
||||
else
|
||||
host_apply_fullscreen(!host.fullscreen);
|
||||
if (server.standalone) {
|
||||
struct kick_tab *at = server.active_tab;
|
||||
struct kick_window *w = at ? tab_current_window(at) : NULL;
|
||||
if (w)
|
||||
window_apply_fullscreen(w, !window_is_fullscreen(w));
|
||||
else
|
||||
fullscreen_sync_aggregate();
|
||||
return;
|
||||
}
|
||||
host_apply_fullscreen(!host.fullscreen);
|
||||
}
|
||||
|
||||
static void handle_request_minimize(struct wl_listener *listener, void *data) {
|
||||
|
|
@ -3558,7 +3672,12 @@ static void handle_toplevel_commit(struct wl_listener *listener, void *data) {
|
|||
if (server.output)
|
||||
wlr_xdg_toplevel_set_size(toplevel, server.output->width,
|
||||
server.output->height);
|
||||
if (server.clients_fullscreen)
|
||||
/* Per-tab: new windows start maximized-bigger, never fullscreen --
|
||||
except a black tab revealed while the host chrome is fullscreen
|
||||
(Alt+F11 landed before its client mapped): the focused entry
|
||||
picks fullscreen up here. */
|
||||
if (host.fullscreen && w->tab == server.active_tab &&
|
||||
w == tab_current_window(server.active_tab))
|
||||
wlr_xdg_toplevel_set_fullscreen(toplevel, true);
|
||||
}
|
||||
/* Geometry can settle after the first frames (kitty/foot report a
|
||||
|
|
@ -3836,6 +3955,12 @@ static void handle_window_map(struct wl_listener *listener, void *data) {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
/* Black-tab fix: a tab revealed while the host chrome is fullscreen
|
||||
(Alt+F11 landed before its client mapped) picks fullscreen up here.
|
||||
Live fullscreen states otherwise survive mapping on their own. */
|
||||
if (host.fullscreen && w->tab == server.active_tab &&
|
||||
w == tab_current_window(server.active_tab) && window_initialized(w))
|
||||
window_set_fullscreen(w, true);
|
||||
if (kick_log_level() >= KICK_LOG_DEBUG)
|
||||
kick_log(KICK_LOG_DEBUG, "[diag] map app=%d content=%s\n",
|
||||
tab_index(w->tab),
|
||||
|
|
@ -4181,8 +4306,6 @@ static void x11_initial_configure(struct wlr_xwayland_surface *xs) {
|
|||
(uint16_t)server.output->width,
|
||||
(uint16_t)server.output->height);
|
||||
wlr_xwayland_surface_set_maximized(xs, true, true);
|
||||
if (server.clients_fullscreen)
|
||||
wlr_xwayland_surface_set_fullscreen(xs, true);
|
||||
}
|
||||
|
||||
/* Claim an X11 toplevel by backing `w` with the xwayland surface: a scene
|
||||
|
|
@ -4418,13 +4541,21 @@ static void handle_xs_request_fullscreen(struct wl_listener *listener,
|
|||
struct kick_window *w = wl_container_of(listener, w, request_fullscreen);
|
||||
struct wlr_xwayland_surface *xs = w->xsurface;
|
||||
if (!xs) return;
|
||||
/* Mirror onto the host window like the xdg path. */
|
||||
/* Per-tab like the xdg path: shown entries take the host chrome too,
|
||||
hidden ones are just marked; exiting always drops the host chrome.
|
||||
(wlroots applies the client's wish to xs->fullscreen before
|
||||
emitting, so the change is already live here: log unconditionally,
|
||||
since window_apply's change detection cannot see it.) */
|
||||
bool fs = xs->fullscreen;
|
||||
wlr_xwayland_surface_set_fullscreen(xs, fs);
|
||||
window_apply_fullscreen(w, fs);
|
||||
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
|
||||
host_apply_fullscreen(fs);
|
||||
if (server.standalone)
|
||||
clients_set_fullscreen(fs);
|
||||
if (server.standalone) return;
|
||||
if (fs) {
|
||||
if (window_is_shown(w))
|
||||
host_apply_fullscreen(true);
|
||||
} else {
|
||||
host_apply_fullscreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_xs_request_configure(struct wl_listener *listener,
|
||||
|
|
@ -7207,9 +7338,14 @@ static void handle_foreign_request_maximize(struct wl_listener *listener, void *
|
|||
static void handle_foreign_request_fullscreen(struct wl_listener *listener, void *data) {
|
||||
struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data;
|
||||
struct kick_window *w = wl_container_of(listener, w, ft_fullscreen);
|
||||
window_set_fullscreen(w, event->fullscreen);
|
||||
if (event->fullscreen)
|
||||
host_apply_fullscreen(true);
|
||||
window_apply_fullscreen(w, event->fullscreen);
|
||||
if (server.standalone) return;
|
||||
if (event->fullscreen) {
|
||||
if (window_is_shown(w))
|
||||
host_apply_fullscreen(true);
|
||||
} else {
|
||||
host_apply_fullscreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_foreign_handle_destroy(struct wl_listener *listener, void *data) {
|
||||
|
|
@ -8122,14 +8258,16 @@ static void host_xdg_toplevel_configure(void *data, struct xdg_toplevel *t,
|
|||
struct wl_array *states)
|
||||
{
|
||||
(void)data; (void)t;
|
||||
/* Mirror the host window's state per entry: entering fullscreen
|
||||
fullscreens the focused overlay entry only (others grow via the
|
||||
resize below); leaving clears every live window. */
|
||||
bool prev_fs = host.fullscreen;
|
||||
host.fullscreen = false;
|
||||
uint32_t *state;
|
||||
wl_array_for_each(state, states)
|
||||
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
|
||||
host.fullscreen = true;
|
||||
/* Mirror the host window's state onto the clients: fullscreen begets
|
||||
fullscreen, anything else begets maximized. */
|
||||
if (host.fullscreen != server.clients_fullscreen) {
|
||||
if (host.fullscreen != prev_fs) {
|
||||
kick_log(KICK_LOG_INFO, "host fullscreen=%d\n", host.fullscreen);
|
||||
clients_set_fullscreen(host.fullscreen);
|
||||
}
|
||||
|
|
@ -8232,9 +8370,9 @@ static void host_frame_configure(struct libdecor_frame *frame,
|
|||
bool fs = (state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0;
|
||||
if (fs != host.fullscreen) {
|
||||
kick_log(KICK_LOG_INFO, "host fullscreen=%d\n", fs);
|
||||
host.fullscreen = fs;
|
||||
clients_set_fullscreen(fs);
|
||||
}
|
||||
host.fullscreen = fs;
|
||||
}
|
||||
/* libdecor reports the *content* size; the host window wraps that content
|
||||
with its decorations. The content is exactly our wlr output. */
|
||||
|
|
@ -9103,14 +9241,15 @@ static bool process_key_event(uint32_t time, uint32_t key, uint32_t state,
|
|||
return true;
|
||||
}
|
||||
}
|
||||
/* prefix+F11 toggles fullscreen. Nested it flips the kick window in
|
||||
the host compositor; the configure that comes back updates every
|
||||
client via the usual host->clients propagation, exactly as if the
|
||||
host compositor had fullscreened us. Standalone has no host window,
|
||||
so it flips every client directly (the escape hatch when a closed
|
||||
tab leaves the rest fullscreened). Grabbed so the focused client
|
||||
never sees the chord; a latch drops host-synthesized repeats so one
|
||||
hold can't flip-flop the toggle. Plain unprefixed F11 still passes
|
||||
/* prefix+F11 toggles fullscreen for the focused overlay entry only.
|
||||
Nested it flips the kick window chrome in the host compositor; the
|
||||
configure that comes back fullscreens the focused entry (siblings,
|
||||
stacked members and other tabs stay maximized and merely grow to
|
||||
the fullscreen output size). A black active tab picks it up at map
|
||||
via host.fullscreen. Standalone has no host window, so it flips
|
||||
the focused entry directly. Grabbed so the focused client never
|
||||
sees the chord; a latch drops host-synthesized repeats so one hold
|
||||
can't flip-flop the toggle. Plain unprefixed F11 still passes
|
||||
through below. */
|
||||
{ bool is_f11 = server.xkb_state
|
||||
? (sym == XKB_KEY_F11)
|
||||
|
|
|
|||
|
|
@ -2706,11 +2706,12 @@ def test_function_keys_pass_through(session):
|
|||
|
||||
|
||||
def test_client_fullscreen_round_trip(tmp_path):
|
||||
"""A client's fullscreen request is honored and mirrored onto the host
|
||||
window; the host configure then propagates fullscreen to every tab.
|
||||
wltest toggles xdg_toplevel fullscreen on the 'F' key, so tap it and
|
||||
watch both directions of the mirror. The host is a private outer kick
|
||||
(not the live desktop), so configure roundtrips are deterministic."""
|
||||
"""A shown client's fullscreen request takes the host window with it
|
||||
(nested) but stays per-entry: only the requesting overlay entry is told
|
||||
fullscreen. Exiting (second toggle) drops the host chrome again.
|
||||
wltest toggles xdg_toplevel fullscreen on the 'F' key. The host is a
|
||||
private outer kick (not the live desktop), so configure roundtrips are
|
||||
deterministic."""
|
||||
outer_log = tmp_path / "outer.log"
|
||||
inner_log = tmp_path / "inner.log"
|
||||
outer = _spawn_standalone(
|
||||
|
|
@ -2727,9 +2728,57 @@ def test_client_fullscreen_round_trip(tmp_path):
|
|||
# the key silently; map alone does not guarantee enter under load.
|
||||
inner.wait_for(r"\[diag\] kbd-enter app=%d\b" % idx)
|
||||
inner.tap_ack(F)
|
||||
inner.wait_for("client fullscreen=1", timeout=30.0)
|
||||
inner.wait_for("host fullscreen=1", timeout=30.0)
|
||||
inner.wait_for("clients fullscreen=1")
|
||||
inner.tap_ack(F)
|
||||
inner.wait_for("client fullscreen=0", timeout=30.0)
|
||||
inner.wait_for("host fullscreen=0", timeout=30.0)
|
||||
inner.wait_for("clients fullscreen=0")
|
||||
_assert_alive(inner)
|
||||
_assert_alive(outer)
|
||||
finally:
|
||||
inner.close()
|
||||
finally:
|
||||
outer.close()
|
||||
|
||||
|
||||
def test_client_fullscreen_is_per_tab(tmp_path):
|
||||
"""Only the requesting window gains fullscreen state: fullscreen one
|
||||
tab via its client's 'F' key, then prove the other tab's client never
|
||||
sees a fullscreen configure (via wltest --req-out)."""
|
||||
outer_log = tmp_path / "outer2.log"
|
||||
inner_log = tmp_path / "inner2.log"
|
||||
out_a = tmp_path / "a.req"
|
||||
out_b = tmp_path / "b.req"
|
||||
outer = _spawn_standalone(
|
||||
{}, "%s --app-id keep '#0000ff' 320x240" % WL, log=str(outer_log))
|
||||
try:
|
||||
outer.wait_any_active()
|
||||
inner = _spawn_nested_in(
|
||||
outer,
|
||||
"%s --app-id tabA --req-out '%s' '#ff0000'" % (WL, out_a),
|
||||
"%s --app-id tabB --req-out '%s' '#00ff00'" % (WL, out_b),
|
||||
log=str(inner_log))
|
||||
try:
|
||||
idx = inner.wait_any_active()
|
||||
inner.wait_surface_mapped(idx)
|
||||
inner.wait_for(r"\[diag\] kbd-enter app=%d\b" % idx)
|
||||
other = 1 - idx
|
||||
other_out = out_a if other == 0 else out_b
|
||||
active_out = out_b if idx == 1 else out_a
|
||||
assert _wait_out(active_out, "maximized=", timeout=15.0)
|
||||
inner.tap_ack(F)
|
||||
inner.wait_for("client fullscreen=1", timeout=30.0)
|
||||
inner.wait_for("host fullscreen=1", timeout=30.0)
|
||||
assert _wait_out(active_out, "fullscreen=1", timeout=15.0), \
|
||||
"active tab's client never got fullscreen configure"
|
||||
time.sleep(1.0)
|
||||
other_text = (other_out.read_text()
|
||||
if other_out.exists() else "")
|
||||
assert "fullscreen=1" not in other_text, \
|
||||
f"other tab wrongly fullscreened: {other_text[-200:]!r}"
|
||||
inner.tap_ack(F)
|
||||
inner.wait_for("host fullscreen=0", timeout=30.0)
|
||||
inner.wait_for("clients fullscreen=0")
|
||||
_assert_alive(inner)
|
||||
|
|
@ -2740,12 +2789,78 @@ def test_client_fullscreen_round_trip(tmp_path):
|
|||
outer.close()
|
||||
|
||||
|
||||
def test_standalone_client_fullscreen_is_per_entry(tmp_path):
|
||||
"""Standalone (no host chrome): a client's fullscreen request tells only
|
||||
the requesting overlay entry fullscreen -- the other entry's client
|
||||
never sees a fullscreen configure (via wltest --req-out). Regression
|
||||
test: the old standalone path broadcast to every tab."""
|
||||
out_a = tmp_path / "a.req"
|
||||
out_b = tmp_path / "b.req"
|
||||
with _run_standalone(
|
||||
{}, "%s --app-id tabA --req-out '%s' '#ff0000'" % (WL, out_a),
|
||||
"%s --app-id tabB --req-out '%s' '#00ff00'" % (WL, out_b),
|
||||
wait="any") as s:
|
||||
idx = s.wait_any_active()
|
||||
s.wait_surface_mapped(idx)
|
||||
s.wait_for(r"\[diag\] kbd-enter app=%d\b" % idx)
|
||||
other = 1 - idx
|
||||
other_out = out_a if other == 0 else out_b
|
||||
active_out = out_b if idx == 1 else out_a
|
||||
assert _wait_out(active_out, "maximized=", timeout=15.0)
|
||||
s.tap_ack(F)
|
||||
s.wait_for("client fullscreen=1", timeout=30.0)
|
||||
s.wait_for("clients fullscreen=1", timeout=30.0)
|
||||
assert _wait_out(active_out, "fullscreen=1", timeout=15.0), \
|
||||
"active entry's client never got fullscreen configure"
|
||||
time.sleep(1.0)
|
||||
other_text = (other_out.read_text()
|
||||
if other_out.exists() else "")
|
||||
assert "fullscreen=1" not in other_text, \
|
||||
f"other entry wrongly fullscreened: {other_text[-200:]!r}"
|
||||
s.tap_ack(F)
|
||||
s.wait_for("client fullscreen=0", timeout=30.0)
|
||||
s.wait_for("clients fullscreen=0", timeout=30.0)
|
||||
_assert_alive(s, "compositor died")
|
||||
|
||||
|
||||
def test_standalone_alt_f11_is_per_entry(tmp_path):
|
||||
"""Standalone prefix+F11 fullscreens the focused overlay entry only --
|
||||
the other entry's client never sees a fullscreen configure. Regression
|
||||
test: the old standalone toggle flipped every tab."""
|
||||
out_a = tmp_path / "a.req"
|
||||
out_b = tmp_path / "b.req"
|
||||
with _run_standalone(
|
||||
{}, "%s --app-id tabA --req-out '%s' '#ff0000'" % (WL, out_a),
|
||||
"%s --app-id tabB --req-out '%s' '#00ff00'" % (WL, out_b),
|
||||
wait="any") as s:
|
||||
idx = s.wait_any_active()
|
||||
s.wait_surface_mapped(idx)
|
||||
s.wait_for(r"\[diag\] kbd-enter app=%d\b" % idx)
|
||||
other = 1 - idx
|
||||
other_out = out_a if other == 0 else out_b
|
||||
active_out = out_b if idx == 1 else out_a
|
||||
assert _wait_out(active_out, "maximized=", timeout=15.0)
|
||||
s.chord_ack(ALT, F11)
|
||||
s.wait_for("client fullscreen=1", timeout=30.0)
|
||||
s.wait_for("clients fullscreen=1", timeout=30.0)
|
||||
assert _wait_out(active_out, "fullscreen=1", timeout=15.0), \
|
||||
"focused entry's client never got fullscreen configure"
|
||||
time.sleep(1.0)
|
||||
other_text = (other_out.read_text()
|
||||
if other_out.exists() else "")
|
||||
assert "fullscreen=1" not in other_text, \
|
||||
f"other entry wrongly fullscreened: {other_text[-200:]!r}"
|
||||
s.chord_ack(ALT, F11)
|
||||
s.wait_for("clients fullscreen=0", timeout=30.0)
|
||||
_assert_alive(s, "compositor died")
|
||||
|
||||
|
||||
def test_alt_f11_toggles_host_fullscreen(tmp_path):
|
||||
"""prefix+F11 toggles the kick window's fullscreen state in the host;
|
||||
the resulting configure propagates to every tab exactly like a
|
||||
host-compositor-initiated fullscreen. Tapping plain F11 (no prefix)
|
||||
must still pass through to the client untouched. The host is a private
|
||||
outer kick, so configure roundtrips are deterministic."""
|
||||
the resulting configure fullscreens the active tab only (other tabs
|
||||
stay maximized and grow to the fullscreen size). Tapping plain F11
|
||||
(no prefix) must still pass through to the client untouched. The host
|
||||
is a private outer kick, so configure roundtrips are deterministic."""
|
||||
outer_log = tmp_path / "outer.log"
|
||||
inner_log = tmp_path / "inner.log"
|
||||
outer = _spawn_standalone(
|
||||
|
|
@ -2772,11 +2887,12 @@ def test_alt_f11_toggles_host_fullscreen(tmp_path):
|
|||
outer.close()
|
||||
|
||||
|
||||
def test_alt_f11_propagates_to_stack_members(tmp_path):
|
||||
"""prefix+F11's host fullscreen reaches not just each tab's main window
|
||||
but every stacked tab-group member too, and leaving fullscreen clears
|
||||
both again. The host is a private outer kick, so configure roundtrips
|
||||
are deterministic."""
|
||||
def test_alt_f11_fullscreens_focused_entry_only(tmp_path):
|
||||
"""prefix+F11 fullscreens the focused overlay entry only: with a stacked
|
||||
tab-group tab active (main + extra member), entering fullscreen must
|
||||
tell exactly one window (one "client fullscreen=1" line), leaving the
|
||||
unfocused member maximized. Leaving clears again. The host is a
|
||||
private outer kick, so configure roundtrips are deterministic."""
|
||||
outer_log = tmp_path / "outer.log"
|
||||
inner_log = tmp_path / "inner.log"
|
||||
outer = _spawn_standalone(
|
||||
|
|
@ -2790,10 +2906,16 @@ def test_alt_f11_propagates_to_stack_members(tmp_path):
|
|||
idx = inner.wait_any_active()
|
||||
inner.wait_surface_mapped(idx)
|
||||
inner.wait_for(r"\[sub\] stack app=0\b")
|
||||
if idx != 0:
|
||||
inner.cycle_ack(-1)
|
||||
inner.wait_active(0, timeout=15.0)
|
||||
inner.chord_ack(ALT, F11)
|
||||
inner.wait_for("host fullscreen=1", timeout=30.0)
|
||||
inner.wait_for("clients fullscreen=1")
|
||||
inner.wait_for("members fullscreen=1")
|
||||
n = inner.read_log().count("client fullscreen=1")
|
||||
assert n == 1, \
|
||||
f"expected exactly one fullscreened entry, got {n}"
|
||||
inner.chord_ack(ALT, F11)
|
||||
inner.wait_for("host fullscreen=0", timeout=30.0)
|
||||
inner.wait_for("clients fullscreen=0")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue