Daylight bar chrome + embedded launcher, v0.7.5 pins #5
2 changed files with 69 additions and 0 deletions
19
src/main.rs
19
src/main.rs
|
|
@ -3333,6 +3333,25 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Best-effort cleanup when killed (Ctrl+C in the terminal, a dev-loop
|
||||||
|
// `kill`, systemd stop): kill the OSD's `pactl subscribe` children
|
||||||
|
// before exiting; the exit hook in osd.rs covers normal exits and
|
||||||
|
// panics. Without either, each breadbar restart orphaned a `pactl
|
||||||
|
// subscribe` holding its PulseAudio connection open until
|
||||||
|
// pipewire-pulse's client cap filled up and new clients — settings
|
||||||
|
// apps among them — were refused ("no devices in settings").
|
||||||
|
relm4::spawn(async {
|
||||||
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
|
let mut term = signal(SignalKind::terminate()).expect("SIGTERM handler");
|
||||||
|
let mut intr = signal(SignalKind::interrupt()).expect("SIGINT handler");
|
||||||
|
tokio::select! {
|
||||||
|
_ = term.recv() => {}
|
||||||
|
_ = intr.recv() => {}
|
||||||
|
}
|
||||||
|
crate::osd::kill_watchers();
|
||||||
|
std::process::exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
// `with_args(vec![])` stops relm4 from handing our own --screenshot/
|
// `with_args(vec![])` stops relm4 from handing our own --screenshot/
|
||||||
// --output flags to GLib's option parser (`app.run()`'s default), which
|
// --output flags to GLib's option parser (`app.run()`'s default), which
|
||||||
// would otherwise reject them as unrecognized before Cli::parse() above
|
// would otherwise reject them as unrecognized before Cli::parse() above
|
||||||
|
|
|
||||||
50
src/osd.rs
50
src/osd.rs
|
|
@ -1,6 +1,8 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
process::Child,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
sync::{Mutex, Once},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -22,6 +24,46 @@ pub enum SampleKind {
|
||||||
Brightness,
|
Brightness,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Live `pactl subscribe` children spawned by [`volume_watcher`]. Kept so a
|
||||||
|
/// best-effort cleanup can kill them when breadbar exits: `pactl subscribe`
|
||||||
|
/// blocks until the server connection dies, so without this every breadbar
|
||||||
|
/// restart orphaned one that kept its PulseAudio connection open — until
|
||||||
|
/// pipewire-pulse's client cap filled up and new clients (settings apps
|
||||||
|
/// included) were refused, showing "no devices". Also reaped here, so a
|
||||||
|
/// watcher that dies on its own never lingers as a zombie.
|
||||||
|
static WATCHER_CHILDREN: Mutex<Vec<Child>> = Mutex::new(Vec::new());
|
||||||
|
static REGISTER_EXIT_HOOK: Once = Once::new();
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
/// libc `atexit(3)`. Declared directly rather than pulling the libc
|
||||||
|
/// crate in for a single function.
|
||||||
|
fn atexit(cb: extern "C" fn()) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn exit_cleanup() {
|
||||||
|
kill_watchers();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_exit_hook() {
|
||||||
|
// Safety: `atexit` is provided by libc on every Linux target; the
|
||||||
|
// callback is a `static` C-ABI fn valid for the whole process.
|
||||||
|
unsafe {
|
||||||
|
let _ = atexit(exit_cleanup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kill any live `pactl subscribe` watcher children and reap them. Safe to
|
||||||
|
/// call more than once (an already-dead child is a no-op). Runs from the
|
||||||
|
/// process-exit hook and the SIGINT/SIGTERM handlers in `main`.
|
||||||
|
pub fn kill_watchers() {
|
||||||
|
if let Ok(mut children) = WATCHER_CHILDREN.lock() {
|
||||||
|
for mut child in children.drain(..) {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SampleKind {
|
impl SampleKind {
|
||||||
fn sample_event(&self) -> OsdEvent {
|
fn sample_event(&self) -> OsdEvent {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -73,6 +115,14 @@ fn volume_watcher(tx: mpsc::Sender<OsdEvent>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(stdout) = child.stdout.take() else { return };
|
let Some(stdout) = child.stdout.take() else { return };
|
||||||
|
// The child is meant to outlive this reader loop (it blocks until
|
||||||
|
// breadbar itself dies), so hand it to `kill_watchers` — the exit
|
||||||
|
// hook plus the SIGINT/SIGTERM handlers in main — instead of letting
|
||||||
|
// it orphan on restart.
|
||||||
|
REGISTER_EXIT_HOOK.call_once(register_exit_hook);
|
||||||
|
if let Ok(mut children) = WATCHER_CHILDREN.lock() {
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
let reader = BufReader::new(stdout);
|
let reader = BufReader::new(stdout);
|
||||||
|
|
||||||
for line in reader.lines().map_while(Result::ok) {
|
for line in reader.lines().map_while(Result::ok) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue