mirror of github.com/Breadway/breadlock
Find a file
Breadway 8232f1b72b
Some checks failed
CI / check (pull_request) Failing after 5s
breadgreet: skip the username step, fix clobbered typography
Login UX
- Enumerate human accounts from /etc/passwd (UID_MIN..UID_MAX per
  /etc/login.defs, real login shell, minus nobody) and skip the
  username field: one account goes straight to the password prompt
  with the name shown, several get a picker. Zero found falls back to
  typing. New [user] config: `name` pins one account, `prompt = true`
  restores the type-it flow.
- The greeter opens the greetd conversation at startup in auto mode so
  it lands directly on the password prompt (AutoStart).
- reset_to_username -> reset_auth: auto mode re-opens the password
  prompt for the same account on error/Escape rather than showing a
  username field it never had.

Typography fix
- bind_window_auto re-broadcasts the shared component sheet (incl. its
  `* { font-size }` base rule) at USER-10, which outranks the
  APPLICATION-priority provider apply_app_css uses regardless of
  selector specificity -- so the hero clock and every other type
  override silently collapsed to base size. Ride breadgreet's sheet
  through bind_window_auto_with_app_css instead (USER-9), matching
  breadbar.

Polish
- Accent rule between the clock and card, drawn in via scaleX with a
  glow halo; card gradient + top highlight + deep shadow, bg-pop
  overshoot entrance, slow bg-breathe idle pulse; layered accent focus
  glow on the entry; gradient session/user pills. Entrance/idle motion
  is CSS @keyframes now (setup_entrance removed).
- Errors pinned to a legible red -- BOS's default @red slot is a warm
  ochre, unreadable as a warning.
- Drop the duplicate prompt text (placeholder + status line both said
  "Password:").

Preview harness
- scripts/preview.sh + scripts/mock-greetd.py run the real greeter in a
  nested Weston window against a stand-in greetd, so the whole flow
  (spinner, wrong-password shake, success) is drivable without a
  reboot. $BREADGREET_CONFIG overrides the config search path so a
  preview never touches /etc/greetd/breadgreet.toml.
2026-08-31 21:23:06 +08:00
.forgejo/workflows Fix audit findings, expand tests, and add a CI quality gate 2026-08-31 18:53:36 +08:00
breadgreet breadgreet: skip the username step, fix clobbered typography 2026-08-31 21:23:06 +08:00
breadlock Fix audit findings, expand tests, and add a CI quality gate 2026-08-31 18:53:36 +08:00
breadlock-ui Fix audit findings, expand tests, and add a CI quality gate 2026-08-31 18:53:36 +08:00
design Breadlock UI update: motion system, depth polish, and new animations 2026-08-21 13:12:31 +08:00
packaging Document the hybrid GPU renderer and fix packaging 2026-08-23 14:21:39 +08:00
.gitignore Breadlock UI update: motion system, depth polish, and new animations 2026-08-21 13:12:31 +08:00
AGENTS.md Track AGENTS.md; point CONTRIBUTING at it 2026-08-15 22:03:30 +08:00
breadgreet.example.toml breadgreet: skip the username step, fix clobbered typography 2026-08-31 21:23:06 +08:00
breadlock.example.toml Add lock-screen status line and harden the locker 2026-08-23 14:21:19 +08:00
Cargo.lock Add lock-screen status line and harden the locker 2026-08-23 14:21:19 +08:00
Cargo.toml can't be bothered writing a commit message 2026-08-16 14:44:07 +08:00
CONTRIBUTING.md Track AGENTS.md; point CONTRIBUTING at it 2026-08-15 22:03:30 +08:00
EVENTS.md Fail-secure bread.command.lock.unlock: never loginctl 2026-08-23 14:38:50 +08:00
LICENSE Initial commit 2026-07-03 13:37:31 +08:00
README.md Fail-secure bread.command.lock.unlock: never loginctl 2026-08-23 14:38:50 +08:00

breadlock

Session locker and graphical greetd greeter for Hyprland on Wayland — the bread-ecosystem replacement for hyprlock and tuigreet. BOS already ships both binaries: breadgreet under cage via greetd, and breadlock via hypridle (SUPER+L is loginctl lock-session).

Two binaries, one workspace:

  • breadlock — locks the already running Hyprland session via ext-session-lock-v1. Drop-in for hyprlock.
  • breadgreet — a graphical greeter that speaks greetd's own IPC protocol (the same architecture as gtkgreet/regreet). greetd keeps owning PAM auth, VT switching, and session launching; breadgreet only draws the login UI and relays the conversation. This is a deliberate choice over reimplementing a display manager from scratch — greetd is already installed and battle-tested.

Both use bread-theme for palette loading, matching the rest of the bread* ecosystem (breadbar, breadbox, bos-settings).

bread event integration

breadlock works the same with or without breadd. When breadd is running, it publishes bread.lock.locked / bread.lock.unlocked and honors bread.command.lock.lock / bread.command.lock.unlock (emits bread.lock.lock.done / .failed and bread.lock.unlock.done / .failed). Run breadlock listen so both commands work while unlocked; the locker also subscribes while the session is locked. Unlock is fail-secure: already-unlocked acks bread.lock.unlock.done; a running locker refuses with bread.lock.unlock.failed (only PAM at the lock screen unlocks). The bus never calls compositor unlock() or loginctl unlock-session. Super+L remains loginctl lock-session (hypridle then runs breadlock). See EVENTS.md. breadgreet is not on the bus. There is no bakery.toml (PAM / pacman exception).

Architecture

breadlock/
├── breadlock-ui/   shared: bread-theme wrapper, TOML config, .desktop parsing,
│                    software-rendering primitives (tiny-skia + cosmic-text,
│                    behind the "paint" feature — only breadlock needs them)
├── breadlock/       the locker (SCTK + PAM; EGL wallpaper + software chrome)
└── breadgreet/      the greeter (GTK4 + relm4 + greetd_ipc)

breadlock

  • Protocol: ext-session-lock-v1 via smithay-client-toolkit — GTK has no session-lock support, so this is a raw Wayland client, not a layer-shell surface like breadbar.
  • Rendering: hybrid — wallpaper via EGL/GLES2 (wl_egl_window wrapping the lock surface); chrome (password pill, clock, status line) is still software (tiny-skia + cosmic-text, "Varela Round" by family name) and blitted over the GPU frame. If EGL init fails, the locker falls back to a fully-software wl_shm path.
  • Background: a solid palette color or a PNG (cover-fit). Ken Burns (background.ken_burns) is opt-in: a slow pan+zoom on image backgrounds — cheap on the GPU path, a continuous software redraw if EGL is unavailable. background.blur is not implemented — the key is accepted and logs a warning; the surface is drawn unblurred. Live blur-of-desktop (hyprlock-style) would need a wlr-screencopy capture.
  • Auth: pam-client2 against the breadlock PAM service (packaging/pam.d/breadlock, installed to /etc/pam.d/breadlock by the package). Runs on its own OS thread — libpam's conversation callback is blocking FFI — and reports back through a calloop::channel registered on the render loop.

breadgreet

  • Protocol: greetd_ipc (greetd's own crate) over the Unix socket at $GREETD_SOCK: CreateSession → answer each AuthMessage via PostAuthMessageResponseStartSession hands the resolved session command to greetd, which execs it and owns the VT switch away.
  • UI: GTK4 + relm4, matching breadbar's stack — without gtk4-layer-shell. greetd hosts the greeter under a single-client kiosk compositor (cage -s), which already fullscreens its one client, so layer-shell's multi-surface/anchor semantics don't apply. Confirmed against ReGreet's real dependency list, which has no layer-shell dependency either.
  • Sessions: scans /usr/share/wayland-sessions and /usr/share/xsessions for .desktop entries and shows a keyboard-accessible picker. The configured default (compiled-in: bos) is pre-selected when that stem exists; otherwise the first discovered session. StartSession is the chosen entry's Exec= argv.

Config

Copy breadlock.example.toml to ~/.config/breadlock/breadlock.toml and breadgreet.example.toml to /etc/greetd/breadgreet.toml (or ~/.config/breadgreet/breadgreet.toml for local testing under a normal session — breadgreet checks the system path first since it typically runs as the dedicated greeter user). Every field is optional; both binaries run with sensible defaults and no config at all.

breadlock.toml's [status] table (both flags default on) shows now-playing (MPRIS) and battery (upower) as a small line under the clock. Polled on a background thread; degrades silently if D-Bus or the service is missing.

Building

cargo build --release --bin breadlock --bin breadgreet
cargo test --workspace

Requires GTK4 (≥ 4.12), libxkbcommon, PAM development headers, git (workspace crates bread-theme / bread-utils are git deps), and pkg-config (gtk4-rs; also provided by base-devel). On Arch:

sudo pacman -S gtk4 wayland libxkbcommon pam rust cargo git pkg-config

breadlock-auth-check and breadlock-preview are extra, dev-only binaries in the breadlock package (see Verification below) — not installed by the package. Build them explicitly with cargo build --bin breadlock-auth-check or --bin breadlock-preview if you need them.

Packaging

packaging/arch/PKGBUILD builds and installs both binaries plus /etc/pam.d/breadlock, published to the [breadway] pacman repo by .forgejo/workflows/package.yml. breadlock is a deliberate pacman-only exception — there is no bakery.toml on purpose. A PAM service and greetd greeter need a root-owned install (/etc/pam.d/breadlock), which bakery has no privileged path for.

BOS already wires the packaged binaries (this repo still does not ship those system files):

# /etc/greetd/config.toml — BOS default
[default_session]
command = "cage -s -- breadgreet"
# hypridle lock_cmd (BOS). SUPER+L is loginctl lock-session, which hypridle picks up.
lock_cmd = breadlock

breadlock listen is the unlocked-path subscriber for bread.command.lock.lock and bread.command.lock.unlock. It is not started by hypridle; add it to session startup (exec-once = breadlock listen) if a Lua workflow should be able to lock the session while it is unlocked, or to ack already-unlocked. bread.command.lock.unlock does not replace PAM and does not run loginctl unlock-session. Super+L / hypridle remain loginctl lock-session.

Verification (why this is safe to test without a lockout risk)

  1. PAM logic in isolation first: cargo run --bin breadlock-auth-check exercises the exact PAM flow breadlock uses, against a typed password, with no Wayland surface at all. A bad /etc/pam.d/breadlock just prints an error here — it can never lock a session.
  2. Locker rendering/lock lifecycle nested, never against the live session: run breadlock inside a nested Hyprland instance or under cage -- breadlock. ext-session-lock-v1 only ever affects the compositor instance the client is connected to (scoped to $WAYLAND_DISPLAY), so a nested lock can never lock the real outer session. Verify the full type-password → PAM check → unlock cycle there, including the wrong-password path, before ever binding a real keybind.
  3. If testing against a live session: keep a second TTY or SSH session open the whole time. Killing the breadlock process is not a safe unlock path — per the protocol, an abnormally-terminated lock client is expected to leave the compositor still locked. The real recovery path is "kill it, then use the second session to restart Hyprland or switch VT."
  4. breadgreet: cargo test -p breadgreet runs the greetd_ipc framing/state-machine tests against a mock Unix-socket server — no real greetd or PAM involved. Manual testing against a real greetd should happen on a disposable VT, not by replacing the live BOS cage -s -- breadgreet session on VT1.

License

MIT