From 61157a2402cee149f4f8c2dd802949dbd571db6f Mon Sep 17 00:00:00 2001 From: jcweaver997 Date: Sat, 31 May 2025 00:38:29 -0500 Subject: [PATCH] bundle cursors into cursor_rush.dat, started animator --- Cargo.toml | 2 +- frontend/src-tauri/Cargo.toml | 6 +- frontend/src-tauri/build.rs | 21 +- frontend/src-tauri/src/animator.rs | 123 ++++++++ frontend/src-tauri/src/cursor_def.json | 14 + frontend/src-tauri/src/lib.rs | 287 ++++++------------ frontend/src-tauri/src/platform/mod.rs | 11 +- frontend/src-tauri/src/platform/types.rs | 37 --- .../src-tauri/src/platform/windows/cursor.rs | 83 ++--- .../src-tauri/src/platform/windows/mod.rs | 25 +- 10 files changed, 292 insertions(+), 317 deletions(-) create mode 100644 frontend/src-tauri/src/animator.rs create mode 100644 frontend/src-tauri/src/cursor_def.json diff --git a/Cargo.toml b/Cargo.toml index 04fbbec..7b0b1b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] members = ["frontend/src-tauri"] -resolver = "3" \ No newline at end of file +resolver = "3" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 8435292..5918f4b 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -6,12 +6,12 @@ authors = ["you"] license = "" repository = "" edition = "2021" -rust-version = "1.56" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] tauri-build = { version = "1.5.2", features = [] } +zip = { version = "4.0.0", features = ["zstd"] } [dependencies] tauri = { version = "1.6.0", features = [ "window-start-dragging", "shell-open", "system-tray", "window-minimize", "window-hide", "window-show", "window-close"] } @@ -23,6 +23,10 @@ winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", " once_cell = "1.19.0" steamworks = "0.11.0" parking_lot = "0.12.3" +rand = "0.9.1" +tokio = { version = "1.45.1", features = ["macros", "parking_lot", "rt-multi-thread", "time"] } +zip = { version = "4.0.0", features = ["zstd"] } +anyhow = "1.0.98" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/frontend/src-tauri/build.rs b/frontend/src-tauri/build.rs index 7047b73..e1da87e 100644 --- a/frontend/src-tauri/build.rs +++ b/frontend/src-tauri/build.rs @@ -1,4 +1,6 @@ -use std::{env, fs::{self, read_dir}, path::PathBuf}; +use std::{env, fs::{self, read_dir}, io::Write, path::PathBuf}; + +use zip::{write::SimpleFileOptions, ZipWriter}; fn main() -> Result<(), Box> { tauri_build::build(); @@ -38,5 +40,22 @@ fn main() -> Result<(), Box> { fs::copy(f.path(), profile_target_dir.join(f.file_name()))?; } + // Build data file + println!("cargo:rerun-if-changed=src/cursors"); + println!("cargo:rerun-if-changed=src/cursor_def.json"); + + let mut zip = ZipWriter::new(fs::OpenOptions::new().write(true).create(true).open(profile_target_dir.join("cursor_rush.dat"))?); + zip.start_file("cursor_def.json", SimpleFileOptions::default())?; + zip.write_all(&fs::read("src/cursor_def.json")?)?; + + for f in read_dir("src/cursors")? { + let f = f?; + zip.start_file(f.file_name().to_string_lossy(), SimpleFileOptions::default())?; + zip.write_all(&fs::read(f.path())?)?; + } + + zip.finish()?; + + Ok(()) } diff --git a/frontend/src-tauri/src/animator.rs b/frontend/src-tauri/src/animator.rs new file mode 100644 index 0000000..eebf3da --- /dev/null +++ b/frontend/src-tauri/src/animator.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use image::{ImageFormat, RgbaImage}; +use std::{env, fs, future::pending, sync::Arc, time::Duration}; +use tokio::time::interval; +use tokio::{ + select, + time::{Instant, Interval}, +}; +use zip::ZipArchive; + +use crate::{ + platform::{restore_default_cursor, set_system_cursor}, + Cursor, CURSOR_CHANGED, CURSOR_CMD, +}; + +#[derive(Debug, Default)] +struct AnimationState { + cursor_index: Option, + loaded_image: Option, + loaded_particles: Option>, +} + +async fn pend_optional_interval(t: &mut Option) -> Instant { + if let Some(t) = t { + t.tick().await + } else { + pending::().await + } +} + +#[tokio::main] +pub async fn animate_loop(cursors: Arc>) -> Result<()> { + let mut cmds = CURSOR_CMD.subscribe(); + let mut selected: Option = None; + let start = Instant::now(); + + let mut animation_state = AnimationState::default(); + + let mut animation_interval: Option = None; + + loop { + select! { + cmd = cmds.recv() => { + let cmd = cmd?; + println!("GOT CURSOR CMD {:?}", cmd); + match cmd { + crate::CursorCmd::Select(i) => { + if i < cursors.len() { + selected = Some(i); + } + if cursors[i].animation_speed > 0.0 { + animation_interval = Some(interval(Duration::from_secs_f32(1.0/cursors[i].animation_speed))); + }else{ + animation_interval = None; + } + } + crate::CursorCmd::Next => { + selected = Some(selected.map(|x| (x + 1) % cursors.len()).unwrap_or(0)) + } + crate::CursorCmd::Reapply => { + + }, + crate::CursorCmd::Restore => { + + }, + } + CURSOR_CHANGED.send(selected.unwrap_or(0)).ok(); + } + + _ = pend_optional_interval(&mut animation_interval) => { + println!("interval {:?}", start.elapsed()); + } + } + + update_cursor(&mut animation_state, selected, &cursors).await?; + } +} + +fn load(path: &str) -> Option { + let mut zip = ZipArchive::new( + fs::OpenOptions::new() + .read(true) + .open(env::current_exe().ok()?.parent()?.join("cursor_rush.dat")) + .ok()?, + ) + .ok()?; + let mut def = zip.by_name(path).ok()?; + let mut bytes = Vec::new(); + std::io::copy(&mut def, &mut bytes).ok(); + Some( + image::load_from_memory_with_format(&bytes, ImageFormat::from_path(path).ok()?) + .ok()? + .to_rgba8(), + ) +} + +async fn update_cursor( + animation_state: &mut AnimationState, + selected: Option, + cursors: &Vec, +) -> Result<()> { + if animation_state.cursor_index != selected { + *animation_state = AnimationState::default(); + animation_state.cursor_index = selected; + + if let Some(selected) = selected { + animation_state.loaded_image = load(&cursors[selected].path); + animation_state.loaded_particles = cursors[selected] + .particles + .iter() + .map(|x| load(&x.path)) + .collect(); + + if let Some(i) = &animation_state.loaded_image { + set_system_cursor(i)?; + } + } else { + restore_default_cursor()?; + } + } + + Ok(()) +} diff --git a/frontend/src-tauri/src/cursor_def.json b/frontend/src-tauri/src/cursor_def.json new file mode 100644 index 0000000..5269f1f --- /dev/null +++ b/frontend/src-tauri/src/cursor_def.json @@ -0,0 +1,14 @@ +[ + { + "name": "white", + "path": "icon.png", + "animation_speed": 0.0, + "particles": [] + }, + { + "name": "red", + "path": "red.png", + "animation_speed": 0.0, + "particles": [] + } +] \ No newline at end of file diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 3422ff7..7b8ddab 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,29 +1,74 @@ use integrations::steam::Steam; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use std::env; -use std::path::{Path, PathBuf}; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; +use std::thread::spawn; +use std::{env, fs}; use tauri::{ AppHandle, CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, WindowEvent, }; +use tokio::sync::broadcast; +use zip::ZipArchive; +use anyhow::{anyhow, Result}; mod integrations; mod platform; +mod animator; static STEAM: LazyLock> = LazyLock::new(|| Mutex::new(Steam::new())); +static CURSOR_CMD: LazyLock> = + LazyLock::new(|| broadcast::channel(5).0); +static CURSOR_CHANGED: LazyLock> = + LazyLock::new(|| broadcast::channel(5).0); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CursorCmd { + Select(usize), + Next, + Reapply, + Restore, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RandomRange { + Constant(f32), + Linear(f32, f32), +} + +impl RandomRange { + pub fn next(&self) -> f32 { + match self { + RandomRange::Constant(_) => todo!(), + RandomRange::Linear(_, _) => todo!(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ParticleConfig { + path: String, + animation_speed: f32, + gravity_x: RandomRange, + gravity_y: RandomRange, + rate: f32, + fade_in: f32, + fade_out: f32, + duration: RandomRange, +} // Define cursor structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Cursor { name: String, path: String, + animation_speed: f32, + particles: Vec, } // App state to track cursors pub struct AppState { - cursors: Vec, + cursors: Arc>, current_index: usize, } @@ -34,7 +79,7 @@ pub struct AppStateWrapper(Mutex); #[tauri::command] fn get_cursors(state: tauri::State) -> Vec { let app_state = state.0.lock(); - app_state.cursors.clone() + (*app_state.cursors).clone() } // Command to get current cursor index @@ -44,33 +89,6 @@ fn get_current_cursor_index(state: tauri::State) -> usize { app_state.current_index } -// Helper function to apply a cursor by index with proper error handling and event emission -fn apply_cursor_by_index( - index: usize, - app_state: &AppState, - app_handle: &AppHandle, -) -> Result<(), String> { - if index >= app_state.cursors.len() { - return Err("Index out of bounds".to_string()); - } - - let cursor = &app_state.cursors[index]; - - // Verify the file exists - if !Path::new(&cursor.path).exists() { - return Err(format!("Cursor file does not exist: {}", cursor.path)); - } - - // Apply the cursor - if let Err(e) = platform::set_system_cursor(&cursor.path) { - return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e)); - } - - // Emit an event to notify the frontend - let _ = app_handle.emit_all("cursor_changed", index); // Ignore error - Ok(()) -} - // Helper function to perform application cleanup (Steam shutdown and cursor restoration) fn perform_app_cleanup() { // Clean up Steam integration @@ -88,52 +106,25 @@ fn perform_app_cleanup() { // Command to select a cursor #[tauri::command] -fn select_cursor( - index: usize, - state: tauri::State, - app_handle: AppHandle, -) -> Result<(), String> { - let mut app_state = state.0.lock(); - if index < app_state.cursors.len() { - app_state.current_index = index; +fn select_cursor(index: usize, app_handle: AppHandle) -> Result<(), String> { + println!("=== RESOURCE DEBUG [select_cursor] ==="); + let mut r = CURSOR_CHANGED.subscribe(); + CURSOR_CMD.send(CursorCmd::Select(index)).ok(); + let new_index = r.blocking_recv().unwrap(); + let _ = app_handle.emit_all("cursor_changed", new_index); // Ignore error - let cursor = &app_state.cursors[index]; - - let _filename = Path::new(&cursor.path) - .file_name() - .and_then(|name| name.to_str()) - .ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?; - - // Use the helper function to apply the cursor - apply_cursor_by_index(index, &app_state, &app_handle) - } else { - Err("Index out of bounds".to_string()) - } + Ok(()) } // Command to go to the next cursor #[tauri::command] -fn next_cursor( - state: tauri::State, - app_handle: AppHandle, -) -> Result { - let mut app_state = state.0.lock(); - if !app_state.cursors.is_empty() { - app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len(); - - let cursor = &app_state.cursors[app_state.current_index]; - - let _filename = Path::new(&cursor.path) - .file_name() - .and_then(|name| name.to_str()) - .ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?; - - // Use the helper function to apply the cursor - apply_cursor_by_index(app_state.current_index, &app_state, &app_handle)?; - Ok(app_state.current_index) - } else { - Err("No cursors available".to_string()) - } +fn next_cursor(app_handle: AppHandle) -> Result { + println!("=== RESOURCE DEBUG [next_cursor] ==="); + let mut r = CURSOR_CHANGED.subscribe(); + CURSOR_CMD.send(CursorCmd::Next).ok(); + let new_index = r.blocking_recv().unwrap(); + let _ = app_handle.emit_all("cursor_changed", new_index); // Ignore error + Ok(new_index) } // Command to quit the application @@ -175,120 +166,34 @@ fn temporarily_restore_cursor() -> Result<(), String> { // Command to re-apply custom cursor (after drag operations) #[tauri::command] -fn reapply_custom_cursor(state: tauri::State) -> Result<(), String> { - let app_state = state.0.lock(); - if !app_state.cursors.is_empty() && app_state.current_index < app_state.cursors.len() { - let current_cursor = &app_state.cursors[app_state.current_index]; - match platform::set_system_cursor(¤t_cursor.path) { - Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to re-apply custom cursor: {}", e)), - } - } else { - Err("No cursor to re-apply".to_string()) - } +fn reapply_custom_cursor() -> Result<(), String> { + CURSOR_CMD.send(CursorCmd::Reapply).ok(); + Ok(()) } // Command to manually restore default cursors #[tauri::command] fn restore_default_cursors() -> Result<(), String> { - match platform::restore_system_cursor() { - Ok(_) => Ok(()), - Err(e) => { - eprintln!("Failed to restore default cursors: {}", e); - Err(format!("Failed to restore default cursors: {}", e)) - } - } -} - -// Get possible cursor directory paths -fn get_cursor_directory_paths() -> Vec { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - - vec![ - manifest_dir.join("../src/cursors"), - manifest_dir.join("src/cursors"), - manifest_dir.join("cursors"), - manifest_dir.clone(), // Check manifest dir itself - ] -} - -// Process a single cursor file entry -fn process_cursor_file(entry: std::fs::DirEntry) -> Option { - let path = entry.path(); - - if path.is_file() - && path - .extension() - .map_or(false, |ext| ext == "png" || ext == "cur") - { - let name = path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("Unknown") - .to_string(); - - Some(Cursor { - name, - path: path.to_string_lossy().to_string(), - }) - } else { - None - } -} - -// Scan cursor directories for cursor files -fn scan_cursor_directories() -> Vec { - let possible_cursor_dirs = get_cursor_directory_paths(); - - for cursors_dir in possible_cursor_dirs.iter() { - if cursors_dir.exists() { - if let Ok(entries) = std::fs::read_dir(cursors_dir) { - let cursors: Vec = entries - .filter_map(Result::ok) - .filter_map(process_cursor_file) - .collect(); - - if !cursors.is_empty() { - return cursors; - } - } - } - } - - Vec::new() -} - -// Create fallback cursors when no files are found -fn create_fallback_cursors() -> Vec { - eprintln!("No cursor files found, using fallback cursors"); - - vec![ - Cursor { - name: "Default".to_string(), - path: "src/cursors/default.png".to_string(), - }, - Cursor { - name: "Icon".to_string(), - path: "src/cursors/icon.png".to_string(), - }, - Cursor { - name: "Red".to_string(), - path: "src/cursors/red.png".to_string(), - }, - ] + println!("Manual restore default cursors command received"); + CURSOR_CMD.send(CursorCmd::Restore).ok(); + Ok(()) } // Function to load cursor files from src/cursors directory -fn load_cursor_files() -> Vec { - let mut cursors = scan_cursor_directories(); - - if cursors.is_empty() { - cursors = create_fallback_cursors(); - } - - // Sort cursors by name for consistent ordering - cursors.sort_by(|a, b| a.name.cmp(&b.name)); - cursors +fn load_cursor_files() -> Result> { + println!("=== RESOURCE DEBUG [load_cursor_files] ==="); + let mut zip = ZipArchive::new( + fs::OpenOptions::new().read(true).open( + env::current_exe()? + .parent() + .ok_or(anyhow!("failed to get parent"))? + .join("cursor_rush.dat"), + )?, + )?; + let mut def = zip.by_name("cursor_def.json")?; + let mut bytes = Vec::new(); + std::io::copy(&mut def, &mut bytes)?; + Ok(serde_json::from_slice(&bytes)?) } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -302,11 +207,17 @@ pub fn run() { } // Load cursors from assets directory - let cursors = load_cursor_files(); + let cursors = Arc::new(load_cursor_files().expect("failed to load cursors")); + + { + let cursors = cursors.clone(); + spawn(move ||animator::animate_loop(cursors)); + } + // Create app state let app_state = AppState { - cursors, + cursors: cursors, current_index: 0, }; @@ -358,18 +269,8 @@ pub fn run() { SystemTrayEvent::MenuItemClick { id, .. } => { match id.as_str() { "next_cursor" => { - let state = app.state::(); - let mut app_state = state.0.lock(); - - if !app_state.cursors.is_empty() { - app_state.current_index = - (app_state.current_index + 1) % app_state.cursors.len(); - - // Use the helper function to apply the cursor - if let Err(e) = apply_cursor_by_index(app_state.current_index, &app_state, app) { - eprintln!("Error applying cursor from tray: {}", e); - } - } + println!("Next cursor requested from tray"); + CURSOR_CMD.send(CursorCmd::Next).ok(); } "show_window" => { if let Some(window) = app.get_window("main") { diff --git a/frontend/src-tauri/src/platform/mod.rs b/frontend/src-tauri/src/platform/mod.rs index f302649..46e7e58 100644 --- a/frontend/src-tauri/src/platform/mod.rs +++ b/frontend/src-tauri/src/platform/mod.rs @@ -15,8 +15,9 @@ mod macos; #[cfg(target_os = "linux")] mod linux; -// Error type for platform operations -pub type Result = std::result::Result; +use anyhow::Result; + +use image::RgbaImage; // Platform-agnostic interface for cursor handling #[cfg(target_os = "windows")] @@ -41,9 +42,9 @@ pub fn initialize() -> Result<()> { } // Cross-platform implementation for setting cursor -pub fn set_system_cursor(path: &str) -> Result<()> { - println!("Setting system cursor: {}", path); - set_cursor(path) +pub fn set_system_cursor(image: &RgbaImage) -> Result<()> { + println!("Setting system cursor"); + set_cursor(image) } // Cross-platform implementation for restoring default cursor diff --git a/frontend/src-tauri/src/platform/types.rs b/frontend/src-tauri/src/platform/types.rs index 55a2c5d..e69de29 100644 --- a/frontend/src-tauri/src/platform/types.rs +++ b/frontend/src-tauri/src/platform/types.rs @@ -1,37 +0,0 @@ -use std::path::PathBuf; -use image::RgbaImage; - -// Windows-specific cursor handle wrapper -// -// SAFETY: This wrapper around HCURSOR implements Send for the same reasons as SyncHCURSOR: -// Windows cursor handles are reference-counted by the kernel and safe to transfer between threads. -// The handle value is just a pointer-sized integer that represents a kernel resource. -// -// NOTE: This type serves a similar purpose to SyncHCURSOR in windows/types.rs but is used -// for cross-platform compatibility. Consider consolidating these types in the future. -#[cfg(target_os = "windows")] -#[derive(Debug, Clone, Copy)] -pub struct WinCursorWrapper { - pub handle: winapi::shared::windef::HCURSOR, -} - -#[cfg(target_os = "windows")] -unsafe impl Send for WinCursorWrapper {} - -// NOTE: WinCursorWrapper doesn't implement Sync because it's only used for transferring -// cursor handles between threads, not for sharing them. If Sync is needed in the future, -// it would be safe to implement for the same reasons as Send. - -// Common type for cursor information -#[derive(Debug, Clone)] -pub struct CursorInfo { - pub name: String, - pub path: PathBuf, - pub image: Option, - pub hotspot_x: i32, - pub hotspot_y: i32, - - // Platform-specific fields - #[cfg(target_os = "windows")] - pub win_cursor: Option, -} diff --git a/frontend/src-tauri/src/platform/windows/cursor.rs b/frontend/src-tauri/src/platform/windows/cursor.rs index 349c921..fce30c5 100644 --- a/frontend/src-tauri/src/platform/windows/cursor.rs +++ b/frontend/src-tauri/src/platform/windows/cursor.rs @@ -1,5 +1,6 @@ // Windows platform cursor management +use ::image::RgbaImage; use std::ptr::null_mut; use std::sync::atomic::Ordering; use winapi::{ @@ -17,10 +18,9 @@ use winapi::{ shared::minwindef::{TRUE, FALSE, DWORD}, shared::windef::{HDC, HBITMAP, HGDIOBJ}, }; -use ::image::{RgbaImage, open as image_open}; -use super::types::*; use super::state::*; +use super::types::*; // Control handler function for console events (Ctrl+C, close, etc.) extern "system" fn ctrl_handler(_ctrl_type: u32) -> i32 { @@ -333,7 +333,8 @@ pub fn cleanup() { // Try to restore each system cursor to its default for &cursor_id in SYSTEM_CURSORS.iter() { - let default_cursor = LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16)); + let default_cursor = + LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16)); if !default_cursor.is_null() { let cursor_copy = CopyIcon(default_cursor); if !cursor_copy.is_null() { @@ -413,7 +414,10 @@ pub fn hide_system_cursor() -> bool { if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) != 0 { CTRL_HANDLER_REGISTERED.store(true, Ordering::SeqCst); } else { - eprintln!("Failed to register control handler: {}", std::io::Error::last_os_error()); + eprintln!( + "Failed to register control handler: {}", + std::io::Error::last_os_error() + ); } } }); @@ -428,7 +432,8 @@ pub fn hide_system_cursor() -> bool { unsafe { for &cursor_id in SYSTEM_CURSORS.iter() { // Get the original cursor - let original_cursor = CopyIcon(LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16))); + 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))); @@ -456,27 +461,8 @@ pub fn hide_system_cursor() -> bool { } // Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) -> bool { - // Use cached image if available, otherwise load from disk - let image: image::RgbaImage = if let Some(ref cached_image) = cursor_info.image { - let img_clone: image::RgbaImage = cached_image.clone(); - img_clone - } else { - match image_open(&cursor_info.path) { - Ok(img) => { - let rgba = img.to_rgba8(); - // Cache the image for future use - // Make sure to specify the type to avoid type inference issues - let image_copy: image::RgbaImage = rgba.clone(); - cursor_info.image = Some(image_copy); - rgba - }, - Err(e) => { - eprintln!("Failed to load cursor image: {}", e); - return false; - } - } - }; +pub fn update_cursor_info(image: &RgbaImage) -> bool { + println!("Updating cursor"); // Store the image in the global state for reference if let Some(mut guard) = safe_lock(&CURRENT_CURSOR_IMAGE, "storing cursor image") { @@ -488,12 +474,7 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) 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) - }; + let new_cursor = create_cursor_from_image(&image); if let Some(new_cursor_wrapper) = new_cursor { // Apply the new cursor to all system cursors first @@ -515,15 +496,6 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) return false; } - // Update the cached cursor in CursorInfo - #[cfg(windows)] - { - // Convert SyncHCURSOR to WinCursorWrapper for storage in cursor_info - cursor_info.win_cursor = Some(crate::platform::types::WinCursorWrapper { - handle: new_cursor_wrapper.0 - }); - } - 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 @@ -533,30 +505,11 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) 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.handle.is_null() { - if let Some(mut cursor_guard) = safe_lock(&CUSTOM_CURSOR, "using cached cursor fallback") { - // Convert WinCursorWrapper to SyncHCURSOR - let sync_cursor = super::types::SyncHCURSOR(cached_cursor.handle); - *cursor_guard = Some(sync_cursor); - SetCursor(cached_cursor.handle); - return true; - } else { - eprintln!("Failed to use cached cursor fallback - mutex lock failed"); - } - } - } - 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; @@ -584,7 +537,10 @@ unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCU 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); + eprintln!( + "Failed to copy cursor for priority ID: {} - Error: {}", + cursor_id, error + ); success = false; continue; } @@ -592,7 +548,10 @@ unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCU // 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); + 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; } diff --git a/frontend/src-tauri/src/platform/windows/mod.rs b/frontend/src-tauri/src/platform/windows/mod.rs index d3fee4f..5812e7e 100644 --- a/frontend/src-tauri/src/platform/windows/mod.rs +++ b/frontend/src-tauri/src/platform/windows/mod.rs @@ -4,26 +4,17 @@ pub mod types; mod cursor; mod state; -use std::path::PathBuf; -use crate::platform::Result; +use image::RgbaImage; + +use anyhow::{anyhow, Result}; // Functions expected by the platform-agnostic interface -pub fn set_cursor(path: &str) -> Result<()> { - - // Create a cursor info struct to pass to update_cursor_info - let mut cursor_info = crate::platform::types::CursorInfo { - name: path.split('\\').last().unwrap_or("unknown").to_string(), - path: PathBuf::from(path), - image: None, - hotspot_x: 0, // Default hotspot at top-left - hotspot_y: 0, - #[cfg(target_os = "windows")] - win_cursor: None, - }; +pub fn set_cursor(image: &RgbaImage) -> Result<()> { + println!("Windows platform: setting cursor"); // Use the existing update_cursor_info function to handle loading and applying the cursor - if !cursor::update_cursor_info(&mut cursor_info) { - return Err(format!("Failed to update cursor from {}", path)); + if !cursor::update_cursor_info(image) { + return Err(anyhow!("Failed to update cursor")); } Ok(()) @@ -44,7 +35,7 @@ pub fn restore_default_cursor_temporarily() -> Result<()> { pub fn init() -> Result<()> { println!("Windows platform: initializing cursor system"); if !cursor::hide_system_cursor() { - return Err("Failed to initialize cursor system".to_string()); + return Err(anyhow!("Failed to initialize cursor system")); } Ok(()) }