Emit bread.box.launched after a successful app launch
Link bread-utils bread-client (tag v0.7.1) from the breadbox crate. Fire-and-forget via BreadClient; silent if breadd is down. No command verbs — breadbox is not a daemon. EVENTS.md is the contract (app id box).
This commit is contained in:
parent
e5ce91a9c9
commit
1b02cec0a6
6 changed files with 93 additions and 7 deletions
|
|
@ -7,7 +7,7 @@ Follow [`CONTRIBUTING.md`](CONTRIBUTING.md). Single-trunk: `main` plus short-liv
|
|||
- `github` — mirror. Day-to-day push `origin` only.
|
||||
|
||||
## Product
|
||||
GTK4 app launcher + `breadbox-sync` icon cache. Theme via `bread-theme` (pin by tag on `git.breadway.dev`). Toggle uses `bread-utils::singleton`, not a homegrown PID file.
|
||||
GTK4 app launcher + `breadbox-sync` icon cache. Theme via `bread-theme` (pin by tag on `git.breadway.dev`). Toggle uses `bread-utils::singleton`, not a homegrown PID file. `EVENTS.md` is the bread-event contract (app id `box`); emit `bread.box.launched` after a successful launch. No command verbs.
|
||||
|
||||
## Distribution
|
||||
Bakery (`bakery.toml`). Forgejo `.forgejo/workflows/` is canonical; do not re-add a GitHub Actions release workflow.
|
||||
|
|
|
|||
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -92,6 +92,17 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
@ -108,6 +119,7 @@ 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",
|
||||
|
|
|
|||
41
EVENTS.md
Normal file
41
EVENTS.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# breadbox — bread event integration
|
||||
|
||||
breadbox is a standalone app launcher: it works exactly the same with or
|
||||
without `breadd` running. When breadd *is* present, the GTK launcher
|
||||
publishes a single event into the shared bread automation fabric after a
|
||||
successful launch. 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: **`box`**. Transport: `bread-utils`'s `bread_client` module
|
||||
(feature `bread-client`) — `breadbox` links it directly. breadbox is a
|
||||
short-lived process (it exits when the launcher closes), so each `emit`
|
||||
is its own short-lived connection. There is no long-running daemon and
|
||||
therefore no command subscription.
|
||||
|
||||
## Events published (`bread.box.*`)
|
||||
|
||||
| Event | Data | When |
|
||||
|-------|------|------|
|
||||
| `bread.box.launched` | `{ "id": "<desktop id or exec>", "name": "<display name>" }` | The user launched an app (Enter / keypad Enter on the selected row, or activating a row) **and** the spawn succeeded. Not emitted if `Command::spawn` fails (missing terminal, `exec` that cannot start). `id` is the desktop-file id (the `.desktop` filename, e.g. `firefox.desktop`), falling back to the stripped `Exec=` line when that id is empty. `name` is the desktop-entry display name. |
|
||||
|
||||
Launch history is local to breadbox (`~/.cache/breadbox/history.json`);
|
||||
the event bus is a notification that a launch happened, not a channel
|
||||
for the exec line's arguments or the resulting process.
|
||||
|
||||
## Commands honored (`bread.command.box.*`)
|
||||
|
||||
None. breadbox is not a daemon — it is not running (and not subscribed)
|
||||
except while the launcher overlay is open. There is no existing
|
||||
"launch this desktop id from the bus" product surface, and inventing
|
||||
`bread.command.box.launch` (or similar) without that surface would be a
|
||||
stub. If/when breadbox grows a long-running piece that can honor a
|
||||
verb, the corresponding `bread.command.box.*` command should be added
|
||||
at the same time, not stubbed out ahead of 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) — launching,
|
||||
history, theming, and the singleton toggle are entirely unaffected.
|
||||
- Closing the launcher without launching anything emits nothing.
|
||||
|
|
@ -54,6 +54,9 @@ pub fn app_dirs() -> Vec<PathBuf> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DesktopEntry {
|
||||
/// Desktop file id (the `.desktop` filename, e.g. `firefox.desktop`).
|
||||
/// Empty only if the path had no file name; callers fall back to `exec`.
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub exec: String,
|
||||
pub icon_name: String,
|
||||
|
|
@ -155,7 +158,14 @@ pub fn parse_desktop(path: &Path) -> Option<DesktopEntry> {
|
|||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
let id = path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_default();
|
||||
|
||||
Some(DesktopEntry {
|
||||
id,
|
||||
name,
|
||||
exec,
|
||||
icon_name,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ path = "src/main.rs"
|
|||
|
||||
[dependencies]
|
||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.7.1", features = ["gtk"] }
|
||||
bread-utils = { 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", features = ["bread-client"] }
|
||||
# Capture primitives for `--screenshot` mode — see src/screenshot.rs.
|
||||
# Not on tag v0.7.1 (crate landed after that tag); rev-pin so this is not
|
||||
# `branch = "main"`.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use bread_theme::{hex_to_rgba, ink_on, load_palette, Palette};
|
||||
use bread_utils::bread_client::BreadClient;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
|
|
@ -11,6 +12,10 @@ use std::{
|
|||
rc::Rc,
|
||||
};
|
||||
|
||||
/// This app's id in bread's sibling-app namespace registry
|
||||
/// (`bread_shared::apps::KNOWN_APPS`) — events publish as `bread.box.*`.
|
||||
const APP_ID: &str = "box";
|
||||
|
||||
use breadbox_shared::{
|
||||
config_dir, load_all_desktop_entries, Config, DesktopEntry, IconCache, LaunchHistory,
|
||||
};
|
||||
|
|
@ -206,24 +211,42 @@ fn pick_terminal() -> String {
|
|||
|
||||
fn do_launch(entry: &DesktopEntry) {
|
||||
let cmd = entry.exec.trim();
|
||||
if entry.terminal {
|
||||
let spawned = if entry.terminal {
|
||||
let term = pick_terminal();
|
||||
let _ = Command::new(&term)
|
||||
Command::new(&term)
|
||||
.args(["-e", "bash", "-c", cmd])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn();
|
||||
.spawn()
|
||||
} else {
|
||||
let _ = Command::new("bash")
|
||||
Command::new("bash")
|
||||
.args(["-c", cmd])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn();
|
||||
.spawn()
|
||||
};
|
||||
if spawned.is_ok() {
|
||||
emit_launched(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Publishes `bread.box.launched` after a successful spawn. Fire-and-forget
|
||||
/// and non-fatal (`BreadClient::emit` never blocks or errors this caller) —
|
||||
/// breadd being absent must never affect launching itself.
|
||||
fn emit_launched(entry: &DesktopEntry) {
|
||||
let id = if entry.id.is_empty() {
|
||||
entry.exec.as_str()
|
||||
} else {
|
||||
entry.id.as_str()
|
||||
};
|
||||
BreadClient::connect(APP_ID).emit(
|
||||
"bread.box.launched",
|
||||
serde_json::json!({ "id": id, "name": entry.name }),
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Fuzzy matching ---------------------------------------------------------
|
||||
|
||||
fn fuzzy_matches(pattern: &str, text: &str) -> bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue