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:
@@ -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"] }
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user