Last active
November 29, 2025 10:09
-
-
Save t-mat/9abf003f000e1351a092981ea686e219 to your computer and use it in GitHub Desktop.
[Windows] Mouse report rate tester
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
| // Mouse report rate tester | |
| #define WIN32_LEAN_AND_MEAN 1 | |
| #define UNICODE 1 | |
| #define NOMINMAX 1 | |
| #include <windows.h> | |
| #include <hidusage.h> | |
| #include <stdio.h> | |
| #include <algorithm> | |
| #include <chrono> | |
| int main() | |
| { | |
| const wchar_t *name = L"MouseTesterWindow"; | |
| { | |
| // Register Window Class | |
| ::WNDCLASSW wc{}; | |
| wc.lpfnWndProc = ::DefWindowProcW; | |
| wc.hInstance = ::GetModuleHandleW(nullptr); | |
| wc.lpszClassName = name; | |
| wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); | |
| wc.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); | |
| if (!::RegisterClassW(&wc)) { | |
| ::fprintf(stderr, "RegisterClassW Failed\n"); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| auto const hwnd = [&name]() -> HWND { | |
| // Create a Visible Window. Use WS_VISIBLE to show it on screen. | |
| constexpr auto ws = WS_TILEDWINDOW | WS_VISIBLE; | |
| return ::CreateWindowW(name, name, ws, 0, 0, 640, 480, 0, 0, ::GetModuleHandleW(nullptr), 0); | |
| }(); | |
| if (!hwnd) { | |
| ::fprintf(stderr, "CreateWindowW Failed\n"); | |
| return EXIT_FAILURE; | |
| } | |
| // Ensure the window is ready | |
| ::SetForegroundWindow(hwnd); | |
| ::SetFocus(hwnd); | |
| // Register Raw Input Device | |
| { | |
| // Don't set RIDEV_INPUTSINK to dwFlags. | |
| // Receive input only when active (High performance mode). | |
| ::RAWINPUTDEVICE rid{}; | |
| rid.usUsagePage = HID_USAGE_PAGE_GENERIC; | |
| rid.usUsage = HID_USAGE_GENERIC_MOUSE; | |
| rid.dwFlags = 0; | |
| rid.hwndTarget = hwnd; | |
| if (!::RegisterRawInputDevices(&rid, 1, sizeof(rid))) { | |
| ::fprintf(stderr, "RegisterRawInputDevices Failed\n"); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| // Raise process priority (Stabilize high-rate measurement) | |
| if (!::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS)) { | |
| ::fprintf(stderr, "SetPriorityClass Failed\n"); | |
| } | |
| const auto get_clock = []() { return std::chrono::high_resolution_clock::now(); }; | |
| auto startTime = get_clock(); | |
| int64_t repCount = 0; | |
| int64_t maxRepRate = 0; | |
| bool stop = false; | |
| while (!stop) { | |
| { | |
| // Buffer for Raw Input | |
| alignas(::RAWINPUT) std::byte rawBuffer[1024 * 16]; | |
| // Try to get raw input data in bulk | |
| UINT cbSize = static_cast<UINT>(sizeof(rawBuffer)); | |
| // GetRawInputBuffer returns the number of RAWINPUT structures | |
| const UINT riCount = | |
| ::GetRawInputBuffer(reinterpret_cast<PRAWINPUT>(rawBuffer), &cbSize, sizeof(::RAWINPUTHEADER)); | |
| if (riCount == static_cast<UINT>(-1)) { | |
| ::fprintf(stderr, "GetRawInputBuffer Error: %lu\n", ::GetLastError()); | |
| ::Sleep(10); | |
| } else if (riCount == 0) { | |
| // No raw input data available, handle window messages | |
| ::MSG msg{}; | |
| // Use PeekMessage to keep the loop non-blocking (Active Polling) | |
| while (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { | |
| if (msg.message == WM_QUIT) { | |
| stop = true; | |
| } else if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) { | |
| stop = true; | |
| } | |
| ::TranslateMessage(&msg); | |
| ::DispatchMessageW(&msg); | |
| } | |
| // NOTE: If you want to reduce CPU usage when idle, you could add: | |
| // ::WaitMessage(); | |
| // However, for a rate tester, we want to stay in a tight loop ("busy wait") | |
| // to catch the next mouse event immediately. | |
| } else { | |
| // Process raw input data | |
| const std::byte *p = rawBuffer; | |
| for (UINT i = 0; i < riCount; ++i) { | |
| const auto &rawInput = *reinterpret_cast<const ::RAWINPUT *>(p); | |
| if (rawInput.header.dwType == RIM_TYPEMOUSE) { | |
| repCount++; | |
| } | |
| p += rawInput.header.dwSize; | |
| } | |
| } | |
| } | |
| { | |
| // Calculate and Display Rate | |
| const auto now = get_clock(); | |
| const std::chrono::duration<double> elapsed = now - startTime; | |
| if (elapsed.count() >= 0.5) { | |
| const int64_t repRate = repCount / elapsed.count(); | |
| maxRepRate = std::max(maxRepRate, repRate); | |
| char buf[256]{}; | |
| _snprintf_s(buf, std::size(buf), "Mouse report rate:%6lld Hz (Max:%6lld Hz)", repRate, maxRepRate); | |
| ::SetWindowTextA(hwnd, buf); | |
| ::fprintf(stderr, "%s%c", buf, repCount == 0 ? '\r' : '\n'); | |
| // Reset counters | |
| repCount = 0; | |
| startTime = now; | |
| } | |
| } | |
| } | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment