195 lines
8.2 KiB
Rust
195 lines
8.2 KiB
Rust
// 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<Mutex<SharedState>>,
|
|
) -> Option<TrayIcon> { // Return Option<TrayIcon>
|
|
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
|
|
}
|