Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

lock

The session lock screen: one ext_session_lock_surface_v1 per connected output, covering it for as long as the compositor holds the session locked. Declaring a lock does not lock; mantle.lock:lock() does, and only a correct password typed into its secure field unlocks. The lock’s state (active, authenticating, attempts, error, unlocking) and actions are on the lock capability. Rules every role shares are in surfaces.

mantle.lock:set_unlock_animation(250)

local up = mantle.lock:map(function(lock) return lock ~= nil and lock.active and not lock.unlocking end)

local hint = mantle.lock:map(function(lock)
    if lock == nil then return "" end
    if lock.error ~= "" then return string.format("%s (%d)", lock.error, lock.attempts) end
    if lock.authenticating then return "Checking…" end
    return lock.active and "Enter your password" or "Locking…"
end)

local lock_screen = lock {
    id = "lock",
    background = "#11111b",
    child = function(output)
        return column {
            width = "Fill", height = "Fill", align_v = "Center", spacing = 12,
            opacity = up:map(function(on) return on and 1 or 0 end),
            animate = { opacity = { duration = 200, from = 0 } },
            children = {
                text { content = output, foreground = "#6c7086", align_h = "Center" },
                rect {
                    width = 320, align_h = "Center", padding = 8, radius = 18, background = "#1e1e2e",
                    children = {
                        textfield {
                            width = "Fill",
                            height = 20,
                            placeholder = "Password",
                            mask_character = "•",
                            secure_submit = { capability = "lock", action = "authenticate" },
                        },
                    },
                },
                text { content = hint, foreground = "#a6adc8", align_h = "Center" },
            },
        }
    end,
}

action("lock", function() mantle.lock:lock() end)

return { lock_screen }

mantle call lock from a keybind (action) locks the session. Each output gets its own card; the field is typable as soon as the compositor gives the lock the keyboard, with no click. After a correct password the lock stays up 250 ms while the card fades out, then the session unlocks.

Properties

A lock takes id, child and the common and box node properties, minus the ones the protocol owns.

PropertyTypeDefaultBehaviour
idstringRequiredThe surface’s identity across reloads, unique among surfaces. A panel’s or lock’s per-output instances are "{id}@{output}"; monitor = "Active" keeps the bare id
childNode|fun(output: string): Node?|BoundNoneThe root’s content. A function runs per output instance with its connector name; nil leaves that instance empty (per-output child)
widthnilNoneRefused: the lock covers each output
heightnilNoneRefused, as width
visiblenilNoneRefused: the session lock decides when it shows

monitor and anchor are refused too: the protocol owns coverage and lifetime. The root is the output’s size; give children "Fill" to cover it. A reload that renames id while the session is locked is refused with a warning in mantle log. A config declares at most one lock; a second is refused at evaluation.

When a lock is refused

The compositor keeps the session locked if the shell dies, so a lock screen with no way to type a password leaves only a VT switch. mantle.lock:lock() checks the lock screen before it asks the compositor. A refusal leaves the session unlocked and puts the reason in mantle.lock’s error and in mantle.rescue.

ConditionResult
The config declares no lockRefused
No lock instance holds exactly one shown secure field, with secure_submit = { capability = "lock", action = "authenticate" }Refused. A hidden field does not count; a second shown secure field of any target refuses too
Another client already holds the session lockThe compositor denies it; reported in error and mantle.rescue
While locked, a reload leaves no lock instance with that single fieldThe reload is refused and the lock screen on screen stays
The compositor ends a held lock by its own mechanismThe session is unlocked; the reason goes to mantle.rescue only

The field’s keystrokes go to PAM and never reach Lua.

How do I…

TaskAnswer
Lock from a keybindaction("lock", ...) as in the example, then mantle call lock
Show “wrong password”Read error and attempts from mantle.lock, as the example’s hint does
Show that PAM is checkingRead authenticating
Animate the lock screen outset_unlock_animation with the animation’s length, and drive opacity or scale from unlocking (lock capability)
Animate it inDrive the same property from active; animate.from covers the first frame
Show the desktop wallpaper behind itAn image in the per-output child, keyed by output (per-output content)
Put a clock on itmantle.system:map(function(system) return system and os.date("%H:%M", system.time) or "" end)
Add an unlock button beside the fieldA button { submit = true } sends the field like Enter (pointer)
Lock before suspendInvoke lock from your idle or suspend handler (idle)

Gotchas

TrapFix
mantle call lock does nothingRead mantle.lock’s error: the config declares no lock, or its tree lacks exactly one shown secure field
Two secure fields in one lock tree make the lock refuseOne shown secure field per lock tree; hide the others
A reload while locked is ignoredIt removed the lock’s password field; the running lock screen stays. Fix the file
Renaming the lock’s id while locked is refusedSave the rename again after unlocking
visible, width, height, monitor or anchor on a lock is refusedRemove them; the lock always covers every output
The card’s exit animation is cut offThe session unlocks when the set_unlock_animation time ends; make it at least the animation’s length

See also: lock capability, secure fields, surfaces, animation.

Source: lock spec, at most one lock, session lock, authenticate check, rename while locked.