Compare commits
2
Commits
b3d85106a8
...
2e71cd8b9e
| 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
|
- 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.
|
||||||
|
|||||||
+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
|
// 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 {
|
unsafe {
|
||||||
// Force Windows to reload default cursor settings
|
// Try multiple approaches to ensure cursors are restored
|
||||||
SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
|
||||||
// Also try the A version just in case
|
// 1. First try to restore original cursors if we saved them
|
||||||
super::state::reset_system_cursors();
|
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
|
// Destroy the original cursor we saved
|
||||||
unsafe {
|
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 {
|
||||||
|
if !cursor_wrapper.0.is_null() {
|
||||||
DestroyIcon(cursor_wrapper.0);
|
DestroyIcon(cursor_wrapper.0);
|
||||||
println!("Custom cursor destroyed during cleanup.");
|
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
|
|
||||||
SetCursor(cached_cursor.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
// Now that the new cursor is applied, we can clean up the old one
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up old cursor if it exists
|
|
||||||
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
|
let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap();
|
||||||
if let Some(cursor_wrapper) = cursor_guard.take() {
|
if let Some(old_cursor) = cursor_guard.take() {
|
||||||
DestroyIcon(cursor_wrapper.0);
|
// Only destroy the old cursor after the new one is applied
|
||||||
|
DestroyIcon(old_cursor.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new cursor
|
// Store the new cursor
|
||||||
if let Some(cursor_wrapper) = create_cursor_from_image(&image) {
|
*cursor_guard = Some(new_cursor_wrapper);
|
||||||
// Store the cursor for later use
|
|
||||||
*cursor_guard = Some(cursor_wrapper);
|
|
||||||
|
|
||||||
// Cache the Windows cursor handle in the CursorInfo
|
// Update the cached cursor in 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user