Wire breadpad into the bread event bus
Emit bread.pad.captured after a successful quick-capture save and bread.pad.reminder.due when breadpad fire actually shows a reminder. Fail-silent if breadd is down. No command stubs. App id: pad. bread-utils bread-client at v0.7.1.
This commit is contained in:
parent
b3e49dc753
commit
9dcc6cfc94
4 changed files with 95 additions and 1 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -308,10 +308,21 @@ version = "0.3.1"
|
||||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d#69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d"
|
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d#69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bread-utils",
|
"bread-utils 0.3.1 (git+https://git.breadway.dev/Breadway/bread-ecosystem?rev=69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d)",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-shared"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b"
|
||||||
|
dependencies = [
|
||||||
|
"dirs 5.0.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml 0.8.23",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-theme"
|
name = "bread-theme"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -323,6 +334,17 @@ dependencies = [
|
||||||
"serde_json",
|
"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 5.0.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-utils"
|
name = "bread-utils"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -359,6 +381,7 @@ version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bread-screenshots",
|
"bread-screenshots",
|
||||||
|
"bread-utils 0.3.1 (git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.7.1)",
|
||||||
"breadpad-shared",
|
"breadpad-shared",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
|
|
|
||||||
49
EVENTS.md
Normal file
49
EVENTS.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# breadpad — bread event integration
|
||||||
|
|
||||||
|
breadpad is a standalone capture popup: it works exactly the same with or
|
||||||
|
without `breadd` running. When breadd *is* present, the `breadpad` binary
|
||||||
|
publishes events into the shared bread automation fabric after actions that
|
||||||
|
already happened. 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: **`pad`**. Transport: `bread-utils`'s `bread_client` module
|
||||||
|
(feature `bread-client`) — the capture popup links it directly. breadpad is
|
||||||
|
short-lived (one popup, or one `fire <id>` invocation from the existing
|
||||||
|
systemd user timer), so each `emit` is its own short-lived connection.
|
||||||
|
There is no long-running breadpad daemon and therefore no command
|
||||||
|
subscription.
|
||||||
|
|
||||||
|
`breadman` (the viewer) does not emit or subscribe. Notes created or edited
|
||||||
|
there are not quick-capture, and it is not on the reminder-fire path.
|
||||||
|
|
||||||
|
## Events published (`bread.pad.*`)
|
||||||
|
|
||||||
|
| Event | Data | When |
|
||||||
|
|-------|------|------|
|
||||||
|
| `bread.pad.captured` | `{ "id": "<note id>" }` | The capture popup saved a note successfully (`Store::save_note` returned `Ok`). Not emitted when the field is empty, the window is dismissed, classification-only preview happens, or the write fails. |
|
||||||
|
| `bread.pad.reminder.due` | `{ "id": "<note id>" }` | `breadpad fire <id>` decided the reminder is due (`Scheduler::fire` returned true) and is about to show the reminder window. This is the existing in-process systemd-timer hook (`breadpad-reminder-<id>.timer` → `breadpad fire <id>`), not a new daemon. Not emitted when the note is missing, the fire is outside the missed-grace window, or the reminder window is opened as a `--screenshot` sample. |
|
||||||
|
|
||||||
|
Note bodies are never included in the payload — only the local note id.
|
||||||
|
Notes stay in `~/.local/share/breadpad/notes.jsonl`; the event bus is for
|
||||||
|
*notifications about* capture and due reminders, not a channel for note
|
||||||
|
content.
|
||||||
|
|
||||||
|
## Commands honored (`bread.command.pad.*`)
|
||||||
|
|
||||||
|
None. breadpad has no persistent process that could subscribe, and the
|
||||||
|
actions a command verb would map to already exist as local CLI / keybind
|
||||||
|
paths (`breadpad` for capture, `breadpad fire <id>` for the reminder
|
||||||
|
window, `breadman` for viewing and editing). Stubbing `capture` / `snooze`
|
||||||
|
/ `done` on the bus without a subscriber (or inventing a daemon just to
|
||||||
|
hold one) would be a no-op dressed up as an API. If breadpad later grows a
|
||||||
|
long-running piece that can honor a verb for real, add the verb then.
|
||||||
|
|
||||||
|
## 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). Capture, save,
|
||||||
|
systemd timers, and the reminder window are entirely unaffected.
|
||||||
|
- There is no command subscription to reconnect. Restarting breadd does
|
||||||
|
not require restarting breadpad; the next real capture or fire will emit
|
||||||
|
again if breadd is reachable at that moment.
|
||||||
|
|
@ -14,6 +14,9 @@ breadpad-shared = { path = "../breadpad-shared" }
|
||||||
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
|
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
|
||||||
# v0.7.1 predates this crate; rev-pin so this is not `branch = "main"`.
|
# v0.7.1 predates this crate; rev-pin so this is not `branch = "main"`.
|
||||||
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", rev = "69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d" }
|
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", rev = "69ce2d67a8de8a09c064aa9d7ff99b46656f0b1d" }
|
||||||
|
# Bread event bus: emit after a real capture/reminder fire. Fail-silent if
|
||||||
|
# breadd is down. See EVENTS.md.
|
||||||
|
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1", features = ["bread-client"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
ort.workspace = true
|
ort.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use breadpad_shared::{
|
||||||
store::Store,
|
store::Store,
|
||||||
types::{Note, NoteType},
|
types::{Note, NoteType},
|
||||||
};
|
};
|
||||||
|
use bread_utils::bread_client::BreadClient;
|
||||||
use gtk4::{glib, prelude::*};
|
use gtk4::{glib, prelude::*};
|
||||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -16,6 +17,21 @@ use std::sync::{Arc, Once};
|
||||||
|
|
||||||
mod screenshot;
|
mod screenshot;
|
||||||
|
|
||||||
|
/// Sibling-app id in `bread_shared::apps::KNOWN_APPS`. Events are `bread.pad.*`.
|
||||||
|
const APP_ID: &str = "pad";
|
||||||
|
|
||||||
|
/// `bread.pad.captured` after a successful quick-capture save. Fire-and-forget:
|
||||||
|
/// `BreadClient::emit` is a silent no-op when breadd is down or missing.
|
||||||
|
fn emit_captured(id: &str) {
|
||||||
|
BreadClient::connect(APP_ID).emit("bread.pad.captured", serde_json::json!({ "id": id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `bread.pad.reminder.due` when `breadpad fire <id>` actually shows a reminder
|
||||||
|
/// (the existing systemd-timer hook — not a new daemon). Same fail-silent emit.
|
||||||
|
fn emit_reminder_due(id: &str) {
|
||||||
|
BreadClient::connect(APP_ID).emit("bread.pad.reminder.due", serde_json::json!({ "id": id }));
|
||||||
|
}
|
||||||
|
|
||||||
static ORT_INIT: Once = Once::new();
|
static ORT_INIT: Once = Once::new();
|
||||||
|
|
||||||
fn init_ort_once(cfg: &Config) {
|
fn init_ort_once(cfg: &Config) {
|
||||||
|
|
@ -331,6 +347,8 @@ fn cmd_fire(id: &str, cfg: &Config) -> Result<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit_reminder_due(¬e.id);
|
||||||
|
|
||||||
// Schedule next recurrence before showing UI
|
// Schedule next recurrence before showing UI
|
||||||
if note.rrule.is_some() {
|
if note.rrule.is_some() {
|
||||||
if let Some(next) = Scheduler::next_recurrence(¬e, &cfg.reminders.default_morning) {
|
if let Some(next) = Scheduler::next_recurrence(¬e, &cfg.reminders.default_morning) {
|
||||||
|
|
@ -911,6 +929,7 @@ fn save_note_classified(
|
||||||
tracing::error!("failed to save note: {}", e);
|
tracing::error!("failed to save note: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
emit_captured(¬e.id);
|
||||||
if note.time.is_some() {
|
if note.time.is_some() {
|
||||||
if let Err(e) = Scheduler::schedule(¬e) {
|
if let Err(e) = Scheduler::schedule(¬e) {
|
||||||
tracing::warn!("failed to schedule reminder: {}", e);
|
tracing::warn!("failed to schedule reminder: {}", e);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue