/** * @file desktop-env.js * @description The graphical session's own environment variables, for panes * the dashboard spawns. The server usually runs as a systemd *user* service, * which has no `DISPLAY`/`WAYLAND_DISPLAY` of its own — so neither does any * tmux pane it starts, and a `claude` running in one cannot reach the X or * Wayland clipboard: pasting an image answers "no image found in clipboard" * while the same CLI in a normal terminal pastes fine. `systemctl --user * show-environment` is the canonical local source for those values (the * desktop session imports them there at login), with this process's own * environment preferred when it already has them — a checkout started from a * terminal inherits the real ones. * @author Nguyễn Ngọc Trí Vĩ */ const { execFileSync } = require("node:child_process"); /** The only names forwarded. A pane gets a clipboard, not the whole session. */ const FORWARDED = ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR"]; let execImpl = (args) => execFileSync("systemctl", args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"], }); /** Test seam — same pattern as tmux.js. */ function __setExecImpl(fn) { execImpl = fn; } /** `KEY=value` lines from `systemctl --user show-environment`, as a map. */ function systemdUserEnv() { let out; try { out = execImpl(["--user", "show-environment"]); } catch { // No systemd user manager (macOS, a container, a plain login shell) — the // process environment below is the only source then. return {}; } const env = {}; for (const line of String(out).split("\n")) { const eq = line.indexOf("="); if (eq > 0) env[line.slice(0, eq)] = line.slice(eq + 1); } return env; } /** * The forwarded variables that actually have a usable value right now. * A value carrying a newline is dropped rather than passed on: these end up in * a `tmux -e NAME=value` argv, and a line break there is not a value tmux can * represent. */ function desktopEnv() { const fromSystemd = systemdUserEnv(); const out = {}; for (const name of FORWARDED) { const value = process.env[name] || fromSystemd[name]; if (typeof value === "string" && value && !/[\r\n\0]/.test(value)) out[name] = value; } return out; } module.exports = { desktopEnv, FORWARDED, __setExecImpl };