diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index 9cfa436..7ee14ba 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -7,10 +7,6 @@ on: jobs: package: runs-on: [self-hosted, hestia] - # Forgejo's Arch package registry does not GPG-sign packages for pacman. - # BOS therefore uses SigLevel=Never on [Breadway.os.git.breadway.dev] - # until a signed repo exists. Keep publishing here — do not flip this - # job to a different registry just to get signatures. container: image: archlinux:latest steps: diff --git a/.gitignore b/.gitignore index 37a962f..d6fe8f9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ Thumbs.db .claude/ # Local hygiene notes (not for commit) +CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index e1c15f4..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,26 +0,0 @@ -# AGENTS.md — Repo hygiene - -Scope: this file covers *repo hygiene* — branching, remotes, CI, cleanup. It is not project documentation. - -## Branch model -- Single-trunk: `main` only. No `dev` or `beta` branch. Land small changes directly, or use short-lived `feature/x`/`fix/x` branches for anything non-trivial and merge back to `main`. -- This replaced an earlier three-branch (`dev`/`beta`/`main`) model after `main` silently rotted across the ecosystem. Don't recreate those branches. - -## Channel -- **Pacman-only, permanently.** There is no `bakery.toml` on purpose: breadlock installs a root-owned `/etc/pam.d/breadlock` PAM service (and `breadgreet` is a greetd greeter). Bakery has no privileged-install path. Do not add `bakery.toml`. -- Releases are `v*` tags. `.forgejo/workflows/package.yml` builds the `[breadway]` pacman package. There are no bakery tracks (`dev`/`beta`/`stable` indexes) for this repo. - -## Remotes -- `origin` — Forgejo (`git.breadway.dev` via Hestia, SSH) — authoritative. Push here. -- `github` — GitHub mirror (push-mirror; do not push to it by hand). - -## CI -- `.forgejo/workflows/package.yml` triggers only on `push: tags: ['v*']` — regular pushes to `main` run nothing. Tag a release to trigger packaging. -- No build/lint/test CI runs on ordinary commits or PRs — test locally before merging. - -## Cleanup -- Delete feature/fix branches (local + remote) once merged. Check with `git branch --merged main`. - -## Don't -- Don't add `bakery.toml`. -- Don't embed credentials in remote URLs — SSH or a credential helper only. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c8b1d9c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,22 +0,0 @@ -# Contributing - -`breadlock` / `breadgreet` — session locker and greetd greeter for Hyprland. - -Single-trunk, same as the rest of the ecosystem: one long-lived branch -(`main`), short-lived `feature/` / `fix/` branches, merge back. -No `dev` or `beta` branch. - -This repo is a deliberate **pacman-only** exception. There is no -`bakery.toml` — breadlock needs a root-owned `/etc/pam.d/breadlock` PAM -service, which bakery cannot install. Don't add one. Releases are `v*` -tags that fire `.forgejo/workflows/package.yml` into the `[breadway]` -pacman repo. There are no bakery tracks. - -See `AGENTS.md` for remotes and CI details. - -## Local development - -```sh -cargo build --release --bin breadlock --bin breadgreet -cargo test --workspace -``` diff --git a/Cargo.lock b/Cargo.lock index 5f96f51..4e04b04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,21 +64,10 @@ dependencies = [ "serde_core", ] -[[package]] -name = "bread-shared" -version = "0.7.0" -source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b" -dependencies = [ - "dirs", - "serde", - "serde_json", - "toml 0.8.23", -] - [[package]] name = "bread-theme" -version = "0.3.1" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1#db2fa3c4b4c1e6933bc5cf62a236d05972fdc886" +version = "0.2.3" +source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.10#17d1bb85801b9a8c195b64c02d288cd662c9c780" dependencies = [ "dirs", "gtk4", @@ -86,17 +75,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "bread-utils" -version = "0.3.1" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1#db2fa3c4b4c1e6933bc5cf62a236d05972fdc886" -dependencies = [ - "bread-shared", - "dirs", - "serde", - "serde_json", -] - [[package]] name = "breadgreet" version = "0.2.0" @@ -119,12 +97,10 @@ dependencies = [ name = "breadlock" version = "0.2.0" dependencies = [ - "bread-utils", "breadlock-ui", "chrono", "pam-client2", "serde", - "serde_json", "smithay-client-toolkit", "thiserror 2.0.18", "tiny-skia", diff --git a/Cargo.toml b/Cargo.toml index 0cfefb2..0c077cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,7 @@ members = ["breadlock-ui", "breadlock", "breadgreet"] resolver = "2" [workspace.dependencies] -bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1" } -bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1" } +bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.10" } serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.8" diff --git a/EVENTS.md b/EVENTS.md deleted file mode 100644 index 208e0f7..0000000 --- a/EVENTS.md +++ /dev/null @@ -1,44 +0,0 @@ -# breadlock — bread event integration - -breadlock is a standalone session locker: it works exactly the same with -or without `breadd` running. When breadd *is* present, `breadlock` -publishes events into the shared bread automation fabric. See the parent -`bread` repo's `Documentation.md` — specifically its "Namespaces" and -"Integrating a bread\* app" sections — for the general convention this -follows. - -App id: **`lock`**. Transport: `bread-utils`'s `bread_client` module -(feature `bread-client`) — `breadlock` links it directly. Each `emit` is -its own short-lived connection (`BreadClient::emit` is fire-and-forget); -there is no long-running subscription half because breadlock has no -command verbs (see below). - -`breadgreet` is not wired to the bus. It runs under greetd (typically as -the dedicated greeter user, before a user session exists), so breadd is -usually not there to receive anything, and login is a different lifecycle -from session lock/unlock. - -## Events published (`bread.lock.*`) - -| Event | Data | When | -|-------|------|------| -| `bread.lock.locked` | `{}` | The compositor accepted the `ext-session-lock-v1` request (`SessionLockHandler::locked`). Not emitted merely because breadlock started or asked to lock. | -| `bread.lock.unlocked` | `{}` | PAM authenticated successfully and breadlock sent `unlock` to the compositor. Not emitted on a compositor-ended lock (`finished`), a dispatch-error exit (fail-secure: the session stays locked), or a failed/typo password. | - -## Commands honored (`bread.command.lock.*`) - -None. breadlock is started by hypridle / `loginctl lock-session` (or -directly) and unlocks only via PAM on this process. There is no -`lock`/`unlock`/`pin`/`blur` verb, and none is stubbed as a no-op. - -`background.blur` in `breadlock.toml` remains a documented locker no-op -(accepted, warned, surface drawn unblurred). That is appearance config, -not a bus command — do not invent `bread.command.lock.blur` for it. - -## Fail-safe behavior - -- If breadd isn't installed or isn't running, `emit` is a silent no-op - (`BreadClient::emit` never blocks or errors the caller) — breadlock's - actual lock/unlock path is entirely unaffected either way. -- There is no command subscription, so a breadd restart while the lock - screen is up changes nothing on this side. diff --git a/README.md b/README.md index a59182f..3b49969 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # breadlock -Session locker and graphical [greetd](https://git.sr.ht/~kennylevinsen/greetd) greeter for [Hyprland](https://hyprland.org/) 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`). +Session locker and graphical [greetd](https://git.sr.ht/~kennylevinsen/greetd) greeter for [Hyprland](https://hyprland.org/) on Wayland — the bread-ecosystem replacement for `hyprlock` and the TUI greeter (`tuigreet`) BOS currently ships. Two binaries, one workspace: @@ -9,13 +9,6 @@ Two binaries, one workspace: Both use [`bread-theme`](https://git.breadway.dev/Breadway/bread-ecosystem) 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`. See -[EVENTS.md](EVENTS.md) for the bus contract. `breadgreet` is not on the -bus. There is no `bakery.toml` (PAM / pacman exception). - ## Architecture ``` @@ -31,7 +24,7 @@ breadlock/ - **Protocol**: `ext-session-lock-v1` via [`smithay-client-toolkit`](https://docs.rs/smithay-client-toolkit) — GTK has no session-lock support, so this is a raw Wayland client, not a layer-shell surface like breadbar. - **Rendering**: fully software — `tiny-skia` composites each frame (background, rounded password pill, clock, status line) into a `wl_shm` buffer; `cosmic-text` shapes and rasterizes text (loads "Varela Round" by family name). No EGL/GL. -- **Background**: a solid palette color or a static PNG (cover-fit). `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. +- **Background**: a solid palette color or a static PNG (cover-fit). Live blur-of-desktop (hyprlock-style) is a **v2 follow-up** — it needs a `wlr-screencopy` capture (`libwayshot` is the right crate when this gets picked up); `background.blur = true` is accepted today but just logs a warning. - **Auth**: [`pam-client2`](https://crates.io/crates/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 @@ -61,27 +54,29 @@ sudo pacman -S gtk4 wayland libxkbcommon pam rust cargo ## 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. +`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 pacman-only — it is not in bread-ecosystem's registry and has no `bakery.toml`; a PAM/greeter component gets installed through the package manager, not the bakery curl-script channel. -BOS already wires the packaged binaries (this repo still does not ship those system files): +**Not included, by design**: this repo does not touch `/etc/greetd/config.toml`, install a lock keybind, or wire up `hypridle`. Once packaged, wiring BOS to actually use these binaries means: ```toml -# /etc/greetd/config.toml — BOS default +# /etc/greetd/config.toml — replace the current tuigreet line [default_session] command = "cage -s -- breadgreet" ``` ``` -# hypridle lock_cmd (BOS). SUPER+L is loginctl lock-session, which hypridle picks up. -lock_cmd = breadlock +# hyprland.conf +bind = SUPER, L, exec, breadlock ``` +That's a separate, later BOS task — deliberately kept out of this change so the existing `tuigreet` login path stays untouched and available as a fallback while these binaries are tested. + ## 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. +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, leaving the existing `tuigreet` config on VT1 untouched as a fallback. ## License diff --git a/breadlock/Cargo.toml b/breadlock/Cargo.toml index 5e2dc70..003840a 100644 --- a/breadlock/Cargo.toml +++ b/breadlock/Cargo.toml @@ -19,7 +19,6 @@ path = "src/bin/breadlock-auth-check.rs" [dependencies] breadlock-ui = { path = "../breadlock-ui", features = ["paint"] } -bread-utils = { workspace = true, features = ["bread-client"] } smithay-client-toolkit = "0.20" wayland-client = "0.31" tiny-skia = "0.12" @@ -27,7 +26,6 @@ chrono = "0.4" pam-client2 = { version = "0.5", default-features = false } zeroize = { version = "1", features = ["std"] } serde.workspace = true -serde_json.workspace = true toml.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/breadlock/src/bread_events.rs b/breadlock/src/bread_events.rs deleted file mode 100644 index 7cc1492..0000000 --- a/breadlock/src/bread_events.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! `bread.lock.*` event integration — optional, non-blocking. See -//! `EVENTS.md` at the repo root for the full contract. breadlock works -//! identically with or without breadd running; every call here is -//! fire-and-forget (`BreadClient::emit` never blocks or errors this -//! process) so a missing or restarting breadd never affects locking -//! itself. - -use bread_utils::bread_client::BreadClient; - -/// This app's id in bread's sibling-app namespace registry -/// (`bread_shared::apps::KNOWN_APPS`) — events publish as `bread.lock.*`. -pub const APP_ID: &str = "lock"; - -pub fn emit_locked() { - BreadClient::connect(APP_ID).emit("bread.lock.locked", serde_json::json!({})); -} - -pub fn emit_unlocked() { - BreadClient::connect(APP_ID).emit("bread.lock.unlocked", serde_json::json!({})); -} diff --git a/breadlock/src/lock/session.rs b/breadlock/src/lock/session.rs index a3c0ba4..a6adf14 100644 --- a/breadlock/src/lock/session.rs +++ b/breadlock/src/lock/session.rs @@ -9,7 +9,6 @@ impl SessionLockHandler for AppState { fn locked(&mut self, _conn: &Connection, _qh: &QueueHandle, session_lock: SessionLock) { tracing::info!("session locked"); self.session_lock = Some(session_lock); - crate::bread_events::emit_locked(); } /// The compositor denied the lock request, or ended an active lock out diff --git a/breadlock/src/main.rs b/breadlock/src/main.rs index 0e9a65f..484b06a 100644 --- a/breadlock/src/main.rs +++ b/breadlock/src/main.rs @@ -1,6 +1,5 @@ mod auth; mod background; -mod bread_events; mod config; mod input; mod lock; @@ -54,7 +53,6 @@ fn main() { tracing::info!("authenticated, unlocking"); if let Some(lock) = state.session_lock.take() { lock.unlock(); - bread_events::emit_unlocked(); } state.exit = true; }