breadbar: capture every view, not just bar/control-panel

breadbar is "a bar + the notification daemon + the OSD" — the screenshot
mode only covered the bar and its control-panel popover, missing seven
more distinct surfaces: the WiFi/Bluetooth connectivity popover (both
tabs), the media-controls popover, the standalone notification window
(both normal and critical urgency), the standalone OSD window (volume
and brightness), and the wifi add-network dialog. All ten views are now
--screenshot targets.

Two real refactors needed to make the standalone notification/OSD
windows screenshot-able at all, not just bigger match arms:

- Both windows are built deep inside an async task (`run_osd`/
  `popup::run`), only reachable after the real event loop starts — no
  window handle ever existed for a caller to hook `connect_map` on before
  that. Window construction is now synchronous in `osd::spawn`/
  `notifications::spawn`, handed to the async loop as a parameter instead
  of created inside it.

- Screenshot mode seeds each with one fixed sample event (SampleKind) via
  the same channel the real pactl/backlight/D-Bus sources feed, instead
  of waiting for real hardware/dbus activity. For notifications
  specifically this also means skipping the real
  org.freedesktop.Notifications D-Bus registration entirely in screenshot
  mode — claiming that well-known name would just race the real breadbar
  (if running) for it, for no benefit, since nothing external needs to
  reach a screenshot-only instance.

show_add_network_dialog gained an `on_build` hook (called before
`.present()`, the only point `connect_map` can still catch the map) so
screenshot mode can capture it without changing its one real call site's
behavior — and its `anchor` parameter widened from `&Button` to
`&impl IsA<Widget>` since the screenshot path anchors off a
`ToggleButton`, not a `Button`.
This commit is contained in:
Breadway 2026-07-29 17:17:17 +08:00
parent 059e11cdeb
commit 566aeeed8b
5 changed files with 335 additions and 84 deletions

View file

@ -9,14 +9,50 @@ enum OsdEvent {
Brightness { pct: u8 },
}
pub fn spawn() {
/// A fixed sample event for `--screenshot osd-volume`/`osd-brightness` —
/// substitutes for the real `pactl subscribe`/backlight-sysfs watchers so a
/// capture doesn't depend on this machine's actual volume/brightness at
/// capture time.
pub enum SampleKind {
Volume,
Brightness,
}
impl SampleKind {
fn sample_event(&self) -> OsdEvent {
match self {
SampleKind::Volume => OsdEvent::Volume { pct: 65, muted: false },
SampleKind::Brightness => OsdEvent::Brightness { pct: 80 },
}
}
}
/// Builds the OSD window synchronously (so a caller — screenshot mode, via
/// `sample`, in particular — has a real window to hook `connect_map` on
/// before the async event loop below ever runs) and spawns the event loop
/// that shows/updates/hides it.
///
/// `sample`: `Some` skips the real volume/brightness watchers entirely and
/// seeds the loop with one fixed sample event instead — screenshot mode
/// only, so a capture never depends on (or is disrupted by) this machine's
/// actual audio/backlight state.
pub fn spawn(sample: Option<SampleKind>) -> gtk4::Window {
let (tx, rx) = mpsc::channel::<OsdEvent>(8);
let tx1 = tx.clone();
std::thread::spawn(move || volume_watcher(tx1));
std::thread::spawn(move || brightness_watcher(tx));
match sample {
Some(kind) => {
let _ = tx.try_send(kind.sample_event());
}
None => {
let tx1 = tx.clone();
std::thread::spawn(move || volume_watcher(tx1));
std::thread::spawn(move || brightness_watcher(tx));
}
}
relm4::spawn_local(run_osd(rx));
let window = create_window();
relm4::spawn_local(run_osd(window.clone(), rx));
window
}
fn volume_watcher(tx: mpsc::Sender<OsdEvent>) {
@ -119,9 +155,7 @@ fn brightness_watcher(tx: mpsc::Sender<OsdEvent>) {
}
}
async fn run_osd(mut rx: mpsc::Receiver<OsdEvent>) {
let window = create_window();
async fn run_osd(window: gtk4::Window, mut rx: mpsc::Receiver<OsdEvent>) {
let container = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
container.set_margin_top(10);
container.set_margin_bottom(10);