The Problem
The middle button on my mouse is actually the mouse wheel. When I click it, most applications go into this "scroll mode", which would be great if I didn't have a mouse wheel, or I suffered RSI and I couldn't use the mouse wheel. But for me, this mode is annoying and useless. So, I decided that I wanted to make it so that clicking the middle mouse button of my mouse would simulate a left-double-click. Now, there are applications out there which let me do that - for example, Cool Mouse does it. As does UltraMouse.
The thing is, both of those apps are shareware, and they both do much more than just simulate double-clicks. (Also, they both seem kind of dodgy, given that I had so much trouble finding the actual publisher's website - rather they seem to be plastered all over those 'shareware download' sites. In fact, I couldn't even find the publisher's site for UltraMouse!).
Enter DblClk
Now, simulating double-clicks is actually really easy using straight Win32 API calls. So I decided to create DblClk which is a very small program, and all it does is simulate left-double-clicks when you click the middle mouse button. That's it.
For anyone interested, the code is simple. A simple call to SetWindowsHookEx to install our hook, and another call to SendInput to simulate the double-click when a middle-button click comes in.
Here's some code which installs the hook:
::SetWindowsHookEx(WH_MOUSE_LL, HookProc, hinst, 0);
The first parameter tells Windows that we want all "low-level" mouse events. The difference between low-level mouse events and the regular kind is that low-level mouse events are posted to our queue before they're sent to the thread which "owns" mouse input. There's probably no reason why we wouldn't want to use WH_MOUSE
, except that that requires a DLL, which gets inserted in every running process (only a big deal because it means we can't just build a single .exe).
The second parameter to SetWindowsHookEx
we set to our procedure which gets called for every mouse event. More on that in a second. The third parameter is the HINSTANCE
of our executable. The last parameter is the thread ID of the thread we want events for (though we set it to zero because we want events for all threads).
Now, the hook procedure is almost just as simple. Essentially we check for a WM_MBUTTONDOWN
event and use the SendInput
method to simulate a left button down, left button up, left button down combo. We don't send the final left button up yet because this way we can simulate the double-click-and-drag operation which is useful in some text editors. When we get a WM_MBUTTONUP
then we send the final left button up message. Here's some code:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// If nCode is < 0, we're supposed to return CallNextHookEx, according
// to MSDN, at least...
if (nCode < 0)
{
return ::CallNextHookEx(0, nCode, wParam, lParam);
}
MSLLHOOKSTRUCT *pHookStruct = (MSLLHOOKSTRUCT *)lParam;
if (wParam == WM_MBUTTONDOWN)
{
// We need to simulate a left-button down, left-button up,
// left-button down combo
INPUT inputs[3];
::ZeroMemory(&inputs, sizeof(inputs));
inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dx = pHookStruct->pt.x;
inputs[0].mi.dy = pHookStruct->pt.y;
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
inputs[0].mi.time = pHookStruct->time;
inputs[1].type = INPUT_MOUSE;
inputs[1].mi.dx = pHookStruct->pt.x;
inputs[1].mi.dy = pHookStruct->pt.y;
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
inputs[1].mi.time = pHookStruct->time;
inputs[2].type = INPUT_MOUSE;
inputs[2].mi.dx = pHookStruct->pt.x;
inputs[2].mi.dy = pHookStruct->pt.y;
inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
inputs[2].mi.time = pHookStruct->time;
::SendInput(3, inputs, sizeof(INPUT));
return 1;
}
else if (wParam == WM_MBUTTONUP)
{
// We need to simulate the final left-button up
INPUT inputs[1];
::ZeroMemory(&inputs, sizeof(inputs));
inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dx = pHookStruct->pt.x;
inputs[0].mi.dy = pHookStruct->pt.y;
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;
inputs[0].mi.time = pHookStruct->time;
::SendInput(1, inputs, sizeof(INPUT));
return 1;
}
// We didn't process the message, so just return CallNextHookEx
return ::CallNextHookEx(0, nCode, wParam, lParam);
}
Now, I'm no Win32 expert, so I might not be doing everything right (and I haven't tested my code all that much), but this works great for me. The executable compiles down to just 6KB and when running uses just 1MB of memory (which is just our program stack). Much better than those shareware apps which try to do so much more :)
Download Now!
You can download version 1.0 of DblClk now. The zip file includes a compiled binary, which currently requires the MSVC.NET 2005 C++ runtime (msvcr80.dll). I don't know the redist rules for this dll, so for now you just have to have it installed already. If not, you can always compile the included source file to make your own binary :)
If I get enough requests, I'll compile a version which can be run without the dll, but for now this is mainly just for my own amusement.
Extra Features
OK, I lied. There is one more feature that I added to the program. You see, normally when you install a low-level mouse hook, you need a running message queue to have events posted to. That means that the app, once started, is always running (at least until you log off). So I added one extra feature, and that's the ability to stop the application by running it again with the "-stop" command-line parameter. If you do that, it'll stop the currently-running instance and the middle-button will function as it had before.