# lockscreen — design

The single unlock surface for Liberux: the boot greeter and the in-session screen lock, rendered by one standalone ltk binary. It lives in the loginmanager workspace (`crates/lockscreen`) because it *is* loginmanager's surface; forge merely launches it.

## Shape: standalone binary, launched by forge

`lockscreen` is a normal ltk application (`ltk::run`), built into the `lockscreen` binary and shipped by `eydos-loginmanager`. forge launches it as its `--exec` target (`forge --exec /usr/bin/lockscreen`); forge does **not** link it. The greeter session entry (`data/loginmanager-greeter`) is just `exec /usr/bin/forge --exec /usr/bin/lockscreen`.

This "binary, launched by forge" model was chosen over rendering the surface in-process inside the compositor (an earlier proposal). The deciding factors, for a security surface:

- **Fail-closed.** Lock mode is an `ext-session-lock-v1` client; the protocol keeps the screen locked (blank) if the client crashes, never exposing the session. An in-process lock would make a panic in the lock UI a compositor panic.
- **Privilege separation.** The greeter runs pre-session as `_loginmanager` and forwards credentials to the privileged `loginmanager-daemon`; keeping it a separate process keeps that credential path out of the compositor.
- **Decoupling.** No forge → loginmanager source dependency; the contract is the Wayland surface + the daemon IPC. Each repo revs independently.
- **Reuse.** forge already speaks `ext-session-lock-v1` (it keeps it for third-party lockers like swaylock); the lock mode rides the same protocol.
- **Input/OSK for free.** ltk's event loop drives keyboard focus, text-input and the OSK trigger; an in-process host would have to replicate that into an offscreen tree.

## Two modes

Selected by `--lock` / `--greeter` / `LOCKSCREEN_MODE`, defaulting by the running user (`_loginmanager` → greeter, else lock):

- **Greeter** (`Mode::Greeter`): runs before any session, as `_loginmanager`, on a `Layer::Top` layer-shell surface. Authenticates by forwarding the credential to `loginmanager-daemon` over the length-prefixed JSON IPC (`auth/daemon.rs`, no tokio — `loginmanager-ipc` with `default-features = false`); the daemon does PAM + `open_session` + fork + VT switch. Shows the username field and power controls.
- **Lock** (`Mode::Lock`): runs inside the logged-in user's session, as that user, on an `ext-session-lock-v1` surface (`ShellMode::SessionLock`). Verifies the current user locally (`auth/local_pam.rs`, `unix_chkpwd`) and dismisses; no session is created. Username is fixed and read-only.

Fingerprint (`auth/fingerprint.rs`, `fprintd-verify`) is shared by both and arrives asynchronously.

## Surfaces: idle panel slides over the unlock form

The screen has two layers:

- **Main surface** — wallpaper, and (once revealed) the unlock form. Until reveal it shows wallpaper only.
- **Idle subsurface** — an input-transparent `wl_subsurface` (via ltk's `App::subsurfaces`) carrying the clock/date/hint over the wallpaper, full-size, at `y = -(drag_progress · height)`.

A swipe up slides the idle panel off the top (the compositor moves the subsurface — no per-frame re-raster; this is the Librem5 swipe-stutter fix). Crossing the threshold fires `Reveal`: the idle subsurface is dropped and the main surface switches to the unlock form, which then takes focus. Because the subsurface is input-transparent and the main surface carries no interactive widgets during idle, input never leaks to the (absent) covered form.

Trade-off: during the slide the user sees the wallpaper beneath the panel, with the form appearing on reveal — not the form continuously revealed beneath. This is what lets the subsurface be input-transparent (no input gating needed).

## Shared chassis (ltk)

The full-screen ambient scaffolding lives in ltk (`ltk::chassis`), used by both this crate and `smartcast`: `set_default_theme`, `theme_logo_rgba`, `theme_icon_tinted`, `wallpaper_bundle_or_solid`, `backdrop`. lockscreen and smartcast are therefore **siblings on the ltk chassis**, not parent/child — smartcast is its own package/binary (`eydos-smartcast`) and shares no authentication path with the greeter.

## Layout

```
crates/lockscreen/
  Cargo.toml          binary crate; deps ltk, loginmanager-ipc (default-features = false), pam, chrono, zbus, rust-i18n
  src/
    main.rs           mode/theme bootstrap, state seed, ltk::run
    app.rs            App impl: update, swipe → drag_progress, subsurfaces(), auth threading, focus
    state.rs          State, Mode { Greeter, Lock }, UnlockMethod { Password, Pin, Gesture, Fingerprint }
    message.rs        Msg
    view.rs           main surface (wallpaper / form) + idle_subsurface() content
    idle.rs           clock/date/hint panel (static; the slide is a compositor move)
    lockscreen_password.rs  password method (Pin / Gesture / Fingerprint are placeholders)
    osk.rs            squeekboard visibility on text-input focus
    config.rs         persisted last greeter username
    auth/
      mod.rs          entry points
      daemon.rs       greeter: blocking loginmanager-daemon IPC client
      local_pam.rs    lock: verify current user
      fingerprint.rs  shared fprintd-verify listener
  locales/app.yml
```
