I’m trying to remap the right mouse button to act as the middle mouse button in C++ on Windows.
What I did
- I wrote code using Raw Input (see below).
- My code successfully sends a middle click when I press the right button.
- But the original right click action still happens at the same time, so I end up with both right click and middle click firing.
I also tried using a WH_MOUSE_LL low-level mouse hook:
- This can block the right button completely.
- However, it introduces a small delay, and the behavior does not feel as smooth or natural as AutoHotkey’s remapping.
What I want
- When I press the right mouse button, I want to completely block the original right click.
- Instead, it should act exactly like the middle mouse button.
- It should feel instant (no noticeable delay), as natural as if I physically pressed the middle button.
My ultimate goal is to achieve AutoHotkey-level remapping in C++:
- Right button is completely suppressed (never reaches applications).
- Only middle button is sent instead.
- The behavior should be instant and natural, with no noticeable delay, just like if I physically pressed the middle button.
My current code (Raw Input version)
#include <windows.h>
#include <iostream>
// SendInput to simulate middle click
void SendMiddleClick(bool down) {
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dwFlags = down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
SendInput(1, &input, sizeof(INPUT));
}
// Window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_INPUT) {
UINT dwSize = 0;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
if (dwSize > 0) {
BYTE* lpb = new BYTE[dwSize];
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize) {
RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE) {
auto flags = raw->data.mouse.usButtonFlags;
if (flags & RI_MOUSE_RIGHT_BUTTON_DOWN) SendMiddleClick(true);
if (flags & RI_MOUSE_RIGHT_BUTTON_UP) SendMiddleClick(false);
}
}
delete[] lpb;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int main() {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = TEXT("RawInputDemo");
RegisterClass(&wc);
HWND hwnd = CreateWindow(wc.lpszClassName, TEXT("Raw Input Window"),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
300, 200, NULL, NULL, wc.hInstance, NULL);
RAWINPUTDEVICE rid;
rid.usUsagePage = 0x01;
rid.usUsage = 0x02;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = hwnd;
RegisterRawInputDevices(&rid, 1, sizeof(rid));
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
WinMaininstead ofmain.