Docs menuIdle and lock
Idle and lock in a shell
What a shell does when the screens blank and when the desktop locks. Part of How a shell works. Config: The config file and The config file. Implementation: IDLE.md, LOCK.md.
Idle
After idle.blank_after_seconds without input, the screens turn off and
domicile.session.idle becomes true:
import { useIdle } from "@domicile-desktop/react/session";const Desktop = () => { // true: the screens are off. false: input turned them back on. const idle = useIdle() === true; return <main className={idle ? "idle" : undefined}>…</main>;};- Use this attribute to detect idle. Only the compositor sees all input. The
page stays visible while the screens are off, so
visibilityStateand idle detectors are wrong. - A newly loaded page reads the current state.
idlechangefires as the screens go dark. To act before that, listen for the warning.- Undo idle changes when
idleisfalse. The repaint finishes before the screens relight. - Input still reaches clients while blanked. Blanking does not lock; see Locking.
- With no timeout configured,
idlestaysundefined.
Warning before blanking
With idle.warn_before_seconds set, the compositor warns the shell before the
screens go dark:
const useDimmed = () => { const { session } = useDomicile(); const [dimmed, setDimmed] = useState(false); useEffect(() => { let stop = () => {}; session.idleWarnings().then((warnings) => warnings.match({ Err: (error) => console.error("no idle warnings", error), Ok: async ({ items }) => { const reader = items.getReader(); stop = () => reader.cancel(); for (;;) { const { done, value: blanksInMs } = await reader.read(); if (done) return; // A number: the screens go dark in that many milliseconds. // `undefined`: input called it off, or the screens went dark. setDimmed(blanksInMs !== undefined); } }, }), ); return () => stop(); }, [session]); return dimmed;};- Any input during the warning keeps the screens on, so a dim that wakes on a key needs no code of its own.
- A page that listens during a warning hears it at once.
- Allowed while locked, so a lock screen can dim too.
- Without
idle.warn_before_seconds, nothing is heard.
Locking
Every desktop has a lock; no config turns it on. The compositor locks it:
- when the shell calls
session.lock(), - before the machine sleeps, and when logind locks the session
(
loginctl lock-session), - after
idle.lock_after_secondswithout input, if that is set. This clock is separate from blanking.
PAM is the only way to unlock it. The compositor checks the passphrase through
the PAM service lock.pam_service, or domicile when that is unset
(The config file).
import { useLocked } from "@domicile-desktop/react/session";const Desktop = () => { const { session } = useDomicile(); return useLocked() === true ? ( <form onSubmit={(event) => { event.preventDefault(); session.unlock(String(new FormData(event.currentTarget).get("passphrase"))); }} > <input name="passphrase" type="password" /> </form> ) : ( // lock now; the lock screen goes up when `locked` is true <button onClick={() => session.lock()}>Lock</button> );};The compositor enforces the lock. While locked is true:
- No client receives any key, click, scroll or pointer motion.
windows.close,processes.launchandclipboard.copydo nothing and log a warning.files.searchnever settles. Retry oncelockedisfalse.browser.fetchrejects. The engine refuses it, from the compositor'slockedmessage.theme.setstill works.- System calls fail with
locked, except reads under/sys, the callssystem-battery,system-backlightandsystem-audiomake for the battery, brightness and output volume, andsystem-bluetooth's watch (LOCK.md).
Reloading the page, restarting the engine, loading another shell or editing the page in devtools does not unlock the desktop.
Drawing the lock screen
- Your page still receives keys, so it can take a passphrase in a field.
unlocksends it to the compositor. On success,lockedbecomesfalseon every shell. Clear the lock screen then, not on your own submit.- A passphrase that did not unlock fires
lockchangewithlockedstilltrue. Treat that while a passphrase is pending as a rejection: clear the field. To say why, listen with
session.unlockRefusals(). Each refusal arrives before itslockchange:useEffect(() => { let stop = () => {}; session.unlockRefusals().then((refusals) => refusals.match({ Err: (error) => console.error(error), Ok: async ({ items }) => { const reader = items.getReader(); stop = () => reader.cancel(); for (;;) { const { done, value } = await reader.read(); if (done) return; // value.refusal: WrongPassphrase, Unverifiable (the check could not // run) or TooSoon (sent during the wait, so not checked). setRefused(value); } }, }), ); return () => stop();}, [session]);- After 3 refused passphrases the compositor waits before checking another,
starting at 5 seconds and doubling up to 5 minutes.
retryInMsis the wait left. See LOCK.md. - A check takes as long as PAM does. PAM delays a wrong password by a few
seconds. Input stays blocked until
lockedisfalse, and anunlocksent during a check is dropped. - A newly loaded page reads the current state. A reloaded shell must check it before drawing an unlocked desktop.
lockedisundefinedonly until the compositor's first message. After that it istrueorfalse.- Without the PAM service's file (
/etc/pam.d/domicileby default), the desktop does not lock:lock()and the idle timeout do nothing, and nolockchangefires. If the file goes missing while locked, every passphrase is refusedUnverifiable. Say so on the lock screen instead of "wrong password". Adding the file fixes the next attempt, with no restart (LOCK.md).
Manganese's lock screen:
lock/Lock.tsx,
lock/LockBar.tsx and
lock/useLocked.ts.