Last active
April 26, 2025 13:23
-
-
Save dinhani/42d4c0cf0431b9a17aef9e906df8a52e to your computer and use it in GitHub Desktop.
Mouse double-click preventer: Python and Rust versions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ctypes | |
from ctypes import wintypes | |
class MouseHook: | |
# Windows hook constants | |
WH_MOUSE_LL = 14 | |
HC_ACTION = 0 | |
# Mouse messages | |
WM_LBUTTONDOWN = 0x0201 | |
WM_LBUTTONDBLCLK = 0x0203 | |
def __init__(self, threshold_ms=200): | |
"""Initialize the mouse hook with configurable threshold.""" | |
self.last_click = 0 | |
self.treshold_ms = threshold_ms | |
# load windows dlls | |
self.user32 = ctypes.WinDLL("user32") | |
self.kernel32 = ctypes.WinDLL("kernel32") | |
# define windows function signatures | |
self._define_winapi_signatures() | |
self._define_hook_structures() | |
# define hook | |
self.hook_proc = self.HOOKPROC(self._mouse_callback) | |
self.hook_id = None | |
def _define_winapi_signatures(self): | |
"""Define Windows API function signatures.""" | |
wintypes.LRESULT = ctypes.c_long | |
self.user32.SetWindowsHookExA.argtypes = [ctypes.c_int, ctypes.c_void_p, wintypes.HANDLE, wintypes.DWORD] | |
self.user32.SetWindowsHookExA.restype = wintypes.HANDLE | |
self.user32.CallNextHookEx.argtypes = [wintypes.HANDLE, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM] | |
self.user32.CallNextHookEx.restype = wintypes.LRESULT | |
self.user32.GetMessageA.argtypes = [ctypes.POINTER(wintypes.MSG), wintypes.HWND, wintypes.UINT, wintypes.UINT] | |
self.user32.GetMessageA.restype = wintypes.BOOL | |
self.kernel32.GetModuleHandleA.argtypes = [ctypes.c_char_p] | |
self.kernel32.GetModuleHandleA.restype = wintypes.HANDLE | |
def _define_hook_structures(self): | |
"""Define hook structure and callback prototype.""" | |
class MSLLHOOKSTRUCT(ctypes.Structure): | |
_fields_ = [ | |
("pt", wintypes.POINT), | |
("mouseData", wintypes.DWORD), | |
("flags", wintypes.DWORD), | |
("time", wintypes.DWORD), | |
("dwExtraInfo", wintypes.WPARAM) | |
] | |
self.HOOKPROC = ctypes.CFUNCTYPE(wintypes.LRESULT, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM) | |
self.MSLLHOOKSTRUCT = MSLLHOOKSTRUCT | |
def _mouse_callback(self, n_code, w_param, l_param): | |
# process only if mouse left-click | |
if n_code == self.HC_ACTION and w_param == self.WM_LBUTTONDOWN: | |
# convert to struct | |
mouse_event = ctypes.cast(l_param, ctypes.POINTER(self.MSLLHOOKSTRUCT)).contents | |
# check double click | |
diff = mouse_event.time - self.last_click | |
if diff < self.treshold_ms: | |
print(diff) | |
return 1 | |
self.last_click = mouse_event.time | |
# call next callback | |
return self.user32.CallNextHookEx(self.hook_id, n_code, w_param, l_param) | |
def start(self): | |
"""Start the mouse hook to prevent double clicks.""" | |
# start hook | |
module_handle = self.kernel32.GetModuleHandleA(None) | |
self.hook_id = self.user32.SetWindowsHookExA(self.WH_MOUSE_LL, self.hook_proc, module_handle, 0) | |
if not self.hook_id: | |
raise RuntimeError(f"Failed to set hook. Error code: {ctypes.get_last_error()}") | |
# loop to keep the program processing events | |
msg = wintypes.MSG() | |
while self.user32.GetMessageA(ctypes.byref(msg), None, 0, 0): | |
pass | |
if __name__ == "__main__": | |
MouseHook(threshold_ms=120).start() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "mouse-double-click-preventer" | |
version = "0.1.0" | |
edition = "2024" | |
[dependencies] | |
windows = { version = "0.61.1", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader"] } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ----------------------------------------------------------------------------- | |
// Libraries | |
// ----------------------------------------------------------------------------- | |
use windows::Win32::Foundation::HINSTANCE; | |
use windows::Win32::Foundation::LPARAM; | |
use windows::Win32::Foundation::LRESULT; | |
use windows::Win32::Foundation::WPARAM; | |
use windows::Win32::System::LibraryLoader::GetModuleHandleA; | |
use windows::Win32::UI::WindowsAndMessaging::CallNextHookEx; | |
use windows::Win32::UI::WindowsAndMessaging::GetMessageA; | |
use windows::Win32::UI::WindowsAndMessaging::HC_ACTION; | |
use windows::Win32::UI::WindowsAndMessaging::HHOOK; | |
use windows::Win32::UI::WindowsAndMessaging::MSG; | |
use windows::Win32::UI::WindowsAndMessaging::MSLLHOOKSTRUCT; | |
use windows::Win32::UI::WindowsAndMessaging::SetWindowsHookExA; | |
use windows::Win32::UI::WindowsAndMessaging::WH_MOUSE_LL; | |
use windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN; | |
use windows::core::Result; | |
// ----------------------------------------------------------------------------- | |
// Structs | |
// ----------------------------------------------------------------------------- | |
struct State { | |
last_click: u32, | |
hook_id: HHOOK, | |
} | |
// ----------------------------------------------------------------------------- | |
// State | |
// ----------------------------------------------------------------------------- | |
const DOUBLE_CLICK_THRESHOLD_MS: u32 = 120; | |
static mut STATE: State = State { | |
last_click: 0, | |
hook_id: HHOOK(std::ptr::null_mut()), | |
}; | |
// ----------------------------------------------------------------------------- | |
// Execution | |
// ----------------------------------------------------------------------------- | |
unsafe extern "system" fn callback(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT { | |
unsafe { | |
// process event only on left-click | |
if n_code == HC_ACTION as i32 && w_param.0 == WM_LBUTTONDOWN as usize { | |
let mouse_event = &*(l_param.0 as *const MSLLHOOKSTRUCT); | |
// check double click | |
let diff = mouse_event.time.wrapping_sub(STATE.last_click); | |
if diff <= DOUBLE_CLICK_THRESHOLD_MS { | |
println!("{}", diff); | |
return LRESULT(1); | |
} | |
STATE.last_click = mouse_event.time; | |
} | |
// call next hook on chain | |
CallNextHookEx(Some(STATE.hook_id), n_code, w_param, l_param) | |
} | |
} | |
fn main() -> Result<()> { | |
// start hook | |
unsafe { | |
let handle = GetModuleHandleA(None)?; | |
let instance = HINSTANCE(handle.0); | |
STATE.hook_id = SetWindowsHookExA(WH_MOUSE_LL, Some(callback), Some(instance), 0)?; | |
} | |
// keep running | |
let mut msg = MSG::default(); | |
while unsafe { GetMessageA(&mut msg, None, 0, 0) }.as_bool() {} | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment