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.
breadgreet's stylesheet was a crude subset of what `design/sketch.html`
specifies for `.greetoverlay`. Ported it faithfully so the greeter and
the lock screen read as one visual identity:
- Card: proper elevation (`box-shadow: 0 6px 28px`), a hairline
`alpha(@overlay, 0.09)` border, 10px radius, 300px min-width.
- Entry: recessed `shade(@surface, 1.5)` fill, and an accent focus ring
(`border-color: @accent` + `box-shadow: 0 0 0 2px alpha(@accent, .28)`)
matching the sketch's `.gentry:focus`.
- Clock/date over the wallpaper get a soft text-shadow for legibility on
any background; sizes match the sketch (46 / 14).
- Session picker styled as a surface pill (button + popover), not a bare
GtkDropDown.
- A CSS-animated ring shown while a greetd request is in flight
(`sync_busy`, driven from the single `update` exit point).
- Shake on a failed attempt (`flash_error`), matching the lock screen's
wrong-password motion — fired from both `AppInput::Error` and a PAM
`AuthPrompt::Error`.
Uses the shared `@define-color` palette (`@surface`, `@overlay`,
`@accent`, `@red`) so it recolours with the theme, same as breadbar.
`cargo test --workspace --all-targets` (157) / `clippy --all-targets -D
warnings` / `fmt --check` all clean. Visual pass still wanted — the CSS
is a direct translation of the mockup, not screenshot-verified.
Addresses the deep codebase audit:
- Center the static wallpaper cover-fit crop so the software (wl_shm)
path agrees with the GPU path and the greeter, instead of anchoring
the crop top-left.
- Desktop-entry Exec tokenizer: respect single quotes and proper
backslash escaping per the freedesktop spec.
- Greeter: dispatch CancelSession whenever the UI resets to the
username stage so the error path can't leave greetd holding a stale
PAM conversation; add a per-roundtrip timeout so a wedged greetd peer
can't strand the "Working" spinner.
- start_locker: return an error instead of spawning a child that
expect()-panics when WAYLAND_DISPLAY is unset.
- Bound in-flight PAM checks with a concurrency cap, since libpam
cannot be cancelled and a stuck module would otherwise leak one
uncancellable thread per retry.
- Expand unit/regression tests (157 total): tokenizer edge cases and a
pseudo-fuzz, blit offset/clamp cases, horizontal+vertical cover
centering, greetd roundtrip-timeout and connection-recovery.
- Reformat the workspace to rustfmt-clean and add a Forgejo CI gate
(fmt --check, clippy -D warnings, all-target tests, locked release
build) — previously the only workflow was an Arch package builder.
Bus clients must not unlock a locked session. Already-unlocked acks
.unlock.done; a running locker emits .failed (PAM only). Super+L /
hypridle remain loginctl lock-session.
README claimed fully software / no EGL; the locker is GLES2 wallpaper
plus software chrome, with opt-in Ken Burns. Building needs git and
pkg-config. PKGBUILD pkgver matches 0.2.0, backs up /etc/pam.d/breadlock,
and notes both dev bins. Release CI runs check() instead of --nocheck.
greetd only launches the session after the greeter process exits. Quit
on successful StartSession instead of sitting on "Starting session…"
until SIGTERM.
Empty Secret/Visible answers are Some("") (not PAM cancel). The greetd
actor stays up across connect failure and reconnects. Escape cancels
the conversation. Session env sets XDG_SESSION_TYPE/DESKTOP. .desktop
parsing honors Hidden/NoDisplay/TryExec and quoted Exec=. Invalid TOML
warns instead of failing silent. Font, date, and Ken Burns config match
what the greeter actually draws.
Show MPRIS now-playing and upower battery under the clock, polled on a
background thread (zbus). Flags gate both drawing and D-Bus; desktops
without a battery stay blank; titles are truncated.
Also the locker audit pass that landed in the same files:
- PAM authenticates getuid()/getpwuid_r, not $USER, and missing
username still takes the lock
- one lock surface per output, generation-gated fail timers, PAM
timeout, Tab-reveal off by default and cleared on keyboard leave
- GPU chrome is a dirty-rect quad with EGL cleanup; shm reuses a
SlotPool; appear/shake/flash/veil/clock-crossfade bugs
- hold-to-reveal, checking ellipsis, and password display length
Live-checked on hermes (GPU path, PAM unlock, Tab-hold).
The greeter previously ignored the configured background image and
rendered a static palette screen. Now the wallpaper is painted behind
the login card via a GtkDrawingArea draw callback (GTK4's GL pipeline),
with a Ken Burns pan driven by a tick callback and a veil gradient
matching breadlock's software renderer. The clock + card cluster fades
in and rises on entry.
Key bug avoided: the first attempt panned by resizing a GtkPicture,
which fed back into the window's minimum size and grew the window
unboundedly (17k x 11k). The draw-callback approach has no layout
feedback.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Four lock-screen niceties:
- Caps Lock / layout chip: a small pill above the password pill shows
when Caps Lock is on or a non-default layout is selected, so all-caps
input never mystifies.
- Hold-to-reveal: holding Tab renders the plain password instead of
dots (Tab produces no utf8, so it can never be part of the password).
- Idle auto-dim: after `animation.idle_dim_after_secs` of no keystrokes
the dim veil deepens past the base gradient (background-only alpha can
legally exceed 1.0), ramping over a few seconds for OLED/burn-in
comfort. Shared `veil_alpha` keeps software and GPU paths identical.
- Attempt counter: repeat failures show "Wrong password — N failed
attempts" so a stuck locker reads differently from ordinary typos.
All four render paths covered by tests (veil math, reveal truncation,
chip/reveal compose) and live-verified in nested Hyprland on the GPU
path; idle dim confirmed to deepen the veil (84.5->75.8 top).
glTexSubImage2D has no row-stride concept on GLES2, so uploading the
chrome sub-rect directly from the full-surface pixmap shifted every row
after the first by (w - rw) pixels — the clock/pill rendered as garbage.
Pack the dirty rows into a tightly-strided buffer before upload instead
(GL_UNPACK_ROW_LENGTH is ES 3.0-only).
Add a regression test for the pack, plus one pinning the background
shader's veil gradient to the software dim_rows (gl_FragCoord.y is 0 at
the bottom of the framebuffer, which is easy to get backwards).
The Ken Burns wallpaper pan and dim veil move to the GPU: the lock
surface is wrapped in a wl_egl_window, the wallpaper is a textured quad
whose shader applies the pan transform and veil (GPU bilinear makes
sub-pixel panning free — the ~19ms/frame software bilinear is gone,
idle CPU drops from 40% to ~0%), and the clock/pill/status chrome is
still composed with tiny-skia and composited over. EGL init failure
falls back to the fully-software path unchanged.
Notable fixes along the way:
- Text blending is now destination-aware: the old blend forced output
alpha to 255, which is correct over the opaque background but made
every glyph opaque in the chrome-only pixmap the GPU path composites.
- wayland-client switches to its C backend for raw surface pointers,
and khronos-egl/glow replace a hand-rolled EGL loader.
Tests grew from 72 to 88: GPU pan_region parity with the software path
(and edge/bounds/corner invariants), shader-uniform/EGL-attrib sanity,
chrome bounding-rect tracking, and a pixel-level parity test proving
the GPU split stays within double-rounding ULP range of the single-pass
compose.
Adds a full motion system to the lock screen (staggered entrance, dot pop
+ caret, wrong-password shake, success flash, clock crossfade), depth
polish (gradient veil, pill shadow + hairline border, "Enter password"
hint, responsive typography), and a second wave of animations (idle
breathing, success dot cascade, status slide-in, parallax unlock drift,
animated checking ellipsis, opt-in Ken Burns wallpaper pan). Includes a
dev-only breadlock-preview harness that renders every state to PNGs with
no Wayland involved, and a design/sketch.html prototype.
Fixes text alpha being dropped by cosmic-text's glyph-mask path (all text
fades now work) and double-drifting that made the date/status overlap the
clock/pill during unlock.
Subscribe the same locker and breadlock listen handlers. Already
unlocked is bread.lock.unlock.done; otherwise run loginctl
unlock-session and emit done / failed. Do not call compositor
unlock() — PAM stays the compositor path (fail-secure). Document
in EVENTS.md and README; drop the "Not implemented: unlock"
paragraph. pin/blur stay unimplemented.
List .desktop sessions from the existing scan, pre-select bos when
present (else the first entry), and pass the chosen Exec= argv to
greetd StartSession.
Pin bread-theme/bread-utils to bread-ecosystem tag v0.7.2.
Subscribe from the locker and from `breadlock listen` so the command
works while unlocked; start breadlock the same way hypridle does.
Emit bread.lock.lock.done / .failed. Document loginctl lock-session
as the Super+L equivalent.
Optional GPG detach-sign of the .pkg.tar.zst when GPG_PRIVATE_KEY is
set; ISO [breadway] stays SigLevel = Never until a signed db exists.
Emit bread.lock.locked when ext-session-lock-v1 is accepted and
bread.lock.unlocked after PAM success. Fail-silent if breadd is down.
No command verbs (no pin/blur stubs). breadgreet is not on the bus.
Note that Forgejo Arch registry packages are unsigned (SigLevel=Never
on BOS) without changing the package.yml registry.
Point workspace bread-theme at git.breadway.dev tag v0.7.1 (gtk feature stays
on breadgreet). README no longer claims BOS ships tuigreet or that Super+L
is unimplemented. CONTRIBUTING records the single-trunk, pacman-only model;
there is still no bakery.toml.
breadlock isn't in bread-ecosystem/registry/bread-ecosystem.toml and
package.yml (the only release workflow this repo has) never reads
bakery.toml — it only touches packaging/arch/PKGBUILD. So the file was
dead metadata, and the README's claim that "bakery.toml is the
bread-ecosystem package index entry" was stale/incorrect: it never
actually was one. breadlock ships pacman-only, which makes sense for a
PAM/greeter component — README now says so explicitly.
- main.rs: dispatch-loop errors no longer silently exit; bounded retry with
loud logging, and explicitly never call unlock() on an error path (would
turn a fail-secure crash into a fail-open one)
- Cargo.toml/state.rs/keyboard.rs/auth/*: password buffer is now
Zeroizing<String>, with explicit zero-then-truncate on backspace and
zeroize on clear/submit; also zeroes pam-client2's internal Conversation
copy after use
- state.rs/main.rs: new AuthState::ConfigError, distinct on-screen message
and error!-level log for PAM context-init failures vs ordinary wrong
password
- lock/surface.rs/state.rs: LockSurface now tracks its wl_output so
output_destroyed can remove it, fixing the surfaces Vec leak on
monitor unplug
- input/keyboard.rs: bind the keyboard via get_keyboard_with_repeat so
held keys (e.g. backspace) actually repeat, regardless of whether the
compositor implements server-side wl_keyboard repeat
Pulls the fix for panels turning brown/off-hue when pywal extracts a light
or muddy background from the wallpaper — background/surface now stay BOS's
fixed dark constants regardless of what the wallpaper looks like.