Add download progress and graceful tray shutdown
- proton: wrap the download sink in a small ProgressWriter that prints percent / MiB to stderr every 1 MiB so the ~600 MB GE-Proton pull isn't silent for minutes. No extra deps. - tray: store the ksni Handle on the tray itself so Quit can call shutdown() before exit(), unregistering the SNI item from D-Bus instead of leaving a stale entry until the session bus notices the PID is gone. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+54
-2
@@ -79,9 +79,12 @@ fn install_version(config: &Config, tag: &str) -> Result<()> {
|
||||
.context("Download failed")?
|
||||
.error_for_status()
|
||||
.context("Download returned an error status")?;
|
||||
let mut f = std::fs::File::create(&tmp_path)
|
||||
let total = resp.content_length();
|
||||
let f = std::fs::File::create(&tmp_path)
|
||||
.with_context(|| format!("Failed to create temp file {tmp_path:?}"))?;
|
||||
std::io::copy(&mut resp, &mut f).context("Failed to stream archive to disk")?;
|
||||
let mut progress = ProgressWriter::new(f, total);
|
||||
std::io::copy(&mut resp, &mut progress).context("Failed to stream archive to disk")?;
|
||||
progress.finish();
|
||||
}
|
||||
|
||||
println!("Extracting to {:?}...", config.proton_compat_dir);
|
||||
@@ -156,6 +159,55 @@ fn print_list(config: &Config) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wraps a writer to print download progress to stderr, throttled to one
|
||||
/// update per megabyte so we don't spam the terminal.
|
||||
struct ProgressWriter<W: Write> {
|
||||
inner: W,
|
||||
total: Option<u64>,
|
||||
written: u64,
|
||||
last_print: u64,
|
||||
}
|
||||
|
||||
impl<W: Write> ProgressWriter<W> {
|
||||
fn new(inner: W, total: Option<u64>) -> Self {
|
||||
Self { inner, total, written: 0, last_print: 0 }
|
||||
}
|
||||
|
||||
fn finish(&mut self) {
|
||||
let _ = self.inner.flush();
|
||||
// Clear the progress line — the caller's next println! starts fresh.
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for ProgressWriter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let n = self.inner.write(buf)?;
|
||||
self.written += n as u64;
|
||||
if self.written - self.last_print >= 1 << 20 {
|
||||
self.last_print = self.written;
|
||||
match self.total {
|
||||
Some(t) => {
|
||||
let pct = (self.written as f64 / t as f64) * 100.0;
|
||||
eprint!(
|
||||
"\r {:.1}% ({} / {} MiB)",
|
||||
pct,
|
||||
self.written >> 20,
|
||||
t >> 20,
|
||||
);
|
||||
}
|
||||
None => eprint!("\r {} MiB", self.written >> 20),
|
||||
}
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_interactively(config: &Config) -> Result<String> {
|
||||
let releases = fetch_releases(10)?;
|
||||
if releases.is_empty() {
|
||||
|
||||
+23
-2
@@ -7,6 +7,9 @@ pub struct BattlenetTray {
|
||||
pub config: Config,
|
||||
/// Whether Battle.net is currently running; updated by background poller.
|
||||
pub running: bool,
|
||||
/// Set after the service spawns so Quit can shut down the SNI item
|
||||
/// cleanly instead of yanking it off the bus via exit().
|
||||
pub handle: Option<ksni::Handle<BattlenetTray>>,
|
||||
}
|
||||
|
||||
impl ksni::Tray for BattlenetTray {
|
||||
@@ -113,8 +116,18 @@ impl ksni::Tray for BattlenetTray {
|
||||
StandardItem {
|
||||
label: "Quit".into(),
|
||||
icon_name: "application-exit".into(),
|
||||
activate: Box::new(|_this: &mut Self| {
|
||||
activate: Box::new(|this: &mut Self| {
|
||||
if let Some(h) = this.handle.clone() {
|
||||
// Run shutdown off-thread: we're currently holding
|
||||
// the tray lock inside update(), and shutdown wants
|
||||
// the service loop to turn.
|
||||
thread::spawn(move || {
|
||||
h.shutdown();
|
||||
std::process::exit(0);
|
||||
});
|
||||
} else {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
@@ -130,6 +143,7 @@ pub fn run(config: &Config) -> Result<()> {
|
||||
let tray = BattlenetTray {
|
||||
config: config.clone(),
|
||||
running: launcher::is_running(),
|
||||
handle: None,
|
||||
};
|
||||
|
||||
let service = ksni::TrayService::new(tray);
|
||||
@@ -137,10 +151,17 @@ pub fn run(config: &Config) -> Result<()> {
|
||||
let handle = service.handle();
|
||||
service.spawn();
|
||||
|
||||
// Hand the tray a clone of its own handle so Quit can shut down cleanly.
|
||||
let handle_for_self = handle.clone();
|
||||
handle.update(move |t: &mut BattlenetTray| {
|
||||
t.handle = Some(handle_for_self);
|
||||
});
|
||||
|
||||
// Background thread: poll Battle.net process state every 2 s and update the tray.
|
||||
let poll_handle = handle;
|
||||
thread::spawn(move || loop {
|
||||
let running = launcher::is_running();
|
||||
handle.update(|tray: &mut BattlenetTray| {
|
||||
poll_handle.update(|tray: &mut BattlenetTray| {
|
||||
tray.running = running;
|
||||
});
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
|
||||
Reference in New Issue
Block a user