Merged system tray functionality with latest Steam integration

This commit is contained in:
2025-05-04 21:26:36 -05:00
parent 1305481a35
commit 80d091eb71
3 changed files with 496 additions and 137 deletions
+1
View File
@@ -12,6 +12,7 @@ global-hotkey = "0.4.2" # Global hotkey re
lazy_static = "1.4.0" # Thread-safe static initialization lazy_static = "1.4.0" # Thread-safe static initialization
steamworks = { version = "0.11.0", features = ["raw-bindings"] } steamworks = { version = "0.11.0", features = ["raw-bindings"] }
anyhow = "1.0.98" anyhow = "1.0.98"
once_cell = "1.18.0" # Thread-safe lazy initialized global variables
# Platform-specific dependencies # Platform-specific dependencies
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
+459 -127
View File
@@ -1,10 +1,11 @@
use iced::{ use iced::{
widget::{button, text, Column, Row, container, image as iced_image}, widget::{button, text, Column, Row, container, image as iced_image},
Element, Length, Subscription, Task, Theme, Element, Length, Subscription, Task, Theme,
futures::{channel::mpsc, StreamExt},
}; };
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{mpsc, Arc, Mutex}, sync::{Arc, Mutex},
thread, thread,
time::Duration, time::Duration,
}; };
@@ -12,6 +13,9 @@ use ::image::{GenericImageView, RgbaImage, open as image_open};
use walkdir::WalkDir; use walkdir::WalkDir;
use tray_icon::{ use tray_icon::{
TrayIconBuilder, TrayIconBuilder,
TrayIcon, // Import the TrayIcon type itself
TrayIconEvent, // Import TrayIconEvent for handling left/right clicks
MouseButton, // Import MouseButton to distinguish between left and right clicks
menu::{Menu, MenuEvent}, menu::{Menu, MenuEvent},
}; };
use global_hotkey::{ use global_hotkey::{
@@ -37,6 +41,7 @@ struct CursorInfo {
// Define menu item IDs // Define menu item IDs
const NEXT_CURSOR_ID: &str = "next_cursor"; const NEXT_CURSOR_ID: &str = "next_cursor";
const SHOW_WINDOW_ID: &str = "show_window";
const QUIT_ID: &str = "quit"; const QUIT_ID: &str = "quit";
// Shared state between the UI and the tray icon // Shared state between the UI and the tray icon
@@ -45,15 +50,31 @@ struct SharedState {
cursors: Vec<CursorInfo>, cursors: Vec<CursorInfo>,
current_cursor_image: Option<RgbaImage>, current_cursor_image: Option<RgbaImage>,
running: bool, running: bool,
window_visible: bool, // Track if the window is visible
} }
// Our application state // Our application state
#[derive(Default)]
struct CursorRush { struct CursorRush {
cursors: Vec<CursorInfo>, cursors: Vec<CursorInfo>,
current_index: usize, current_index: usize,
shared_state: Option<Arc<Mutex<SharedState>>>, shared_state: Option<Arc<Mutex<SharedState>>>,
cursor_position: (f32, f32), // Track the mouse position main_window: Option<iced::window::Id>, // Store the main window ID
tray_icon: Option<TrayIcon>, // Store the TrayIcon object to keep it alive
tray_tx: Option<mpsc::UnboundedSender<Message>>, // Sender for tray events
}
// Make sure the default impl initializes tray_icon to None
impl Default for CursorRush {
fn default() -> Self {
Self {
cursors: Vec::new(),
current_index: 0,
shared_state: None,
main_window: None,
tray_icon: None, // Initialize as None
tray_tx: None, // Initialize as None
}
}
} }
// Messages that can be sent in our application // Messages that can be sent in our application
@@ -62,8 +83,11 @@ enum Message {
NextCursor, NextCursor,
SelectCursor(usize), SelectCursor(usize),
Quit, Quit,
TrayEvent, TrayEvent, // Used as a no-op message
CursorMoved((f32, f32)), // New message for cursor movement CloseRequested(iced::window::Id), // Message for window close requests with the real window ID
ShowWindow, // Message to show the window from tray
WindowHidden, // Message indicating window was successfully hidden
WindowShown, // Message indicating window was successfully shown
} }
// Function to load all PNG files from the assets directory // Function to load all PNG files from the assets directory
@@ -139,17 +163,74 @@ fn apply_cursor(path: &Path) -> bool {
false false
} }
// Global channel for tray events
use std::sync::Once;
use std::sync::atomic::{AtomicBool, Ordering};
use once_cell::sync::Lazy;
static INIT: Once = Once::new();
static INITIALIZED: AtomicBool = AtomicBool::new(false);
static TRAY_SENDER: Lazy<std::sync::Mutex<Option<mpsc::UnboundedSender<Message>>>> =
Lazy::new(|| std::sync::Mutex::new(None));
// Function to get or create the global tray sender
fn get_tray_sender() -> mpsc::UnboundedSender<Message> {
if !INITIALIZED.load(Ordering::SeqCst) {
INIT.call_once(|| {
let (tx, _) = mpsc::unbounded();
*TRAY_SENDER.lock().unwrap() = Some(tx);
INITIALIZED.store(true, Ordering::SeqCst);
});
}
TRAY_SENDER.lock().unwrap().clone().unwrap()
}
// Top-level subscription function to listen for events // Top-level subscription function to listen for events
fn subscription(_state: &CursorRush) -> Subscription<Message> { fn subscription(_state: &CursorRush) -> Subscription<Message> {
// Create a vector to hold all subscriptions
let mut subs = vec![
// Listen for events but filter out mouse movement events to avoid spamming
iced::event::listen().map(|event| { iced::event::listen().map(|event| {
if let iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) = event { match event {
// Track cursor position for UI display purposes // Skip cursor movement events to avoid spamming
Message::CursorMoved((position.x, position.y)) iced::Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => {
} else { // Return TrayEvent which is essentially a no-op
// We need to return some message, so we'll use TrayEvent as a no-op
Message::TrayEvent Message::TrayEvent
},
// Handle other events
_ => Message::TrayEvent, // Use TrayEvent as a no-op message
} }
}) }),
// Specifically subscribe to window close requests
// This is the key to handling the X button properly
iced::window::close_requests().map(|window_id| {
println!("Window close requested via X button for window ID: {:?}", window_id);
Message::CloseRequested(window_id)
}),
];
// Add a subscription for tray events
// We'll use a simple channel that the tray icon can send messages to
let tray_subscription = Subscription::run(
// Stream generator function
|| {
// Create a channel that the tray icon can send messages to
let (tx, rx) = mpsc::unbounded::<Message>();
// Store the sender in the global state
*TRAY_SENDER.lock().unwrap() = Some(tx);
// Return the receiver as a stream
rx
}
);
subs.push(tray_subscription);
// Combine all subscriptions
Subscription::batch(subs)
} }
// Boot function to initialize the application // Boot function to initialize the application
@@ -185,72 +266,119 @@ fn boot() -> (CursorRush, Task<Message>) {
cursors: cursors.clone(), cursors: cursors.clone(),
current_cursor_image, current_cursor_image,
running: true, running: true,
window_visible: true, // Window is initially visible
})); }));
// Create a channel to communicate with the UI
let (tx, rx) = mpsc::channel();
// Set up the tray icon
setup_tray_icon(&cursors, shared_state.clone(), tx.clone());
// Initialize the application state with loaded cursors // Initialize the application state with loaded cursors
let mut state = CursorRush::default(); let mut state = CursorRush::default();
state.cursors = cursors; state.cursors = cursors.clone(); // Clone here
state.shared_state = Some(shared_state.clone()); state.shared_state = Some(shared_state.clone());
// Get the global tray sender (will be initialized by the subscription)
// We'll set it up in the state after the subscription is running
// Set up the tray icon AND store the result in the state
// The tray sender will be available from the global state
state.tray_icon = setup_tray_icon(&state.cursors, shared_state.clone()); // Pass the cloned cursors
if state.tray_icon.is_none() {
eprintln!("Failed to initialize tray icon, application might not function correctly in background.");
// Optionally, decide how to handle this failure (e.g., exit or run without tray)
}
// We'll get the real window ID from the CloseRequested event
// No need to create a window ID here
// Set up global hotkey manager // Set up global hotkey manager
let hotkey_manager = GlobalHotKeyManager::new().expect("Failed to create hotkey manager"); let hotkey_manager = match GlobalHotKeyManager::new() {
Ok(manager) => manager,
Err(e) => {
eprintln!("Failed to create hotkey manager: {}", e);
// Continue without hotkey support
GlobalHotKeyManager::new().expect("Critical failure: could not create hotkey manager")
}
};
// Register Ctrl+Shift+F12 as the exit hotkey (uncommon but easy to remember) // Register Ctrl+Shift+F12 as the exit hotkey (uncommon but easy to remember)
use global_hotkey::hotkey::{Code, Modifiers}; use global_hotkey::hotkey::{Code, Modifiers};
// We don't need a unique ID for the hotkey in this simple case
let hotkey = GlobalHotKey::new( let hotkey = GlobalHotKey::new(
Some(Modifiers::CONTROL | Modifiers::SHIFT), Some(Modifiers::CONTROL | Modifiers::SHIFT),
Code::F12, Code::F12,
); );
if let Err(e) = hotkey_manager.register(hotkey) { // Register the hotkey with better error handling
match hotkey_manager.register(hotkey) {
Ok(_) => {
println!("Successfully registered Ctrl+Shift+F12 as exit hotkey");
},
Err(e) => {
eprintln!("Failed to register hotkey: {}", e); eprintln!("Failed to register hotkey: {}", e);
} else { // Try registering with a different hotkey as fallback
println!("Press Ctrl+Shift+F12 to exit the application"); let fallback_hotkey = GlobalHotKey::new(
Some(Modifiers::CONTROL | Modifiers::ALT),
Code::F12,
);
match hotkey_manager.register(fallback_hotkey) {
Ok(_) => println!("Registered fallback hotkey Ctrl+Alt+F12 instead"),
Err(e) => eprintln!("Failed to register fallback hotkey: {}", e),
} }
}
}
println!("Press Ctrl+Shift+F12 to exit the application");
// We're using the event-driven approach with the subscription function for mouse tracking // We're using the event-driven approach with the subscription function for mouse tracking
// Spawn a thread to listen for tray events // We no longer need the thread to forward messages from rx to tx_clone_ui
let tx_clone = tx.clone(); // since we're using the iced subscription system directly
thread::spawn(move || {
loop {
if let Ok(_) = rx.recv() {
// Send a message to the UI
let _ = tx_clone.send(Message::TrayEvent);
}
}
});
// Spawn a thread to listen for hotkeys // Spawn a thread to listen for hotkeys
let state_clone = shared_state.clone(); let state_clone = shared_state.clone();
thread::spawn(move || { thread::spawn(move || {
println!("Hotkey listener thread started");
let event_receiver = global_hotkey::GlobalHotKeyEvent::receiver(); let event_receiver = global_hotkey::GlobalHotKeyEvent::receiver();
loop { loop {
if let Ok(event) = event_receiver.try_recv() { match event_receiver.try_recv() {
Ok(event) => {
println!("Received hotkey event: {:?}", event);
if event.state == HotKeyState::Pressed { if event.state == HotKeyState::Pressed {
println!("Exit hotkey pressed, shutting down..."); println!("Exit hotkey pressed, sending Quit message...");
// Send Quit message instead of direct exit
// Set running to false // Get the global sender
if let Ok(mut state) = state_clone.lock() { if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() {
state.running = false; if let Err(e) = tx.unbounded_send(Message::Quit) {
} eprintln!("Failed to send Quit message from hotkey thread: {}", e);
// Fallback to direct exit if channel fails
// Restore the system cursor
platform::restore_cursor(); platform::restore_cursor();
// Exit the application
std::process::exit(0); std::process::exit(0);
} }
} else {
eprintln!("Hotkey: Global sender not initialized yet");
// Fallback to direct exit if sender not available
platform::restore_cursor();
std::process::exit(0);
} }
// Don't exit directly here, let the main thread handle it.
}
},
Err(_) => {
// No events or channel disconnected, sleep briefly
std::thread::sleep(Duration::from_millis(50));
// Small sleep to prevent high CPU usage // Check if we should exit (will be checked in the loop condition below)
std::thread::sleep(Duration::from_millis(10)); }
}
// Check shared state running flag (optional, main thread handles exit)
if let Ok(state) = state_clone.lock() {
if !state.running {
println!("Hotkey listener detected running=false, exiting thread.");
break;
}
}
} }
}); });
@@ -260,120 +388,223 @@ fn boot() -> (CursorRush, Task<Message>) {
} }
fn main() -> iced::Result { fn main() -> iced::Result {
// Initialize Steam
let _steam = steam::Steam::new(); let _steam = steam::Steam::new();
// Note: We can't use a consistent window ID across boot() and main()
// because iced creates its own window ID internally
// Instead, we'll capture the real window ID in the CloseRequested event
iced::application("Cursor Rush", update, view) iced::application("Cursor Rush", update, view)
.theme(|_| Theme::Dark) .theme(|_| Theme::Dark)
.subscription(subscription) .subscription(subscription)
.centered() .centered()
// This is the key - we need to tell iced not to exit when the window close button is clicked
.exit_on_close_request(false)
// run_with expects a function that returns (State, Task), not the tuple directly
.run_with(boot) .run_with(boot)
} }
// Set up the system tray icon // Set up the system tray icon
// Simplified signature without passing the channel
fn setup_tray_icon( fn setup_tray_icon(
cursors: &[CursorInfo], cursors: &[CursorInfo],
shared_state: Arc<Mutex<SharedState>>, shared_state: Arc<Mutex<SharedState>>,
tx: mpsc::Sender<Message>, ) -> Option<TrayIcon> { // Return Option<TrayIcon>
) {
if cursors.is_empty() { if cursors.is_empty() {
return; return None; // Return None if no cursors
} }
// Create a menu // Create a menu
let mut menu = Menu::new(); 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
// Add menu items with IDs
let next_item = tray_icon::menu::MenuItem::with_id(
NEXT_CURSOR_ID,
"Next cursor",
true,
None, // No accelerator
);
let quit_item = tray_icon::menu::MenuItem::with_id(
QUIT_ID,
"Quit",
true,
None, // No accelerator
);
// Add items to menu
if let Ok(menu_with_items) = Menu::with_items(&[&next_item, &quit_item]) {
menu = menu_with_items;
}
// Use the first PNG as the tray icon // Use the first PNG as the tray icon
let icon_path = &cursors[0].path; let icon_path = &cursors[0].path;
let img = image_open(icon_path).ok()?; // Use ok()?
// Load the image
let img = match image_open(icon_path) {
Ok(img) => img,
Err(e) => {
eprintln!("Failed to open image {}: {}", icon_path.display(), e);
return;
}
};
// Convert to RGBA
let rgba_img = img.to_rgba8(); let rgba_img = img.to_rgba8();
let (width, height) = rgba_img.dimensions(); let (width, height) = rgba_img.dimensions();
let icon = tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height).ok()?; // Use ok()?
// Create icon from RGBA data
let icon = match tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height) {
Ok(icon) => icon,
Err(e) => {
eprintln!("Failed to create icon: {}", e);
return;
}
};
// Build the tray icon // Build the tray icon
let _tray = TrayIconBuilder::new() println!("Building tray icon...");
let tray_result = TrayIconBuilder::new()
.with_tooltip("Cursor Rush") .with_tooltip("Cursor Rush")
.with_icon(icon) .with_icon(icon)
.with_menu(Box::new(menu)) .with_menu(Box::new(menu))
.build() .with_menu_on_left_click(false) // Disable menu on left click - only show on right click
.unwrap(); .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 // Listen for tray menu events in a separate thread
let state_clone = shared_state.clone(); let state_clone = shared_state.clone();
println!("Spawning tray menu event listener thread...");
thread::spawn(move || { thread::spawn(move || {
let menu_channel = MenuEvent::receiver(); let menu_channel = MenuEvent::receiver();
println!("Tray menu event listener waiting for events...");
while let Ok(event) = menu_channel.recv() { while let Ok(event) = menu_channel.recv() {
match event.id.as_ref() { println!("Tray menu event received: ID = {:?}", event.id);
let msg_to_send = match event.id.as_ref() {
NEXT_CURSOR_ID => { NEXT_CURSOR_ID => {
// Handle next cursor event println!("Tray: Next Cursor requested");
let mut state = state_clone.lock().unwrap(); match state_clone.lock() {
Ok(mut state) => {
if !state.cursors.is_empty() { if !state.cursors.is_empty() {
state.current_index = (state.current_index + 1) % state.cursors.len(); state.current_index = (state.current_index + 1) % state.cursors.len();
if let Some(cursor) = state.cursors.get(state.current_index) { if let Some(cursor) = state.cursors.get(state.current_index) {
println!("Tray: Applying next cursor: {}", cursor.name);
apply_cursor(&cursor.path); apply_cursor(&cursor.path);
// Ensure the render thread is running with the new cursor platform::render_cursor(); // Ensure render starts/updates
platform::render_cursor(); state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8());
// Load the new cursor image
let current_cursor_image = match image_open(&cursor.path) {
Ok(img) => Some(img.to_rgba8()),
Err(_) => None,
};
// Update the cursor image in shared state
state.current_cursor_image = current_cursor_image;
} }
} }
// Notify the UI },
let _ = tx.send(Message::TrayEvent); 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 => { QUIT_ID => {
// Restore the system cursor before exiting println!("Tray: Quit requested");
platform::restore_cursor(); // Send Quit message to main thread
Some(Message::Quit)
// Exit the application
std::process::exit(0);
}, },
_ => {} // any other menu items you add later _ => {
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
}
// Helper function to hide a window to the tray
fn hide_window_to_tray(window_id: iced::window::Id) -> Task<Message> {
println!("Attempting to hide window with ID: {:?}", window_id);
// Use ONLY the iced method for hiding. Platform specific might interfere.
let hide_task: Task<()> = iced::window::change_mode(window_id, iced::window::Mode::Hidden);
hide_task.map(|_: ()| {
println!("Window hide task completed (via iced)");
// Return a specific message to confirm the window was hidden
Message::WindowHidden
})
}
// Helper function to show a window from the tray
fn show_window_from_tray(window_id: iced::window::Id) -> Task<Message> {
println!("Attempting to show window with ID: {:?}", window_id);
// Use ONLY the iced methods for showing and focusing.
let show_task: Task<()> = iced::window::change_mode(window_id, iced::window::Mode::Windowed);
let focus_task: Task<()> = iced::window::gain_focus(window_id);
Task::batch(vec![show_task, focus_task]).map(|_: ()| {
println!("Window show/focus tasks completed (via iced)");
// Return a specific message to confirm the window was shown
Message::WindowShown
})
} }
// Update function to handle messages // Update function to handle messages
@@ -434,11 +665,28 @@ fn update(state: &mut CursorRush, message: Message) -> Task<Message> {
Task::none() Task::none()
}, },
Message::Quit => { Message::Quit => {
println!("Update: Handling Quit message");
// Set running flag to false to signal other threads (like hotkey listener)
if let Some(shared) = &state.shared_state {
if let Ok(mut shared_state) = shared.lock() {
shared_state.running = false;
}
}
// Restore the system cursor before exiting // Restore the system cursor before exiting
platform::restore_cursor(); platform::restore_cursor();
// Exit the application // Cleanly drop the tray icon *before* exiting iced
std::process::exit(0); // This removes the icon from the system tray immediately
if let Some(tray) = state.tray_icon.take() {
drop(tray);
println!("Tray icon explicitly dropped.");
}
// Use iced's exit mechanism for a cleaner shutdown
println!("Requesting application exit via iced::exit...");
iced::exit()
}, },
Message::TrayEvent => { Message::TrayEvent => {
// Sync with shared state // Sync with shared state
@@ -449,11 +697,95 @@ fn update(state: &mut CursorRush, message: Message) -> Task<Message> {
} }
Task::none() Task::none()
}, },
Message::CursorMoved(position) => { Message::CloseRequested(window_id) => {
// Update the cursor position println!("Handling CloseRequested: Hiding window with ID: {:?}", window_id);
state.cursor_position = position;
// Store the real window ID for future use
// This is critical for showing the window later
state.main_window = Some(window_id);
println!("Stored window ID: {:?} for future use", window_id);
// Update shared state first
if let Some(shared) = &state.shared_state {
match shared.lock() {
Ok(mut shared_state) => {
if shared_state.window_visible { // Only hide if visible
shared_state.window_visible = false;
println!("Set window_visible to false in shared state");
println!("Hiding window with ID: {:?}, app continues running in tray", window_id);
return hide_window_to_tray(window_id);
} else {
println!("Window already hidden, ignoring CloseRequested.");
return Task::none(); // Don't try to hide again
}
}
Err(e) => {
eprintln!("Error locking shared state for hiding: {}", e);
return Task::none();
}
}
} else {
eprintln!("Error: Shared state not available for hiding.");
return Task::none();
}
},
Message::WindowHidden => {
println!("Window successfully hidden to tray");
Task::none() Task::none()
}, },
Message::WindowShown => {
println!("Update: Received WindowShown message. Window should now be visible.");
Task::none()
},
Message::ShowWindow => {
println!("Handling ShowWindow: Attempting to show window"); // More specific starting log
if let Some(window_id) = state.main_window {
println!("Using stored window ID: {:?}", window_id);
// It's generally safer to attempt the show operation even if the state *thinks* it's visible.
// Let's update the shared state *after* we know the task is being created.
println!("Creating show/focus tasks for window ID: {:?}", window_id);
let show_task = iced::window::change_mode(window_id, iced::window::Mode::Windowed);
let focus_task = iced::window::gain_focus(window_id);
// Update shared state *before* returning the task, but after creating it.
if let Some(shared) = &state.shared_state {
match shared.lock() {
Ok(mut shared_state) => {
if !shared_state.window_visible { // Only log if changing state
println!("Updating shared state: window_visible = true");
shared_state.window_visible = true;
} else {
println!("Shared state already indicates window is visible.");
}
}
Err(e) => {
eprintln!("Error locking shared state during show: {}", e);
// Proceed with showing the window anyway, state might be out of sync
}
}
} else {
eprintln!("Shared state unavailable during show.");
// Proceed with showing the window anyway
}
println!("Returning Task::batch to show and focus window.");
// Return the batch task
return Task::batch(vec![show_task, focus_task])
.map(|_: ()| {
// This message confirms the Iced runtime *completed* the tasks.
println!("Iced runtime reports show/focus tasks completed successfully.");
Message::WindowShown // Send confirmation message
});
} else {
eprintln!("Error: Main window ID not found when trying to show. Cannot create tasks.");
Task::none() // Cannot proceed without window_id
}
},
} }
} }
@@ -502,21 +834,21 @@ fn view(state: &CursorRush) -> Element<Message> {
.push(button("Next cursor").on_press(Message::NextCursor)) .push(button("Next cursor").on_press(Message::NextCursor))
.push(button("Quit").on_press(Message::Quit)); .push(button("Quit").on_press(Message::Quit));
// Add instructions for the hotkey // Add instructions for the hotkey and close button behavior
content = content.push( content = content.push(
text("Press Ctrl+Shift+F12 to exit the application").size(14) container(
Column::new()
.spacing(5)
.push(text("Press Ctrl+Shift+F12 to exit the application").size(16))
.push(text("Close button (X) minimizes to system tray").size(16))
)
.padding(10)
); );
} else { } else {
content = content.push(text("No cursors found in assets directory")); content = content.push(text("No cursors found in assets directory"));
} }
// Add cursor position information for debugging // Cursor position display removed to avoid spammy updates
if !state.cursors.is_empty() {
let (x, y) = state.cursor_position;
content = content.push(
text(format!("Cursor position: ({:.1}, {:.1})", x, y)).size(12)
);
}
// Create the main container // Create the main container
container(content) container(content)
+26
View File
@@ -731,6 +731,32 @@ mod linux {
} }
} }
// Windows-specific function to hide a window - DEPRECATED
// We now use iced's window::change_mode instead for cross-platform compatibility
#[cfg(windows)]
pub fn hide_window() {
println!("WARNING: platform::hide_window is deprecated. Use iced::window::change_mode instead.");
}
// Windows-specific function to show a window - DEPRECATED
// We now use iced's window::change_mode instead for cross-platform compatibility
#[cfg(windows)]
pub fn show_window() {
println!("WARNING: platform::show_window is deprecated. Use iced::window::change_mode instead.");
}
// Placeholder for non-Windows platforms - DEPRECATED
#[cfg(not(windows))]
pub fn hide_window() {
println!("WARNING: platform::hide_window is deprecated. Use iced::window::change_mode instead.");
}
// Placeholder for non-Windows platforms - DEPRECATED
#[cfg(not(windows))]
pub fn show_window() {
println!("WARNING: platform::show_window is deprecated. Use iced::window::change_mode instead.");
}
// Export the appropriate implementation based on platform // Export the appropriate implementation based on platform
#[cfg(windows)] #[cfg(windows)]
pub use windows::{set_cursor, restore_cursor, render_cursor}; pub use windows::{set_cursor, restore_cursor, render_cursor};