From 924d5c07f402ce873e84c186813bd7205402342f Mon Sep 17 00:00:00 2001 From: nevaforget <> Date: Wed, 9 Sep 2026 15:53:03 +0200 Subject: [PATCH] fix(quickshell): start launched apps in their own systemd scopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app.execute() spawns the app as a child of quickshell, and a child inherits its parent's cgroup, so everything started from the launcher ended up inside quickshell.service. systemd kills a unit's whole cgroup on stop, so a crash or restart of the bar took the open apps with it — Librewolf counted each one as a crash and eventually stopped restoring the session at all. app2unit wraps systemd-run and gives each app its own transient app-*.scope, the same shape niri gives the windows it spawns itself. Quickshell 0.3.0 exports no desktop-entry id, only the command, so the prefix takes that; Terminal=true entries need the -term variant, and a Path= from the entry travels as a unit property because app2unit otherwise starts the app in the bar's directory. --- defaults/xdg/quickshell/moonarch/LauncherPopout.qml | 10 +++++++++- defaults/xdg/quickshell/moonarch/NetworkPopout.qml | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/defaults/xdg/quickshell/moonarch/LauncherPopout.qml b/defaults/xdg/quickshell/moonarch/LauncherPopout.qml index 861b1f5..118f2ca 100644 --- a/defaults/xdg/quickshell/moonarch/LauncherPopout.qml +++ b/defaults/xdg/quickshell/moonarch/LauncherPopout.qml @@ -37,7 +37,15 @@ Item { function launch(app) { if (!app) return; - app.execute(); + // Started through app2unit, not app.execute(): execute() spawns the app as a child of quickshell, + // and a child inherits its parent's cgroup, so the app lands inside quickshell.service and + // systemd kills it along with the unit on every restart of the bar. app2unit wraps systemd-run + // and hands each app its own transient app-*.scope — the same shape niri gives the windows it + // spawns itself. Terminal entries need the -term variant, and a Path= from the entry has to + // travel as a unit property because app2unit otherwise starts the app in the bar's directory. + const unit = app.runInTerminal ? "app2unit-term" : "app2unit"; + const props = app.workingDirectory ? ["-p", "WorkingDirectory=" + app.workingDirectory] : []; + Quickshell.execDetached([unit].concat(props, ["--"], app.command)); if (root.popouts) root.popouts.close(); } diff --git a/defaults/xdg/quickshell/moonarch/NetworkPopout.qml b/defaults/xdg/quickshell/moonarch/NetworkPopout.qml index 04c538d..2bd03f4 100644 --- a/defaults/xdg/quickshell/moonarch/NetworkPopout.qml +++ b/defaults/xdg/quickshell/moonarch/NetworkPopout.qml @@ -408,7 +408,9 @@ PopoutPanel { id: managerRow width: root.contentWidth onClicked: { - Quickshell.execDetached(["nm-connection-editor"]); + // Through app2unit for the same reason as the launcher: a child of quickshell shares its + // cgroup, so a restart of the bar would close the editor mid-edit. See LauncherPopout. + Quickshell.execDetached(["app2unit", "--", "nm-connection-editor"]); if (root.popouts) root.popouts.close(); }