on even implimentation, known bug is that is that cursor dies when task manager is opened may fix tomorrow I've run out of time and energy today
This commit is contained in:
+5
-8
@@ -238,11 +238,10 @@ fn boot() -> (CursorRush, Task<Message>) {
|
||||
eprintln!("Failed to hide system cursor at startup!");
|
||||
}
|
||||
|
||||
// Apply the first cursor by default (this will update the cursor image and start the render thread)
|
||||
// Apply the first cursor by default
|
||||
if let Some(cursor) = cursors.first() {
|
||||
apply_cursor(&cursor.path);
|
||||
// Explicitly call render_cursor to ensure the render thread starts
|
||||
platform::render_cursor();
|
||||
// No need to call render_cursor anymore as the mouse hook handles rendering
|
||||
}
|
||||
|
||||
// Create shared state
|
||||
@@ -456,7 +455,7 @@ fn setup_tray_icon(
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
println!("Tray: Updating cursor image: {}", cursor.name);
|
||||
apply_cursor(&cursor.path);
|
||||
platform::render_cursor(); // Ensure render starts/updates
|
||||
// No need to call render_cursor anymore as the mouse hook handles rendering
|
||||
state.current_cursor_image = image_open(&cursor.path).ok().map(|img| img.to_rgba8());
|
||||
}
|
||||
}
|
||||
@@ -587,8 +586,7 @@ fn update(state: &mut CursorRush, message: Message) -> Task<Message> {
|
||||
// Apply the new cursor
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
apply_cursor(&cursor.path);
|
||||
// Ensure the render thread is running with the new cursor
|
||||
platform::render_cursor();
|
||||
// No need to call render_cursor anymore as the mouse hook handles rendering
|
||||
|
||||
// Load the new cursor image
|
||||
let current_cursor_image = match image::open(&cursor.path) {
|
||||
@@ -614,8 +612,7 @@ fn update(state: &mut CursorRush, message: Message) -> Task<Message> {
|
||||
// Apply the selected cursor
|
||||
if let Some(cursor) = state.cursors.get(state.current_index) {
|
||||
apply_cursor(&cursor.path);
|
||||
// Ensure the render thread is running with the new cursor
|
||||
platform::render_cursor();
|
||||
// No need to call render_cursor anymore as the mouse hook handles rendering
|
||||
|
||||
// Load the new cursor image
|
||||
let current_cursor_image = match image_open(&cursor.path) {
|
||||
|
||||
+153
-89
@@ -7,7 +7,6 @@ mod windows {
|
||||
use std::path::Path;
|
||||
// Make Mutex and AtomicBool readily available
|
||||
use std::sync::{Mutex, atomic::{AtomicBool, Ordering}};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
// Remove Once - Lazy handles initialization
|
||||
// use std::sync::Once;
|
||||
@@ -20,11 +19,16 @@ mod windows {
|
||||
ShowWindow, SetWindowPos,
|
||||
RegisterClassExW, DefWindowProcW,
|
||||
WS_EX_LAYERED, WS_EX_TRANSPARENT, WS_EX_TOPMOST, WS_EX_TOOLWINDOW, WS_EX_NOACTIVATE, // Added NOACTIVATE
|
||||
WS_POPUP, SW_SHOW, HWND_TOPMOST, SWP_NOSIZE, SWP_NOMOVE,
|
||||
WM_DESTROY, WM_CLOSE, /* WM_MOUSEMOVE, */ // Remove WM_MOUSEMOVE handling from window proc
|
||||
WS_POPUP, SW_SHOW, HWND_TOPMOST, SWP_NOSIZE, SWP_NOMOVE, SWP_NOACTIVATE, // Added SWP_NOACTIVATE
|
||||
WM_DESTROY, WM_CLOSE, WM_MOUSEMOVE, WM_RBUTTONDOWN, WM_RBUTTONUP, // Added right mouse button events
|
||||
CS_HREDRAW, CS_VREDRAW, ULW_ALPHA, WNDCLASSEXW, // Import WNDCLASSEXW
|
||||
MAKEINTRESOURCEW, // Add this for converting cursor IDs to resource pointers
|
||||
// Add mouse hook related imports
|
||||
SetWindowsHookExW, UnhookWindowsHookEx, CallNextHookEx,
|
||||
WH_MOUSE_LL, HC_ACTION, MSLLHOOKSTRUCT,
|
||||
},
|
||||
um::libloaderapi::GetModuleHandleW,
|
||||
shared::windef::HHOOK,
|
||||
um::wingdi::{
|
||||
CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject,
|
||||
BLENDFUNCTION, AC_SRC_OVER, AC_SRC_ALPHA,
|
||||
@@ -80,19 +84,21 @@ mod windows {
|
||||
unsafe impl Send for SyncHDC {}
|
||||
unsafe impl Sync for SyncHDC {}
|
||||
|
||||
// Wrapper for HHOOK (hook handle)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct SyncHHOOK(HHOOK);
|
||||
unsafe impl Send for SyncHHOOK {}
|
||||
unsafe impl Sync for SyncHHOOK {}
|
||||
|
||||
// --- Thread-Safe Static Variables ---
|
||||
|
||||
// Use AtomicBool for simple flags
|
||||
static CURSOR_HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
static CTRL_HANDLER_REGISTERED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Flag to control the rendering thread (already AtomicBool, keep it)
|
||||
static RENDERING_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Use Lazy<Mutex<T>> for complex Option types
|
||||
static ORIGINAL_CURSORS: Lazy<Mutex<Option<Vec<(DWORD, SyncHCURSOR)>>>> = Lazy::new(|| Mutex::new(None));
|
||||
static CURRENT_CURSOR_IMAGE: Lazy<Mutex<Option<RgbaImage>>> = Lazy::new(|| Mutex::new(None));
|
||||
static RENDER_THREAD: Lazy<Mutex<Option<thread::JoinHandle<()>>>> = Lazy::new(|| Mutex::new(None));
|
||||
static LAST_CURSOR_POS: Lazy<Mutex<Option<POINT>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
// Use Lazy<Mutex<Option<Handle>>> for WinAPI handles with our thread-safe wrappers
|
||||
@@ -100,6 +106,14 @@ mod windows {
|
||||
static CURSOR_BITMAP: Lazy<Mutex<Option<SyncHBITMAP>>> = Lazy::new(|| Mutex::new(None));
|
||||
static CURSOR_DC: Lazy<Mutex<Option<SyncHDC>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
// Mouse hook related variables
|
||||
static MOUSE_HOOK_HANDLE: Lazy<Mutex<Option<SyncHHOOK>>> = Lazy::new(|| Mutex::new(None));
|
||||
// This one is for CallNextHookEx to avoid locking inside the hook callback
|
||||
static CURRENT_HOOK_FOR_CALLBACK: std::sync::atomic::AtomicPtr<std::os::raw::c_void> =
|
||||
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
|
||||
static LAST_MOUSE_MOVE_TIME: Lazy<Mutex<Option<std::time::Instant>>> = Lazy::new(|| Mutex::new(None));
|
||||
const MOUSE_UPDATE_INTERVAL: Duration = Duration::from_millis(8); // Throttle to ~120Hz
|
||||
|
||||
// --- End Thread-Safe Static Variables ---
|
||||
|
||||
|
||||
@@ -132,11 +146,118 @@ mod windows {
|
||||
*CURSOR_WINDOW.lock().unwrap() = None;
|
||||
0
|
||||
},
|
||||
// WM_MOUSEMOVE is handled by the render thread polling GetCursorPos
|
||||
// WM_MOUSEMOVE is handled by the low-level mouse hook
|
||||
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
|
||||
}
|
||||
}
|
||||
|
||||
// Low-level mouse hook procedure
|
||||
unsafe extern "system" fn low_level_mouse_proc(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
|
||||
if n_code == HC_ACTION {
|
||||
// Extract mouse position from the hook struct for any mouse event
|
||||
let p_msll_hook_struct = l_param as *const MSLLHOOKSTRUCT;
|
||||
if !p_msll_hook_struct.is_null() {
|
||||
let msll_hook_struct = *p_msll_hook_struct;
|
||||
let current_pos = msll_hook_struct.pt;
|
||||
|
||||
match w_param as UINT {
|
||||
WM_MOUSEMOVE => {
|
||||
let mut last_update_time_guard = LAST_MOUSE_MOVE_TIME.lock().unwrap();
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
if last_update_time_guard.map_or(true, |last_time| now.duration_since(last_time) >= MOUSE_UPDATE_INTERVAL) {
|
||||
// update_cursor_position already contains logic to only update if position actually changed via LAST_CURSOR_POS
|
||||
update_cursor_position(¤t_pos);
|
||||
*last_update_time_guard = Some(now);
|
||||
}
|
||||
},
|
||||
// Handle right mouse button down - immediately update cursor position and force topmost
|
||||
WM_RBUTTONDOWN => {
|
||||
// Force an immediate update of the cursor position when right-clicking
|
||||
// This ensures the overlay is visible before the menu appears
|
||||
update_cursor_position(¤t_pos);
|
||||
|
||||
// Force the cursor window to be topmost
|
||||
if let Some(hwnd_wrapper) = *CURSOR_WINDOW.lock().unwrap() {
|
||||
SetWindowPos(
|
||||
hwnd_wrapper.0,
|
||||
HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, // SWP_NOACTIVATE is critical
|
||||
);
|
||||
}
|
||||
},
|
||||
// Handle other mouse events if needed
|
||||
_ => { /* Other mouse messages can be handled here if needed */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the next hook in the chain.
|
||||
// Pass the HHOOK of our own hook, loaded from CURRENT_HOOK_FOR_CALLBACK.
|
||||
CallNextHookEx(
|
||||
CURRENT_HOOK_FOR_CALLBACK.load(std::sync::atomic::Ordering::Relaxed) as HHOOK,
|
||||
n_code,
|
||||
w_param,
|
||||
l_param,
|
||||
)
|
||||
}
|
||||
|
||||
// Install mouse hook function
|
||||
fn install_mouse_hook_if_needed() -> bool {
|
||||
let mut hook_guard = MOUSE_HOOK_HANDLE.lock().unwrap();
|
||||
if hook_guard.is_none() {
|
||||
unsafe {
|
||||
// GetModuleHandleW(null_mut()) gets the HMODULE for the current process (EXE)
|
||||
// which is correct for a hook procedure within the same process.
|
||||
let h_instance = GetModuleHandleW(null_mut());
|
||||
if h_instance.is_null() {
|
||||
eprintln!("Failed to get module handle for setting mouse hook: {}", std::io::Error::last_os_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
let hook = SetWindowsHookExW(
|
||||
WH_MOUSE_LL, // Hook type: Low-level mouse
|
||||
Some(low_level_mouse_proc), // Pointer to hook procedure
|
||||
h_instance, // HMODULE of the DLL containing the hook proc (or current process)
|
||||
0 // Thread ID (0 for all threads on current desktop)
|
||||
);
|
||||
|
||||
if !hook.is_null() {
|
||||
// Create wrapped handle
|
||||
let sync_hook = SyncHHOOK(hook);
|
||||
*hook_guard = Some(sync_hook);
|
||||
CURRENT_HOOK_FOR_CALLBACK.store(hook as *mut _, std::sync::atomic::Ordering::SeqCst);
|
||||
println!("Low-level mouse hook installed successfully.");
|
||||
return true;
|
||||
} else {
|
||||
eprintln!("Failed to install low-level mouse hook: {}", std::io::Error::last_os_error());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true // Already installed
|
||||
}
|
||||
|
||||
// Uninstall mouse hook function
|
||||
fn uninstall_mouse_hook() {
|
||||
let mut hook_guard = MOUSE_HOOK_HANDLE.lock().unwrap();
|
||||
if let Some(hook_wrapper) = hook_guard.take() { // .take() removes it from Option and returns it
|
||||
unsafe {
|
||||
// Extract the raw handle using .0
|
||||
let hook = hook_wrapper.0;
|
||||
if UnhookWindowsHookEx(hook) != 0 {
|
||||
CURRENT_HOOK_FOR_CALLBACK.store(std::ptr::null_mut(), std::sync::atomic::Ordering::SeqCst);
|
||||
println!("Low-level mouse hook uninstalled successfully.");
|
||||
} else {
|
||||
eprintln!("Failed to uninstall low-level mouse hook: {}", std::io::Error::last_os_error());
|
||||
// Put it back if unhooking failed, though this is unlikely.
|
||||
*hook_guard = Some(hook_wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the cursor position (Render thread responsibility)
|
||||
fn update_cursor_position(point: &POINT) {
|
||||
// Lock the statics needed
|
||||
@@ -189,10 +310,9 @@ mod windows {
|
||||
AlphaFormat: AC_SRC_ALPHA, // Use per-pixel alpha
|
||||
};
|
||||
|
||||
// Ensure the window is visible and topmost *before* updating
|
||||
// This helps prevent flicker and ensures it stays on top
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
// The window is already set to visible and topmost when created in create_cursor_window()
|
||||
// But we need to re-assert TOPMOST after UpdateLayeredWindow to ensure it stays above system tray menus
|
||||
// The key is to use SWP_NOACTIVATE to prevent stealing focus from the menu
|
||||
|
||||
// Update the layered window atomically
|
||||
let result = UpdateLayeredWindow(
|
||||
@@ -207,6 +327,16 @@ mod windows {
|
||||
ULW_ALPHA,
|
||||
);
|
||||
|
||||
// Re-assert TOPMOST after successful update, with SWP_NOACTIVATE to prevent stealing focus
|
||||
if result != 0 {
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, // Added SWP_NOACTIVATE to prevent stealing focus
|
||||
);
|
||||
}
|
||||
|
||||
if result == 0 {
|
||||
use std::io::Error;
|
||||
let error = Error::last_os_error();
|
||||
@@ -297,20 +427,8 @@ mod windows {
|
||||
|
||||
// Cleanup function to restore cursors and clean up resources
|
||||
fn cleanup() {
|
||||
// Stop the rendering thread FIRST
|
||||
if RENDERING_ACTIVE.swap(false, Ordering::SeqCst) { // Use swap to ensure it's only attempted once
|
||||
println!("Stopping render thread...");
|
||||
// Join the thread handle
|
||||
if let Some(handle) = RENDER_THREAD.lock().unwrap().take() {
|
||||
match handle.join() {
|
||||
Ok(_) => println!("Render thread joined successfully."),
|
||||
Err(e) => eprintln!("Error joining render thread: {:?}", e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Render thread was already stopped or never started
|
||||
// println!("Render thread already stopped.");
|
||||
}
|
||||
// Uninstall the mouse hook FIRST
|
||||
uninstall_mouse_hook();
|
||||
|
||||
|
||||
// Restore system cursors if they were hidden
|
||||
@@ -777,6 +895,12 @@ mod windows {
|
||||
drop(window_guard);
|
||||
|
||||
|
||||
// Install the mouse hook
|
||||
if !install_mouse_hook_if_needed() {
|
||||
eprintln!("Mouse hook installation failed, custom cursor might not follow mouse.");
|
||||
// We'll continue anyway as this is not a fatal error
|
||||
}
|
||||
|
||||
// Initialize cursor position immediately after update
|
||||
unsafe {
|
||||
let mut point = POINT { x: 0, y: 0 };
|
||||
@@ -785,9 +909,6 @@ mod windows {
|
||||
}
|
||||
}
|
||||
|
||||
// Start the rendering thread if it's not already running
|
||||
start_render_thread_if_needed();
|
||||
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
@@ -815,66 +936,9 @@ mod windows {
|
||||
// No need to duplicate cleanup logic here
|
||||
}
|
||||
|
||||
// Function to start the rendering thread if it's not already running
|
||||
fn start_render_thread_if_needed() {
|
||||
// Check if active *before* locking the thread handle mutex for performance
|
||||
if !RENDERING_ACTIVE.load(Ordering::SeqCst) {
|
||||
let mut render_thread_guard = RENDER_THREAD.lock().unwrap();
|
||||
// Double check inside the lock
|
||||
if render_thread_guard.is_none() {
|
||||
// Set the flag *before* spawning the thread
|
||||
RENDERING_ACTIVE.store(true, Ordering::SeqCst);
|
||||
|
||||
// Spawn the thread
|
||||
let handle = thread::spawn(|| {
|
||||
println!("Starting cursor render thread...");
|
||||
// Render loop
|
||||
while RENDERING_ACTIVE.load(Ordering::Relaxed) { // Relaxed is fine for loop check
|
||||
render_cursor_internal(); // Call internal render function
|
||||
// Use a variable sleep time, e.g., target 120Hz (approx 8ms)
|
||||
thread::sleep(Duration::from_millis(8));
|
||||
}
|
||||
println!("Cursor render thread stopped.");
|
||||
});
|
||||
|
||||
// Store the thread handle
|
||||
*render_thread_guard = Some(handle);
|
||||
println!("Render thread started and handle stored.");
|
||||
}
|
||||
// else: Another thread started it between the outer check and acquiring the lock
|
||||
}
|
||||
// else: Already running
|
||||
}
|
||||
|
||||
// Internal function to render the cursor (called from the render thread)
|
||||
fn render_cursor_internal() {
|
||||
unsafe {
|
||||
let mut point = POINT { x: 0, y: 0 };
|
||||
if GetCursorPos(&mut point) == 0 {
|
||||
// Failed to get cursor position, maybe log error?
|
||||
// eprintln!("GetCursorPos failed in render loop");
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Check if window needs to be topmost ---
|
||||
// It's generally better to ensure topmost status less frequently,
|
||||
// perhaps only when the window is created or maybe periodically,
|
||||
// as calling SetWindowPos constantly can have overhead.
|
||||
// However, keeping it here ensures responsiveness if other apps fight for topmost.
|
||||
// Let's keep it for now.
|
||||
if let Some(hwnd_wrapper) = *CURSOR_WINDOW.lock().unwrap() { // Get handle copy
|
||||
SetWindowPos(hwnd_wrapper.0, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
}
|
||||
// --- End Topmost Check ---
|
||||
|
||||
// Update the cursor graphic position
|
||||
update_cursor_position(&point);
|
||||
}
|
||||
}
|
||||
|
||||
// Public function to ensure the custom cursor is rendering (main purpose now is to start thread)
|
||||
// Public function that used to start the render thread, now a no-op since we use the mouse hook
|
||||
pub fn render_cursor() {
|
||||
start_render_thread_if_needed();
|
||||
// No-op: Cursor rendering is now handled by the mouse hook
|
||||
}
|
||||
} // end mod windows
|
||||
|
||||
@@ -920,7 +984,7 @@ mod linux {
|
||||
// (Keep these as they are)
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::{restore_cursor, render_cursor, hide_system_cursor, update_cursor_image};
|
||||
pub use windows::{restore_cursor, hide_system_cursor, update_cursor_image, render_cursor};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::{restore_cursor, render_cursor, hide_system_cursor, update_cursor_image};
|
||||
|
||||
Reference in New Issue
Block a user