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")?
|
.context("Download failed")?
|
||||||
.error_for_status()
|
.error_for_status()
|
||||||
.context("Download returned an error 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:?}"))?;
|
.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);
|
println!("Extracting to {:?}...", config.proton_compat_dir);
|
||||||
@@ -156,6 +159,55 @@ fn print_list(config: &Config) -> Result<()> {
|
|||||||
Ok(())
|
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> {
|
fn pick_interactively(config: &Config) -> Result<String> {
|
||||||
let releases = fetch_releases(10)?;
|
let releases = fetch_releases(10)?;
|
||||||
if releases.is_empty() {
|
if releases.is_empty() {
|
||||||
|
|||||||
+24
-3
@@ -7,6 +7,9 @@ pub struct BattlenetTray {
|
|||||||
pub config: Config,
|
pub config: Config,
|
||||||
/// Whether Battle.net is currently running; updated by background poller.
|
/// Whether Battle.net is currently running; updated by background poller.
|
||||||
pub running: bool,
|
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 {
|
impl ksni::Tray for BattlenetTray {
|
||||||
@@ -113,8 +116,18 @@ impl ksni::Tray for BattlenetTray {
|
|||||||
StandardItem {
|
StandardItem {
|
||||||
label: "Quit".into(),
|
label: "Quit".into(),
|
||||||
icon_name: "application-exit".into(),
|
icon_name: "application-exit".into(),
|
||||||
activate: Box::new(|_this: &mut Self| {
|
activate: Box::new(|this: &mut Self| {
|
||||||
std::process::exit(0);
|
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()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@@ -130,6 +143,7 @@ pub fn run(config: &Config) -> Result<()> {
|
|||||||
let tray = BattlenetTray {
|
let tray = BattlenetTray {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
running: launcher::is_running(),
|
running: launcher::is_running(),
|
||||||
|
handle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let service = ksni::TrayService::new(tray);
|
let service = ksni::TrayService::new(tray);
|
||||||
@@ -137,10 +151,17 @@ pub fn run(config: &Config) -> Result<()> {
|
|||||||
let handle = service.handle();
|
let handle = service.handle();
|
||||||
service.spawn();
|
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.
|
// Background thread: poll Battle.net process state every 2 s and update the tray.
|
||||||
|
let poll_handle = handle;
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
let running = launcher::is_running();
|
let running = launcher::is_running();
|
||||||
handle.update(|tray: &mut BattlenetTray| {
|
poll_handle.update(|tray: &mut BattlenetTray| {
|
||||||
tray.running = running;
|
tray.running = running;
|
||||||
});
|
});
|
||||||
thread::sleep(Duration::from_secs(2));
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|||||||
Reference in New Issue
Block a user