Honor bread.command.paper.set via breadpaper listen
All checks were successful
dev release / build (push) Successful in 21s

Pin bread-utils to v0.7.2 (command/subscribe). Add a long-running
listen subcommand that subscribes to bread.command.paper.**, calls
set() on set, and emits bread.paper.set.done / .failed. Fail-silent
if breadd is down. No slideshow verbs. See EVENTS.md.
This commit is contained in:
Breadway 2026-08-15 22:47:12 +08:00
parent 54210ddc93
commit 0283ac4e81
6 changed files with 133 additions and 30 deletions

View file

@ -1,57 +1,67 @@
# breadpaper — bread event integration
breadpaper is a one-shot CLI wallpaper setter: it works exactly the same
with or without `breadd` running. When breadd *is* present, each successful
`breadpaper set` (or the bare-path shorthand) publishes one event into the
shared bread automation fabric. See the parent `bread` repo's
breadpaper is a wallpaper setter: it works exactly the same with or
without `breadd` running. When breadd *is* present, a successful
`breadpaper set` (or the bare-path shorthand) publishes `bread.paper.changed`
into the shared bread automation fabric, and `breadpaper listen` honors
`bread.command.paper.set`. 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: **`paper`**. Transport: `bread-utils`'s `bread_client` module
(feature `bread-client`) — the CLI links it directly and uses
`BreadClient::connect("paper")` + `emit` only. v0.7.1 has no `command()`
helper, and breadpaper has no long-running process that could hold a
`subscribe` open.
(feature `bread-client`) — the CLI links it directly. One-shot
`set`/`get` use `BreadClient::connect("paper")` + `emit` only. The
long-running `listen` subcommand holds a `subscribe` open.
There is no `breadpaper` daemon and no `watch` subcommand. A `bread-emit
bread.command.paper.set` (or any other `bread.command.paper.*`) with no
subscriber is a silent no-op — that is the documented bread convention,
not a breadpaper bug. Modules that want to change the wallpaper should
`breadpaper listen` is fail-silent if breadd is down: `subscribe`
reconnects with backoff and simply delivers nothing until the daemon
comes back. The one-shot `set`/`get` path does not require `listen`.
Modules that want to change the wallpaper without a listener can still
shell out:
```lua
bread.exec("breadpaper set /path/to/image.png")
```
The one-shot process still emits `bread.paper.changed` on success, so a
workflow can `bread.wait("bread.paper.changed", …)` for the real outcome
instead of assuming the exec finished the set.
A workflow that publishes the command instead should wait for the
confirmation, not assume the emit finished the set:
```lua
bread.emit("bread.command.paper.set", { path = "/path/to/image.png" })
bread.wait("bread.paper.set.done", { timeout = 10000 })
```
## Events published (`bread.paper.*`)
| Event | Data | When |
|-------|------|------|
| `bread.paper.changed` | `{ "path": "<wallpaper>" }` | After a successful `set` (awww + wal + `bread-theme reload`). `path` is the canonical absolute path that was applied. Not emitted on `get`, and not emitted if any of the three steps fail. |
| `bread.paper.changed` | `{ "path": "<wallpaper>" }` | After a successful `set` (awww + wal + `bread-theme reload`), including when `listen` honors `bread.command.paper.set`. `path` is the canonical absolute path that was applied. Not emitted on `get`, and not emitted if any of the three steps fail. |
| `bread.paper.set.done` | `{ "path": "<wallpaper>" }` | `bread.command.paper.set` was received and `set()` succeeded. `path` is the canonical absolute path that was applied. Not emitted by the one-shot CLI `set` — that path only publishes `changed`. |
| `bread.paper.set.failed` | `{ "error": "<message>", "path"?: "<requested>" }` | `bread.command.paper.set` was received but `set()` failed, or `data.path` was missing/not a string. `path` is the requested (not canonical) path when one was supplied. |
## Commands honored (`bread.command.paper.*`)
None, because there is nobody listening.
Honored only while `breadpaper listen` is running. A
`bread-emit bread.command.paper.set` with no listener is a silent no-op
— that is the documented bread convention, not a breadpaper bug.
| Verb | Data | Status |
| Verb | Data | Effect |
|------|------|--------|
| `set` | `{ "path": "..." }` | **Not subscribed.** The same work is `bread.exec("breadpaper set …")`. A future `breadpaper watch` (or a service-mode of this binary) could honor `bread.command.paper.set` and emit `bread.paper.set.done` / `.failed`; that is deliberately not added here — a long-running process whose only job is to re-exec the existing one-shot CLI is not worth the extra surface. |
| `set` | `{ "path": "..." }` | Calls the existing `set()` (awww + wal + `bread-theme reload`). Emits `bread.paper.set.done` / `.failed`. A successful set also emits `bread.paper.changed`. |
### Not implemented: slideshow / library / random / next
breadpaper is not a wallpaper library, a slideshow daemon, or a picker.
Browsing `~/Pictures/Backgrounds` lives in bos-settings. Do not invent
`bread.command.paper.next` / `.random` / `.cycle` (or matching events)
ahead of a real product feature.
ahead of a real product feature. Unrecognized verbs are ignored.
## 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) — breadpaper
still sets the wallpaper, generates the palette, and reloads themes.
- There is no command subscription to reconnect, because there is no
long-running subscriber.
- `breadpaper listen` does not exit if breadd is down. The command
subscription reconnects automatically (`BreadClient::subscribe`'s
background thread has its own backoff loop); no restart of `listen`
is needed once breadd returns.