Add system tray, shutdown-after-finish scheduler, repoint updater
- src/tray.rs: system tray with show/hide/quit menu, left-click
toggles main window visibility (minimize-to-tray parity with v1).
- src/shutdown.rs: 60s countdown with per-second 'shutdown-countdown'
event; sleep/shutdown/restart via rundll32/shutdown on Windows,
cancel-aware.
- cancel_shutdown + set_shutdown_after_finish commands now drive the
scheduler so the renderer's existing countdown UI works unchanged.
- Cargo features + tray-icon + image-png added.
- Updater pointed at new Gitea repo Administrator/Multi-Hoster-Upload-2.
This commit is contained in:
parent
100bda60cd
commit
2958dca282
57
src-tauri/Cargo.lock
generated
57
src-tauri/Cargo.lock
generated
@ -477,6 +477,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
@ -2080,7 +2086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"png",
|
||||
"png 0.17.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2198,6 +2204,19 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@ -2730,6 +2749,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.17.2"
|
||||
@ -2745,7 +2774,7 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"png",
|
||||
"png 0.17.16",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.60.2",
|
||||
@ -3454,6 +3483,19 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
@ -3610,6 +3652,12 @@ dependencies = [
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.23.1"
|
||||
@ -4845,6 +4893,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"image",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
@ -4912,7 +4961,7 @@ dependencies = [
|
||||
"ico",
|
||||
"json-patch",
|
||||
"plist",
|
||||
"png",
|
||||
"png 0.17.16",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"semver",
|
||||
@ -5587,7 +5636,7 @@ dependencies = [
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"png",
|
||||
"png 0.17.16",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.60.2",
|
||||
|
||||
@ -10,7 +10,7 @@ rust-version = "1.77"
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["tray-icon", "image-png"] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-opener = "2"
|
||||
|
||||
@ -26,6 +26,7 @@ pub struct AppState {
|
||||
pub folder_monitor: Arc<crate::folder_monitor::FolderMonitor>,
|
||||
pub remote_server: Arc<crate::remote_server::RemoteServer>,
|
||||
pub otp_broker: Arc<crate::otp::OtpBroker>,
|
||||
pub shutdown_scheduler: Arc<crate::shutdown::ShutdownScheduler>,
|
||||
pub rot_log_path: Mutex<PathBuf>,
|
||||
pub upload_log_path: Mutex<PathBuf>,
|
||||
pub always_on_top: PLMutex<bool>,
|
||||
@ -107,8 +108,17 @@ pub async fn get_always_on_top(state: State<'_, AppState>) -> AppResult<bool> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_shutdown_after_finish(state: State<'_, AppState>, mode: String) -> AppResult<()> {
|
||||
*state.shutdown_mode.lock() = mode;
|
||||
pub async fn set_shutdown_after_finish(
|
||||
app: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
mode: String,
|
||||
) -> AppResult<()> {
|
||||
*state.shutdown_mode.lock() = mode.clone();
|
||||
if mode != "nothing" {
|
||||
state.shutdown_scheduler.schedule(app, mode);
|
||||
} else {
|
||||
state.shutdown_scheduler.cancel();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -120,6 +130,7 @@ pub async fn get_shutdown_after_finish(state: State<'_, AppState>) -> AppResult<
|
||||
#[tauri::command]
|
||||
pub async fn cancel_shutdown(state: State<'_, AppState>) -> AppResult<()> {
|
||||
*state.shutdown_mode.lock() = "nothing".into();
|
||||
state.shutdown_scheduler.cancel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ pub mod remote_server;
|
||||
pub mod updater;
|
||||
pub mod upload_log;
|
||||
pub mod otp;
|
||||
pub mod tray;
|
||||
pub mod shutdown;
|
||||
pub mod commands;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -53,12 +55,20 @@ pub fn run() {
|
||||
let otp_broker = otp::OtpBroker::new();
|
||||
manager.set_otp_broker(otp_broker.clone(), app.handle().clone());
|
||||
|
||||
let shutdown_scheduler = shutdown::ShutdownScheduler::new();
|
||||
|
||||
// System tray (Windows/Linux/macOS). Non-fatal if it fails.
|
||||
if let Err(e) = tray::install(app.handle()) {
|
||||
tracing::warn!("tray install: {e}");
|
||||
}
|
||||
|
||||
app.manage(commands::AppState {
|
||||
config: store.clone(),
|
||||
uploads: manager,
|
||||
folder_monitor: fm.clone(),
|
||||
remote_server: remote.clone(),
|
||||
otp_broker,
|
||||
shutdown_scheduler,
|
||||
rot_log_path: Mutex::new(data_dir.join("account-rotation.log")),
|
||||
upload_log_path: Mutex::new(data_dir.join("fileuploader.log")),
|
||||
always_on_top: parking_lot::Mutex::new(false),
|
||||
|
||||
71
src-tauri/src/shutdown.rs
Normal file
71
src-tauri/src/shutdown.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! Shutdown-after-finish — emits a 60 second countdown to the renderer so
|
||||
//! the user can cancel, then runs the chosen system command.
|
||||
//!
|
||||
//! Modes: "nothing" | "sleep" | "shutdown" | "restart".
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
pub struct ShutdownScheduler {
|
||||
pending: Arc<AtomicBool>,
|
||||
cancel: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ShutdownScheduler {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
pending: Arc::new(AtomicBool::new(false)),
|
||||
cancel: Arc::new(AtomicBool::new(false)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
if self.pending.load(Ordering::Relaxed) {
|
||||
self.cancel.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule(self: &Arc<Self>, app: AppHandle, mode: String) {
|
||||
if mode == "nothing" || self.pending.load(Ordering::Relaxed) { return; }
|
||||
self.pending.store(true, Ordering::Relaxed);
|
||||
self.cancel.store(false, Ordering::Relaxed);
|
||||
let pending = self.pending.clone();
|
||||
let cancel = self.cancel.clone();
|
||||
let mode_clone = mode.clone();
|
||||
tokio::spawn(async move {
|
||||
for remaining in (0..=60u32).rev() {
|
||||
if cancel.load(Ordering::Relaxed) {
|
||||
let _ = app.emit("shutdown-countdown",
|
||||
serde_json::json!({ "mode": mode_clone, "seconds": 0, "cancelled": true }));
|
||||
pending.store(false, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
let _ = app.emit("shutdown-countdown",
|
||||
serde_json::json!({ "mode": mode_clone, "seconds": remaining }));
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
if !cancel.load(Ordering::Relaxed) {
|
||||
execute(&mode_clone);
|
||||
app.exit(0);
|
||||
}
|
||||
pending.store(false, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(mode: &str) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::process::Command;
|
||||
match mode {
|
||||
"sleep" => { Command::new("rundll32.exe").args(["powrprof.dll,SetSuspendState", "0,1,0"]).spawn().ok(); }
|
||||
"shutdown" => { Command::new("shutdown").args(["/s", "/t", "0"]).spawn().ok(); }
|
||||
"restart" => { Command::new("shutdown").args(["/r", "/t", "0"]).spawn().ok(); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{ let _ = mode; }
|
||||
}
|
||||
42
src-tauri/src/tray.rs
Normal file
42
src-tauri/src/tray.rs
Normal file
@ -0,0 +1,42 @@
|
||||
//! System tray icon — matches v1's behavior: minimize-to-tray, left-click
|
||||
//! restores window, right-click menu for show / hide / quit.
|
||||
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
AppHandle, Manager,
|
||||
};
|
||||
|
||||
pub fn install(app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let show = MenuItem::with_id(app, "show", "Anzeigen", true, None::<&str>)?;
|
||||
let hide = MenuItem::with_id(app, "hide", "Ausblenden", true, None::<&str>)?;
|
||||
let sep = tauri::menu::PredefinedMenuItem::separator(app)?;
|
||||
let quit = MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?;
|
||||
let menu = Menu::with_items(app, &[&show, &hide, &sep, &quit])?;
|
||||
|
||||
let _tray = TrayIconBuilder::with_id("main-tray")
|
||||
.tooltip("Multi-Hoster-Upload")
|
||||
.icon(app.default_window_icon().cloned().unwrap_or_else(|| {
|
||||
// Fallback to a 1x1 transparent PNG — Tauri requires an image.
|
||||
tauri::image::Image::new_owned(vec![0u8; 4], 1, 1)
|
||||
}))
|
||||
.menu(&menu)
|
||||
.menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"show" => { if let Some(w) = app.get_webview_window("main") { let _ = w.show(); let _ = w.set_focus(); } }
|
||||
"hide" => { if let Some(w) = app.get_webview_window("main") { let _ = w.hide(); } }
|
||||
"quit" => { app.exit(0); }
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, ev| {
|
||||
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = ev {
|
||||
let app = tray.app_handle();
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let visible = w.is_visible().unwrap_or(true);
|
||||
if visible { let _ = w.hide(); } else { let _ = w.show(); let _ = w.set_focus(); }
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -8,7 +8,7 @@ use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
const REPO: &str = "Administrator/Multi-Hoster-Upload";
|
||||
const REPO: &str = "Administrator/Multi-Hoster-Upload-2";
|
||||
const BASE: &str = "https://git.24-music.de/api/v1/repos";
|
||||
|
||||
#[derive(Serialize, Default, Clone, Debug)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user