Add system tray icon (Show/Hide/Refresh/Quit; left-click restores window)
Solves the 'lost window' problem — if the widget gets dragged off-screen or hidden behind other apps, users can recover it via the tray. Enables tauri 'tray-icon' feature; tray runs entirely Rust-side so no new JS capabilities needed.
This commit is contained in:
parent
9be856d37c
commit
79fc144235
3 changed files with 75 additions and 1 deletions
|
|
@ -52,6 +52,9 @@ displays, refreshed every 5 minutes.
|
|||
- **⚙** — Settings (custom claude command, refresh interval, autostart, distro override).
|
||||
- **×** — quit.
|
||||
- The window is **resizable** — drag any edge.
|
||||
- **System tray** — there's a tray icon next to the Windows clock while the
|
||||
widget is running. Left-click to bring the window back if you lose it
|
||||
off-screen; right-click for Show / Hide / Refresh / Quit.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["tray-icon"] }
|
||||
tauri-plugin-autostart = "2"
|
||||
tauri-plugin-store = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
|
|
|||
|
|
@ -13,8 +13,24 @@ mod usage;
|
|||
mod watch;
|
||||
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri::menu::{MenuBuilder, MenuItem};
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
use std::time::Duration;
|
||||
|
||||
fn show_main_window(app: &tauri::AppHandle) {
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let _ = w.show();
|
||||
let _ = w.unminimize();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_main_window(app: &tauri::AppHandle) {
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let _ = w.hide();
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_cli_loop(app: tauri::AppHandle, state: state::SharedState) {
|
||||
// Small initial delay so we don't compete with cold-start file scanning.
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
|
@ -66,6 +82,61 @@ pub fn run() {
|
|||
let shared = state::AppState::new(settings);
|
||||
app.manage(shared.clone());
|
||||
|
||||
// Tray icon: always-visible recovery handle in case the window
|
||||
// gets lost (off-screen, hidden by another app, etc.).
|
||||
let show_item = MenuItem::with_id(app, "tray-show", "Show", true, None::<&str>)?;
|
||||
let hide_item = MenuItem::with_id(app, "tray-hide", "Hide", true, None::<&str>)?;
|
||||
let refresh_item = MenuItem::with_id(app, "tray-refresh", "Refresh /usage", true, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "tray-quit", "Quit", true, None::<&str>)?;
|
||||
|
||||
let menu = MenuBuilder::new(app)
|
||||
.item(&show_item)
|
||||
.item(&hide_item)
|
||||
.separator()
|
||||
.item(&refresh_item)
|
||||
.separator()
|
||||
.item(&quit_item)
|
||||
.build()?;
|
||||
|
||||
let state_for_tray = shared.clone();
|
||||
let _tray = TrayIconBuilder::with_id("main-tray")
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.tooltip("Claude Usage Widget")
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(move |app, event| match event.id().as_ref() {
|
||||
"tray-show" => show_main_window(app),
|
||||
"tray-hide" => hide_main_window(app),
|
||||
"tray-refresh" => {
|
||||
let app = app.clone();
|
||||
let state = state_for_tray.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let cmd = state.settings.read().claude_command.clone();
|
||||
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||
cli_usage::fetch_blocking(cmd.as_deref())
|
||||
})
|
||||
.await;
|
||||
if let Ok(Ok(u)) = result {
|
||||
*state.cli_usage.write() = Some(u.clone());
|
||||
let _ = app.emit("cli-usage-updated", &u);
|
||||
}
|
||||
});
|
||||
}
|
||||
"tray-quit" => app.exit(0),
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
show_main_window(tray.app_handle());
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
let handle = app.handle().clone();
|
||||
let state_for_task = shared.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue