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 Screen

Amane can lock your session with a lock screen you design yourself, and check the password through PAM, like your login screen does.

Your compositor must support the ext-session-lock protocol. niri, Hyprland, and Sway do.

A lock screen

use amane::{
    App, Center, Color, Column, LayerWindow, Lock, Monitor, Parent, Rectangle, Service, Text,
    TextInput, children,
};

fn main() {
    App::new().lock(view).run();
}

fn view(_: &Monitor) -> LayerWindow {
    let lock = Lock::read();

    let status = if lock.checking() {
        "checking..."
    } else if lock.failed() {
        "wrong password"
    } else {
        "locked"
    };

    // the compositor sizes lock screens to the monitor, so this size is never used
    LayerWindow::new().width(1.0).height(1.0).child(
        Rectangle::new()
            .width(Parent)
            .height(Parent)
            .fill("#1e1e2e")
            .align_child(Center, Center)
            .child(
                Column::new(children![
                    Text::new(status).size(24.0).color(Color::WHITE),
                    Rectangle::new()
                        .width(300.0)
                        .height(40.0)
                        .fill(Color::WHITE)
                        .padding(8.0)
                        .child(
                            TextInput::new("password")
                                .placeholder("password")
                                .password()
                                .focused()
                                .on_submit(|password| Lock::unlock(&password)),
                        ),
                ])
                .gap(12.0),
            ),
    )
}

This sets up the lock screen, but doesn’t lock anything yet. Locking the session shows how.

How locking works

  • App::lock takes a view that’s shown on every monitor while the session is locked. Like window_per_monitor, it gets the &Monitor it’s on.
  • While the session is locked, the compositor shows only the lock screen, sends it all keys, and keeps every other window hidden. The lock screen’s size, anchors, and layer are ignored. It always covers the whole monitor.
  • Lock::unlock(password) checks the password through PAM (with the login service, as your user). That can take a few seconds, so it runs on its own thread. If the password is right, the session unlocks. If not, the screen stays locked.
  • The Lock Service tells the view what’s happening, through checking() and failed().

Locking the session

Lock::start() locks the session. Call it from anywhere: a button, an IPC handler, or a Service’s thread. Calling it while already locked does nothing.

The usual setup is an IPC handler, so a keybind or an idle daemon can lock:

fn main() {
    App::new()
        .ipc("lock", |_| {
            Lock::start();

            String::from("locked")
        })
        .window(bar)
        .lock(lock_screen)
        .run();
}

Then lock from a keybind, or from swayidle or hypridle:

amane ipc call lock

For niri:

binds {
    Mod+Alt+L { spawn "amane" "ipc" "call" "lock"; }
}

Lock reference

FunctionMeaning
Lock::read().checking()a password is being checked right now
Lock::read().failed()the last password was wrong
Lock::start()locks the session
Lock::unlock(password)checks a password, and unlocks if it’s right

Only one password is checked at a time. Calling unlock while a check is running does nothing. Each new lock starts clean, without the last lock’s failed state.

Testing safely

A locked session only opens with the right password. If your lock screen has a bug, like an input that doesn’t take keys, you can’t get back in from that session.

Killing the shell doesn’t help either. If the program that locked the session exits without unlocking, the compositor keeps the session locked. That’s on purpose: otherwise, crashing the lock screen would be a way past it.

Before using a new lock screen for real:

  • Save your work in other apps.
  • Switch to another TTY (Ctrl+Alt+F3), log in there, and keep it open. If you get stuck, switch to it and end the locked session with loginctl terminate-session <id> (loginctl list-sessions shows the id). This closes every app in that session.
  • Try a wrong password first, then the right one.

A blurred wallpaper background

Rectangle::new()
    .width(Parent)
    .height(Parent)
    .fill(Image::cover("/home/you/Pictures/wall.jpg").thumbnail(64, 36).blurred(4))

See Images for why the small thumbnail makes this cheap.