Skip to main content

max / alloy_tui

Lockscreen: bar-as-visual-reference, async services, no-crash auth Three design directives from the user, encoded in the README: 1. The lockscreen and the bar share visual language — same Departure Mono treatment, same case and tracking, same density. Alloy doesn't ship an authored bar yet, so the lockscreen sets the precedent: the time/date treatment authored here becomes the reference the Ironbar Alloy theme mirrors in v0, and the eventual egui bar inherits in v3+. 2. Services launch async. PAM init, clock ticker, Wayland event pump, output enumeration all start concurrently at boot. The render loop draws the first frame as soon as the Wayland surface is up — it never blocks on PAM. PAM submit calls run on worker tasks; the UI thread never blocks on auth. Snapshot-struct pattern feeds task state into the render pass each frame. 3. The login option never crashes. Every error on the auth path (PAM init failure, PAM auth error, worker-task panic, locale lookup failure) is caught and rendered as status text. The password field stays present and focused regardless of upstream failure. No .unwrap(), no .expect() on any code path reachable from a keystroke. Stricter than the whole-process recovery question (#4): auth is the user's only way out, so it cannot fail loudly. The Architecture section is updated to call out the async render loop and reference the resilience contract.
Co-Authored-By
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-26 19:45 UTC
Signed with PGP, not checked
Commit: 5e5c44b824e6adb6669d3190ff3b9ce9f93b71d5
Parent: 4dcac45
1 file changed, +9 insertions, -1 deletion
@@ -44,12 +44,20 @@
44 44 - This is the first place Departure Mono renders in a real Alloy surface. Verify the font registration in `alloy_ui::theme` before relying on it here, and document the fallback chain if Departure's unicode coverage misses anything in the user's locale-formatted date.
45 45 - The focus ring is `border-strong`, never accent. Failed-auth state may *temporarily* color the status text in `accent-error`, per the accent-on-text rule in [TOKENS.md](../../docs/TOKENS.md#accent-on-surface-text-rule). The password field's border does **not** flip to accent.
46 46 - The lockscreen runs at `surface` tier. The password field sits on `surface-raised`. No popovers, no overlays — there is nowhere for an overlay to go on a lockscreen.
47 + - **Visual reference: the bar.** The lockscreen and the bar are both persistent chrome surfaces and should share visual language — same Departure Mono treatment for time and date, same case and tracking, same density. Alloy doesn't ship an authored bar yet (Ironbar is the curated v0 pick, an egui bar is v3+), so the lockscreen *sets* the precedent: whatever the time/date readout looks like on the lockscreen is what the bar's clock module mirrors when it gets authored. When the Ironbar Alloy theme is written for v0, port the lockscreen's time/date treatment to it as the canonical reference.
48 +
49 + ## Resilience contract
50 +
51 + The lockscreen has a sharper failure profile than any other Alloy surface: when it breaks, the user is locked out of their session with no fallback UI to debug from. Two non-negotiable rules:
52 +
53 + - **Services launch async.** PAM context init, clock ticker, Wayland event pump, and output enumeration all start concurrently at process boot. The render loop draws the first frame as soon as the Wayland surface is up — it does not wait for PAM. PAM submit calls run on a worker task; the UI thread never blocks on auth. Default runtime: a single current-thread async executor (likely tokio with `rt` only — no IO/net features needed). Each service feeds its state into a snapshot struct the render pass reads each frame.
54 + - **The login option never crashes.** Every error on the auth path (PAM context init failure, PAM auth error, worker-task panic, locale lookup failure) is caught and rendered as status text in the surface. The password field stays present and focused regardless of upstream failure. No `.unwrap()`, no `.expect()`, no panic on any code path reachable from a keystroke. If PAM cannot be initialized at startup, the surface shows the failure mode explicitly and offers a retry — it does not exit the process. This rule is stricter than open question #4 (whole-process crash recovery): the auth path is the user's only way out, so it cannot be allowed to fail loudly.
47 55
48 56 ## Architecture
49 57
50 58 The lockscreen does **not** use `eframe`. eframe drives Wayland through winit, which opens its own `wl_display` connection — that connection can't share state with whatever owns the session lock, and the session lock has to be owned by the same client that owns the surfaces it draws into. So alloy_lockscreen owns its Wayland connection directly via `smithay-client-toolkit` and drives egui via `egui_glow` on raw GL contexts (one per output).
51 59
52 - Practically: a small render loop pulls Wayland events from SCTK's `calloop` source, feeds keyboard/pointer events into an `egui::Context`, runs the egui pass, and submits the painted output through `egui_glow` to the GL context bound to each `ExtSessionLockSurfaceV1`. The alloy_ui crate is consumed exactly as in the showcase example — `apply_alloy_visuals` + primitives — only the host loop differs.
60 + Practically: a small async render loop pulls Wayland events from SCTK's `calloop` source, feeds keyboard/pointer events into an `egui::Context`, runs the egui pass, and submits the painted output through `egui_glow` to the GL context bound to each `ExtSessionLockSurfaceV1`. PAM, the clock ticker, and Wayland I/O each run as their own async task feeding a snapshot the render pass reads each frame — per the resilience contract below. The alloy_ui crate is consumed exactly as in the showcase example — `apply_alloy_visuals` + primitives — only the host loop differs.
53 61
54 62 ## Stack picks
55 63