Allow well-formed bread.command events on the emit bus

Docs already said any module or app could publish bread.command.<app>.<verb>,
but command is reserved so both unsourced bread-emit and sourced App emit
rejected the whole namespace. Keep command unclaimable as an app id; accept
bread.command.<known-app>.<verb> (and let an app command another known app).
Also ship bread-emit and bread-module-host, and give udev enumerate the same
classification fields as a live add so boot-time devices are not all unknown.
This commit is contained in:
Breadway 2026-08-15 21:41:40 +08:00
parent a6973360bd
commit d3517d1433
52 changed files with 26405 additions and 6811 deletions

View file

@ -24,12 +24,15 @@ pub const KNOWN_APPS: &[&str] = &[
/// socket client may freely emit a custom/test event, but not one whose
/// top-level segment is one of these, since that would let it impersonate
/// a real adapter (or another daemon-internal event family) rather than
/// producing an obviously-manual one. See [`is_reserved_domain`] and
/// `breadd/src/ipc/mod.rs`'s `emit` handler. *Since: v1.5 — `bluetooth`,
/// producing an obviously-manual one. The one exception is a well-formed
/// [`validate_command_event`] name (`bread.command.<known-app>.<verb>`):
/// `command` stays reserved so it cannot be claimed as an app id, but the
/// command bus itself is meant to be publishable. See [`is_reserved_domain`]
/// and `breadd/src/ipc/mod.rs`'s `emit` handler. *Since: v1.5 — `bluetooth`,
/// `workspace`, `window`, and `monitor` added (event families the Hyprland
/// and Bluetooth adapters already published under, but that were missing
/// from this list) when this became a spoofing-prevention boundary and not
/// just an app-id-conflict one.*
/// just an app-id-conflict one. Since: v1.7 — command-bus exception.*
const RESERVED_DOMAINS: &[&str] = &[
"terminal",
"git",
@ -66,11 +69,54 @@ pub fn is_reserved_domain(id: &str) -> bool {
/// Whether `event` is a well-formed event name for `app` — i.e. it starts
/// with `bread.<app>.`. An app may only publish within its own namespace
/// segment; this is what the IPC boundary checks before constructing a
/// `RawEvent` tagged `AdapterSource::App(app)`.
/// `RawEvent` tagged `AdapterSource::App(app)`. Command events addressed
/// to another known app are a separate, explicit exception — see
/// [`validate_command_event`].
pub fn validate_app_namespace(app: &str, event: &str) -> bool {
event.starts_with(&format!("bread.{app}."))
}
/// Whether `event` is in the outbound command namespace
/// (`bread.command.*`). Does not check that the target is a known app —
/// use [`validate_command_event`] for that.
pub fn is_command_event(event: &str) -> bool {
event.starts_with("bread.command.")
}
/// The app id a command event is addressed to — the segment immediately
/// after `bread.command.`. Returns `None` if `event` is not a command
/// event or the app-id segment is empty.
pub fn command_target(event: &str) -> Option<&str> {
let rest = event.strip_prefix("bread.command.")?;
let app = rest.split('.').next()?;
if app.is_empty() {
None
} else {
Some(app)
}
}
/// Whether `event` is a well-formed command to a registered sibling app:
/// `bread.command.<known_app>.<verb>` with `known_app` in [`KNOWN_APPS`]
/// and a non-empty verb (at least one extra dotted segment).
///
/// This is the exception the IPC unsourced/`bread-emit` path (and sourced
/// `AdapterSource::App` emit) use so any module or app can publish
/// commands without `command` leaving [`is_reserved_domain`] — `command`
/// must stay unclaimable as an app id. `bread.command.power.off` and
/// `bread.command.notanapp.x` still fail because the target is not in
/// [`KNOWN_APPS`].
pub fn validate_command_event(event: &str) -> bool {
let rest = match event.strip_prefix("bread.command.") {
Some(rest) => rest,
None => return false,
};
let Some((app, verb)) = rest.split_once('.') else {
return false;
};
is_known_app(app) && !verb.is_empty()
}
/// The top-level dotted segment after `bread.` in an event name — e.g.
/// `Some("power")` for `"bread.power.ac.connected"`. Returns `None` for
/// event names that don't start with `bread.` at all, which are always
@ -119,8 +165,15 @@ mod tests {
// daemon itself publishes under must be reserved, or a manual/no-source
// `emit` over the IPC socket could impersonate it undetected.
for domain in [
"power", "network", "device", "bluetooth", "hyprland", "workspace", "monitor",
"window", "system",
"power",
"network",
"device",
"bluetooth",
"hyprland",
"workspace",
"monitor",
"window",
"system",
] {
assert!(
is_reserved_domain(domain),
@ -164,4 +217,47 @@ mod tests {
// because it shares a string prefix.
assert!(!validate_app_namespace("clip", "bread.clipx.copied"));
}
#[test]
fn is_command_event_requires_command_prefix() {
assert!(is_command_event("bread.command.clip.clear"));
assert!(is_command_event("bread.command.power.off"));
assert!(!is_command_event("bread.command"));
assert!(!is_command_event("bread.clip.copied"));
assert!(!is_command_event("command.clip.clear"));
}
#[test]
fn command_target_extracts_app_id() {
assert_eq!(command_target("bread.command.clip.clear"), Some("clip"));
assert_eq!(command_target("bread.command.cast.start.now"), Some("cast"));
assert_eq!(command_target("bread.command.clip"), Some("clip"));
assert_eq!(command_target("bread.command."), None);
assert_eq!(command_target("bread.clip.copied"), None);
}
#[test]
fn validate_command_event_accepts_known_app_with_verb() {
assert!(validate_command_event("bread.command.clip.clear"));
assert!(validate_command_event("bread.command.cast.start"));
assert!(validate_command_event("bread.command.clip.stack.clear"));
}
#[test]
fn validate_command_event_rejects_unknown_target_or_missing_verb() {
assert!(!validate_command_event("bread.command.power.off"));
assert!(!validate_command_event("bread.command.notanapp.x"));
assert!(!validate_command_event("bread.command.clip"));
assert!(!validate_command_event("bread.command.clip."));
assert!(!validate_command_event("bread.command."));
assert!(!validate_command_event("bread.hyprland.workspace.changed"));
assert!(!validate_command_event("bread.clip.copied"));
}
#[test]
fn command_stays_reserved_and_is_not_a_known_app() {
assert!(is_reserved_domain("command"));
assert!(!is_known_app("command"));
assert!(!validate_command_event("bread.command.command.x"));
}
}