Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e71cd8b9e | ||
|
|
a92ef1e07c |
@@ -6,8 +6,11 @@ A cross-platform application for customizing your cursor with gameified logic.
|
||||
|
||||
- Replace the system cursor with custom PNG images
|
||||
- Minimize to system tray for background operation
|
||||
- Cross-platform support (Windows, macOS, Linux)
|
||||
- Steam integration
|
||||
- Windows support (macOS and Linux support in development)
|
||||
- Optimized rendering with low CPU usage
|
||||
- System tray integration with context menu
|
||||
- Global hotkey support
|
||||
- Steam integration for item drops
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -28,12 +31,19 @@ src/
|
||||
│ │ ├── mod.rs # Windows module exports
|
||||
│ │ ├── types.rs # Windows type definitions
|
||||
│ │ ├── state.rs # Windows state management
|
||||
│ │ ├── cursor.rs # Windows cursor handling
|
||||
│ │ ├── window.rs # Windows window management
|
||||
│ │ └── hook.rs # Windows mouse hook handling
|
||||
│ ├── macos.rs # macOS-specific code
|
||||
│ ├── linux.rs # Linux-specific code
|
||||
│ └── unsupported.rs # Fallback for unsupported platforms
|
||||
│ │ └── cursor.rs # Windows cursor handling
|
||||
│ ├── macos/ # macOS-specific code (in development)
|
||||
│ │ ├── mod.rs # macOS module exports
|
||||
│ │ ├── state.rs # macOS state management
|
||||
│ │ └── cursor.rs # macOS cursor handling
|
||||
│ ├── 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
|
||||
│ ├── mod.rs # Integrations module exports
|
||||
│ └── steam.rs # Steam integration
|
||||
@@ -50,6 +60,19 @@ src/
|
||||
|
||||
- Rust 1.70.0 or later
|
||||
- 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
|
||||
|
||||
@@ -63,21 +86,68 @@ cargo build --release
|
||||
|
||||
## Usage
|
||||
|
||||
1. Place your cursor PNG files in the `assets` directory
|
||||
2. Run the application
|
||||
3. Select a cursor from the list
|
||||
4. Press Ctrl+Shift+F12 to exit the application
|
||||
1. Place your cursor PNG files in the `assets` directory (PNG format only)
|
||||
2. Run the application using `cargo run` or the compiled executable
|
||||
3. The application will load all PNG files from the assets directory
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- Left-click: Show the application window
|
||||
- Right-click: Open the context menu
|
||||
- Next cursor: Switch to the next cursor
|
||||
- Right-click: Open the context menu with the following options:
|
||||
- Next cursor: Switch to the next cursor in the list
|
||||
- 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.
|
||||
|
||||
+132
-38
@@ -196,18 +196,49 @@ unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option<SyncHCURSOR> {
|
||||
|
||||
// Cleanup function to restore cursors and clean up resources
|
||||
pub fn cleanup() {
|
||||
println!("Running cursor cleanup...");
|
||||
|
||||
// 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...");
|
||||
|
||||
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();
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Reset to default arrow cursor
|
||||
unsafe {
|
||||
// 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);
|
||||
@@ -222,14 +253,19 @@ pub fn cleanup() {
|
||||
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 {
|
||||
@@ -241,6 +277,8 @@ pub fn cleanup() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Cursor cleanup complete.");
|
||||
}
|
||||
|
||||
// --- Public API Functions ---
|
||||
@@ -291,6 +329,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::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);
|
||||
@@ -315,51 +355,58 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone());
|
||||
|
||||
unsafe {
|
||||
// Use cached Windows cursor if available
|
||||
#[cfg(windows)]
|
||||
if let Some(cached_cursor) = cursor_info.win_cursor {
|
||||
println!("Using cached Windows cursor handle for: {}", cursor_info.name);
|
||||
// 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)
|
||||
};
|
||||
|
||||
// Apply the cached cursor to all system cursors
|
||||
let success = apply_cursor_to_all_system_cursors(cached_cursor.0);
|
||||
if !success {
|
||||
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
|
||||
SetCursor(cached_cursor.0);
|
||||
}
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clean up old cursor if it exists
|
||||
// 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(cursor_wrapper) = cursor_guard.take() {
|
||||
DestroyIcon(cursor_wrapper.0);
|
||||
if let Some(old_cursor) = cursor_guard.take() {
|
||||
// Only destroy the old cursor after the new one is applied
|
||||
DestroyIcon(old_cursor.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);
|
||||
// Store the new cursor
|
||||
*cursor_guard = Some(new_cursor_wrapper);
|
||||
|
||||
// Cache the Windows cursor handle in the CursorInfo
|
||||
// Update the cached cursor in CursorInfo
|
||||
#[cfg(windows)]
|
||||
{
|
||||
println!("Caching Windows cursor handle for: {}", cursor_info.name);
|
||||
cursor_info.win_cursor = Some(cursor_wrapper);
|
||||
// Always update the cached cursor to the fresh one
|
||||
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 {
|
||||
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(cursor_wrapper.0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
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
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
// Make a copy of the cursor for each system cursor type
|
||||
// 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() {
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the system cursor
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user