Skip to content

Instantly share code, notes, and snippets.

@dinhani
Last active April 26, 2025 13:23
Show Gist options
  • Save dinhani/42d4c0cf0431b9a17aef9e906df8a52e to your computer and use it in GitHub Desktop.
Save dinhani/42d4c0cf0431b9a17aef9e906df8a52e to your computer and use it in GitHub Desktop.
Mouse double-click preventer: Python and Rust versions
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()
[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"] }
// -----------------------------------------------------------------------------
// 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