Skip to content

Instantly share code, notes, and snippets.

@t-mat
Last active November 29, 2025 10:09
Show Gist options
  • Select an option

  • Save t-mat/9abf003f000e1351a092981ea686e219 to your computer and use it in GitHub Desktop.

Select an option

Save t-mat/9abf003f000e1351a092981ea686e219 to your computer and use it in GitHub Desktop.
[Windows] Mouse report rate tester
// 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