diff --git a/README.md b/README.md index cc52e7c..b4e43f3 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,18 @@ appear: tabs: the dialogs and auxiliaries feel like part of the one window. `off` keeps every surface individually reachable, which is kick's original model. +### Panel (`--panel`) + +`--panel CMD` launches a wlr-layer-shell panel (e.g. waybar) as an +always-on-top bar that sizes the tabs underneath it. The panel is launched +with a plain exec and shares the login session's D-Bus bus, like every other +compositor's panels. A single-instance panel therefore cannot nest: if the +host session already runs one (e.g. `wf-panel-pi` >= 1.31, which owns +`com.raspberrypi.wfpanelpi`), the nested copy defers to it and exits without +ever mapping, so no bar appears. Use a multi-instance panel, stop the host +copy, or run kick as the session itself (standalone), where no competitor +exists. + ## Key bindings | Key | Action | diff --git a/kick.c b/kick.c index f372dd5..d6455cb 100644 --- a/kick.c +++ b/kick.c @@ -5853,6 +5853,11 @@ static void launch_panel(void) { if (nullfd > STDERR_FILENO) close(nullfd); } } + /* The panel shares the login session's D-Bus bus like every other + compositor's panels do (labwc/sway exec theirs plainly too), so + single-instance panels (wf-panel-pi >= 1.31 owns + com.raspberrypi.wfpanelpi) defer to an already-running host copy + and exit without mapping -- see the --panel README note. */ execlp("/bin/sh", "sh", "-c", server.panel_cmd, NULL); _exit(127); } @@ -12370,6 +12375,14 @@ int main(int argc, char *argv[]) { "panel (e.g. waybar) as an\n" " always-on-top bar that " "sizes the tabs underneath it.\n" + " The panel shares the login " + "session's D-Bus bus,\n" + " so a single-instance panel " + "already running on the\n" + " host (e.g. wf-panel-pi) " + "exits without mapping;\n" + " use a multi-instance panel " + "or stop the host copy.\n" " --focus-priority Favor the active window: " "it runs at normal\n" " CPU/IO priority (nice 0, " diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 4a3ab68..8d1fe9c 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -1413,6 +1413,24 @@ def _require_host_wayland(): pytest.skip("needs a live host Wayland session (WAYLAND_DISPLAY)") +def _dbus_name_owned(name): + """True when a session-bus name is already owned (a host singleton runs). + + Used to skip tests that nest a single-instance client (wf-panel-pi owns + com.raspberrypi.wfpanelpi): the nested copy would defer to the host's and + exit without mapping. False when there is no session bus to ask.""" + try: + out = subprocess.run( + ["dbus-send", "--session", "--print-reply", "--dest=org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus.NameHasOwner", + f"string:{name}"], + capture_output=True, text=True, timeout=5, + ) + except (OSError, subprocess.SubprocessError): + return False + return out.returncode == 0 and "boolean true" in out.stdout + + def _require_xwayland(): """Skip unless Xwayland and xmessage are available.""" if not HAVE_XWAYLAND: @@ -4979,6 +4997,9 @@ def test_panel_menu_flush_under_bar(): panel = shutil.which("wf-panel-pi") if not panel: pytest.skip("wf-panel-pi not installed") + if _dbus_name_owned("com.raspberrypi.wfpanelpi"): + pytest.skip("host already runs wf-panel-pi: the nested single-instance " + "copy would exit without mapping") with _run_nested(f"{WL} '{RED}'", extra_args=("--panel", panel)) as s: s.wait_for(r"\[diag\] map app=0", timeout=20.0)