Compare commits

..
2 Commits
Author SHA1 Message Date
Joby 2e71cd8b9e Updated README 2025-05-11 18:52:52 -05:00
Joby a92ef1e07c Fix cursor swapping issues and minimize cursor flashing 2025-05-11 18:48:04 -05:00
2 changed files with 223 additions and 59 deletions
+86 -16
View File
@@ -6,8 +6,11 @@ A cross-platform application for customizing your cursor with gameified logic.
- Replace the system cursor with custom PNG images - Replace the system cursor with custom PNG images
- Minimize to system tray for background operation - Minimize to system tray for background operation
- Cross-platform support (Windows, macOS, Linux) - Windows support (macOS and Linux support in development)
- Steam integration - Optimized rendering with low CPU usage
- System tray integration with context menu
- Global hotkey support
- Steam integration for item drops
## Project Structure ## Project Structure
@@ -28,12 +31,19 @@ src/
│ │ ├── mod.rs # Windows module exports │ │ ├── mod.rs # Windows module exports
│ │ ├── types.rs # Windows type definitions │ │ ├── types.rs # Windows type definitions
│ │ ├── state.rs # Windows state management │ │ ├── state.rs # Windows state management
│ │ ├── cursor.rs # Windows cursor handling │ │ └── cursor.rs # Windows cursor handling
│ │ ├── window.rs # Windows window management │ ├── macos/ # macOS-specific code (in development)
│ │ └── hook.rs # Windows mouse hook handling │ │ ├── mod.rs # macOS module exports
│ ├── macos.rs # macOS-specific code │ │ ├── state.rs # macOS state management
│ ├── linux.rs # Linux-specific code │ │ └── cursor.rs # macOS cursor handling
│ └── unsupported.rs # Fallback for unsupported platforms │ ├── linux/ # Linux-specific code (in development)
│ │ ├── mod.rs # Linux module exports
│ │ ├── state.rs # Linux state management
│ │ └── cursor.rs # Linux cursor handling
│ └── unsupported/ # Fallback for unsupported platforms
│ ├── mod.rs # Unsupported module exports
│ ├── state.rs # Unsupported state management
│ └── cursor.rs # Unsupported cursor handling
├── integrations/ # External integrations ├── integrations/ # External integrations
│ ├── mod.rs # Integrations module exports │ ├── mod.rs # Integrations module exports
│ └── steam.rs # Steam integration │ └── steam.rs # Steam integration
@@ -50,6 +60,19 @@ src/
- Rust 1.70.0 or later - Rust 1.70.0 or later
- Cargo - Cargo
- Platform-specific dependencies:
- Windows: None (fully supported)
- macOS: Development in progress
- Linux: Development in progress
### Dependencies
The application uses the following major dependencies:
- iced 0.13.1 - GUI framework
- tray-icon 0.20.1 - System tray functionality
- image 0.24.7 - Image processing
- global-hotkey 0.4.2 - Global hotkey registration
- steamworks 0.11.0 - Steam integration
### Build Commands ### Build Commands
@@ -63,21 +86,68 @@ cargo build --release
## Usage ## Usage
1. Place your cursor PNG files in the `assets` directory 1. Place your cursor PNG files in the `assets` directory (PNG format only)
2. Run the application 2. Run the application using `cargo run` or the compiled executable
3. Select a cursor from the list 3. The application will load all PNG files from the assets directory
4. Press Ctrl+Shift+F12 to exit the application 4. Select a cursor from the list in the UI
5. The application will replace your system cursor with the selected image
6. When closing the window, the application minimizes to the system tray
7. To fully exit, use the system tray menu or press the exit hotkey
### Custom Cursors
The application comes with two sample cursors in the `assets` directory:
- `default.png` - A simple default cursor
- `red.png` - A red-colored cursor
To add your own cursors:
1. Create PNG images with transparent backgrounds
2. Place them in the `assets` directory
3. Restart the application to load the new cursors
4. The cursor name in the UI will be derived from the filename
## Hotkeys ## Hotkeys
- Ctrl+Shift+F12: Exit the application - Ctrl+Shift+F12: Exit the application (restores original system cursor)
- If this hotkey fails, the application will try to use Ctrl+Alt+F12 or F12 as fallbacks
## System Tray ## System Tray
- Left-click: Show the application window - Left-click: Show the application window
- Right-click: Open the context menu - Right-click: Open the context menu with the following options:
- Next cursor: Switch to the next cursor - Next cursor: Switch to the next cursor in the list
- Show window: Show the application window - Show window: Show the application window
- Quit: Exit the application - Quit: Exit the application and restore the original cursor
## Performance
- The application uses optimized rendering techniques to minimize CPU usage
- On Windows, it uses the Windows API to efficiently replace system cursors
- The application uses event-driven cursor updates instead of polling when possible
- Cursor images are cached to improve performance when switching between cursors
## Previously Fixed Issues
The following issues have been addressed in recent updates:
- Task Manager compatibility: The application now properly handles elevated applications
- System tray menu stability: Fixed issues with the tray menu disappearing when mousing over it
## Steam Integration
The application includes integration with the Steam API for future gameified features:
- Item drop functionality is implemented but not yet active
- Steam must be running for this functionality to work
## Contributing
Contributions to Cursor Rush are welcome! Here are some ways you can contribute:
- Report bugs and issues
- Suggest new features or improvements
- Submit pull requests with bug fixes or new features
- Help with documentation
- Contribute to macOS and Linux platform support
## License
This project is currently in development and does not yet have a specified license. A license will be added in a future update.
+137 -43
View File
@@ -196,18 +196,49 @@ unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option<SyncHCURSOR> {
// Cleanup function to restore cursors and clean up resources // Cleanup function to restore cursors and clean up resources
pub fn cleanup() { pub fn cleanup() {
println!("Running cursor cleanup...");
// Restore system cursors if they were hidden // Restore system cursors if they were hidden
if CURSOR_HIDDEN.swap(false, Ordering::SeqCst) { // Use swap to ensure it runs only once // Use swap to ensure it runs only once
if CURSOR_HIDDEN.swap(false, Ordering::SeqCst) {
println!("Restoring system cursors..."); println!("Restoring system cursors...");
unsafe {
// Force Windows to reload default cursor settings
SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
// Also try the A version just in case
super::state::reset_system_cursors();
}
// Reset to default arrow cursor
unsafe { 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)); let arrow_cursor = LoadCursorW(null_mut(), MAKEINTRESOURCEW(IDC_ARROW as u16));
if !arrow_cursor.is_null() { if !arrow_cursor.is_null() {
SetCursor(arrow_cursor); SetCursor(arrow_cursor);
@@ -222,14 +253,19 @@ pub fn cleanup() {
let custom_cursor_to_destroy = CUSTOM_CURSOR.lock().unwrap().take(); let custom_cursor_to_destroy = CUSTOM_CURSOR.lock().unwrap().take();
if let Some(cursor_wrapper) = custom_cursor_to_destroy { if let Some(cursor_wrapper) = custom_cursor_to_destroy {
unsafe { unsafe {
DestroyIcon(cursor_wrapper.0); if !cursor_wrapper.0.is_null() {
println!("Custom cursor destroyed during cleanup."); DestroyIcon(cursor_wrapper.0);
println!("Custom cursor destroyed during cleanup.");
}
} }
} }
// Reset the last known cursor position // Reset the last known cursor position
*LAST_CURSOR_POS.lock().unwrap() = None; *LAST_CURSOR_POS.lock().unwrap() = None;
// Clear the current cursor image
*CURRENT_CURSOR_IMAGE.lock().unwrap() = None;
// Unregister control handler // Unregister control handler
if CTRL_HANDLER_REGISTERED.load(Ordering::SeqCst) { if CTRL_HANDLER_REGISTERED.load(Ordering::SeqCst) {
unsafe { unsafe {
@@ -241,6 +277,8 @@ pub fn cleanup() {
} }
} }
} }
println!("Cursor cleanup complete.");
} }
// --- Public API Functions --- // --- Public API Functions ---
@@ -291,6 +329,8 @@ pub fn hide_system_cursor() -> bool {
// Update the cursor image using CursorInfo (with caching) // Update the cursor image using CursorInfo (with caching)
pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool { 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 // Use cached image if available, otherwise load from disk
let image = if let Some(ref cached_image) = cursor_info.image { let image = if let Some(ref cached_image) = cursor_info.image {
println!("Using cached image for cursor: {}", cursor_info.name); println!("Using cached image for cursor: {}", cursor_info.name);
@@ -315,51 +355,58 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone()); *CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone());
unsafe { unsafe {
// Use cached Windows cursor if available // Create a new cursor first before destroying the old one
#[cfg(windows)] // This minimizes the time when no custom cursor is active
if let Some(cached_cursor) = cursor_info.win_cursor { let new_cursor = if let Some(ref cached_image) = cursor_info.image {
println!("Using cached Windows cursor handle for: {}", cursor_info.name); // 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)
};
// Apply the cached cursor to all system cursors if let Some(new_cursor_wrapper) = new_cursor {
let success = apply_cursor_to_all_system_cursors(cached_cursor.0); // Apply the new cursor to all system cursors first
if !success { let success = apply_cursor_to_all_system_cursors(new_cursor_wrapper.0);
eprintln!("Failed to apply cached cursor to all system cursors");
// Even if we failed to set all system cursors, try to at least set the current cursor // Now that the new cursor is applied, we can clean up the old one
SetCursor(cached_cursor.0); 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);
} }
return true; // Store the new cursor
} *cursor_guard = Some(new_cursor_wrapper);
// Clean up old cursor if it exists // Update the cached cursor in CursorInfo
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
if let Some(cursor_wrapper) = cursor_guard.take() {
DestroyIcon(cursor_wrapper.0);
}
// Create new cursor
if let Some(cursor_wrapper) = create_cursor_from_image(&image) {
// Store the cursor for later use
*cursor_guard = Some(cursor_wrapper);
// Cache the Windows cursor handle in the CursorInfo
#[cfg(windows)] #[cfg(windows)]
{ {
println!("Caching Windows cursor handle for: {}", cursor_info.name); // Always update the cached cursor to the fresh one
cursor_info.win_cursor = Some(cursor_wrapper); cursor_info.win_cursor = Some(new_cursor_wrapper);
} }
// Apply the custom cursor to all system cursors
let success = apply_cursor_to_all_system_cursors(cursor_wrapper.0);
if !success { if !success {
eprintln!("Failed to apply cursor to all system cursors"); 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 // Even if we failed to set all system cursors, try to at least set the current cursor
SetCursor(cursor_wrapper.0); SetCursor(new_cursor_wrapper.0);
} }
return true; return true;
} else { } else {
eprintln!("Failed to create cursor from image"); 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; return false;
} }
} }
@@ -371,24 +418,71 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool
unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool { unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool {
let mut success = true; 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 // Apply the cursor to all system cursor types
for &cursor_id in SYSTEM_CURSORS.iter() { // Start with the most commonly used cursors first
// Make a copy of the cursor for each system cursor type 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); let cursor_copy = CopyIcon(cursor);
if cursor_copy.is_null() { if cursor_copy.is_null() {
eprintln!("Failed to copy cursor for system cursor ID: {}", cursor_id); let error = std::io::Error::last_os_error();
eprintln!("Failed to copy cursor for priority ID: {} - Error: {}", cursor_id, error);
success = false; success = false;
continue; continue;
} }
// Set the system cursor // Set the system cursor
if SetSystemCursor(cursor_copy, cursor_id) == 0 { if SetSystemCursor(cursor_copy, cursor_id) == 0 {
eprintln!("Failed to set system cursor for ID: {}", cursor_id); 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 DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it
success = false; 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 success
} }