Niri Plus / Desktop guide
Manual display-away mode
The optional desktop-away companion lets you latch Niri displays off during
unattended SSH or browser-agent work. It starts in normal mode and changes
display power only after an explicit desktop-away away command or shortcut.
The existing desktop-power/swayidle pair remains the sole automatic idle
and suspend policy. Do not connect its automatic resume event to
desktop-away resume.
Away mode controls display power only; it does not keep the computer awake or
inhibit system sleep. Existing suspend timers, lid behavior and power policy
continue to apply while away.
The latch deliberately ignores application inhibitors and corrects later application or hotplug wakeups. Normal video and presentation behavior remains under the current policy until you enter away mode; resume before starting interactive playback or a presentation.
Select and stage the separate release
The numbered Arch reference selects components 0.4.3. Away mode is a separate, opt-in selection from the published components 0.5.0 archive. Its input pin and stager verify all 46 archive payloads, then extract only the canonical helper and unit. The stager retains the original archive, full manifest, input pin and provenance receipt. It does not install files, start services or replace the reference bundle. The adjacent archive verifier is required by the stager.
Use a Wayland Niri session with niri.service and
graphical-session.target active, systemd --user, and Python 3.11 or newer.
The helper uses existing readable physical evdev input or
libwayland-client with ext-idle-notify version 2. No additional raw-input
permissions, group membership or device rules are required.
Start from the root of a reviewed Niri Plus checkout. Record its revision; the
profile later in this recipe comes from this checkout, not the frozen 0.4.3
$SOURCE tree:
export NIRI_PLUS_CHECKOUT="$PWD"
git -C "$NIRI_PLUS_CHECKOUT" rev-parse HEAD
test -f "$NIRI_PLUS_CHECKOUT/scripts/stage-desktop-away.py"
test -f "$NIRI_PLUS_CHECKOUT/tools/reference/away-inputs.json"
mkdir -p "$HOME/.local/share/niri-plus"
export AWAY_STAGE="$HOME/.local/share/niri-plus/desktop-away-0.5.0"
test ! -e "$AWAY_STAGE" && test ! -L "$AWAY_STAGE"
python3 -B "$NIRI_PLUS_CHECKOUT/scripts/stage-desktop-away.py" --output "$AWAY_STAGE"
Review $AWAY_STAGE/inputs.json and $AWAY_STAGE/provenance.json before
installing. Confirm the release tuple is 0.5.0 and the selected file records
are exactly components/desktop/bin/desktop-away and
components/desktop/systemd/user/desktop-away.service.
Inspect and install the selected files
Check these paths and any existing unit wants before changing them:
~/.local/bin/desktop-away~/.config/systemd/user/desktop-away.service~/.config/niri/config.kdland the selected Niri config's existing includes~/.config/niri/local/away-mode.kdl~/.config/systemd/user/niri.service.wants/desktop-away.service
The commands below are for a new, unmanaged adoption only. Keep exact preimages
and modes for any existing targets. Do not follow or overwrite a symlink, or
replace a different service/helper without reviewing its owner. If an active or
managed away helper already exists, inspect its running service and configuration
owner, then make this change in that source; installing these files and running
start would not replace the already running process.
After review, install only the two selected 0.5.0 files:
install -Dm755 "$AWAY_STAGE/components/desktop/bin/desktop-away" \
"$HOME/.local/bin/desktop-away"
install -Dm644 "$AWAY_STAGE/components/desktop/systemd/user/desktop-away.service" \
"$HOME/.config/systemd/user/desktop-away.service"
Keep ~/.local/bin in the graphical session's PATH so Niri can run the
binding. The user unit runs the helper at its fixed %h/.local/bin/desktop-away
path and is tied to graphical-session.target.
Add the optional Niri binding
The authored minimal Niri profile fragment
comes from the reviewed NIRI_PLUS_CHECKOUT, not the reference's frozen
0.4.3 $SOURCE tree. It is deliberately separate from the generated helper
and unit.
Inspect the current configuration for a Mod+Shift+P binding. Keep a copy of
the existing config before changing it, and resolve any conflict intentionally.
On Niri 25.11 or newer, install the fragment and add its include at the
top level of config.kdl, after the current binds section:
install -Dm644 \
"$NIRI_PLUS_CHECKOUT/examples/session/profiles/minimal-niri/away-mode.kdl" \
"$HOME/.config/niri/local/away-mode.kdl"
include "local/away-mode.kdl"
The path is relative to config.kdl. Niri merges included binds sections;
the included binding overrides an earlier binding for the same key. If your
Niri version does not support includes, copy only the one Mod+Shift+P entry
into your existing binds block instead. Use one route, not both, and do not
add a second copy of the binding. See Niri's
include rules.
Validate the complete config before starting the service:
niri validate --config "$HOME/.config/niri/config.kdl"
systemd-analyze --user verify "$HOME/.config/systemd/user/desktop-away.service"
systemctl --user daemon-reload
systemctl --user add-wants niri.service desktop-away.service
systemctl --user start desktop-away.service
The service's normal startup does not latch displays off. Mod+Shift+P runs
desktop-away away; desktop-away status reports the latch, selected input
backend and DRM power states. desktop-away resume releases the explicit
latch remotely. Stopping the service while away restores ordinary display
power, and a new graphical session starts in normal mode.
If desktop-away away cannot reach its controller, it sends a one-shot normal
DPMS-off request and returns a nonzero status; no enforced latch is active.
Treat that result as a failure, not as away mode. Use ordinary session input or
display controls to wake the display, then recover or start the service in the
active graphical user's session before relying on status or resume.
The separate unified session screen offers Display off without locking and Lock followed by display-off, with the same meanings on every machine. Its optional Ctrl+Alt+Delete profile is independent of this recipe's retained Mod+Shift+P binding and 0.5.0 Away pin. Adopting that screen does not change the manual latch's input/resume behavior or make this dated evidence new screen acceptance.
Input, inhibitors and display limits
When physical evdev input is already readable, the helper observes keyboard, mouse and touchpad events without a grab. It rejects virtual devices, stale buffered input, shortcut releases/repeats and audio/display jack events. It does not store key codes, text or pointer coordinates.
Otherwise it uses libwayland-client and ext-idle-notify version 2 in
input-only mode. It ignores application inhibitors and exposes no key codes,
text or pointer positions. This follows compositor seat input, including
OS-level injected input; it cannot establish that input was physical. Neither
route adds input-group membership, ACLs, udev rules or a privileged service.
Check desktop-away status to see which backend was selected.
The helper polls DRM power every 250 ms and corrects wakeups at most twice per second. A brief flash before reblanking is possible; unknown output state is not proof that a display is off. Normal video/presentation inhibitors are retained outside the explicit away latch.
On Starship, Chrome navigation/clicks and fresh standard screenshots worked with DRM reporting Off, and mouse input restored the display. An explicitly cropped screenshot timed out; the headless-extension path and other capture modes remain unqualified. Physical panel and input return on Snap and Carbon remain separate acceptance items. See the dated integration record.
Verify and roll back
After installation, confirm desktop-away status reports normal mode. Enter
away mode once, confirm the latch state, then release it with desktop-away resume and confirm ordinary display power returns. Separately check the actual
browser automation and physical input path on the target machine. Do not claim
panel acceptance from a DRM state alone.
To remove a new unmanaged extension, stop only desktop-away.service, which
restores ordinary display power. Remove only the
niri.service.wants/desktop-away.service link and Niri include if this setup
introduced them; if you merged the binding directly, remove only that line.
Restore any saved config, helper or unit preimages and their modes. Remove the
installed fragment and selected files only when newly created by this setup.
If a wants link, unit or service was already present, preserve it and restore
its recorded activation state. Reload the user manager. Leave desktop-power,
swayidle, locker, suspend and their existing startup links untouched.
The isolated source checks are in
scripts/check-desktop-away.py. The
original local trial remains a historical repository note. The
integration record keeps source,
staging, consumer delivery and physical acceptance as separate evidence.