fix(session): persist the idle inhibitor across shell restarts - #3123
Conversation
The idle inhibitor ("Keep Awake") lives only in memory: SessionService holds it
as a plain `property bool idleInhibited: false` and nothing ever writes it out.
Every restart of the shell silently clears it — the user's explicit choice is
dropped with no message and no trace, and the pill just disappears from the bar.
Restarting is not an edge case. `dms restart` is a first-class CLI command, and
the unit shipped in this repo (assets/systemd/dms.service) sets
`Restart=on-failure` with `TimeoutStopSec=10`, so a shell that is slow to stop
is killed and brought back automatically. Each of those paths turns Keep Awake
off without saying so.
Every other Control Center toggle already survives a restart. Measured on
upstream master, over the state read by Modules/ControlCenter/Components/
DragDropGrid.qml and its neighbours:
doNotDisturb in SessionSpec: yes
isLightMode in SessionSpec: yes
nightModeEnabled in SessionSpec: yes
wallpaperCyclingEnabled in SessionSpec: yes
idleInhibited in SessionSpec: NO
So this follows the doNotDisturb idiom exactly rather than inventing a
mechanism: one spec entry, one property, one setter that saves.
Restore happens through two entry points because the singletons are lazily
created and their order is not fixed: `onLoaded` covers "service instantiated
before the session file is read", `Component.onCompleted` covers the opposite.
enableIdleInhibit() is idempotent, so whichever runs second is a no-op.
The default stays false and SessionStore.toJson omits defaults, so an untouched
session.json gains no new key.
Related: AvengeMedia#1483 asks for a configurable timer. The field added here is the same
shape doNotDisturb uses with doNotDisturbUntil, so a later `idleInhibitedUntil`
slots in beside it without another migration.
870dabe to
036254e
Compare
|
/claude review |
Claude reviewNo issues found. Verified the restore path both ways: Checked: restore ordering/idempotency, consumers of |
…138) Persistence: the inhibitor now survives a shell restart (AvengeMedia/DankMaterialShell#3123). Before that change it lived only in memory, so `dms restart` — or systemd bringing the service back under Restart=on-failure — silently turned it off. Worth stating explicitly, because "Keep Awake" that quietly stops keeping the machine awake is the kind of thing you only notice when the screen locks. While in this section, two verbs the shell implements were missing from the table. Both checked against a running shell rather than read off the source: dms ipc call inhibit status -> "Idle inhibit is enabled" dms ipc call inhibit reason "" -> "Current reason: Keep system awake" `reason` is documented as taking a required argument because the CLI rejects the bare form ("Too few arguments provided") even though the QML handler treats an empty string as a read. Edited docs/ only, not versioned_docs/: the persistence lands in a release that has not been cut yet. Co-authored-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
Description
The idle inhibitor ("Keep Awake") does not survive a shell restart.
SessionServiceholds it as a plain in-memory property and nothing ever writes it out:Every restart silently clears it. The user's explicit choice is dropped with no message — the pill disappears from the bar and the screen starts sleeping again mid-task.
Restarting is not an edge case:
dms restartis a first-class CLI command, andassets/systemd/dms.servicein this repo setsRestart=on-failurewithTimeoutStopSec=10, so a shell that is slow to stop is killed and brought back automatically.Every other Control Center toggle already survives a restart. Measured on upstream
master, over the state read byModules/ControlCenter/Components/DragDropGrid.qmland its neighbours:SessionSpec.jsdoNotDisturbisLightModenightModeEnabledwallpaperCyclingEnabledidleInhibitedSo this follows the
doNotDisturbidiom exactly rather than inventing a mechanism: one spec entry, one property, one setter that saves.SessionSpec.js—idleInhibited: { def: false }SessionData.qml— the property plussetIdleInhibited(enabled)SessionService.qml—enableIdleInhibit()/disableIdleInhibit()write through; the state is restored at startupRestore goes through two entry points because the singletons are lazily created and their order is not fixed:
onLoadedcovers "service instantiated before the session file is read",Component.onCompletedcovers the opposite.enableIdleInhibit()is idempotent, so whichever runs second is a no-op.The default stays
falseandSessionStore.toJsonomits defaults, so an untouchedsession.jsongains no new key.Type of change
Related issues
None open for this. #1483 (configurable timer) is adjacent: the field added here has the same shape
doNotDisturbuses alongsidedoNotDisturbUntil, so a lateridleInhibitedUntilslots in beside it without another migration.Screenshots / video
No visual change. The bar pill already reflects
SessionService.idleInhibited; it simply now shows the state the user left it in. Behaviour is shown as measurements below instead.How this was verified
In a nested niri with isolated
XDG_CONFIG_HOME/XDG_STATE_HOME, so the bench could never touch the live session:Counter-test on the same
session.json, with the patch reverted to plain upstream:Idle inhibit is enabledIdle inhibit is disabledAlso exercised directly against
SessionStore.js+SessionSpec.js:parse({idleInhibited: true})→true,toJson→{"idleInhibited": true}, and with the value at its default the key is omitted from the output.Checklist
I18n.tr()— n/a, this PR adds no user-facing stringsmake lint-qmlwith no new warnings — could not run locally: the script requires the Quickshell tooling VFS (quickshell/.qmlls.ini+<buildDir>/qs/qmldir), and on quickshell 0.3.0 here the VFS directory only ever containstooling.lock, solint-qmlrefuses to start. The two touched QML files parse cleanly underqmlformat, and the pre-commit hooks that apply were run by hand (no trailing whitespace, single trailing newline, noconsole.*). Happy to re-run if you can point me at what generates the VFS.