this definitely needed to be over 1k lines of code for our initial push
This commit is contained in:
+759
@@ -0,0 +1,759 @@
|
||||
// Platform-specific cursor handling
|
||||
|
||||
// Windows implementation
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::sync::Once;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use winapi::{
|
||||
um::winuser::{
|
||||
GetDC, ReleaseDC, GetCursorPos, LoadCursorW,
|
||||
SetSystemCursor, CopyIcon, IDC_ARROW,
|
||||
CreateCursor, SystemParametersInfoW, SPI_SETCURSORS,
|
||||
CreateWindowExW, DestroyWindow, UpdateLayeredWindow,
|
||||
SetLayeredWindowAttributes, ShowWindow, SetWindowPos,
|
||||
RegisterClassExW, DefWindowProcW,
|
||||
WS_EX_LAYERED, WS_EX_TRANSPARENT, WS_EX_TOPMOST, WS_EX_TOOLWINDOW,
|
||||
WS_POPUP, SW_SHOW, HWND_TOPMOST, SWP_NOSIZE, SWP_NOMOVE,
|
||||
LWA_ALPHA, LWA_COLORKEY, WM_DESTROY, WM_CLOSE,
|
||||
CS_HREDRAW, CS_VREDRAW,
|
||||
},
|
||||
um::wingdi::{
|
||||
SetPixel, CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject,
|
||||
CreateCompatibleBitmap, RGB, BLENDFUNCTION, AC_SRC_OVER, AC_SRC_ALPHA,
|
||||
},
|
||||
um::consoleapi::SetConsoleCtrlHandler,
|
||||
shared::windef::{HWND, POINT, HCURSOR, SIZE},
|
||||
shared::minwindef::{DWORD, BOOL, TRUE, FALSE, UINT, WPARAM, LPARAM, LRESULT},
|
||||
};
|
||||
|
||||
// Import the Windows API function to reset system cursors
|
||||
#[link(name = "user32")]
|
||||
extern "system" {
|
||||
fn SystemParametersInfoA(uiAction: u32, uiParam: u32, pvParam: *mut std::ffi::c_void, fWinIni: u32) -> BOOL;
|
||||
}
|
||||
|
||||
// Constants for SystemParametersInfo
|
||||
const SPI_SETCURSORS_A: u32 = 0x0057;
|
||||
const SPIF_UPDATEINIFILE: u32 = 0x01;
|
||||
const SPIF_SENDCHANGE: u32 = 0x02;
|
||||
use std::ptr::null_mut;
|
||||
use ::image::{RgbaImage, open as image_open};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
// Static variables for cursor state
|
||||
static mut CURSOR_HIDDEN: bool = false;
|
||||
|
||||
// Flag to control the rendering thread
|
||||
static RENDERING_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Define all system cursor IDs we'll replace
|
||||
static SYSTEM_CURSORS: [DWORD; 11] = [
|
||||
32512, // IDC_ARROW
|
||||
32513, // IDC_IBEAM
|
||||
32515, // IDC_CROSS
|
||||
32514, // IDC_WAIT
|
||||
32649, // IDC_HAND
|
||||
32650, // IDC_APPSTARTING
|
||||
32648, // IDC_NO
|
||||
32646, // IDC_SIZEALL
|
||||
32645, // IDC_SIZENS
|
||||
32644, // IDC_SIZEWE
|
||||
32642, // IDC_SIZENWSE
|
||||
];
|
||||
|
||||
// Use lazy_static for thread-safe access to the cursor image
|
||||
lazy_static! {
|
||||
static ref CURRENT_CURSOR_IMAGE: Arc<Mutex<Option<RgbaImage>>> = Arc::new(Mutex::new(None));
|
||||
static ref RENDER_THREAD: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
// We need to use a static variable for the window handle since HWND isn't Send
|
||||
static mut CURSOR_WINDOW: Option<HWND> = None;
|
||||
|
||||
// Window class name for our cursor window
|
||||
const CURSOR_WINDOW_CLASS: &str = "CursorRushOverlay";
|
||||
|
||||
// Window procedure for our cursor window
|
||||
unsafe extern "system" fn window_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
match msg {
|
||||
WM_CLOSE | WM_DESTROY => {
|
||||
DestroyWindow(hwnd);
|
||||
0
|
||||
},
|
||||
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a layered, topmost window for our cursor
|
||||
unsafe fn create_cursor_window() -> Option<HWND> {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
// Convert class name to wide string
|
||||
let class_name: Vec<u16> = OsStr::new(CURSOR_WINDOW_CLASS)
|
||||
.encode_wide()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
// Register window class
|
||||
let mut wc = std::mem::zeroed::<winapi::um::winuser::WNDCLASSEXW>();
|
||||
wc.cbSize = std::mem::size_of::<winapi::um::winuser::WNDCLASSEXW>() as u32;
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = Some(window_proc);
|
||||
wc.hInstance = std::ptr::null_mut();
|
||||
wc.lpszClassName = class_name.as_ptr();
|
||||
wc.hCursor = LoadCursorW(std::ptr::null_mut(), IDC_ARROW as _);
|
||||
|
||||
let atom = RegisterClassExW(&wc);
|
||||
if atom == 0 {
|
||||
eprintln!("Failed to register window class");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create window with layered, transparent, and topmost attributes
|
||||
// WS_EX_TRANSPARENT: Makes the window transparent to mouse clicks
|
||||
// WS_EX_LAYERED: Required for transparency and UpdateLayeredWindow
|
||||
// WS_EX_TOPMOST: Keeps the window above all other windows
|
||||
// WS_EX_TOOLWINDOW: Prevents the window from showing in the taskbar
|
||||
// WS_EX_NOACTIVATE: Prevents the window from becoming active when clicked
|
||||
let hwnd = CreateWindowExW(
|
||||
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | 0x08000000, // WS_EX_NOACTIVATE
|
||||
class_name.as_ptr(),
|
||||
std::ptr::null(), // Window title (none)
|
||||
WS_POPUP, // Popup window with no border
|
||||
0, 0, // Position (will be updated based on cursor position)
|
||||
128, 128, // Size (will be updated based on cursor image)
|
||||
std::ptr::null_mut(), // Parent window
|
||||
std::ptr::null_mut(), // Menu
|
||||
std::ptr::null_mut(), // Instance
|
||||
std::ptr::null_mut(), // Additional data
|
||||
);
|
||||
|
||||
if hwnd.is_null() {
|
||||
eprintln!("Failed to create cursor window");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Make the window transparent with click-through capability
|
||||
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA | LWA_COLORKEY);
|
||||
|
||||
// Show the window
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
|
||||
// Ensure it stays on top
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE
|
||||
);
|
||||
|
||||
Some(hwnd)
|
||||
}
|
||||
|
||||
// We'll use a static variable for original cursors since they're not thread-safe
|
||||
static mut ORIGINAL_CURSORS: Option<Vec<(DWORD, HCURSOR)>> = None;
|
||||
|
||||
// Flag to track if we've registered the control handler
|
||||
static mut CTRL_HANDLER_REGISTERED: bool = false;
|
||||
|
||||
// Use Once to ensure initialization happens only once
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
// Control handler function for console events (Ctrl+C, close, etc.)
|
||||
extern "system" fn ctrl_handler(_ctrl_type: DWORD) -> BOOL {
|
||||
println!("Control event received, restoring cursors...");
|
||||
// Call our cleanup function to restore cursors
|
||||
cleanup();
|
||||
// Return FALSE to allow other handlers to process the event
|
||||
FALSE
|
||||
}
|
||||
|
||||
// Cleanup function to restore cursors and clean up resources
|
||||
fn cleanup() {
|
||||
unsafe {
|
||||
// Only restore if we've hidden the cursor
|
||||
if CURSOR_HIDDEN {
|
||||
println!("Restoring system cursors...");
|
||||
|
||||
// First, force Windows to reload the cursors
|
||||
SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
||||
|
||||
// Try multiple restoration methods and track success
|
||||
let mut restoration_success = false;
|
||||
|
||||
// Method 1: Try to restore the original system cursors if we have them
|
||||
if let Some(ref original_cursors) = ORIGINAL_CURSORS {
|
||||
if !original_cursors.is_empty() {
|
||||
let mut method1_success = true;
|
||||
println!("Attempting to restore {} saved original cursors", original_cursors.len());
|
||||
|
||||
for &(cursor_id, cursor) in original_cursors.iter() {
|
||||
// Make a copy of the original cursor
|
||||
let cursor_copy = CopyIcon(cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
// Restore the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to restore system cursor {}", cursor_id);
|
||||
method1_success = false;
|
||||
}
|
||||
} else {
|
||||
method1_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if method1_success {
|
||||
println!("Successfully restored original cursors");
|
||||
restoration_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: If method 1 failed, try with the default arrow cursor
|
||||
if !restoration_success {
|
||||
println!("Using arrow cursor restoration method");
|
||||
let mut method2_success = true;
|
||||
|
||||
// Load the default arrow cursor
|
||||
let arrow_cursor = LoadCursorW(null_mut(), 32512 as _); // IDC_ARROW = 32512
|
||||
if !arrow_cursor.is_null() {
|
||||
// Make a copy for each system cursor
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
let cursor_copy = CopyIcon(arrow_cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to set system cursor {} to arrow", cursor_id);
|
||||
method2_success = false;
|
||||
}
|
||||
} else {
|
||||
method2_success = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method2_success = false;
|
||||
}
|
||||
|
||||
if method2_success {
|
||||
println!("Successfully restored cursors using arrow cursor");
|
||||
restoration_success = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Reset all system cursors to their defaults using Windows API
|
||||
if !restoration_success {
|
||||
println!("Using system reset method");
|
||||
// Call our reset function which uses both W and A versions of SystemParametersInfo
|
||||
reset_system_cursors();
|
||||
|
||||
// We'll assume this worked unless we have evidence it didn't
|
||||
restoration_success = true;
|
||||
|
||||
// Try to verify by checking if we can load a cursor
|
||||
let test_cursor = LoadCursorW(null_mut(), 32512 as _); // IDC_ARROW
|
||||
if test_cursor.is_null() {
|
||||
restoration_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 4: Only create and run a batch file as a last resort if all other methods failed
|
||||
if !restoration_success {
|
||||
println!("All direct restoration methods failed, using batch file fallback");
|
||||
create_cursor_reset_file();
|
||||
}
|
||||
|
||||
CURSOR_HIDDEN = false;
|
||||
println!("System cursors restoration complete");
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the rendering thread
|
||||
RENDERING_ACTIVE.store(false, Ordering::SeqCst);
|
||||
|
||||
// Destroy the cursor window if it exists
|
||||
unsafe {
|
||||
if let Some(hwnd) = CURSOR_WINDOW.take() {
|
||||
DestroyWindow(hwnd);
|
||||
println!("Cursor window destroyed during cleanup");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the cleanup function with control handler
|
||||
fn register_cleanup() {
|
||||
// Use Once to ensure this only happens once
|
||||
INIT.call_once(|| {
|
||||
unsafe {
|
||||
// Register our control handler
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) == 0 {
|
||||
eprintln!("Failed to register control handler");
|
||||
} else {
|
||||
println!("Control handler registered for cursor restoration");
|
||||
CTRL_HANDLER_REGISTERED = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize module
|
||||
fn initialize() {
|
||||
// Register cleanup handler on module load
|
||||
register_cleanup();
|
||||
|
||||
// Reset system cursors on startup in case they were left in a bad state
|
||||
reset_system_cursors();
|
||||
}
|
||||
|
||||
// Call initialize when module is loaded
|
||||
#[ctor::ctor]
|
||||
fn module_init() {
|
||||
initialize();
|
||||
println!("Windows cursor module initialized with cleanup handlers");
|
||||
}
|
||||
|
||||
// Call cleanup when module is unloaded
|
||||
#[ctor::dtor]
|
||||
fn module_cleanup() {
|
||||
println!("Module unloading, restoring cursors...");
|
||||
// Just call cleanup which now handles all restoration methods
|
||||
cleanup();
|
||||
// No need to create a reset file here as cleanup() will do it if needed
|
||||
}
|
||||
|
||||
// Function to reset all system cursors to their defaults
|
||||
fn reset_system_cursors() {
|
||||
unsafe {
|
||||
// Try multiple approaches to reset cursors
|
||||
|
||||
// First try with the W version
|
||||
let result1 = SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0);
|
||||
|
||||
// Then try with the A version with flags to update the system settings
|
||||
let result2 = SystemParametersInfoA(
|
||||
SPI_SETCURSORS_A,
|
||||
0,
|
||||
null_mut(),
|
||||
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE
|
||||
);
|
||||
|
||||
// Try direct call to rundll32 as a more forceful approach
|
||||
if result1 == 0 && result2 == 0 {
|
||||
// Both API calls failed, try using Command
|
||||
match Command::new("rundll32.exe")
|
||||
.args(&["user32.dll,SystemParametersInfo", "0x0057", "0", "0", "0x01"])
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
println!("Reset system cursors using rundll32");
|
||||
} else {
|
||||
eprintln!("rundll32 cursor reset failed with status: {}", status);
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Failed to execute rundll32: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!("Reset system cursors to defaults via API");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a batch file that will reset the cursors and run it
|
||||
fn create_cursor_reset_file() {
|
||||
// Create a temporary batch file to reset cursors
|
||||
let batch_content = r#"@echo off
|
||||
echo Resetting system cursors...
|
||||
rundll32.exe user32.dll,SystemParametersInfo 0x0057 0 0 0x01 | 0x02
|
||||
echo Cursors reset.
|
||||
timeout /t 2
|
||||
del "%~f0"
|
||||
"#;
|
||||
|
||||
let file_path = "reset_cursors.bat";
|
||||
|
||||
// Write the batch file
|
||||
match File::create(file_path) {
|
||||
Ok(mut file) => {
|
||||
if let Err(e) = file.write_all(batch_content.as_bytes()) {
|
||||
eprintln!("Failed to write to batch file: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Created cursor reset batch file");
|
||||
|
||||
// Run the batch file in a separate process
|
||||
match Command::new("cmd")
|
||||
.args(&["/C", "start", "/min", file_path])
|
||||
.spawn()
|
||||
{
|
||||
Ok(_) => println!("Launched cursor reset batch file"),
|
||||
Err(e) => eprintln!("Failed to launch batch file: {}", e),
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Failed to create batch file: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// Function to save the original system cursors
|
||||
fn save_original_cursors() {
|
||||
unsafe {
|
||||
// Only save if we haven't already
|
||||
if ORIGINAL_CURSORS.is_none() {
|
||||
let mut cursors = Vec::new();
|
||||
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
let cursor = LoadCursorW(null_mut(), cursor_id as _);
|
||||
if !cursor.is_null() {
|
||||
// Make a copy of the cursor
|
||||
let cursor_copy = CopyIcon(cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
cursors.push((cursor_id, cursor_copy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ORIGINAL_CURSORS = Some(cursors);
|
||||
println!("Saved {} original system cursors", ORIGINAL_CURSORS.as_ref().unwrap().len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor(path: &Path) -> bool {
|
||||
// Save the original system cursors before hiding them
|
||||
save_original_cursors();
|
||||
|
||||
// Register cleanup handler to ensure cursor is restored on exit
|
||||
register_cleanup();
|
||||
|
||||
// Hide the system cursor
|
||||
unsafe {
|
||||
if !CURSOR_HIDDEN {
|
||||
// Create an invisible cursor (1x1 transparent cursor)
|
||||
// Create bitmap data for a 1x1 cursor
|
||||
let and_plane: [u8; 1] = [0xFF]; // All bits 1 = transparent
|
||||
let xor_plane: [u8; 1] = [0x00]; // All bits 0 = black
|
||||
|
||||
let empty_cursor = CreateCursor(
|
||||
null_mut(), // hInst
|
||||
0, // xHotSpot
|
||||
0, // yHotSpot
|
||||
1, // nWidth
|
||||
1, // nHeight
|
||||
and_plane.as_ptr() as *const _, // pvANDPlane
|
||||
xor_plane.as_ptr() as *const _, // pvXORPlane
|
||||
);
|
||||
|
||||
if empty_cursor.is_null() {
|
||||
eprintln!("Failed to create empty cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace all system cursors with our empty cursor
|
||||
for &cursor_id in SYSTEM_CURSORS.iter() {
|
||||
// Make a copy of our empty cursor for each system cursor
|
||||
let cursor_copy = CopyIcon(empty_cursor as _) as HCURSOR;
|
||||
if !cursor_copy.is_null() {
|
||||
// Set the system cursor
|
||||
if SetSystemCursor(cursor_copy, cursor_id) == 0 {
|
||||
eprintln!("Failed to set system cursor {}", cursor_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CURSOR_HIDDEN = true;
|
||||
println!("System cursors hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the cursor image
|
||||
match image_open(path) {
|
||||
Ok(img) => {
|
||||
let rgba_img = img.to_rgba8();
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(rgba_img);
|
||||
println!("Using PNG cursor: {:?}", path);
|
||||
|
||||
// Start the rendering thread if it's not already running
|
||||
start_render_thread_if_needed();
|
||||
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load cursor image: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to restore the cursor when the program exits
|
||||
pub fn restore_cursor() {
|
||||
println!("Explicit restore_cursor called");
|
||||
|
||||
// Call our cleanup function which handles cursor restoration
|
||||
cleanup();
|
||||
|
||||
// Join the thread if it exists
|
||||
if let Some(handle) = RENDER_THREAD.lock().unwrap().take() {
|
||||
// Use a timeout to avoid hanging if the thread is stuck
|
||||
let thread_handle = handle;
|
||||
match thread_handle.join() {
|
||||
Ok(_) => println!("Render thread joined successfully"),
|
||||
Err(_) => eprintln!("Error joining render thread"),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the cursor window if it exists
|
||||
unsafe {
|
||||
if let Some(hwnd) = CURSOR_WINDOW.take() {
|
||||
if DestroyWindow(hwnd) == 0 {
|
||||
eprintln!("Failed to destroy cursor window");
|
||||
} else {
|
||||
println!("Cursor window destroyed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the cursor image
|
||||
*CURRENT_CURSOR_IMAGE.lock().unwrap() = None;
|
||||
|
||||
// Unregister our control handler to avoid double cleanup
|
||||
unsafe {
|
||||
if CTRL_HANDLER_REGISTERED {
|
||||
if SetConsoleCtrlHandler(Some(ctrl_handler), FALSE) == 0 {
|
||||
eprintln!("Failed to unregister control handler");
|
||||
} else {
|
||||
CTRL_HANDLER_REGISTERED = false;
|
||||
println!("Control handler unregistered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final verification that cursors are restored
|
||||
unsafe {
|
||||
if CURSOR_HIDDEN {
|
||||
println!("Cursor still marked as hidden after cleanup, forcing reset");
|
||||
reset_system_cursors();
|
||||
CURSOR_HIDDEN = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start the rendering thread if it's not already running
|
||||
fn start_render_thread_if_needed() {
|
||||
let mut render_thread_guard = RENDER_THREAD.lock().unwrap();
|
||||
|
||||
// Only start a new thread if one isn't already running
|
||||
if render_thread_guard.is_none() {
|
||||
// Set the rendering active flag
|
||||
RENDERING_ACTIVE.store(true, Ordering::SeqCst);
|
||||
|
||||
// Clone the Arc for the thread
|
||||
let cursor_image_arc = CURRENT_CURSOR_IMAGE.clone();
|
||||
|
||||
// Create a new thread for continuous rendering
|
||||
let handle = thread::spawn(move || {
|
||||
println!("Starting cursor render thread");
|
||||
|
||||
// Render loop
|
||||
while RENDERING_ACTIVE.load(Ordering::SeqCst) {
|
||||
render_cursor_internal(&cursor_image_arc);
|
||||
|
||||
// Sleep to control frame rate (60 FPS)
|
||||
thread::sleep(Duration::from_millis(16));
|
||||
}
|
||||
|
||||
println!("Cursor render thread stopped");
|
||||
});
|
||||
|
||||
// Store the thread handle
|
||||
*render_thread_guard = Some(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal function to render the cursor (called from the render thread)
|
||||
fn render_cursor_internal(cursor_image_arc: &Arc<Mutex<Option<RgbaImage>>>) {
|
||||
// Get the cursor image
|
||||
let cursor_image_lock = cursor_image_arc.lock().unwrap();
|
||||
if let Some(ref cursor_image) = *cursor_image_lock {
|
||||
unsafe {
|
||||
// Get the current mouse position
|
||||
let mut point = POINT { x: 0, y: 0 };
|
||||
if GetCursorPos(&mut point) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
let (width, height) = cursor_image.dimensions();
|
||||
|
||||
// Get or create the cursor window
|
||||
let hwnd = unsafe {
|
||||
if CURSOR_WINDOW.is_none() {
|
||||
// Create a new window if we don't have one
|
||||
if let Some(window) = create_cursor_window() {
|
||||
CURSOR_WINDOW = Some(window);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap is safe because we just checked it's Some
|
||||
CURSOR_WINDOW.unwrap()
|
||||
};
|
||||
|
||||
// Move the window to the cursor position
|
||||
// Adjust position to center the cursor image on the actual cursor point
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOPMOST,
|
||||
point.x - (width as i32 / 2),
|
||||
point.y - (height as i32 / 2),
|
||||
width as i32,
|
||||
height as i32,
|
||||
0
|
||||
);
|
||||
|
||||
// Get the window DC
|
||||
let hwnd_dc = GetDC(hwnd);
|
||||
if hwnd_dc.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a compatible DC for double-buffering
|
||||
let mem_dc = CreateCompatibleDC(hwnd_dc);
|
||||
if mem_dc.is_null() {
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a compatible bitmap
|
||||
let bitmap = CreateCompatibleBitmap(hwnd_dc, width as i32, height as i32);
|
||||
if bitmap.is_null() {
|
||||
DeleteDC(mem_dc);
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the bitmap into the memory DC
|
||||
let old_bitmap = SelectObject(mem_dc, bitmap as _);
|
||||
|
||||
// Draw the cursor image to the memory DC
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pixel = cursor_image.get_pixel(x, y);
|
||||
|
||||
// Only draw non-transparent pixels
|
||||
if pixel[3] > 128 { // Alpha channel > 50%
|
||||
let color = RGB(pixel[0], pixel[1], pixel[2]);
|
||||
SetPixel(mem_dc, x as i32, y as i32, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the memory DC to the window DC using BitBlt
|
||||
use winapi::um::wingdi::BitBlt;
|
||||
use winapi::um::wingdi::SRCCOPY;
|
||||
|
||||
BitBlt(
|
||||
hwnd_dc,
|
||||
0, 0,
|
||||
width as i32, height as i32,
|
||||
mem_dc,
|
||||
0, 0,
|
||||
SRCCOPY
|
||||
);
|
||||
|
||||
// Clean up
|
||||
SelectObject(mem_dc, old_bitmap as _);
|
||||
DeleteObject(bitmap as _);
|
||||
DeleteDC(mem_dc);
|
||||
ReleaseDC(hwnd, hwnd_dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to render the custom cursor (called from the application)
|
||||
pub fn render_cursor() {
|
||||
// This is now just a trigger to ensure the render thread is running
|
||||
start_render_thread_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
// macOS implementation
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos {
|
||||
use std::path::Path;
|
||||
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
// macOS cursor implementation would go here
|
||||
// This is a placeholder - actual implementation would require
|
||||
// Objective-C/Cocoa bindings to NSCursor
|
||||
println!("macOS cursor swapping not yet implemented");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn restore_cursor() {
|
||||
// Placeholder for restoring cursor on macOS
|
||||
println!("macOS cursor restoration not yet implemented");
|
||||
}
|
||||
|
||||
pub fn render_cursor() {
|
||||
// On macOS, we would update the cursor on mouse movement
|
||||
// This is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
// Linux implementation
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux {
|
||||
use std::path::Path;
|
||||
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
// Linux cursor implementation would go here
|
||||
// This is a placeholder - actual implementation would require
|
||||
// X11 or Wayland bindings
|
||||
println!("Linux cursor swapping not yet implemented");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn restore_cursor() {
|
||||
// Placeholder for restoring cursor on Linux
|
||||
println!("Linux cursor restoration not yet implemented");
|
||||
}
|
||||
|
||||
pub fn render_cursor() {
|
||||
// On Linux, we would update the cursor on mouse movement
|
||||
// This is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
// Export the appropriate implementation based on platform
|
||||
#[cfg(windows)]
|
||||
pub use windows::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::{set_cursor, restore_cursor, render_cursor};
|
||||
|
||||
// Default implementation for other platforms
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn set_cursor(_path: &Path) -> bool {
|
||||
println!("Cursor swapping not supported on this platform");
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn restore_cursor() {
|
||||
println!("Cursor restoration not supported on this platform");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn render_cursor() {
|
||||
// No-op for unsupported platforms
|
||||
}
|
||||
Reference in New Issue
Block a user