bundle cursors into cursor_rush.dat, started animator

This commit is contained in:
2025-05-31 00:38:29 -05:00
parent 67ee3dc8d1
commit 61157a2402
10 changed files with 292 additions and 317 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
[workspace]
members = ["frontend/src-tauri"]
resolver = "3"
resolver = "3"
+5 -1
View File
@@ -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
+20 -1
View File
@@ -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<dyn std::error::Error>> {
tauri_build::build();
@@ -38,5 +40,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}
+123
View File
@@ -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<usize>,
loaded_image: Option<RgbaImage>,
loaded_particles: Option<Vec<RgbaImage>>,
}
async fn pend_optional_interval(t: &mut Option<Interval>) -> Instant {
if let Some(t) = t {
t.tick().await
} else {
pending::<Instant>().await
}
}
#[tokio::main]
pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
let mut cmds = CURSOR_CMD.subscribe();
let mut selected: Option<usize> = None;
let start = Instant::now();
let mut animation_state = AnimationState::default();
let mut animation_interval: Option<Interval> = 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<RgbaImage> {
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<usize>,
cursors: &Vec<Cursor>,
) -> 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(())
}
+14
View File
@@ -0,0 +1,14 @@
[
{
"name": "white",
"path": "icon.png",
"animation_speed": 0.0,
"particles": []
},
{
"name": "red",
"path": "red.png",
"animation_speed": 0.0,
"particles": []
}
]
+94 -193
View File
@@ -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<Mutex<Steam>> = LazyLock::new(|| Mutex::new(Steam::new()));
static CURSOR_CMD: LazyLock<broadcast::Sender<CursorCmd>> =
LazyLock::new(|| broadcast::channel(5).0);
static CURSOR_CHANGED: LazyLock<broadcast::Sender<usize>> =
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<ParticleConfig>,
}
// App state to track cursors
pub struct AppState {
cursors: Vec<Cursor>,
cursors: Arc<Vec<Cursor>>,
current_index: usize,
}
@@ -34,7 +79,7 @@ pub struct AppStateWrapper(Mutex<AppState>);
#[tauri::command]
fn get_cursors(state: tauri::State<AppStateWrapper>) -> Vec<Cursor> {
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<AppStateWrapper>) -> 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<AppStateWrapper>,
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<AppStateWrapper>,
app_handle: AppHandle,
) -> Result<usize, String> {
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<usize, String> {
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<AppStateWrapper>) -> 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(&current_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<PathBuf> {
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<Cursor> {
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<Cursor> {
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<Cursor> = 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<Cursor> {
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<Cursor> {
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<Vec<Cursor>> {
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::<AppStateWrapper>();
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") {
+6 -5
View File
@@ -15,8 +15,9 @@ mod macos;
#[cfg(target_os = "linux")]
mod linux;
// Error type for platform operations
pub type Result<T> = std::result::Result<T, String>;
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
-37
View File
@@ -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<RgbaImage>,
pub hotspot_x: i32,
pub hotspot_y: i32,
// Platform-specific fields
#[cfg(target_os = "windows")]
pub win_cursor: Option<WinCursorWrapper>,
}
@@ -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;
}
+8 -17
View File
@@ -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(())
}