feat: add system tray icon with minimize-to-tray behavior

Enable tray-icon feature in Tauri dependencies. Add system tray with Open and Quit menu items. Implement tray icon click handlers to restore main window. Add window event handlers to hide window on close/minimize instead of exiting application. Add restore_main_window and hide_main_window helper functions for window visibility management.
This commit is contained in:
2026-03-17 19:56:46 +01:00
parent f596f89665
commit b288f0d155
2 changed files with 68 additions and 2 deletions

View File

@@ -18,5 +18,5 @@ rand_core = { version = "0.6", features = ["getrandom"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tauri = { version = "2.10.1", features = [] } tauri = { version = "2.10.1", features = ["tray-icon"] }
x25519-dalek = { version = "2.0", features = ["static_secrets"] } x25519-dalek = { version = "2.0", features = ["static_secrets"] }

View File

@@ -6,10 +6,15 @@ use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
use rand_core::OsRng; use rand_core::OsRng;
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager, State}; use tauri::{
menu::{MenuBuilder, MenuItemBuilder},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
AppHandle, Manager, State, WebviewWindow, WindowEvent,
};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::{PublicKey, StaticSecret};
const PROFILE_NAME: &str = "NexaVPN"; const PROFILE_NAME: &str = "NexaVPN";
const MAIN_WINDOW_LABEL: &str = "main";
struct AppState { struct AppState {
session: Mutex<Option<SessionState>>, session: Mutex<Option<SessionState>>,
@@ -263,12 +268,73 @@ fn ensure_app_dir(app: &AppHandle) -> Result<PathBuf, String> {
Ok(dir) Ok(dir)
} }
fn restore_main_window(window: &WebviewWindow) {
let _ = window.show();
let _ = window.unminimize();
let _ = window.set_focus();
}
fn hide_main_window(window: &WebviewWindow) {
let _ = window.hide();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.setup(|app| {
let open_item = MenuItemBuilder::with_id("open", "Open NexaVPN").build(app)?;
let quit_item = MenuItemBuilder::with_id("quit", "Quit NexaVPN").build(app)?;
let menu = MenuBuilder::new(app).items(&[&open_item, &quit_item]).build()?;
let mut tray = TrayIconBuilder::new().menu(&menu).show_menu_on_left_click(false);
if let Some(icon) = app.default_window_icon() {
tray = tray.icon(icon.clone());
}
tray
.on_menu_event(|app, event| match event.id().as_ref() {
"open" => {
if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
restore_main_window(&window);
}
}
"quit" => {
app.exit(0);
}
_ => {}
})
.on_tray_icon_event(|tray, event| {
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} = event
{
let app = tray.app_handle();
if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) {
restore_main_window(&window);
}
}
})
.build(app)?;
Ok(())
})
.manage(AppState { .manage(AppState {
session: Mutex::new(None), session: Mutex::new(None),
}) })
.on_window_event(|window, event| match event {
WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
hide_main_window(window);
}
WindowEvent::Resized(_) => {
if window.is_minimized().unwrap_or(false) {
hide_main_window(window);
}
}
_ => {}
})
.invoke_handler(tauri::generate_handler![load_state, enroll_device, connect_tunnel, disconnect_tunnel]) .invoke_handler(tauri::generate_handler![load_state, enroll_device, connect_tunnel, disconnect_tunnel])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");