From 2a541ca5e9b7c89ff4301f64ffbe75f26a381989 Mon Sep 17 00:00:00 2001 From: Breadway Date: Sat, 15 Aug 2026 22:15:17 +0800 Subject: [PATCH] Wire breadlock to the bread event bus (app id lock) 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. --- .forgejo/workflows/package.yml | 4 ++++ Cargo.lock | 24 +++++++++++++++++++ Cargo.toml | 1 + EVENTS.md | 44 ++++++++++++++++++++++++++++++++++ README.md | 7 ++++++ breadlock/Cargo.toml | 2 ++ breadlock/src/bread_events.rs | 20 ++++++++++++++++ breadlock/src/lock/session.rs | 1 + breadlock/src/main.rs | 2 ++ 9 files changed, 105 insertions(+) create mode 100644 EVENTS.md create mode 100644 breadlock/src/bread_events.rs diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml index 7ee14ba..9cfa436 100644 --- a/.forgejo/workflows/package.yml +++ b/.forgejo/workflows/package.yml @@ -7,6 +7,10 @@ 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/Cargo.lock b/Cargo.lock index 7b3404e..5f96f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,17 @@ 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" @@ -75,6 +86,17 @@ 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" @@ -97,10 +119,12 @@ 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 eb392dc..0cfefb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ 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" } serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.8" diff --git a/EVENTS.md b/EVENTS.md new file mode 100644 index 0000000..208e0f7 --- /dev/null +++ b/EVENTS.md @@ -0,0 +1,44 @@ +# 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 67449ee..a59182f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ 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 ``` diff --git a/breadlock/Cargo.toml b/breadlock/Cargo.toml index 003840a..5e2dc70 100644 --- a/breadlock/Cargo.toml +++ b/breadlock/Cargo.toml @@ -19,6 +19,7 @@ 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" @@ -26,6 +27,7 @@ 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 new file mode 100644 index 0000000..7cc1492 --- /dev/null +++ b/breadlock/src/bread_events.rs @@ -0,0 +1,20 @@ +//! `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 a6adf14..a3c0ba4 100644 --- a/breadlock/src/lock/session.rs +++ b/breadlock/src/lock/session.rs @@ -9,6 +9,7 @@ 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 484b06a..0e9a65f 100644 --- a/breadlock/src/main.rs +++ b/breadlock/src/main.rs @@ -1,5 +1,6 @@ mod auth; mod background; +mod bread_events; mod config; mod input; mod lock; @@ -53,6 +54,7 @@ fn main() { tracing::info!("authenticated, unlocking"); if let Some(lock) = state.session_lock.take() { lock.unlock(); + bread_events::emit_unlocked(); } state.exit = true; }