// System tray functionality use std::{ sync::{Arc, Mutex}, thread, }; use ::image::open as image_open; use tray_icon::{ TrayIconBuilder, TrayIcon, TrayIconEvent, MouseButton, menu::{Menu, MenuEvent}, }; use crate::app::{Message, CursorInfo, TRAY_SENDER, SharedState}; // Define menu item IDs const NEXT_CURSOR_ID: &str = "next_cursor"; const SHOW_WINDOW_ID: &str = "show_window"; const QUIT_ID: &str = "quit"; // Set up the system tray icon pub fn setup_tray_icon( cursors: &[CursorInfo], shared_state: Arc>, ) -> Option { // Return Option if cursors.is_empty() { return None; // Return None if no cursors } // Create a menu let menu = Menu::with_items(&[ &tray_icon::menu::MenuItem::with_id(NEXT_CURSOR_ID, "Next cursor", true, None), &tray_icon::menu::MenuItem::with_id(SHOW_WINDOW_ID, "Show window", true, None), &tray_icon::menu::MenuItem::with_id(QUIT_ID, "Quit", true, None), ]).ok()?; // Use ok()? to convert Result to Option // Use the first PNG as the tray icon let icon_path = &cursors[0].path; let img = image_open(icon_path).ok()?; // Use ok()? let rgba_img = img.to_rgba8(); let (width, height) = rgba_img.dimensions(); let icon = tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height).ok()?; // Use ok()? // Build the tray icon println!("Building tray icon..."); let tray_result = TrayIconBuilder::new() .with_tooltip("Cursor Rush") .with_icon(icon) .with_menu(Box::new(menu)) .with_menu_on_left_click(false) // Disable menu on left click - only show on right click .build(); println!("Tray icon configuration: menu_on_left_click = false"); // Store the tray icon itself let tray = match tray_result { Ok(tray_instance) => { println!("Tray icon built successfully"); tray_instance // Assign the TrayIcon object }, Err(e) => { eprintln!("Failed to build tray icon: {}", e); return None; // Return None on error } }; // Listen for tray menu events in a separate thread let state_clone = shared_state.clone(); println!("Spawning tray menu event listener thread..."); thread::spawn(move || { let menu_channel = MenuEvent::receiver(); println!("Tray menu event listener waiting for events..."); while let Ok(event) = menu_channel.recv() { println!("Tray menu event received: ID = {:?}", event.id); let msg_to_send = match event.id.as_ref() { NEXT_CURSOR_ID => { println!("Tray: Next Cursor requested"); match state_clone.lock() { Ok(mut state) => { if !state.cursors.is_empty() { state.current_index = (state.current_index + 1) % state.cursors.len(); if let Some(cursor) = state.cursors.get(state.current_index) { println!("Tray: Updating cursor image: {}", cursor.name); crate::app::apply_cursor(&cursor.path); // No need to call render_cursor anymore as the mouse hook handles rendering state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8()); } } }, Err(e) => eprintln!("Tray: Failed to lock shared state for next cursor: {}", e), } // Send TrayEvent just to trigger UI update if needed Some(Message::TrayEvent) }, SHOW_WINDOW_ID => { println!("Tray: Show Window requested"); // Send ShowWindow message to main thread Some(Message::ShowWindow) }, QUIT_ID => { println!("Tray: Quit requested"); // Send Quit message to main thread Some(Message::Quit) }, _ => { println!("Tray: Unknown menu event ID: {:?}", event.id); None // Don't send a message for unknown IDs } }; // Send the determined message to the main UI thread using the global sender if let Some(msg) = msg_to_send { // Get the global sender if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { if let Err(e) = tx.unbounded_send(msg.clone()) { // Use clone if msg is needed after send eprintln!("Tray: Failed to send {:?} message: {}", msg, e); break; // Stop thread if channel broken } // If Quit message was sent, break the loop to exit the listener thread if matches!(msg, Message::Quit) { println!("Tray: Quit message sent, exiting listener thread"); break; } } else { eprintln!("Tray: Global sender not initialized yet"); } } } println!("Tray menu event listener thread finished"); }); // Set up a direct event handler for tray icon events // This is the preferred way to handle tray icon clicks println!("Setting up tray icon click event handler..."); // Use set_event_handler to directly handle tray icon events TrayIconEvent::set_event_handler(Some(move |event| { // Only process important events, filter out mouse movements match event { // Handle click events TrayIconEvent::Click { button, .. } => { println!("Tray: Click detected with button: {:?}", button); // Left click should show the window if button == MouseButton::Left { println!("Tray: Left click detected, sending ShowWindow message"); // Get the global sender if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { if let Err(e) = tx.unbounded_send(Message::ShowWindow) { eprintln!("Tray: Failed to send ShowWindow message via handler: {}", e); } } else { eprintln!("Tray: Global sender not initialized yet for click handler"); } } }, // Handle double-click events TrayIconEvent::DoubleClick { button, .. } => { println!("Tray: Double click detected with button: {:?}", button); // Double left click should also show the window if button == MouseButton::Left { println!("Tray: Double left click detected, sending ShowWindow message"); // Get the global sender if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { if let Err(e) = tx.unbounded_send(Message::ShowWindow) { eprintln!("Tray: Failed to send ShowWindow message via handler on double click: {}", e); } } else { eprintln!("Tray: Global sender not initialized yet for double click handler"); } } }, // Silently ignore mouse movement events TrayIconEvent::Move { .. } => { // Don't log anything for mouse movements }, // Silently ignore mouse enter/leave events TrayIconEvent::Enter { .. } | TrayIconEvent::Leave { .. } => { // Don't log anything for mouse enter/leave }, // Log other events for debugging (but not mouse-related ones) _ => { println!("Tray: Event received: {:?}", event); } } })); println!("Tray setup complete using set_event_handler for clicks."); // Updated log Some(tray) // Return the TrayIcon object }