on event implementation, known bug 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