From 20592954f78bd482fea368a06a4317fb2e610697 Mon Sep 17 00:00:00 2001 From: Emre040 Date: Fri, 16 May 2025 17:44:04 +0200 Subject: [PATCH] tauri integration --- Cargo.toml | 27 -- src/app/cursor.rs | 111 ------ src/app/messages.rs | 14 - src/app/mod.rs | 46 --- src/app/state.rs | 534 ----------------------------- src/integrations/mod.rs | 3 - src/integrations/steam.rs | 27 -- src/main.rs | 21 -- src/platform/linux/cursor.rs | 28 -- src/platform/linux/mod.rs | 7 - src/platform/linux/state.rs | 4 - src/platform/macos/cursor.rs | 28 -- src/platform/macos/mod.rs | 7 - src/platform/macos/state.rs | 4 - src/platform/mod.rs | 52 --- src/platform/unsupported/cursor.rs | 28 -- src/platform/unsupported/mod.rs | 7 - src/platform/unsupported/state.rs | 4 - src/platform/windows/cursor.rs | 501 --------------------------- src/platform/windows/mod.rs | 8 - src/platform/windows/state.rs | 57 --- src/platform/windows/types.rs | 40 --- src/ui/mod.rs | 7 - src/ui/tray.rs | 195 ----------- src/ui/view.rs | 77 ----- src/utils/config.rs | 34 -- src/utils/logging.rs | 58 ---- src/utils/mod.rs | 4 - 28 files changed, 1933 deletions(-) delete mode 100644 Cargo.toml delete mode 100644 src/app/cursor.rs delete mode 100644 src/app/messages.rs delete mode 100644 src/app/mod.rs delete mode 100644 src/app/state.rs delete mode 100644 src/integrations/mod.rs delete mode 100644 src/integrations/steam.rs delete mode 100644 src/main.rs delete mode 100644 src/platform/linux/cursor.rs delete mode 100644 src/platform/linux/mod.rs delete mode 100644 src/platform/linux/state.rs delete mode 100644 src/platform/macos/cursor.rs delete mode 100644 src/platform/macos/mod.rs delete mode 100644 src/platform/macos/state.rs delete mode 100644 src/platform/mod.rs delete mode 100644 src/platform/unsupported/cursor.rs delete mode 100644 src/platform/unsupported/mod.rs delete mode 100644 src/platform/unsupported/state.rs delete mode 100644 src/platform/windows/cursor.rs delete mode 100644 src/platform/windows/mod.rs delete mode 100644 src/platform/windows/state.rs delete mode 100644 src/platform/windows/types.rs delete mode 100644 src/ui/mod.rs delete mode 100644 src/ui/tray.rs delete mode 100644 src/ui/view.rs delete mode 100644 src/utils/config.rs delete mode 100644 src/utils/logging.rs delete mode 100644 src/utils/mod.rs diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index adab236..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "cursor_rush" -version = "0.1.0" -edition = "2021" - -[dependencies] -iced = { version = "0.13.1", default-features = true, features = ["image", "advanced"] } # GUI, bundles wgpu + winit -tray-icon = "0.20.1" # cross‑platform system tray -image = "0.24.7" # Image processing -walkdir = "2.4.0" # Directory traversal -global-hotkey = "0.4.2" # Global hotkey registration -lazy_static = "1.4.0" # Thread-safe static initialization -steamworks = { version = "0.11.0", features = ["raw-bindings"] } -anyhow = "1.0.98" -once_cell = "1.18.0" # Thread-safe lazy initialized global variables - -# Platform-specific dependencies -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9", features = ["winuser", "wingdi", "consoleapi"] } # Windows API -ctor = "0.2.4" # Constructor/destructor attributes - -[target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.7" # Objective-C bindings -cocoa = "0.24.1" # macOS Cocoa bindings - -[target.'cfg(target_os = "linux")'.dependencies] -x11 = { version = "2.21.0", features = ["xlib"] } # X11 bindings diff --git a/src/app/cursor.rs b/src/app/cursor.rs deleted file mode 100644 index 9e4a08d..0000000 --- a/src/app/cursor.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Cursor management functionality - -use std::path::{Path, PathBuf}; -use walkdir::WalkDir; -use ::image::open as image_open; -use ::image::GenericImageView; - -use crate::platform; - -// Define a struct to hold cursor information -#[derive(Clone)] -pub struct CursorInfo { - pub name: String, - pub path: PathBuf, - pub image: Option<::image::RgbaImage>, - #[cfg(windows)] - pub win_cursor: Option, -} - -// Function to load all PNG files from the assets directory -pub fn load_cursor_files() -> Vec { - let mut cursors = Vec::new(); - - println!("Looking for cursor PNG files in assets directory..."); - - // Check if assets directory exists - if !std::path::Path::new("assets").exists() { - println!("Assets directory not found!"); - return cursors; - } - - for entry in WalkDir::new("assets").into_iter().filter_map(|e| e.ok()) { - let path = entry.path(); - println!("Found file: {}", path.display()); - if path.is_file() && path.extension().map_or(false, |ext| ext == "png") { - let name = path.file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("Unknown") - .to_string(); - - println!("Adding cursor: {} at path {}", name, path.display()); - - // Try to load the image during initialization - let image = match image_open(path) { - Ok(img) => { - println!("Successfully pre-loaded image for cursor: {}", name); - Some(img.to_rgba8()) - }, - Err(e) => { - eprintln!("Failed to pre-load image for cursor {}: {}", name, e); - None - } - }; - - cursors.push(CursorInfo { - name, - path: path.to_path_buf(), - image, - #[cfg(windows)] - win_cursor: None, // Windows cursor will be created on first use - }); - } - } - - println!("Found {} cursor PNG files", cursors.len()); - - // Sort cursors by name for consistent ordering - cursors.sort_by(|a, b| a.name.cmp(&b.name)); - cursors -} - -// Function to validate a cursor image -pub fn validate_cursor_image(path: &Path) -> bool { - // Load the image to validate it - match image_open(path) { - Ok(img) => { - let (width, height) = img.dimensions(); - - // Ensure reasonable dimensions - if width > 128 || height > 128 || width == 0 || height == 0 { - eprintln!("Image dimensions not suitable for cursor: {}x{}", width, height); - return false; - } - - true - }, - Err(e) => { - eprintln!("Failed to open image {}: {}", path.display(), e); - false - }, - } -} - -// Apply the cursor from CursorInfo -pub fn apply_cursor_info(cursor_info: &mut CursorInfo) -> bool { - // Validate the cursor image if not already cached - if cursor_info.image.is_none() && !validate_cursor_image(&cursor_info.path) { - return false; - } - - // Use platform-specific cursor image update with the cursor info - if platform::update_cursor_info(cursor_info) { - println!("Successfully updated cursor image: {}", cursor_info.name); - return true; - } else { - eprintln!("Failed to update cursor image: {}", cursor_info.name); - return false; - } -} - - diff --git a/src/app/messages.rs b/src/app/messages.rs deleted file mode 100644 index fd84eff..0000000 --- a/src/app/messages.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Application messages - -// Messages that can be sent in our application -#[derive(Debug, Clone)] -pub enum Message { - NextCursor, - SelectCursor(usize), - Quit, - TrayEvent, // Used as a no-op message - 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 -} diff --git a/src/app/mod.rs b/src/app/mod.rs deleted file mode 100644 index ab255e7..0000000 --- a/src/app/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Application core module -// Contains the main application state and logic - -mod state; -mod messages; -mod cursor; - -pub use state::CursorRush; -pub use messages::Message; -pub use cursor::{CursorInfo, load_cursor_files, apply_cursor_info}; -pub use state::SharedState; - -use iced::{Subscription, Task}; -use once_cell::sync::Lazy; -use iced::futures::channel::mpsc; - -// Global channel for tray events -pub static TRAY_SENDER: Lazy>>> = - Lazy::new(|| std::sync::Mutex::new(None)); - -// Boot function to initialize the application -pub fn boot() -> (CursorRush, Task) { - state::boot_app() -} - -// Update function to handle messages -pub fn update(state: &mut CursorRush, message: Message) -> Task { - state::update_app(state, message) -} - -// Top-level subscription function to listen for events -pub fn subscription(_state: &CursorRush) -> Subscription { - state::app_subscription(_state) -} - -// Helper function to hide a window to the tray -pub fn hide_window_to_tray(window_id: iced::window::Id) -> Task { - // 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 - }) -} diff --git a/src/app/state.rs b/src/app/state.rs deleted file mode 100644 index 7b87cd9..0000000 --- a/src/app/state.rs +++ /dev/null @@ -1,534 +0,0 @@ -// Application state management - -use std::{ - sync::{Arc, Mutex}, - thread, - time::Duration, -}; -use ::image::{RgbaImage, open as image_open}; -use iced::{Task, Subscription, window, event}; -use iced::futures::channel::mpsc; -use global_hotkey::{ - GlobalHotKeyManager, - hotkey::HotKey as GlobalHotKey, - HotKeyState, -}; - -// Update function to handle messages -pub fn update_app(state: &mut CursorRush, message: Message) -> Task { - match message { - Message::NextCursor => { - if !state.cursors.is_empty() { - state.current_index = (state.current_index + 1) % state.cursors.len(); - - // Apply the new cursor - if let Some(cursor) = state.cursors.get_mut(state.current_index) { - // Use the cached version with the cursor info - apply_cursor_info(cursor); - - // Update shared state - if let Some(shared) = &state.shared_state { - if let Ok(mut shared_state) = shared.lock() { - shared_state.current_index = state.current_index; - // Use the cached image directly - shared_state.current_cursor_image = cursor.image.clone(); - } - } - } - } - Task::none() - }, - Message::SelectCursor(idx) => { - if idx < state.cursors.len() { - state.current_index = idx; - - // Apply the selected cursor - if let Some(cursor) = state.cursors.get_mut(state.current_index) { - // Use the cached version with the cursor info - apply_cursor_info(cursor); - - // Update shared state - if let Some(shared) = &state.shared_state { - if let Ok(mut shared_state) = shared.lock() { - shared_state.current_index = state.current_index; - // Use the cached image directly - shared_state.current_cursor_image = cursor.image.clone(); - } - } - } - } - Task::none() - }, - 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; - println!("Set running flag to false in shared state"); - } else { - println!("Failed to lock shared state to set running flag"); - } - } else { - println!("Shared state not available when handling Quit message"); - } - - // Unregister hotkeys if possible - if let Some(_) = &state.hotkey_manager { - println!("Unregistering hotkeys before exit"); - // We don't have a list of registered hotkeys, so we'll just drop the manager - // The hotkeys will be unregistered when the manager is dropped - println!("Hotkey manager will be dropped during exit"); - } else { - println!("No hotkey manager available to unregister hotkeys"); - } - - // Restore the system cursor before exiting - platform::restore_cursor(); - println!("System cursor restored"); - - // Cleanly drop the tray icon *before* exiting iced - // This removes the icon from the system tray immediately - if let Some(tray) = state.tray_icon.take() { - println!("Dropping tray icon..."); - drop(tray); - println!("Tray icon explicitly dropped."); - } else { - println!("No tray icon to drop"); - } - - // Use iced's exit mechanism for a cleaner shutdown - println!("Requesting application exit via iced::exit..."); - iced::exit() - }, - Message::TrayEvent => { - // Sync with shared state - if let Some(shared) = &state.shared_state { - if let Ok(shared_state) = shared.lock() { - state.current_index = shared_state.current_index; - } - } - Task::none() - }, - Message::CloseRequested(window_id) => { - println!("Handling CloseRequested: Hiding window with ID: {:?}", window_id); - - // 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 crate::app::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() - }, - 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 - } - }, - } -} - -use crate::platform; -use crate::ui::tray::setup_tray_icon; -use crate::app::{Message, CursorInfo, load_cursor_files, apply_cursor_info, TRAY_SENDER}; - -// Shared state between the UI and the tray icon -pub struct SharedState { - pub current_index: usize, - pub cursors: Vec, - pub current_cursor_image: Option, - pub running: bool, - pub window_visible: bool, // Track if the window is visible -} - -// Our application state -pub struct CursorRush { - pub cursors: Vec, - pub current_index: usize, - pub shared_state: Option>>, - pub main_window: Option, // Store the main window ID - pub tray_icon: Option, // Store the TrayIcon object to keep it alive - pub hotkey_manager: Option, // Store the hotkey manager to keep it alive -} - -// Make sure the default impl initializes tray_icon and hotkey_manager 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 - hotkey_manager: None, // Initialize as None - } - } -} - -// Top-level subscription function to listen for events -pub fn app_subscription(_state: &CursorRush) -> Subscription { - // Create a vector to hold all subscriptions - let mut subs = vec![ - // Listen for events - event::listen().map(|event| { - match event { - // We no longer need to handle cursor movement events since we're using SetSystemCursor - iced::Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => { - // Return TrayEvent which is essentially a no-op - Message::TrayEvent - }, - // Add a fallback keyboard handler for Ctrl+Shift+F12 - iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { - key, - modifiers, - .. - }) => { - // Check if Ctrl+Shift+F12 is pressed - if key == iced::keyboard::Key::Named(iced::keyboard::key::Named::F12) && - modifiers.control() && - modifiers.shift() { - println!("Detected Ctrl+Shift+F12 via iced keyboard event"); - Message::Quit - } else { - Message::TrayEvent // No-op for other key events - } - }, - // 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 - 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::(); - - // 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 implementation -pub fn boot_app() -> (CursorRush, Task) { - // Load cursor files from assets directory - let mut cursors = load_cursor_files(); - - if cursors.is_empty() { - eprintln!("No cursor PNG files found in assets directory!"); - return (CursorRush::default(), Task::none()); - } - - // Load the first cursor image - let current_cursor_image = if let Some(cursor) = cursors.first() { - match image_open(&cursor.path) { - Ok(img) => Some(img.to_rgba8()), - Err(_) => None, - } - } else { - None - }; - - // Hide the system cursor once at startup - if !platform::hide_system_cursor() { - eprintln!("Failed to hide system cursor at startup!"); - } - - // Apply the first cursor by default - if let Some(cursor) = cursors.first_mut() { - apply_cursor_info(cursor); - } - - // Create shared state - let shared_state = Arc::new(Mutex::new(SharedState { - current_index: 0, - cursors: cursors.clone(), - current_cursor_image, - running: true, - window_visible: true, // Window is initially visible - })); - - // Initialize the application state with loaded cursors - let mut state = CursorRush::default(); - state.cursors = cursors.clone(); // Clone here - state.shared_state = Some(shared_state.clone()); - - // Set up the tray icon AND store the result in the 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."); - } - - // Set up hotkeys and store the manager in the application state - state.hotkey_manager = setup_hotkeys(shared_state.clone()); - if state.hotkey_manager.is_none() { - eprintln!("Failed to initialize hotkey manager, hotkeys will not work!"); - } else { - println!("Hotkey manager successfully initialized and stored in application state"); - } - - (state, Task::none()) -} - -// Set up global hotkeys -fn setup_hotkeys(shared_state: Arc>) -> Option { - // Set up global hotkey manager - let hotkey_manager = match GlobalHotKeyManager::new() { - Ok(manager) => { - println!("Successfully created hotkey manager"); - manager - }, - Err(e) => { - eprintln!("Failed to create hotkey manager: {}", e); - // Continue without hotkey support - match GlobalHotKeyManager::new() { - Ok(manager) => { - println!("Second attempt to create hotkey manager succeeded"); - manager - }, - Err(e) => { - eprintln!("Critical failure: could not create hotkey manager on second attempt: {}", e); - return None; - } - } - } - }; - - // Register Ctrl+Shift+F12 as the exit hotkey (uncommon but easy to remember) - use global_hotkey::hotkey::{Code, Modifiers}; - - let hotkey = GlobalHotKey::new( - Some(Modifiers::CONTROL | Modifiers::SHIFT), - Code::F12, - ); - - println!("Attempting to register Ctrl+Shift+F12 as exit hotkey"); - - // Register the hotkey with better error handling - let registered_hotkey = match hotkey_manager.register(hotkey) { - Ok(_) => { - println!("Successfully registered Ctrl+Shift+F12 as exit hotkey"); - hotkey - }, - Err(e) => { - eprintln!("Failed to register hotkey: {}", e); - // Try registering with a different hotkey as fallback - let fallback_hotkey = GlobalHotKey::new( - Some(Modifiers::CONTROL | Modifiers::ALT), - Code::F12, - ); - - println!("Attempting to register fallback hotkey Ctrl+Alt+F12"); - match hotkey_manager.register(fallback_hotkey) { - Ok(_) => { - println!("Registered fallback hotkey Ctrl+Alt+F12 instead"); - fallback_hotkey - }, - Err(e) => { - eprintln!("Failed to register fallback hotkey: {}", e); - - // Try one more fallback with just F12 - let last_fallback = GlobalHotKey::new(None, Code::F12); - println!("Attempting to register last fallback hotkey F12"); - match hotkey_manager.register(last_fallback) { - Ok(_) => { - println!("Registered last fallback hotkey F12"); - last_fallback - }, - Err(e) => { - eprintln!("Failed to register any hotkey: {}", e); - return None; - } - } - } - } - } - }; - - println!("Hotkey registered: {:?}", registered_hotkey); - println!("Press Ctrl+Shift+F12 to exit the application"); - - // We can't clone the hotkey manager, so we'll use Arc to share it - let manager_arc = Arc::new(hotkey_manager); - let manager_for_thread = Arc::clone(&manager_arc); - - // Spawn a thread to listen for hotkeys - let state_clone = shared_state.clone(); - thread::spawn(move || { - println!("Hotkey listener thread started"); - let event_receiver = global_hotkey::GlobalHotKeyEvent::receiver(); - - // Keep a reference to the manager in this thread to ensure it stays alive - let _manager_ref = &manager_for_thread; - println!("Hotkey manager reference maintained in thread"); - - // Create a channel to signal thread termination - let (exit_tx, exit_rx) = std::sync::mpsc::channel(); - - // Spawn a thread to monitor the running flag - let monitor_state = state_clone.clone(); - thread::spawn(move || { - loop { - // Check if we should exit - if let Ok(state) = monitor_state.lock() { - if !state.running { - println!("Monitor thread detected running=false, signaling hotkey thread to exit"); - let _ = exit_tx.send(()); - break; - } - } - // Sleep briefly to avoid high CPU usage - std::thread::sleep(Duration::from_millis(100)); - } - }); - - // Main event loop - efficiently wait for events using blocking recv() - loop { - // Use std::sync::mpsc::TryRecv to check if we should exit without blocking - if exit_rx.try_recv().is_ok() { - println!("Hotkey listener received exit signal, terminating thread"); - break; - } - - // Use blocking recv() with a timeout to efficiently wait for events - // This avoids busy-waiting and reduces CPU usage to near 0% when idle - match event_receiver.recv_timeout(Duration::from_millis(500)) { - Ok(event) => { - println!("Received hotkey event: {:?}", event); - if event.state == HotKeyState::Pressed { - println!("Exit hotkey pressed, sending Quit message..."); - // Send Quit message instead of direct exit - // Get the global sender - if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { - println!("Found global sender, sending Quit message"); - 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 - println!("Using fallback exit mechanism"); - platform::restore_cursor(); - std::process::exit(0); - } else { - println!("Successfully sent Quit message through channel"); - } - } else { - eprintln!("Hotkey: Global sender not initialized yet"); - // Fallback to direct exit if sender not available - println!("Global sender not available, using direct exit"); - platform::restore_cursor(); - std::process::exit(0); - } - // Don't exit directly here, let the main thread handle it. - } - }, - Err(_) => { - // Timeout or channel disconnected, just continue the loop - // No need to sleep as recv_timeout already waited - } - } - } - }); - - // Return the hotkey manager from the Arc so it can be stored in the application state - match Arc::try_unwrap(manager_arc) { - Ok(manager) => Some(manager), - Err(_arc) => { - // If we can't unwrap the Arc (which shouldn't happen), extract a clone from it - println!("Warning: Could not unwrap Arc, using a new instance"); - // Create a new instance as a fallback - GlobalHotKeyManager::new().ok() - } - } -} diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs deleted file mode 100644 index d320bd0..0000000 --- a/src/integrations/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// External integrations module - -pub mod steam; diff --git a/src/integrations/steam.rs b/src/integrations/steam.rs deleted file mode 100644 index f1d3843..0000000 --- a/src/integrations/steam.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Steam integration - -use steamworks::{ - sys::{SteamAPI_ISteamInventory_TriggerItemDrop, SteamAPI_SteamInventory_v003}, - AppId, Client, SingleClient, -}; - -#[allow(dead_code)] -pub struct Steam { - client: Client, - single: SingleClient, -} - -impl Steam { - pub fn new() -> Self { - let (client, single) = Client::init_app(AppId(1430270)).unwrap(); - Self { client, single } - } - - #[allow(dead_code)] - pub fn drop_item(&self) { - unsafe { - let mut r = 0; - SteamAPI_ISteamInventory_TriggerItemDrop(SteamAPI_SteamInventory_v003(), &mut r, 1); - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 6ac58e0..0000000 --- a/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Main entry point for the Cursor Rush application - -mod app; -mod ui; -mod platform; -mod integrations; - -fn main() -> iced::Result { - // Initialize Steam - let _steam = integrations::steam::Steam::new(); - - // Run the application - iced::application("Cursor Rush", app::update, ui::view_app) - .theme(|_| iced::Theme::Dark) - .subscription(app::subscription) - .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(app::boot) -} diff --git a/src/platform/linux/cursor.rs b/src/platform/linux/cursor.rs deleted file mode 100644 index 54630f5..0000000 --- a/src/platform/linux/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Linux cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("Linux system cursor hiding not yet implemented"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("Linux cursor image update with caching not yet implemented"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("Linux cursor restoration not yet implemented"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs deleted file mode 100644 index 34b272c..0000000 --- a/src/platform/linux/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Linux platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/linux/state.rs b/src/platform/linux/state.rs deleted file mode 100644 index e32f987..0000000 --- a/src/platform/linux/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Linux platform state management - -// This file will contain Linux-specific state management -// Currently a placeholder for future implementation diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs deleted file mode 100644 index 0b1ea28..0000000 --- a/src/platform/macos/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// macOS cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("macOS system cursor hiding not yet implemented"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("macOS cursor image update with caching not yet implemented"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("macOS cursor restoration not yet implemented"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs deleted file mode 100644 index d35a84d..0000000 --- a/src/platform/macos/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// macOS platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/macos/state.rs b/src/platform/macos/state.rs deleted file mode 100644 index 9e5a511..0000000 --- a/src/platform/macos/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// macOS platform state management - -// This file will contain macOS-specific state management -// Currently a placeholder for future implementation diff --git a/src/platform/mod.rs b/src/platform/mod.rs deleted file mode 100644 index 95f1057..0000000 --- a/src/platform/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Platform-specific cursor handling - -// Windows implementation -#[cfg(windows)] -mod windows; - -// macOS implementation -#[cfg(target_os = "macos")] -mod macos; - -// Linux implementation -#[cfg(target_os = "linux")] -mod linux; - -// Unsupported platforms -#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] -mod unsupported; - -// --- Platform-independent types --- - -// Define a cross-platform cursor handle type -#[cfg(windows)] -pub use windows::types::SyncHCURSOR; - -#[cfg(not(windows))] -#[derive(Copy, Clone, Debug)] -pub struct SyncHCURSOR(pub usize); - -#[cfg(not(windows))] -unsafe impl Send for SyncHCURSOR {} - -#[cfg(not(windows))] -unsafe impl Sync for SyncHCURSOR {} - -// --- Public Exports --- - -// Export the platform-specific functions -#[cfg(windows)] -pub use windows::{hide_system_cursor, update_cursor_info, restore_cursor}; - -// Re-export CursorInfo for platform-specific implementations -pub use crate::app::CursorInfo; - -#[cfg(target_os = "macos")] -pub use macos::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; - -#[cfg(target_os = "linux")] -pub use linux::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; - -// Default implementation for other platforms -#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] -pub use unsupported::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/unsupported/cursor.rs b/src/platform/unsupported/cursor.rs deleted file mode 100644 index 365f401..0000000 --- a/src/platform/unsupported/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Unsupported platform cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("System cursor hiding not supported"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("Cursor image update with caching not supported"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("Cursor restoration not supported"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/unsupported/mod.rs b/src/platform/unsupported/mod.rs deleted file mode 100644 index 70332b5..0000000 --- a/src/platform/unsupported/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Unsupported platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/unsupported/state.rs b/src/platform/unsupported/state.rs deleted file mode 100644 index caeca00..0000000 --- a/src/platform/unsupported/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Unsupported platform state management - -// This file will contain fallback state management for unsupported platforms -// Currently a placeholder for future implementation diff --git a/src/platform/windows/cursor.rs b/src/platform/windows/cursor.rs deleted file mode 100644 index 0d1e3bd..0000000 --- a/src/platform/windows/cursor.rs +++ /dev/null @@ -1,501 +0,0 @@ -// Windows platform cursor management - -use std::ptr::null_mut; -use std::sync::atomic::Ordering; -use winapi::{ - um::winuser::{ - GetDC, ReleaseDC, LoadCursorW, SetSystemCursor, CopyIcon, - SystemParametersInfoW, SPI_SETCURSORS, SetCursor, - MAKEINTRESOURCEW, IDC_ARROW, CreateIconIndirect, ICONINFO, - DestroyIcon, - }, - um::wingdi::{ - CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject, - BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, BI_RGB, CreateDIBSection, - }, - um::consoleapi::SetConsoleCtrlHandler, - shared::minwindef::{TRUE, FALSE, DWORD}, -}; -use ::image::{RgbaImage, open as image_open}; - -use super::types::*; -use super::state::*; - -// Control handler function for console events (Ctrl+C, close, etc.) -extern "system" fn ctrl_handler(_ctrl_type: u32) -> i32 { - println!("Control event received, attempting cleanup..."); - cleanup(); // Call cleanup logic - FALSE // Allow other handlers (like default exit) to run -} - -// Create a cursor from an RGBA image -unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option { - let (width, height) = image.dimensions(); - let width_i32 = width as i32; - let height_i32 = height as i32; - - // Create device context and bitmap for the color data - let screen_dc = GetDC(null_mut()); - if screen_dc.is_null() { - eprintln!("Failed to get screen DC"); - return None; - } - - // Create a compatible DC for our bitmap - let color_dc = CreateCompatibleDC(screen_dc); - if color_dc.is_null() { - eprintln!("Failed to create compatible DC for color"); - ReleaseDC(null_mut(), screen_dc); - return None; - } - - // Create a compatible DC for our mask bitmap - let mask_dc = CreateCompatibleDC(screen_dc); - if mask_dc.is_null() { - eprintln!("Failed to create compatible DC for mask"); - DeleteDC(color_dc); - ReleaseDC(null_mut(), screen_dc); - return None; - } - - // Set up BITMAPINFO structure for DIB - let mut bitmap_info: BITMAPINFO = std::mem::zeroed(); - bitmap_info.bmiHeader.biSize = std::mem::size_of::() as u32; - bitmap_info.bmiHeader.biWidth = width_i32; - bitmap_info.bmiHeader.biHeight = -height_i32; // Negative for top-down DIB - bitmap_info.bmiHeader.biPlanes = 1; - bitmap_info.bmiHeader.biBitCount = 32; // 32 bits per pixel (BGRA) - bitmap_info.bmiHeader.biCompression = BI_RGB; - - // Create the color DIB section - let mut color_bits_ptr: *mut std::ffi::c_void = null_mut(); - let color_bitmap = CreateDIBSection( - color_dc, - &bitmap_info, - DIB_RGB_COLORS, - &mut color_bits_ptr, - null_mut(), - 0, - ); - - if color_bitmap.is_null() || color_bits_ptr.is_null() { - eprintln!("Failed to create color DIB section"); - DeleteDC(mask_dc); - DeleteDC(color_dc); - ReleaseDC(null_mut(), screen_dc); - return None; - } - - // Create the mask DIB section (1-bit monochrome) - bitmap_info.bmiHeader.biBitCount = 1; // 1 bit per pixel for mask - let mut mask_bits_ptr: *mut std::ffi::c_void = null_mut(); - let mask_bitmap = CreateDIBSection( - mask_dc, - &bitmap_info, - DIB_RGB_COLORS, - &mut mask_bits_ptr, - null_mut(), - 0, - ); - - if mask_bitmap.is_null() || mask_bits_ptr.is_null() { - eprintln!("Failed to create mask DIB section"); - DeleteObject(color_bitmap as _); - DeleteDC(mask_dc); - DeleteDC(color_dc); - ReleaseDC(null_mut(), screen_dc); - return None; - } - - // Select the bitmaps into the DCs - let old_color_bitmap = SelectObject(color_dc, color_bitmap as _); - let old_mask_bitmap = SelectObject(mask_dc, mask_bitmap as _); - - if old_color_bitmap.is_null() || old_mask_bitmap.is_null() { - eprintln!("Failed to select bitmaps into DCs"); - DeleteObject(mask_bitmap as _); - DeleteObject(color_bitmap as _); - DeleteDC(mask_dc); - DeleteDC(color_dc); - ReleaseDC(null_mut(), screen_dc); - return None; - } - - // Copy the image data to the color DIB, converting RGBA to BGRA - let color_bits = std::slice::from_raw_parts_mut(color_bits_ptr as *mut u8, (width * height * 4) as usize); - - // Calculate mask stride in bytes (1 bit per pixel, padded to DWORD) - let mask_stride = ((width + 31) / 32) * 4; - let mask_bits = std::slice::from_raw_parts_mut(mask_bits_ptr as *mut u8, (mask_stride * height) as usize); - - // Clear mask bits (all transparent initially) - for i in 0..(mask_stride * height) as usize { - mask_bits[i] = 0xFF; // All bits set = transparent - } - - // Process image pixels - for y in 0..height { - for x in 0..width { - let pixel = image.get_pixel(x, y); - let color_idx = ((y * width + x) * 4) as usize; - - // Get RGBA components - let r = pixel[0]; - let g = pixel[1]; - let b = pixel[2]; - let a = pixel[3]; - - // Set color data (BGR order) - color_bits[color_idx + 0] = b; - color_bits[color_idx + 1] = g; - color_bits[color_idx + 2] = r; - color_bits[color_idx + 3] = a; - - // Update mask bit if pixel is not transparent - if a > 0 { - // Calculate bit position in mask - let byte_idx = (y * mask_stride + (x / 8)) as usize; - let bit_idx = 7 - (x % 8); // Bits are stored MSB first - - // Clear the bit (0 = opaque in AND mask) - mask_bits[byte_idx] &= !(1 << bit_idx); - } - } - } - - // Create ICONINFO structure - let mut icon_info: ICONINFO = std::mem::zeroed(); - icon_info.fIcon = FALSE; // This is a cursor, not an icon - icon_info.xHotspot = width as DWORD / 2; // Center hotspot - icon_info.yHotspot = height as DWORD / 2; - icon_info.hbmMask = mask_bitmap; - icon_info.hbmColor = color_bitmap; - - // Create the cursor - let cursor = CreateIconIndirect(&mut icon_info as *mut _); - - // Clean up resources - SelectObject(color_dc, old_color_bitmap); - SelectObject(mask_dc, old_mask_bitmap); - DeleteDC(color_dc); - DeleteDC(mask_dc); - ReleaseDC(null_mut(), screen_dc); - - // Windows makes a copy of the bitmaps for the cursor, so we can delete them - DeleteObject(color_bitmap as _); - DeleteObject(mask_bitmap as _); - - if cursor.is_null() { - eprintln!("Failed to create cursor"); - return None; - } - - // Return the cursor wrapped in our thread-safe type - Some(SyncHCURSOR(cursor)) -} - -// Cleanup function to restore cursors and clean up resources -pub fn cleanup() { - println!("Running cursor cleanup..."); - - // Restore system cursors if they were hidden - // Use swap to ensure it runs only once - if CURSOR_HIDDEN.swap(false, Ordering::SeqCst) { - println!("Restoring system cursors..."); - - unsafe { - // Try multiple approaches to ensure cursors are restored - - // 1. First try to restore original cursors if we saved them - let original_cursors_opt = ORIGINAL_CURSORS.lock().unwrap().take(); - if let Some(original_cursors) = original_cursors_opt { - println!("Restoring {} saved original cursors", original_cursors.len()); - for (cursor_id, cursor) in original_cursors { - if !cursor.0.is_null() { - // Make a copy of the original cursor - let cursor_copy = CopyIcon(cursor.0); - if !cursor_copy.is_null() { - if SetSystemCursor(cursor_copy, cursor_id) == 0 { - eprintln!("Failed to restore original cursor for ID: {}", cursor_id); - DestroyIcon(cursor_copy); - } - } else { - eprintln!("Failed to copy original cursor for ID: {}", cursor_id); - } - - // Destroy the original cursor we saved - DestroyIcon(cursor.0); - } - } - } else { - println!("No saved original cursors found"); - } - - // 2. Force Windows to reload default cursor settings - println!("Forcing Windows to reload default cursor settings"); - SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0); - - // 3. Also try the A version for maximum compatibility - super::state::reset_system_cursors(); - - // 4. Reset to default arrow cursor - let arrow_cursor = LoadCursorW(null_mut(), MAKEINTRESOURCEW(IDC_ARROW as u16)); - if !arrow_cursor.is_null() { - SetCursor(arrow_cursor); - println!("Reset to default arrow cursor."); - } - } - - println!("System cursors restoration complete."); - } - - // Destroy the custom cursor if it exists - let custom_cursor_to_destroy = CUSTOM_CURSOR.lock().unwrap().take(); - if let Some(cursor_wrapper) = custom_cursor_to_destroy { - unsafe { - if !cursor_wrapper.0.is_null() { - DestroyIcon(cursor_wrapper.0); - println!("Custom cursor destroyed during cleanup."); - } - } - } - - // Reset the last known cursor position - *LAST_CURSOR_POS.lock().unwrap() = None; - - // Clear the current cursor image - *CURRENT_CURSOR_IMAGE.lock().unwrap() = None; - - // Unregister control handler - if CTRL_HANDLER_REGISTERED.load(Ordering::SeqCst) { - unsafe { - if SetConsoleCtrlHandler(Some(ctrl_handler), FALSE) != 0 { - CTRL_HANDLER_REGISTERED.store(false, Ordering::SeqCst); - println!("Control handler unregistered."); - } else { - eprintln!("Failed to unregister control handler."); - } - } - } - - println!("Cursor cleanup complete."); -} - -// --- Public API Functions --- - -// Hide the system cursor - now just initializes the control handler -pub fn hide_system_cursor() -> bool { - // Initialize once - INIT.get_or_init(|| { - // Register control handler for clean exit - unsafe { - if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) != 0 { - CTRL_HANDLER_REGISTERED.store(true, Ordering::SeqCst); - println!("Control handler registered for clean exit."); - } else { - eprintln!("Failed to register control handler: {}", std::io::Error::last_os_error()); - } - } - }); - - // Check if already hidden - if CURSOR_HIDDEN.load(Ordering::SeqCst) { - return true; // Already hidden - } - - // Save original cursors for restoration - let mut original_cursors = Vec::new(); - unsafe { - for &cursor_id in SYSTEM_CURSORS.iter() { - // Get the original cursor - let original_cursor = CopyIcon(LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16))); - if !original_cursor.is_null() { - // Save the original cursor for restoration - original_cursors.push((cursor_id, SyncHCURSOR(original_cursor))); - } - } - } - - // Store original cursors for restoration - if !original_cursors.is_empty() { - *ORIGINAL_CURSORS.lock().unwrap() = Some(original_cursors); - } - - // Mark as hidden - this is now just a flag to indicate we've saved the original cursors - CURSOR_HIDDEN.store(true, Ordering::SeqCst); - - true -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool { - println!("Updating cursor to: {}", cursor_info.name); - - // Use cached image if available, otherwise load from disk - let image = if let Some(ref cached_image) = cursor_info.image { - println!("Using cached image for cursor: {}", cursor_info.name); - cached_image.clone() - } else { - println!("Loading image from disk for cursor: {}", cursor_info.name); - match image_open(&cursor_info.path) { - Ok(img) => { - let rgba = img.to_rgba8(); - // Cache the image for future use - cursor_info.image = Some(rgba.clone()); - rgba - }, - Err(e) => { - eprintln!("Failed to load cursor image: {}", e); - return false; - } - } - }; - - // Store the image in the global state for reference - *CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone()); - - unsafe { - // Create a new cursor first before destroying the old one - // This minimizes the time when no custom cursor is active - let new_cursor = if let Some(ref cached_image) = cursor_info.image { - // Always create a fresh cursor from the image to avoid invalid handle issues - create_cursor_from_image(cached_image) - } else { - create_cursor_from_image(&image) - }; - - if let Some(new_cursor_wrapper) = new_cursor { - // Apply the new cursor to all system cursors first - let success = apply_cursor_to_all_system_cursors(new_cursor_wrapper.0); - - // Now that the new cursor is applied, we can clean up the old one - let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); - if let Some(old_cursor) = cursor_guard.take() { - // Only destroy the old cursor after the new one is applied - DestroyIcon(old_cursor.0); - } - - // Store the new cursor - *cursor_guard = Some(new_cursor_wrapper); - - // Update the cached cursor in CursorInfo - #[cfg(windows)] - { - // Always update the cached cursor to the fresh one - cursor_info.win_cursor = Some(new_cursor_wrapper); - } - - if !success { - eprintln!("Failed to apply cursor to all system cursors"); - // Even if we failed to set all system cursors, try to at least set the current cursor - SetCursor(new_cursor_wrapper.0); - } - - return true; - } else { - eprintln!("Failed to create cursor from image"); - - // If we failed to create a new cursor, try to use the cached one as a fallback - #[cfg(windows)] - if let Some(cached_cursor) = cursor_info.win_cursor { - if !cached_cursor.0.is_null() { - println!("Falling back to cached cursor as a last resort"); - let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); - *cursor_guard = Some(cached_cursor); - SetCursor(cached_cursor.0); - return true; - } - } - - return false; - } - } -} - - - -// Apply a cursor to all system cursors -unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool { - let mut success = true; - - // First, ensure the cursor is valid - if cursor.is_null() { - eprintln!("Cannot apply null cursor to system cursors"); - return false; - } - - // Immediately set the current cursor to minimize flashing - SetCursor(cursor); - - // Apply the cursor to all system cursor types - // Start with the most commonly used cursors first - let priority_cursors = [ - 32512, // IDC_ARROW (most important) - 32513, // IDC_IBEAM (text cursor) - 32649, // IDC_HAND (links) - ]; - - // Apply to priority cursors first - for &cursor_id in priority_cursors.iter() { - // Make a copy of the cursor - let cursor_copy = CopyIcon(cursor); - if cursor_copy.is_null() { - let error = std::io::Error::last_os_error(); - eprintln!("Failed to copy cursor for priority ID: {} - Error: {}", cursor_id, error); - success = false; - continue; - } - - // Set the system cursor - if SetSystemCursor(cursor_copy, cursor_id) == 0 { - let error = std::io::Error::last_os_error(); - eprintln!("Failed to set cursor for priority ID: {} - Error: {}", cursor_id, error); - DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it - success = false; - } - } - - // Now apply to the remaining cursors - for &cursor_id in SYSTEM_CURSORS.iter() { - // Skip the priority cursors we already processed - if priority_cursors.contains(&cursor_id) { - continue; - } - - // Make a copy of the cursor - let cursor_copy = CopyIcon(cursor); - if cursor_copy.is_null() { - // Don't log errors for non-priority cursors to reduce console spam - success = false; - continue; - } - - // Set the system cursor - if SetSystemCursor(cursor_copy, cursor_id) == 0 { - // Don't log errors for non-priority cursors to reduce console spam - DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it - success = false; - } - } - - // If we had any failures, set the current cursor again as a fallback - if !success { - SetCursor(cursor); - } - - success -} - -// Restore the system cursor -pub fn restore_cursor() { - cleanup(); -} - -// Function to handle mouse movement and update the cursor -// Kept for potential future use but currently not needed with SetSystemCursor approach -#[allow(dead_code)] -pub fn handle_mouse_move() { - // With SetSystemCursor, we don't need to constantly update the cursor on mouse move - // This function is kept for API compatibility, but it's now a no-op - // System cursors are already set to our custom cursor -} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs deleted file mode 100644 index 534fbc7..0000000 --- a/src/platform/windows/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Windows platform implementation module - -pub mod types; -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor}; diff --git a/src/platform/windows/state.rs b/src/platform/windows/state.rs deleted file mode 100644 index aa1e86c..0000000 --- a/src/platform/windows/state.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Windows platform state management - -use std::sync::{Mutex, atomic::AtomicBool}; -use winapi::{ - shared::minwindef::DWORD, - shared::windef::POINT, -}; -use ::image::RgbaImage; -use once_cell::sync::{Lazy, OnceCell}; - -use super::types::*; - -// --- Thread-Safe Static Variables --- - -// Use AtomicBool for simple flags -pub static CURSOR_HIDDEN: AtomicBool = AtomicBool::new(false); -pub static CTRL_HANDLER_REGISTERED: AtomicBool = AtomicBool::new(false); - -// Use Lazy> for complex Option types -pub static ORIGINAL_CURSORS: Lazy>>> = Lazy::new(|| Mutex::new(None)); -pub static CURRENT_CURSOR_IMAGE: Lazy>> = Lazy::new(|| Mutex::new(None)); -pub static LAST_CURSOR_POS: Lazy>> = Lazy::new(|| Mutex::new(None)); - -// Use Lazy>> for WinAPI handles with our thread-safe wrappers -pub static CUSTOM_CURSOR: Lazy>> = Lazy::new(|| Mutex::new(None)); - -// Note: Legacy variables for window-based cursor approach and mouse hook tracking -// have been removed as part of code cleanup - -// Use OnceCell for INIT to avoid `static mut` -pub static INIT: OnceCell<()> = OnceCell::new(); - -// Reset system cursors using the Windows API -pub fn reset_system_cursors() { - unsafe { - // Use both versions for maximum compatibility - winapi::um::winuser::SystemParametersInfoW( - winapi::um::winuser::SPI_SETCURSORS, - 0, - std::ptr::null_mut(), - 0 - ); - - // Import the Windows API function to reset system cursors - #[link(name = "user32")] - extern "system" { - fn SystemParametersInfoA(uiAction: u32, uiParam: u32, pvParam: *mut std::ffi::c_void, fWinIni: u32) -> i32; - } - - SystemParametersInfoA( - SPI_SETCURSORS_A, - 0, - std::ptr::null_mut(), - SPIF_UPDATEINIFILE | SPIF_SENDCHANGE - ); - } -} diff --git a/src/platform/windows/types.rs b/src/platform/windows/types.rs deleted file mode 100644 index ca658ef..0000000 --- a/src/platform/windows/types.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Windows platform types and constants - - -use winapi::{ - shared::windef::HCURSOR, - shared::minwindef::DWORD, -}; - -// --- Thread-Safe Handle Wrappers --- -// These wrappers make raw Windows handles safe to share between threads - -// Wrapper for HCURSOR (cursor handle) -#[derive(Copy, Clone, Debug)] -pub struct SyncHCURSOR(pub HCURSOR); -unsafe impl Send for SyncHCURSOR {} -unsafe impl Sync for SyncHCURSOR {} - -// --- Constants --- - -// Constants for SystemParametersInfo -pub const SPI_SETCURSORS_A: u32 = 0x0057; -pub const SPIF_UPDATEINIFILE: u32 = 0x01; -pub const SPIF_SENDCHANGE: u32 = 0x02; - -// Note: Constants for window-based cursor approach have been removed as part of code cleanup - -// Define all system cursor IDs we'll replace -pub static SYSTEM_CURSORS: [DWORD; 11] = [ - 32512, // IDC_ARROW - 32513, // IDC_IBEAM - 32515, // IDC_CROSS - 32514, // IDC_WAIT - 32649, // IDC_HAND - 32650, // IDC_APPSTARTING - 32648, // IDC_NO - 32646, // IDC_SIZEALL - 32645, // IDC_SIZENS - 32644, // IDC_SIZEWE - 32642, // IDC_SIZENWSE -]; diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index c7aecaf..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// UI module -// Contains UI components and rendering logic - -pub mod view; -pub mod tray; - -pub use view::view_app; diff --git a/src/ui/tray.rs b/src/ui/tray.rs deleted file mode 100644 index c772b84..0000000 --- a/src/ui/tray.rs +++ /dev/null @@ -1,195 +0,0 @@ -// 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(); - let current_index = state.current_index; - if let Some(cursor) = state.cursors.get_mut(current_index) { - println!("Tray: Updating cursor image: {}", cursor.name); - crate::app::apply_cursor_info(cursor); - // Use the cached image directly - state.current_cursor_image = cursor.image.clone(); - } - } - }, - 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 -} diff --git a/src/ui/view.rs b/src/ui/view.rs deleted file mode 100644 index e6cd934..0000000 --- a/src/ui/view.rs +++ /dev/null @@ -1,77 +0,0 @@ -// UI view components - -use iced::{ - widget::{button, text, Column, Row, container, image as iced_image}, - Element, Length, -}; - -use crate::app::{CursorRush, Message}; - -// View function to render the UI -pub fn view_app(state: &CursorRush) -> Element { - let mut content = Column::new() - .spacing(16) - .push(text("Cursor Rush").size(24)); - - // Add a button for each cursor - if !state.cursors.is_empty() { - let current_cursor = &state.cursors[state.current_index]; - content = content.push(text(format!("Current cursor: {}", current_cursor.name))); - - // Display the current cursor image as a preview - let img_handle = iced_image::Handle::from_path(¤t_cursor.path); - let cursor_image = iced_image::Image::new(img_handle) - .width(Length::Fixed(64.0)) - .height(Length::Fixed(64.0)); - - content = content.push(cursor_image); - - // Create a row of cursor buttons - let mut cursor_row = Row::new().spacing(10); - - for (i, cursor) in state.cursors.iter().enumerate() { - // Create a small preview image for each cursor - let preview_handle = iced_image::Handle::from_path(&cursor.path); - let preview_image = iced_image::Image::new(preview_handle) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)); - - let btn = button( - Row::new() - .spacing(5) - .push(preview_image) - .push(text(&cursor.name).size(16)) - ) - .on_press(Message::SelectCursor(i)); - - cursor_row = cursor_row.push(btn); - } - - content = content - .push(cursor_row) - .push(button("Next cursor").on_press(Message::NextCursor)) - .push(button("Quit").on_press(Message::Quit)); - - // Add instructions for the hotkey and close button behavior - content = content.push( - 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 { - content = content.push(text("No cursors found in assets directory")); - } - - // Create the main container - container(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x(Length::Fill) - .center_y(Length::Fill) - .padding(20) - .into() -} diff --git a/src/utils/config.rs b/src/utils/config.rs deleted file mode 100644 index 78a5c4e..0000000 --- a/src/utils/config.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Application configuration - -use std::path::PathBuf; - -// Application configuration -pub struct Config { - pub assets_dir: PathBuf, - pub default_cursor: String, - pub auto_start: bool, - pub minimize_to_tray: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - assets_dir: PathBuf::from("assets"), - default_cursor: String::from("default"), - auto_start: false, - minimize_to_tray: true, - } - } -} - -// Load configuration from file or use defaults -pub fn load_config() -> Config { - // TODO: Implement loading from a config file - Config::default() -} - -// Save configuration to file -pub fn save_config(_config: &Config) -> Result<(), String> { - // TODO: Implement saving to a config file - Ok(()) -} diff --git a/src/utils/logging.rs b/src/utils/logging.rs deleted file mode 100644 index bf2a00f..0000000 --- a/src/utils/logging.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Logging utilities - -// Log levels -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum LogLevel { - Debug, - Info, - Warning, - Error, -} - -// Global log level (could be made configurable) -static mut CURRENT_LOG_LEVEL: LogLevel = LogLevel::Info; - -// Set the global log level -pub fn set_log_level(level: LogLevel) { - unsafe { - CURRENT_LOG_LEVEL = level; - } -} - -// Get the current log level -pub fn get_log_level() -> LogLevel { - unsafe { - CURRENT_LOG_LEVEL - } -} - -// Log a message at the specified level -pub fn log(level: LogLevel, message: &str) { - if level >= unsafe { CURRENT_LOG_LEVEL } { - let prefix = match level { - LogLevel::Debug => "[DEBUG]", - LogLevel::Info => "[INFO]", - LogLevel::Warning => "[WARNING]", - LogLevel::Error => "[ERROR]", - }; - - println!("{} {}", prefix, message); - } -} - -// Convenience functions for different log levels -pub fn debug(message: &str) { - log(LogLevel::Debug, message); -} - -pub fn info(message: &str) { - log(LogLevel::Info, message); -} - -pub fn warning(message: &str) { - log(LogLevel::Warning, message); -} - -pub fn error(message: &str) { - log(LogLevel::Error, message); -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 952511b..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Utility functions and helpers - -pub mod config; -pub mod logging;