feat: standalone autodetect for DM Wayland sessions

This commit is contained in:
Andrew Hamilton 2026-09-14 10:58:03 +10:00
parent 2838a5a71e
commit 2a2aa2fbcb
3 changed files with 54 additions and 12 deletions

View file

@ -323,9 +323,11 @@ composited onto the output in a second render pass instead of being drawn
into a host window.
The auto-detection fires only when none of `KICK_MODE`, `WLR_BACKENDS`,
`WAYLAND_DISPLAY` or `DISPLAY` is set **and** kick's stdin is a real VT
(`/dev/tty<digits>`): there must be nothing to nest into, and SSH sessions,
terminal emulators and pipes never match the VT shape test. The chosen mode
`WAYLAND_DISPLAY` or `DISPLAY` is set **and** either kick's stdin is a real
VT (`/dev/tty<digits>`) or the login advertises a Wayland session
(`XDG_SESSION_TYPE=wayland`, as every display manager/greeter sets): there
must be nothing to nest into, and SSH sessions, terminal emulators and
pipes match neither test, so they keep the nested default. The chosen mode
is logged at startup as `[mode] standalone|nested (reason)`. Escape hatches,
in precedence order:

29
kick.c
View file

@ -12141,16 +12141,22 @@ static bool parse_screen_mode(const char *val) {
return true;
}
/* True when this process was started from a Linux virtual console: our
controlling terminal is a real VT (/dev/tty<digits>) and there is no
ambient graphical session (neither WAYLAND_DISPLAY nor DISPLAY) that
nested mode could open its window in. SSH sessions, terminal emulators
and pipes all fail the tty shape test (/dev/pts/N, /dev/ttyS*, not a tty),
so they keep the nested default. */
/* True when this process is the graphical session itself (standalone) rather
than a client of one (nested): our controlling terminal is a real VT
(/dev/tty<digits>) and there is no ambient graphical session (neither
WAYLAND_DISPLAY nor DISPLAY) that nested mode could open its window in --
or a display manager launched us as its Wayland session (XDG_SESSION_TYPE
says so; every major DM/greeter sets it, and the VT test can never fire
there since stdin is no virtual console). SSH sessions, terminal
emulators and pipes all fail both tests (/dev/pts/N, /dev/ttyS*, not a
tty; no session type), so they keep the nested default. */
static bool started_on_console(void) {
char name[64];
if (getenv("WAYLAND_DISPLAY") || getenv("DISPLAY"))
return false;
const char *session_type = getenv("XDG_SESSION_TYPE");
if (session_type && strcmp(session_type, "wayland") == 0)
return true;
if (!isatty(STDIN_FILENO))
return false;
if (ttyname_r(STDIN_FILENO, name, sizeof(name)) != 0)
@ -12511,13 +12517,15 @@ int main(int argc, char *argv[]) {
drives a native output directly (DRM, or headless for tests); the
default nested mode blits a headless output into a host window.
With neither variable set, launching from a Linux virtual console
picks standalone automatically so plain `kick` works at a TTY;
KICK_MODE=nested pins the old behaviour there. */
or as a display manager's Wayland session picks standalone
automatically so plain `kick` works at a TTY and in lightdm/greetd
entries; KICK_MODE=nested pins the old behaviour there. */
server.standalone = false;
const char *mode_reason = "default";
{
const char *mode = getenv("KICK_MODE");
const char *wlr_backends = getenv("WLR_BACKENDS");
const char *session_type = getenv("XDG_SESSION_TYPE");
if (mode && strcmp(mode, "standalone") == 0) {
server.standalone = true;
mode_reason = "KICK_MODE";
@ -12526,7 +12534,10 @@ int main(int argc, char *argv[]) {
mode_reason = "KICK_MODE";
} else if (!wlr_backends || !wlr_backends[0]) {
server.standalone = started_on_console();
mode_reason = server.standalone ? "console" : "default";
if (server.standalone)
mode_reason = (session_type &&
strcmp(session_type, "wayland") == 0) ?
"session" : "console";
}
if (wlr_backends && wlr_backends[0]) {
server.standalone = true;

View file

@ -4636,6 +4636,35 @@ def test_mode_autodetect_stays_nested_without_console():
s.close()
def test_mode_autodetect_standalone_wayland_session():
"""A display manager launching kick as its Wayland session (no KICK_MODE,
no WLR_BACKENDS, no displays, piped stdin like lightdm provides) must
pick standalone via XDG_SESSION_TYPE: the VT shape test can never fire
there, and without this the session tries to nest into nothing and the
login loops back to the greeter."""
env = dict(os.environ)
log = _fresh_log()
dump_dir = _mk_dump_dir()
proc = subprocess.Popen(
["env", "-i",
"PATH=" + env["PATH"],
"HOME=" + env.get("HOME", "/"),
"XDG_RUNTIME_DIR=" + env.get("XDG_RUNTIME_DIR", "/tmp"),
"XDG_SESSION_TYPE=wayland",
"KICK_LOG=1",
"KICK_DUMP_DIR=" + dump_dir,
"stdbuf", "-oL", BIN],
stdin=subprocess.PIPE, stdout=open(log, "ab"), stderr=subprocess.STDOUT,
preexec_fn=_preexec_death(os.getpid()),
)
s = Session(proc, log, dump_dir)
try:
s.wait_for(r"\[mode\] standalone \(session\)")
assert "[mode] nested" not in s.read_log()
finally:
s.close()
@pytest.mark.flaky
def test_x11_leader_window_never_becomes_tab():
"""A GTK-style X11 app (unmapped leader window created first, real frame