From 64cc17905de1c1afd65ac5b81305935da7b8d939 Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 17:17:35 +0800 Subject: [PATCH 1/6] bread-capture: add breadbar's 8 new views to target registry --- bread-capture/src/main.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index 022b9ef..b4e2558 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -32,7 +32,18 @@ const CAPTURE_TIMEOUT: Duration = Duration::from_secs(10); const TARGETS: &[(&str, &[(&str, &str)])] = &[ ( "breadbar", - &[("bar", "breadbar-bar.png"), ("control-panel", "breadbar-control-panel.png")], + &[ + ("bar", "breadbar-bar.png"), + ("control-panel", "breadbar-control-panel.png"), + ("connectivity-wifi", "breadbar-connectivity-wifi.png"), + ("connectivity-bluetooth", "breadbar-connectivity-bluetooth.png"), + ("media-popover", "breadbar-media-popover.png"), + ("notification", "breadbar-notification.png"), + ("notification-critical", "breadbar-notification-critical.png"), + ("osd-volume", "breadbar-osd-volume.png"), + ("osd-brightness", "breadbar-osd-brightness.png"), + ("wifi-add-dialog", "breadbar-wifi-add-dialog.png"), + ], ), ("breadbox", &[("launcher", "breadbox-launcher.png")]), ("breadclip", &[("history", "breadclip-history.png")]), From c0b489fa67db30257c2f12fe7b971bd238a87fdd Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 17:27:12 +0800 Subject: [PATCH 2/6] bread-capture: add breadman's 11 additional views to target registry --- bread-capture/src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index b4e2558..a58dc49 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -57,7 +57,23 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[ ("ask", "breadhelp-ask.png"), ], ), - ("breadman", &[("all", "breadman-all.png")]), + ( + "breadman", + &[ + ("all", "breadman-all.png"), + ("upcoming", "breadman-upcoming.png"), + ("todo", "breadman-todo.png"), + ("reminder", "breadman-reminder.png"), + ("idea", "breadman-idea.png"), + ("note", "breadman-note.png"), + ("question", "breadman-question.png"), + ("archive", "breadman-archive.png"), + ("settings", "breadman-settings.png"), + ("errors", "breadman-errors.png"), + ("editor", "breadman-editor.png"), + ("new-note", "breadman-new-note.png"), + ], + ), ("bos-settings", &[("default", "bos-settings-default.png")]), ]; From 271555fb80c867e5473a55eb57ab5678c9b5c2d1 Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 21:32:44 +0800 Subject: [PATCH 3/6] bread-capture: add breadpad's reminder + reminder-snooze views to target registry --- bread-capture/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index a58dc49..99f5b7e 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -48,7 +48,14 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[ ("breadbox", &[("launcher", "breadbox-launcher.png")]), ("breadclip", &[("history", "breadclip-history.png")]), ("breadsearch", &[("search", "breadsearch-search.png")]), - ("breadpad", &[("popup", "breadpad-popup.png")]), + ( + "breadpad", + &[ + ("popup", "breadpad-popup.png"), + ("reminder", "breadpad-reminder.png"), + ("reminder-snooze", "breadpad-reminder-snooze.png"), + ], + ), ( "breadhelp", &[ From 669ca642847f7897cf952c69315a7997a2c00c5b Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 21:37:33 +0800 Subject: [PATCH 4/6] bread-capture: add breadhelp's troubleshoot-wizard view to target registry --- bread-capture/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index 99f5b7e..f6d5427 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -62,6 +62,7 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[ ("home", "breadhelp-home.png"), ("learn", "breadhelp-learn.png"), ("ask", "breadhelp-ask.png"), + ("troubleshoot-wizard", "breadhelp-troubleshoot-wizard.png"), ], ), ( From 94feaa6f9be7feb6b1f0c4c75cff2f2994f4e0d7 Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 21:45:18 +0800 Subject: [PATCH 5/6] bread-capture: add bos-settings' 24 sidebar-section views to target registry --- bread-capture/src/main.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index f6d5427..2f26756 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -82,7 +82,35 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[ ("new-note", "breadman-new-note.png"), ], ), - ("bos-settings", &[("default", "bos-settings-default.png")]), + ( + "bos-settings", + &[ + ("network", "bos-settings-network.png"), + ("breadcrumbs", "bos-settings-breadcrumbs.png"), + ("bluetooth", "bos-settings-bluetooth.png"), + ("firewall", "bos-settings-firewall.png"), + ("sound", "bos-settings-sound.png"), + ("power", "bos-settings-power.png"), + ("datetime", "bos-settings-datetime.png"), + ("hyprland", "bos-settings-hyprland.png"), + ("keybinds", "bos-settings-keybinds.png"), + ("autostart", "bos-settings-autostart.png"), + ("users", "bos-settings-users.png"), + ("appearance", "bos-settings-appearance.png"), + ("breadpaper", "bos-settings-breadpaper.png"), + ("breadbar", "bos-settings-breadbar.png"), + ("breadbox", "bos-settings-breadbox.png"), + ("breadclip", "bos-settings-breadclip.png"), + ("breadpad", "bos-settings-breadpad.png"), + ("breadsearch", "bos-settings-breadsearch.png"), + ("bread", "bos-settings-bread.png"), + ("packages", "bos-settings-packages.png"), + ("aur", "bos-settings-aur.png"), + ("firmware", "bos-settings-firmware.png"), + ("snapshots", "bos-settings-snapshots.png"), + ("about", "bos-settings-about.png"), + ], + ), ]; #[derive(Parser)] From 7ec232b86d8e1dd3e9bb993314c1a08b220d84e2 Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 29 Jul 2026 21:54:46 +0800 Subject: [PATCH 6/6] bread-capture: one command for every app, flags for a single one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plain `bread-capture` with no flags now captures every known app's every view in one run — each binary resolved by its own bare name via $PATH, same as invoking it directly by name would (so an installed bread ecosystem needs nothing but `bread-capture` to regenerate every screenshot). Previously --app-path was required, so there was no way to run more than one app per invocation. --app restricts to a single app (resolved via $PATH, no path needed); --app-path still works alone too, inferring which app by its file stem exactly as before. --view further restricts to one view — apps without a matching view are silently skipped rather than treated as an error, since view names naturally don't overlap across apps in a multi-app run, but an unmatched --view in a single-app run (or one that matches nothing across every selected app) is still a real error. --- bread-capture/src/main.rs | 127 ++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 39 deletions(-) diff --git a/bread-capture/src/main.rs b/bread-capture/src/main.rs index 2f26756..5af8be9 100644 --- a/bread-capture/src/main.rs +++ b/bread-capture/src/main.rs @@ -2,10 +2,14 @@ //! //! Drives each target app's `--screenshot --output ` mode (see //! `bread-screenshots` for what that mode does inside the app) and reports -//! pass/fail per view. One app per invocation, selected by `--app-name` -//! (defaults to `--app-path`'s file stem, so `--app-path -//! ./target/release/breadbox` needs no separate `--app-name`) — the view -//! list for each app is looked up from [`TARGETS`] below. Flat output +//! pass/fail per view/app. Plain `bread-capture` with no flags captures +//! every known app's every view in one run — each app's binary is resolved +//! by its own bare name via `$PATH`, same as running it directly by name +//! would. `--app ` restricts to one app; `--app-path ` +//! overrides where its binary is found (and, without `--app`, also selects +//! which app by its file stem — so `--app-path ./target/release/breadbox` +//! alone still works); `--view ` further restricts to one view. The +//! view list for each app is looked up from [`TARGETS`] below. Flat output //! directory for now — no versioned `screenshots/vX.Y.Z/latest` structure //! or manifest file yet, since that's still not earning its complexity over //! a handful of apps. @@ -115,14 +119,26 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[ #[derive(Parser)] struct Cli { - /// Path to the target app's binary (resolved via $PATH if not a path). + /// Restrict to one app (see `TARGETS` for known names). Omit to capture + /// every known app's every view in one run. #[arg(long)] - app_path: String, + app: Option, - /// Which app's view list to use (see `TARGETS`). Defaults to - /// `--app-path`'s file stem, e.g. `./target/release/breadbox` -> `breadbox`. + /// Path to that app's binary (resolved via $PATH if not a path). + /// Without `--app`, this also selects *which* app by its file stem + /// (e.g. `./target/release/breadbox` -> `breadbox`) — so a single-app + /// run never needs both flags. Ignored (with a warning) if given + /// together with a multi-app run (no `--app`, and the path isn't + /// resolvable to exactly one app). #[arg(long)] - app_name: Option, + app_path: Option, + + /// Restrict to one view within the selected app(s) (see each app's + /// entry in `TARGETS` for known view names). Apps that don't have a + /// view by this name are skipped, not treated as an error, since a + /// multi-app run's view names naturally don't all overlap. + #[arg(long)] + view: Option, /// Directory to write captured PNGs into. #[arg(long, default_value = "./screenshots")] @@ -143,21 +159,49 @@ struct Cli { isolate_height: u32, } -fn main() -> Result { - let cli = Cli::parse(); +fn known_app_names() -> String { + TARGETS.iter().map(|(n, _)| *n).collect::>().join(", ") +} - let app_name = cli.app_name.clone().unwrap_or_else(|| { - PathBuf::from(&cli.app_path) +/// (app_name, binary_path, views) per selected app. +type SelectedTarget = (&'static str, String, &'static [(&'static str, &'static str)]); + +/// Resolves which `TARGETS` entries this run covers, and the binary path +/// to use for each. +fn selected_targets(cli: &Cli) -> Result> { + if let Some(app) = &cli.app { + let Some((name, views)) = TARGETS.iter().find(|(n, _)| n == app) else { + bail!("no known view list for app '{app}' (known: {})", known_app_names()); + }; + let path = cli.app_path.clone().unwrap_or_else(|| name.to_string()); + return Ok(vec![(name, path, views)]); + } + + if let Some(path) = &cli.app_path { + let stem = PathBuf::from(path) .file_stem() .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_else(|| cli.app_path.clone()) - }); - let Some((_, views)) = TARGETS.iter().find(|(name, _)| *name == app_name) else { - bail!( - "no known view list for app '{app_name}' (known: {})", - TARGETS.iter().map(|(n, _)| *n).collect::>().join(", ") - ); - }; + .unwrap_or_else(|| path.clone()); + let Some((name, views)) = TARGETS.iter().find(|(n, _)| *n == stem) else { + bail!("no known view list for app '{stem}' (known: {})", known_app_names()); + }; + return Ok(vec![(name, path.clone(), views)]); + } + + // No --app / --app-path at all: every known app, resolved by its own + // bare name via $PATH. + Ok(TARGETS.iter().map(|(name, views)| (*name, name.to_string(), *views)).collect()) +} + +fn main() -> Result { + let cli = Cli::parse(); + let targets = selected_targets(&cli)?; + + if let Some(view) = &cli.view { + if !targets.iter().any(|(_, _, views)| views.iter().any(|(v, _)| v == view)) { + bail!("view '{view}' doesn't match any selected app's views"); + } + } // Bound, not dropped-and-discarded: `_isolation`'s teardown (kill the // compositor, remove its socket/config) must run via Drop regardless of @@ -174,24 +218,29 @@ fn main() -> Result { let height_str = cli.isolate_height.to_string(); let mut failed = false; - for (view, filename) in *views { - let out_path = cli.out_dir.join(filename); - let out_str = out_path.to_string_lossy(); - let result = bread_utils::proc::run( - &cli.app_path, - &[ - "--screenshot", view, - "--output", &out_str, - "--width", &width_str, - "--height", &height_str, - ], - CAPTURE_TIMEOUT, - ); - if result.success { - println!("ok {app_name}/{view} -> {}", out_path.display()); - } else { - failed = true; - println!("FAIL {app_name}/{view}: {}", result.stderr.trim()); + for (app_name, app_path, views) in &targets { + for (view, filename) in *views { + if cli.view.as_deref().is_some_and(|v| v != *view) { + continue; + } + let out_path = cli.out_dir.join(filename); + let out_str = out_path.to_string_lossy(); + let result = bread_utils::proc::run( + app_path, + &[ + "--screenshot", view, + "--output", &out_str, + "--width", &width_str, + "--height", &height_str, + ], + CAPTURE_TIMEOUT, + ); + if result.success { + println!("ok {app_name}/{view} -> {}", out_path.display()); + } else { + failed = true; + println!("FAIL {app_name}/{view}: {}", result.stderr.trim()); + } } }