pragma Singleton import Quickshell // Required: execDetached's context overload takes a processContext value type // from this module, and without the import the call fails to resolve. import Quickshell.Io import qs.config // Quickshell's DesktopEntry.execute() deliberately ignores both Terminal=true // and Exec field codes, so entries are launched here instead. Without this, // entries like `firefox %U` are started with a literal "%U" argument. Singleton { id: root // Set only when att_menu itself was started by systemd, which is the one // case where the control group is worth escaping. Started from a shell or // by the compositor the panel shares the session's group, and wrapping // would buy nothing. readonly property bool inSystemdUnit: !!Quickshell.env("INVOCATION_ID") // Moves a launched app into a transient scope of its own, outside att_menu's // control group. Stopping a systemd unit signals every process in its group // and execDetached's fork does not leave it, so without this the panel takes // down everything it ever launched — which is what happens each time tablet // mode ends and the unit is stopped. // // A scope, not a service: the app keeps the environment, working directory // and stdio it was given, so WAYLAND_DISPLAY and friends still reach it. // The exec'd shell leaves no process of its own behind, and passing the app // as "$@" keeps its arguments intact. systemd-run is only assumed to be on // PATH when it is actually there — the panel runs on systems packaged // without it too. readonly property var scopeWrapper: ["sh", "-c", "command -v systemd-run >/dev/null 2>&1 && exec systemd-run --user --scope --collect --quiet -- \"$@\"; exec \"$@\"", "att_menu"] function withoutFieldCodes(command: var): var { const out = []; for (const arg of command) { // "%%" is an escaped percent and collapses to one. Every other // % is a field code the spec says to substitute with files, // URLs or icons — launching from a menu has nothing to substitute, // so they are removed. const cleaned = arg.replace(/%(.)/g, (match, char) => char === "%" ? "%" : ""); // An argument that was nothing but a field code is dropped. if (cleaned !== "") out.push(cleaned); } return out; } function launch(entry: var): void { const command = root.withoutFieldCodes(entry.command); if (command.length === 0) return; const argv = entry.runInTerminal ? [...Config.terminal, ...command] : command; const options = { command: root.inSystemdUnit ? [...root.scopeWrapper, ...argv] : argv }; if (entry.workingDirectory) options.workingDirectory = entry.workingDirectory; Quickshell.execDetached(options); } }