route the host window through xdg-shell directly on SSD compositors
libdecor sat on the critical path of every host-window transition even where it drew nothing: on compositors honoring server-side decorations (labwc/sway/KDE) it negotiated SSD, then still injected permanent set_window_geometry hints and batched acks inside its own commit call. labwc 0.9.8 expires a 100 ms configure deadline under load and answers "move but keep old size" -- anchoring fullscreen views to stale geometry forever. the host window now selects its backend at startup: compositors advertising zxdg_decoration_manager_v1 get kick-owned xdg_shell objects with immediate configure acks, an explicit server-side decoration request, and no geometry hints at all (KICK_LIBDECOR=1 forces the libdecor path); hosts without the manager -- Mutter/GNOME forces CSD -- keep libdecor unchanged. this also restores mirrored fullscreen semantics: a client's fullscreen request again fullscreens the host window in one step.
This commit is contained in:
parent
74a2ac72a8
commit
c10845f2c1
4 changed files with 202 additions and 62 deletions
224
kick.c
224
kick.c
|
|
@ -37,6 +37,7 @@
|
|||
#include <libdecor.h>
|
||||
|
||||
#include "xdg-shell-client.h"
|
||||
#include "xdg-decoration-client.h"
|
||||
#include "wlr-data-control-client.h"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
|
@ -421,12 +422,22 @@ struct kick_host {
|
|||
struct wl_compositor *compositor;
|
||||
struct wl_surface *surface;
|
||||
struct xdg_wm_base *wm_base;
|
||||
/* The nested window is a libdecor frame around our wl_surface: libdecor
|
||||
owns the xdg_surface/xdg_toplevel plus the window decorations,
|
||||
negotiating server-side decoration when the host offers it and drawing
|
||||
its own titlebar (with move/resize/close) otherwise (GNOME). */
|
||||
/* The nested host window has two backends selected at startup:
|
||||
- direct xdg-shell (default on compositors advertising
|
||||
zxdg_decoration_manager_v1 -- labwc/sway/KDE): kick owns the
|
||||
xdg_surface/xdg_toplevel, requests server-side decorations and
|
||||
acks configure events immediately. No set_window_geometry hints,
|
||||
so no stale-view anchoring on fullscreen (labwc 0.9.8 bug).
|
||||
- libdecor (hosts forcing CSD, e.g. Mutter/GNOME): libdecor owns the
|
||||
shell objects and draws the titlebar.
|
||||
KICK_LIBDECOR=1 forces the libdecor path for testing. */
|
||||
bool use_libdecor;
|
||||
struct libdecor *libdecor;
|
||||
struct libdecor_frame *frame;
|
||||
struct xdg_surface *xdg_surface;
|
||||
struct xdg_toplevel *toplevel;
|
||||
struct zxdg_decoration_manager_v1 *decoration;
|
||||
struct zxdg_toplevel_decoration_v1 *decoration_obj;
|
||||
struct wl_seat *seat;
|
||||
struct wl_keyboard *keyboard;
|
||||
struct wl_pointer *pointer;
|
||||
|
|
@ -468,7 +479,7 @@ struct kick_host {
|
|||
bool present_pending;
|
||||
bool back_dirty;
|
||||
/* Host-window fullscreen request waiting for the present pipeline to
|
||||
drain (see host_queue_fullscreen). */
|
||||
drain (see host_apply_fullscreen). */
|
||||
bool fs_queued;
|
||||
bool fs_target;
|
||||
/* Keybind repeat for the captured tab-switch binding. The host
|
||||
|
|
@ -2622,7 +2633,20 @@ static void handle_request_maximize(struct wl_listener *listener, void *data) {
|
|||
window was", margins top/left, cropped bottom/right). Waiting for the
|
||||
frame callback proves the last swap reached the screen, so nothing stale
|
||||
can follow the ack. Rapid requests collapse to the latest target. */
|
||||
static void host_queue_fullscreen(bool fs) {
|
||||
/* Apply a fullscreen change to the kick window on the active backend:
|
||||
direct path talks xdg-shell immediately (its ack precedes every commit
|
||||
deterministically, so labwc's deadline race cannot anchor stale state);
|
||||
libdecor path queues until the present pipeline drains, for the same
|
||||
reason. */
|
||||
static void host_apply_fullscreen(bool fs) {
|
||||
if (!host.use_libdecor) {
|
||||
if (!host.toplevel) return; /* standalone has no host window */
|
||||
if (fs)
|
||||
xdg_toplevel_set_fullscreen(host.toplevel, NULL);
|
||||
else
|
||||
xdg_toplevel_unset_fullscreen(host.toplevel);
|
||||
return;
|
||||
}
|
||||
if (!host.frame) return; /* standalone has no host window */
|
||||
if (host.present_pending) {
|
||||
host.fs_queued = true;
|
||||
|
|
@ -2642,13 +2666,12 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
|
|||
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
|
||||
within kick only: the window fills our output while the host window
|
||||
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(). */
|
||||
and mirror it onto the host window: one action fullscreens the whole
|
||||
monitor. */
|
||||
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);
|
||||
}
|
||||
|
||||
/* Propagate the host window's fullscreen state onto every client -- each
|
||||
|
|
@ -2684,11 +2707,11 @@ static void clients_set_fullscreen(bool fs) {
|
|||
}
|
||||
|
||||
/* Toggle the host window's fullscreen state. The request is queued until
|
||||
the present pipeline drains (see host_queue_fullscreen); the actual flip
|
||||
the present pipeline drains (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. */
|
||||
static void host_toggle_fullscreen(void) {
|
||||
host_queue_fullscreen(!host.fullscreen);
|
||||
host_apply_fullscreen(!host.fullscreen);
|
||||
}
|
||||
|
||||
static void handle_request_minimize(struct wl_listener *listener, void *data) {
|
||||
|
|
@ -3645,10 +3668,11 @@ static void handle_xs_request_fullscreen(struct wl_listener *listener,
|
|||
struct kick_app *app = wl_container_of(listener, app, request_fullscreen);
|
||||
struct wlr_xwayland_surface *xs = app->xsurface;
|
||||
if (!xs) return;
|
||||
/* Same containment as the xdg path: fullscreen stays inside kick. */
|
||||
/* Mirror onto the host window like the xdg path. */
|
||||
bool fs = xs->fullscreen;
|
||||
wlr_xwayland_surface_set_fullscreen(xs, fs);
|
||||
kick_log(KICK_LOG_INFO, "client fullscreen=%d\n", fs);
|
||||
host_apply_fullscreen(fs);
|
||||
}
|
||||
|
||||
static void handle_xs_request_configure(struct wl_listener *listener,
|
||||
|
|
@ -5651,6 +5675,13 @@ static void host_registry_global(void *data, struct wl_registry *registry,
|
|||
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
||||
host.wm_base = wl_registry_bind(registry, name,
|
||||
&xdg_wm_base_interface, version < 1 ? version : 1);
|
||||
} else if (strcmp(interface,
|
||||
zxdg_decoration_manager_v1_interface.name) == 0) {
|
||||
/* Advertised by compositors that honor server-side decorations
|
||||
(labwc/sway/KDE): marks the direct-xdg host-window path. */
|
||||
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
|
||||
|
|
@ -5753,9 +5784,20 @@ static void host_resize(int width, int height) {
|
|||
subsurfaces) and the content surface. Only called from the exit paths, so
|
||||
no ack/commit dance is needed. */
|
||||
static void host_teardown_window(void) {
|
||||
if (host.frame) {
|
||||
libdecor_frame_unref(host.frame);
|
||||
host.frame = NULL;
|
||||
if (host.use_libdecor) {
|
||||
if (host.frame) {
|
||||
libdecor_frame_unref(host.frame);
|
||||
host.frame = NULL;
|
||||
}
|
||||
} else {
|
||||
if (host.toplevel) {
|
||||
xdg_toplevel_destroy(host.toplevel);
|
||||
host.toplevel = NULL;
|
||||
}
|
||||
if (host.xdg_surface) {
|
||||
xdg_surface_destroy(host.xdg_surface);
|
||||
host.xdg_surface = NULL;
|
||||
}
|
||||
}
|
||||
if (host.surface) {
|
||||
wl_surface_destroy(host.surface);
|
||||
|
|
@ -5763,6 +5805,72 @@ static void host_teardown_window(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* ---- Direct-xdg host-window path (SSD compositors: labwc/sway/KDE) ----
|
||||
Kick owns the shell objects and acks configure events immediately -- the
|
||||
arrangement that predates libdecor, where fullscreen state application
|
||||
cannot be anchored to stale geometry because no set_window_geometry hint
|
||||
is ever sent and the ack precedes every commit. */
|
||||
|
||||
static void host_xdg_surface_configure(void *data, struct xdg_surface *s,
|
||||
uint32_t serial)
|
||||
{
|
||||
(void)data;
|
||||
xdg_surface_ack_configure(s, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener host_xdg_surface_listener = {
|
||||
.configure = host_xdg_surface_configure,
|
||||
};
|
||||
|
||||
static void host_xdg_toplevel_configure(void *data, struct xdg_toplevel *t,
|
||||
int32_t width, int32_t height,
|
||||
struct wl_array *states)
|
||||
{
|
||||
(void)data; (void)t;
|
||||
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) {
|
||||
kick_log(KICK_LOG_INFO, "host fullscreen=%d\n", host.fullscreen);
|
||||
clients_set_fullscreen(host.fullscreen);
|
||||
}
|
||||
if (width > 0 && height > 0)
|
||||
host_resize(width, height);
|
||||
host.configured = true;
|
||||
}
|
||||
|
||||
static void host_xdg_toplevel_close(void *data, struct xdg_toplevel *t) {
|
||||
(void)data; (void)t;
|
||||
kick_log(KICK_LOG_INFO, "kick: window closed\n");
|
||||
shutdown_cleanup();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener host_xdg_toplevel_listener = {
|
||||
.configure = host_xdg_toplevel_configure,
|
||||
.close = host_xdg_toplevel_close,
|
||||
};
|
||||
|
||||
static void host_decoration_configure(void *data,
|
||||
struct zxdg_toplevel_decoration_v1 *dec,
|
||||
uint32_t mode)
|
||||
{
|
||||
(void)data; (void)dec;
|
||||
kick_log(KICK_LOG_INFO, "kick: host decoration mode=%u (%s)\n",
|
||||
mode,
|
||||
mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
|
||||
? "server-side" : "client-side");
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener
|
||||
host_decoration_listener = {
|
||||
.configure = host_decoration_configure,
|
||||
};
|
||||
|
||||
static void host_frame_configure(struct libdecor_frame *frame,
|
||||
struct libdecor_configuration *config,
|
||||
void *data)
|
||||
|
|
@ -7431,31 +7539,59 @@ static bool host_init(void) {
|
|||
}
|
||||
|
||||
host.surface = wl_compositor_create_surface(host.compositor);
|
||||
host.libdecor = libdecor_new(host.display, &host_libdecor_iface);
|
||||
if (!host.libdecor) {
|
||||
fprintf(stderr, "kick: libdecor failed to initialise\n");
|
||||
return false;
|
||||
}
|
||||
host.frame = libdecor_decorate(host.libdecor, host.surface,
|
||||
&host_frame_iface, NULL);
|
||||
if (!host.frame) {
|
||||
fprintf(stderr, "kick: libdecor_decorate failed\n");
|
||||
return false;
|
||||
}
|
||||
libdecor_frame_set_title(host.frame, "kick");
|
||||
libdecor_frame_set_app_id(host.frame, "kick");
|
||||
/* Ask libdecor for a frame around our content. It negotiates xdg
|
||||
decorations with the host: a server-side title bar when the host offers
|
||||
one (labwc, sway), its own client-side title bar (drawing in a
|
||||
subsurface above our content) otherwise -- GNOME/Mutter forces CSD, so
|
||||
the window becomes draggable and resizable through that bar. */
|
||||
libdecor_frame_map(host.frame);
|
||||
/* libdecor pongs pings on its own wm_base binding; ours is separate, so
|
||||
keep ponging the ones addressed to it or the host declares us hung. */
|
||||
|
||||
/* Backend selection: compositors advertising zxdg_decoration_manager_v1
|
||||
honor server-side decorations, so kick owns the shell objects and
|
||||
talks xdg-shell directly -- no set_window_geometry hints, immediate
|
||||
acks, and fullscreen state application that cannot anchor to stale
|
||||
geometry (the labwc 0.9.8 misplacement). Hosts without it force
|
||||
CSD (Mutter/GNOME): hand the window to libdecor, which draws its own
|
||||
titlebar. KICK_LIBDECOR=1 forces the libdecor path for testing. */
|
||||
host.use_libdecor = getenv("KICK_LIBDECOR") != NULL || !host.decoration;
|
||||
|
||||
xdg_wm_base_add_listener(host.wm_base, &host_xdg_wm_listener, NULL);
|
||||
wl_surface_commit(host.surface);
|
||||
wl_display_roundtrip(host.display);
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
if (host.use_libdecor) {
|
||||
host.libdecor = libdecor_new(host.display, &host_libdecor_iface);
|
||||
if (!host.libdecor) {
|
||||
fprintf(stderr, "kick: libdecor failed to initialise\n");
|
||||
return false;
|
||||
}
|
||||
host.frame = libdecor_decorate(host.libdecor, host.surface,
|
||||
&host_frame_iface, NULL);
|
||||
if (!host.frame) {
|
||||
fprintf(stderr, "kick: libdecor_decorate failed\n");
|
||||
return false;
|
||||
}
|
||||
libdecor_frame_set_title(host.frame, "kick");
|
||||
libdecor_frame_set_app_id(host.frame, "kick");
|
||||
/* libdecor negotiates xdg decorations with the host: a server-side
|
||||
title bar when offered, its own client-side titlebar otherwise. */
|
||||
libdecor_frame_map(host.frame);
|
||||
wl_surface_commit(host.surface);
|
||||
wl_display_roundtrip(host.display);
|
||||
if (host.use_libdecor)
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
} else {
|
||||
kick_log(KICK_LOG_INFO,
|
||||
"host window: direct xdg-shell with server-side decorations\n");
|
||||
host.xdg_surface = xdg_wm_base_get_xdg_surface(host.wm_base,
|
||||
host.surface);
|
||||
host.toplevel = xdg_surface_get_toplevel(host.xdg_surface);
|
||||
xdg_toplevel_set_title(host.toplevel, "kick");
|
||||
xdg_toplevel_set_app_id(host.toplevel, "kick");
|
||||
host.decoration_obj = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
host.decoration, host.toplevel);
|
||||
zxdg_toplevel_decoration_v1_add_listener(host.decoration_obj,
|
||||
&host_decoration_listener, NULL);
|
||||
zxdg_toplevel_decoration_v1_set_mode(host.decoration_obj,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
xdg_surface_add_listener(host.xdg_surface,
|
||||
&host_xdg_surface_listener, NULL);
|
||||
xdg_toplevel_add_listener(host.toplevel,
|
||||
&host_xdg_toplevel_listener, NULL);
|
||||
wl_surface_commit(host.surface);
|
||||
wl_display_roundtrip(host.display);
|
||||
}
|
||||
|
||||
/* Host keyboard/pointer are created by the seat capabilities handler
|
||||
during the registry round-trip above; nothing more to fetch here. */
|
||||
|
|
@ -7654,7 +7790,7 @@ static void host_frame_done(void *data, struct wl_callback *callback,
|
|||
ack will carry the freshly sized buffer. */
|
||||
if (host.fs_queued) {
|
||||
host.fs_queued = false;
|
||||
host_queue_fullscreen(host.fs_target);
|
||||
host_apply_fullscreen(host.fs_target);
|
||||
}
|
||||
|
||||
if (host.back_dirty) {
|
||||
|
|
@ -8973,7 +9109,8 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
for (;;) {
|
||||
wl_display_dispatch_pending(host.display);
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
if (host.use_libdecor)
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
/* Flush queued nested-client messages before blocking: a data-control
|
||||
`send` queued while dispatching the host side would otherwise sit
|
||||
in the client connection until some later event wakes the poll
|
||||
|
|
@ -9009,7 +9146,8 @@ int main(int argc, char *argv[]) {
|
|||
wl_display_flush_clients(server.wl_display);
|
||||
sweep_dead_startup();
|
||||
wl_display_dispatch_pending(host.display);
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
if (host.use_libdecor)
|
||||
libdecor_dispatch(host.libdecor, 0);
|
||||
|
||||
/* Render only when something changed (kick's own redraws or scene
|
||||
damage) and no page flip is in flight; an idle screen never
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue