diff --git a/Cargo.toml b/Cargo.toml index e750b59..fe967e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ categories = ["gui", "network-programming"] botlib = { workspace = true, features = ["http-client"] } # Tauri -tauri = { workspace = true } +tauri = { workspace = true, features = ["tray-icon", "image"] } tauri-plugin-dialog = { workspace = true } tauri-plugin-opener = { workspace = true } @@ -26,22 +26,20 @@ env_logger = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } -reqwest = { workspace = true, features = ["json"] } +reqwest = { workspace = true, features = ["json", "rustls-tls"] } # Unix process control [target.'cfg(unix)'.dependencies] libc = { workspace = true } -ksni = { workspace = true, optional = true } [target.'cfg(windows)'.dependencies] -trayicon = { workspace = true, optional = true } image = { workspace = true } thiserror = { workspace = true } [features] default = ["desktop"] -desktop = [] -desktop-tray = ["dep:ksni", "dep:trayicon"] +desktop = ["desktop-tray"] +desktop-tray = [] [build-dependencies] tauri-build = { workspace = true } diff --git a/src/desktop/tray.rs b/src/desktop/tray.rs index 150d692..993ff97 100644 --- a/src/desktop/tray.rs +++ b/src/desktop/tray.rs @@ -3,12 +3,27 @@ use anyhow::Result; use serde::Serialize; use std::sync::Arc; use tokio::sync::RwLock; +use tauri::AppHandle; +use tauri::tray::{TrayIcon, TrayIconBuilder}; +use tauri::menu::{Menu, MenuItem}; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct TrayManager { hostname: Arc>>, running_mode: RunningMode, tray_active: Arc>, + #[cfg(feature = "desktop-tray")] + tray_handle: Arc>>, +} + +impl std::fmt::Debug for TrayManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TrayManager") + .field("hostname", &self.hostname) + .field("running_mode", &self.running_mode) + .field("tray_active", &self.tray_active) + .finish() + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,6 +48,8 @@ impl TrayManager { hostname: Arc::new(RwLock::new(None)), running_mode: RunningMode::Desktop, tray_active: Arc::new(RwLock::new(false)), + #[cfg(feature = "desktop-tray")] + tray_handle: Arc::new(std::sync::Mutex::new(None)), } } @@ -42,71 +59,83 @@ impl TrayManager { hostname: Arc::new(RwLock::new(None)), running_mode: mode, tray_active: Arc::new(RwLock::new(false)), + #[cfg(feature = "desktop-tray")] + tray_handle: Arc::new(std::sync::Mutex::new(None)), } } - pub async fn start(&self) -> Result<()> { + pub async fn start(&self, app: &AppHandle) -> Result<()> { match self.running_mode { RunningMode::Desktop => { - self.start_desktop_mode().await?; + self.start_desktop_mode(app).await?; } RunningMode::Server => { log::info!("Running in server mode - tray icon disabled"); } RunningMode::Client => { - self.start_client_mode().await; + self.start_client_mode(app).await; } } Ok(()) } - async fn start_desktop_mode(&self) -> Result<()> { + pub async fn start_desktop_mode(&self, app: &AppHandle) -> Result<()> { log::info!("Starting desktop mode tray icon"); let mut active = self.tray_active.write().await; *active = true; drop(active); - #[cfg(target_os = "linux")] - self.setup_linux_tray(); - - #[cfg(target_os = "windows")] - self.setup_windows_tray(); - - #[cfg(target_os = "macos")] - self.setup_macos_tray(); - + self.setup_tray(app); Ok(()) } - async fn start_client_mode(&self) { + fn setup_tray(&self, app: &AppHandle) { + #[cfg(feature = "desktop-tray")] + { + log::info!( + "Initializing unified system tray via tauri::tray for mode: {:?}", + self.running_mode + ); + + let tray_menu = Menu::new(app).unwrap(); + let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap(); + let _ = tray_menu.append(&quit_i); + + // Create a simple red icon + let w = 32; + let h = 32; + let mut rgba = Vec::with_capacity((w * h * 4) as usize); + for _ in 0..(w * h) { + rgba.extend_from_slice(&[255, 0, 0, 255]); // Red + } + + let icon = tauri::image::Image::new_owned(rgba, w, h); + + let tray_builder = TrayIconBuilder::with_id("main") + .menu(&tray_menu) + .tooltip("General Bots") + .icon(icon); + + match tray_builder.build(app) { + Ok(tray) => { + if let Ok(mut handle) = self.tray_handle.lock() { + *handle = Some(tray); + log::info!("Tray icon created successfully"); + } + } + Err(e) => { + log::error!("Failed to build tray icon: {}", e); + } + } + } + } + + async fn start_client_mode(&self, app: &AppHandle) { log::info!("Starting client mode with minimal tray"); let mut active = self.tray_active.write().await; *active = true; drop(active); - } - - #[cfg(target_os = "linux")] - fn setup_linux_tray(&self) { - log::info!( - "Initializing Linux system tray via DBus/StatusNotifierItem for mode: {:?}", - self.running_mode - ); - } - - #[cfg(target_os = "windows")] - fn setup_windows_tray(&self) { - log::info!( - "Initializing Windows system tray via Shell_NotifyIcon for mode: {:?}", - self.running_mode - ); - } - - #[cfg(target_os = "macos")] - fn setup_macos_tray(&self) { - log::info!( - "Initializing macOS menu bar via NSStatusItem for mode: {:?}", - self.running_mode - ); + self.setup_tray(app); } #[must_use] diff --git a/src/main.rs b/src/main.rs index 38786c7..c528695 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,8 @@ async fn get_tray_status(tray: tauri::State<'_, TrayManager>) -> Result) -> Result<(), String> { - tray.start().await.map_err(|e| e.to_string()) +async fn start_tray(tray: tauri::State<'_, TrayManager>, app: tauri::AppHandle) -> Result<(), String> { + tray.start(&app).await.map_err(|e| e.to_string()) } #[tauri::command] @@ -208,6 +208,7 @@ fn main() { info!("BotApp setup complete in {mode} mode"); let tray_clone = tray.inner().clone(); + let app_handle = app.handle().clone(); std::thread::spawn(move || { let rt = match tokio::runtime::Runtime::new() { Ok(rt) => rt, @@ -217,7 +218,7 @@ fn main() { } }; rt.block_on(async { - if let Err(e) = tray_clone.start().await { + if let Err(e) = tray_clone.start(&app_handle).await { log::error!("Failed to start tray: {e}"); } });