Last active
November 13, 2025 01:57
-
-
Save GeeLaw/8eed8a71d8e270a52deec23498e90c24 to your computer and use it in GitHub Desktop.
An example usage of `WH_MOUSE_LL` (low-level mouse hook).
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
| /* | |
| Copyright 2025 Ji Luo | |
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| */ | |
| #ifndef UNICODE | |
| #define UNICODE | |
| #endif | |
| #ifndef UNICODE_ | |
| #define UNICODE_ | |
| #endif | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #include<Windows.h> | |
| #include<conio.h> | |
| #include<cstdio> | |
| #include<mutex> | |
| #include<condition_variable> | |
| enum MyExitCode : int | |
| { | |
| MyExitCode_EscKeyPressed = 0, | |
| MyExitCode_HookNegativeCode = 1, | |
| MyExitCode_GetMessageFailed = 2, | |
| MyExitCode_SetWindowsHookExFailed = 3, | |
| }; | |
| UINT const WM_USER_HookCursorPosDirty = WM_USER; | |
| int const GETCH_EscKey = 27; | |
| HHOOK hook_handle; | |
| std::mutex monitor_mutex; | |
| std::condition_variable monitor_cv; | |
| bool worker_should_exit = false; | |
| bool coalesced_cursor_pos_updated = false; | |
| POINT coalesced_cursor_pos; | |
| bool cursor_pos_update_scheduled = false; | |
| POINT hook_cursor_pos; | |
| LRESULT CALLBACK MyLowLevelMouseHook(int nCode, WPARAM wParam, LPARAM lParam) | |
| { | |
| /* The hook procedure should not block (synchronize) so that it returns timely. | |
| ** It also does not do any I/O. */ | |
| if (nCode < 0) | |
| { | |
| /* The docs require us to refrain from any further processing of this message | |
| ** and just call CallNextHookEx. | |
| ** I have no idea why this would happen, so I'm treating it as an exceptional condition. | |
| ** However, it's not necessary, from the docs' face value, to exit. */ | |
| PostQuitMessage(MyExitCode_HookNegativeCode); | |
| } | |
| else if (wParam == WM_MOUSEMOVE) | |
| { | |
| hook_cursor_pos = ((MSLLHOOKSTRUCT*)lParam)->pt; | |
| if (!cursor_pos_update_scheduled) | |
| { | |
| cursor_pos_update_scheduled = (bool)PostMessage(nullptr, WM_USER_HookCursorPosDirty, 0, 0); | |
| } | |
| } | |
| return CallNextHookEx(hook_handle, nCode, wParam, lParam); | |
| } | |
| void WorkerThread() | |
| { | |
| while (true) | |
| { | |
| POINT cursor_pos; | |
| do | |
| { | |
| std::unique_lock<std::mutex> lock{ monitor_mutex }; | |
| while (true) | |
| { | |
| if (worker_should_exit) | |
| { | |
| return; | |
| } | |
| if (coalesced_cursor_pos_updated) | |
| { | |
| cursor_pos = coalesced_cursor_pos; | |
| coalesced_cursor_pos_updated = false; | |
| break; | |
| } | |
| monitor_cv.wait(lock); | |
| } | |
| } while (false); | |
| /* stderr is owned by the worker at this moment. | |
| ** Output without lock, so it won't unnecessarily | |
| ** block the message handler on the main thread (which could block the hook). */ | |
| std::fprintf(stderr, "\rcoalesced position = { %d, %d } ", | |
| (int)cursor_pos.x, (int)cursor_pos.y); | |
| } | |
| } | |
| int main() | |
| { | |
| hook_handle = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)MyLowLevelMouseHook, nullptr, 0); | |
| if (!hook_handle) | |
| { | |
| std::fputs("SetWindowsHookEx failed.\n", stderr); | |
| return MyExitCode_SetWindowsHookExFailed; | |
| } | |
| std::fputs("Press Esc then move mouse to exit. DO NOT USE X BUTTON.\n", stderr); | |
| /* Provide the initial position is there is a pointer at all. */ | |
| coalesced_cursor_pos_updated = (bool)GetCursorPos(&coalesced_cursor_pos); | |
| /* Ownership of stderr transfers to the worker. */ | |
| std::thread worker{ WorkerThread }; | |
| BOOL ret; | |
| MSG msg; | |
| while (true) | |
| { | |
| while (_kbhit()) | |
| { | |
| if (_getch() == GETCH_EscKey) | |
| { | |
| PostQuitMessage(MyExitCode_EscKeyPressed); | |
| } | |
| } | |
| /* Console keyboard messages are not handled here, | |
| ** so after pressing Esc key, you must move mouse to trigger a hook message, | |
| ** then the previous _kbhit will be inspected. */ | |
| if ((ret = GetMessage(&msg, nullptr, 0, 0)) == -1 || !ret) | |
| { | |
| break; | |
| } | |
| if (msg.message == WM_USER_HookCursorPosDirty) | |
| { | |
| cursor_pos_update_scheduled = false; | |
| monitor_mutex.lock(); | |
| coalesced_cursor_pos = hook_cursor_pos; | |
| coalesced_cursor_pos_updated = true; | |
| monitor_mutex.unlock(); | |
| monitor_cv.notify_all(); | |
| continue; | |
| } | |
| TranslateMessage(&msg); | |
| DispatchMessage(&msg); | |
| } | |
| /* Wait for the worker to exit gracefully. */ | |
| monitor_mutex.lock(); | |
| worker_should_exit = true; | |
| monitor_mutex.unlock(); | |
| monitor_cv.notify_all(); | |
| worker.join(); | |
| /* Ownership of stderr transfers back to the main thread. | |
| ** Leave the line displaying the coalesced positions. */ | |
| std::fputc('\n', stderr); | |
| if (!UnhookWindowsHookEx(hook_handle)) | |
| { | |
| std::fputs("UnhookWindowsHookEx failed.\n", stderr); | |
| } | |
| if (!ret) | |
| { | |
| /* Exited from look because received WM_QUIT. */ | |
| switch (msg.wParam) | |
| { | |
| case MyExitCode_EscKeyPressed: | |
| std::fputs("Exiting because Esc key was pressed.\n", stderr); | |
| break; | |
| case MyExitCode_HookNegativeCode: | |
| std::fputs("Exiting because hook was called with nCode < 0.\n", stderr); | |
| break; | |
| default: | |
| std::fprintf(stderr, "Exiting because I got WM_QUIT with exit code = %d\n", (int)msg.wParam); | |
| break; | |
| } | |
| return (int)msg.wParam; | |
| } | |
| std::fputs("Exiting because GetMessage failed.\n", stderr); | |
| return MyExitCode_GetMessageFailed; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment