Contents
When you connect more than one keyboard to your pc, you might get some interesting ideas what to do with them. Perhaps you could use one for standard typing, and the other one for some special tasks. When you delve deeper into the multiple keyboard setup, you might find yourself asking a question, "Can I block the key input for running application based on what keyboard was used to generate it?". Let's say you have Notepad opened, when you press "a" key on the keyboard 1, you want the "a" letter to be written into the document, but when you press the "a" key on the keyboard 2, you want some task to be performed in the background, and Notepad not even notice the keystroke. When I encountered this problem, I had some troubles finding the answer and solution. Therefore I decided to write this article, to make it easier for you, if you ever happen to ask the same question. The article will assume some knowledge of the Windows programming, but I will try to point you to relevant resources, so you should be able to learn everything you need.
I had to solve this problem when I wanted to have a custom hotkey manager for multiple keyboards. Basic idea was to have a standard keyboard for standard input, and one small numeric keyboard that would be used for hotkeys. The hotkeys should be configured for various applications, so I can have one key to perform an action in one application, and the same key to perform different action in another application. First I looked around for existing solutions, but I couldn't find any that would meet all my requirements. "AutoHotkey" is unable to differentiate between multiple keyboards on its own. "HID macros" doesn't allow application specific hotkey setup. It is possible to combine the two and use both at the same time, but I didn't like that solution very much. I came across some managers, that seemed they would do the job, but they were all licensed software. So I decided to write my own.
Simply put, the problem at hand is to decide whether to block a key input based on the device that generated the input in the managing application, and then block it successfully in the running application that has user focus. When you want to work with keyboard input on Windows, you have basicly two choices: use some custom driver for the device, or use one of the Windows' APIs for working with keyboard input. There are two APIs to be considered: Raw Input and keyboard Hooks.
- Custom device driver: So far I had no experience with writing my own driver for a device. I walked through some documentation and code samples, and it seemed pretty complicated for my project. Also it would have some negative consequences. The manager would require administrator priviliges to install the driver, so that would restrict portability. And the users might feel uncomfortable installing such driver. The driver might also have to be signed by certified authority (I'm not completely sure if this is true, since I haven't looked deeper into the issue).
Another option would be to use a custom driver that provides a programming interface for me, so I wouldn't have to write my own driver. "Interception library" seems to offer such option. But some of the negative aspects of custom driver would hold still (administrator rights requirement, user unease), plus there would be some more. The source code of the library itself is available, however sources of the driver are not, so I wouldn't have full control over the product I provide to the potential users.
- Raw Input: This interface provides more complex way to accept input from various devices, including the keyboard, than the standard Windows keyboard input messages. Most notably, it is possible to identify on what keyboard a key was struck, which is impossible using the standard input messages. Raw Input is capable of monitoring keyboard events system wide, so the decision part of the problem could be solved with it. Unfortunately, the second part can be not. At least I wasn't able to find any way how to block the input from reaching its destination window with Raw Input API.
- Keyboard Hooks: Windows Hooks are mechanisms that allow an application to intercept system messages, such as keyboard events. When set up correctly, they work system wide. They even allow an application to modify or stop the propagation of the appropriate message. That means, that Hook can be used to block the keystroke. The problem is, there is no way how to generally identify the keyboard on which the key was struck.
As you can see, unless you want to use some custom device driver, there is no single API that would allow you to solve the whole problem. "Can I combine the two APIs to make it work?", you might ask at this point. And as it turns out, yes, you can. Unfortunately, documentation of the interaction is basicly non-existent, and it is very counter-intuitive. I hope, this article will shed some light on the matter for anyone struggling with the issue.
As I mentioned earlier, I will assume some level of the Windows programming knowledge on your part. I don't want to go into much detail regarding every single API setup, because I feel that these things are covered already enough. If you are unfamiliar with either API, I would recommend walking through the documentation to you. For Windows messages system in general: "About Messages and Message Queues" [1]; for the Raw Input: "About Raw Input" [2]; for Hooks: "Hooks Overview" [3].
I use C++ in my project, but I believe the code can be adapted for .NET with some effort. For the sake of simplicity, I will omit any error checking for system calls, allocations, etc., unless they are part of the problem itself. The demo project uses a simple debugging output, just for the demonstration purposes, and is aiming only to showcase the main idea. It can be definitely structured better. Also the code samples are meant to include only the most important things. There won't be any complete functions or modules, that you would just copy-paste (for that you can download the attached demo project), but I will always point out the function and file, so you can get an idea, where the particular code belongs. These samples should really just help you to understand the problem.
I have tested the code only on Windows 7 (64 bit) so far. It is possible that on other systems the described behaviour of the APIs might differ.
I found this concept of combining Raw Input with Hooks thanks to a comment from Petr Medek, author of the "HID macros" [4], here on CodeProject. He guided me to the basic idea of this solution. So I must give credit where it is due; huge thanks to Petr! I built my project on the basic idea and went through some additional pitfalls. With Petr's permission I will describe to you the whole concept with all the complications I encountered.
To remind you, our goal is to intercept keystrokes, decide whether to block them or pass them to the active application; the decision is based on which keyboard was used. We will use Raw Input for the decision-making and Hook for the input blocking. So let's get into it!
In order to use them, we will first need to set up both APIs in our application. For the Raw Input, there is no hidden catch, so we simply register for receiving keyboard inputs globally (by specifying RIDEV_INPUTSINK
flag).
RAWINPUTDEVICE rawInputDevice[1];
rawInputDevice[0].usUsagePage = 1;
rawInputDevice[0].usUsage = 6;
rawInputDevice[0].dwFlags = RIDEV_INPUTSINK;
rawInputDevice[0].hwndTarget = hWnd;
RegisterRawInputDevices (rawInputDevice, 1, sizeof (rawInputDevice[0]));
As for the processing of the Raw Input messages
, let's just check that we are receiving them properly now. We will keep track of the virtual key code of the key and whether it is being pressed or released.
case WM_INPUT:
{
USHORT virtualKeyCode = raw->data.keyboard.VKey;
USHORT keyPressed = raw->data.keyboard.Flags & RI_KEY_BREAK ? 0 : 1;
WCHAR text[128];
swprintf_s (text, 128, L"Raw Input: %X (%d)\n", virtualKeyCode, keyPressed);
OutputDebugString (text);
}
Regarding the Hook, the things get a little tricky already. When I was first experimenting with the combination of the APIs, I tried to use a global Low Level Keyboard Hook (WH_KEYBOARD_LL
). The problem is, when we use the Low Level Keyboard Hook to block some input (we stop the progress of the message), Windows won't generate the Raw Input event, meaning no application will get the appropriate Raw Input message
(WM_INPUT
). Because of that, we can't use Low Level Keyboard Hook, but we have to use a standard Keyboard Hook (WH_KEYBOARD
), which is a bit harder to set up. When we want to use this Hook globally, i.e. for any running application, its procedure has to be in a separate DLL module. If you don't know how to set up a Hook in a DLL module, one of these articles should help you out: "Hooks and DLLs" [5], "Mouse and KeyBoard Hooking utility with VC++" [6]. The Hook should be registered like this:
hookHandle = SetWindowsHookEx (WH_KEYBOARD, (HOOKPROC)KeyboardProc, instanceHandle, 0);
Let's have the Hook procedure do nothing with the input for now, just check it as with the Raw Input, and pass it along the Hook chain.
USHORT virtualKeyCode = (USHORT)wParam;
USHORT keyPressed = lParam & 0x80000000 ? 0 : 1;
WCHAR text[128];
swprintf_s (text, 128, L"Hook: %X (%d)\n", virtualKeyCode, keyPressed);
OutputDebugString (text);
return CallNextHookEx (hookHandle, code, wParam, lParam);
Now when you run the application, you should get proper notifications for both Raw Input and for Hook as you type something in another window.
We expect to use the Hook for the input blocking, and the Raw Input for the decision-making. But they are in separate modules, therefore we will use the Windows messaging system to take care of the communication between the two. The communication will be quite simple. We will send a message from the Hook procedure to the main window, along with the original Hook message
parameters. Main window will decide what to do with the input. If the Hook is supposed to block the input, the return value of the message call is 1; 0 otherwise. We can try it out, without making any reasonable decision yet.
if (SendMessage (hwndServer, WM_HOOK, wParam, lParam))
{
return 1;
}
case WM_HOOK:
{
return 0;
}
Notice it is important to use SendMessage
, rather than the PostMessage
, because we want to wait for the decision.
Now that we established the communication, we can finally get something done. Let's take a look what are we dealing with. The most simple situation, when there are just a few single keystrokes, should look like this (the first number is hexadecimal virtual key code of the key, and the number in the brackets says whether the key is being pressed):
Raw Input: 67 (1)
Hook: 67 (1)
Raw Input: 67 (0)
Hook: 67 (0)
Raw Input: 63 (1)
Hook: 63 (1)
Raw Input: 63 (0)
Hook: 63 (0)
For every key press, we will get Raw Input message
first, followed by its Hook message
. I can tell you in advance, that things can get a bit messy when you type faster or use some special keys, but we will deal with that later, for now we will consider just this clean case. Bacause of that, whenever we get some Raw Input message
, we can decide what to do with the input (based on the keyboard that was used), remember the decision, and when we receive Hook message
, we will simply reply with the decision we made.
Let's improve our Raw Input processing first (that is where the decisions are made) to incorporate keyboard identification. Our decision will be quite simple, if the key struck was a "7" on the numeric keyboard, we will block it. All other keystrokes will be just passed.
case WM_INPUT:
{
GetRawInputDeviceInfo (raw->header.hDevice, RIDI_DEVICENAME, NULL, &bufferSize);
WCHAR* stringBuffer = new WCHAR[bufferSize];
GetRawInputDeviceInfo (raw->header.hDevice, RIDI_DEVICENAME, stringBuffer, &bufferSize);
if (virtualKeyCode == 0x67 && wcscmp (stringBuffer, numericKeyboardDeviceName) == 0)
{
blockNextHook = TRUE;
}
else
{
blockNextHook = FALSE;
}
}
The only remaining thing to do is to use the decision in Hook event processing.
case WM_HOOK:
{
if (blockNextHook)
{
swprintf_s (text, 128, L"Keyboard event: %X (%d) is being blocked!\n",
virtualKeyCode, keyPressed);
OutputDebugString (text);
return 1;
}
}
And here we have it! Our application, that blocks keyboard input based on what keyboard was used, is working. And the main idea of the Raw Input and keyboard Hook combination should be clear to you.
Unfortunately, the things tend to get a bit more complicated in a real world. There are many occasions, where we won't get nice ordered sequence of Raw Input
and Hook messages
, as we assumed earlier. And we will have to deal with these situations. Let's take a look at these complications, one at a time. You might wonder something like, "What if this...? And could this...?", when you read through the various situations. And you are probably right, things can get much more messy than a single section below would suggest. I'm trying to keep things as simple as possible, although we will have to rework some solutions because of that. I think this is the easiest way to comprehend the whole issue. And I believe it will be clear to you in the end.
When you start typing real fast (or like smash your keyboard with fingers), it is quite common, that you will get multiple Raw Input messages
in a sequence, and only after that the matching Hook messages
arrive. For example like this:
Raw Input: 44 (1)
Hook: 44 (1)
Raw Input: 4B (1)
Raw Input: 53 (1)
Hook: 4B (1)
Hook: 53 (1)
If we would settle for the simple solution we have so far, our application wouldn't work correctly, because it would block the input of "k" key (4B) and "s" key (53) both based on a single decision made for input of the "s" key (53), since we remember only the very last decision we made.
Therefore we have to remember more than one decision and use them in order. To achieve this, we will use some FIFO (first-in first-out) container. I choose to use a simple Deque
. As we are receiving the Raw Input messages
, we will decide what to do with the input, and push the decision into the Deque
. When we receive Hook message
, we will just pop the decision.
case WM_INPUT:
{
if (virtualKeyCode == 0x67 && wcscmp (stringBuffer, numericKeyboardDeviceName) == 0)
{
decisionBuffer.push_back (TRUE);
}
else
{
decisionBuffer.push_back (FALSE);
}
}
case WM_HOOK:
{
BOOL blockThisHook = FALSE;
if (!decisionBuffer.empty ())
{
blockThisHook = decisionBuffer.front ();
decisionBuffer.pop_front ();
}
if (blockThisHook)
{
return 1;
}
}
So far we've assumed that the Raw Input messages
always arrive before the Hook messages
. Commonly they do, but there are occasions, where this is untrue, and the Hook message
arrives first, like:
Raw Input: 4A (0)
Hook: 4A (0)
Hook: 48 (0)
Raw Input: 48 (0)
This would mean, that we would want to pop a decision from our buffer when there is none left. When this happens, we have to wait until we receive our delayed Raw Input message
. After we get the message, we can decide on the spot whether to block the input or not.
case WM_HOOK:
{
BOOL blockThisHook = FALSE;
if (!decisionBuffer.empty ())
{
}
else
{
MSG rawMessage;
while (!PeekMessage (&rawMessage, mainHwnd, WM_INPUT, WM_INPUT, PM_REMOVE))
{
}
}
}
We have somehow dealt with the mixed up sequence of the messages, but you might already wondered, "Is it possible for some message to not arrive at all? And if so, what happens?". Indeed it is possible. Let's take a look what happens when we lose some Hook message
, i.e. we get Raw Input message
for some event, but no Hook message
for it. This happens to me for example when I use "Ctrl" + "Esc" shortcut. Messages for this sequence come as:
Raw Input: 11 (1)
Hook: 11 (1)
Raw Input: 1B (0)
Raw Input: 11 (0)
As you can see, Windows neglect to deliver couple of messages to us. Specifically both Raw Input
and Hook events
for key press of "Esc" key (1B), and Hook events
for "Esc" (1B) and "Ctrl" (11) key releases. This could cause us some trouble, if we would just push the decisions for these strokes into the buffer, we would use them incorrectly later on. This problem can be solved (sort of) by enhancing our decision buffer. We won't be remembering only the decision itself, but also the virtual key code of the key it belongs to (you might even include whether the key is being pressed for more robust solution). This way, when Hook message
arrives, and we are looking for the decision what to do with it, we will not simply pop the first decision, but we will check if its virtual key code matches. If it doesn't, we will look through the whole buffer for the correct one. I mentioned that this solves the problem only "sort of". Even with this virtual key code checking, the potential danger is that there could be some button pushed twice, each time on a different keyboard. If the Hook message
for the first keystroke doesn't arrive, we would incorrectly evaluate the second keystroke (based on the decision made for the first one). Unfortunately, I wasn't able to think of any reasonable solution to this. If you happen to know one, I would much appreciate if you would share it with the rest of us in the comments section, or you could send me an e-mail. This is definitely one of the things to look out for when using this APIs combination.
struct DecisionRecord
{
USHORT virtualKeyCode;
BOOL decision;
DecisionRecord (USHORT _virtualKeyCode, BOOL _decision) : virtualKeyCode (_virtualKeyCode),
decision (_decision) {}
};
case WM_HOOK:
{
BOOL blockThisHook = FALSE;
BOOL recordFound = FALSE;
UINT index = 1;
if (!decisionBuffer.empty ())
{
std::deque<decisionrecord>::iterator iterator = decisionBuffer.begin ();
while (iterator != decisionBuffer.end ())
{
if (iterator->virtualKeyCode == virtualKeyCode)
{
blockThisHook = iterator->decision;
recordFound = TRUE;
for (int i = 0; i < index; ++i)
{
decisionBuffer.pop_front ();
}
break;
}
++iterator;
++index;
}
}
}</decisionrecord>
Notice here, that when we pop the decision, we don't pop only the matching decision we found, but also all decisions older than the current one. Assuming nothing else went wrong, these decisions are those that have no matching Hook message
. This is useful to keep the buffer from growing unnecessarily big. But there are certainly more ways how you can keep it under control as you see fit.
When we were dealing with the situation where a Hook message
comes before its Raw Input message
, you might have got a little worried about that potentially infinite while loop we introduced. You really should be worried, actually. We already noticed, that not all messages will always be delivered. We walked through what can happen when a Hook message
is lost. Now let's take a look what happens when a Raw Input message
isn't delivered properly. Because there are real scenarios where this occurs. For example "AltGr" key gives me these messages (you might not encounter this, because this behaviour is locale specific):
Raw Input: 12 (1)
Hook: 11 (1)
Hook: 12 (1)
Raw Input: 12 (0)
Hook: 11 (0)
Hook: 12 (0)
Windows deliver Hook messages
"Ctrl" (11) + "Alt" (12) for a single "AltGr" press ("Ctrl" + "Alt" is common substitute for "AltGr"). The problem is, it delivers Raw Input message
for only "AltGr" (12) itself ("AltGr" is identical to "Alt" for this purpose; it has even the same scan code, as far as I know). Employing our Raw Input waiting loop can cause some serious issues now, because it might never get the Raw Input message
it is waiting for. We have to make some constraint on the loop.
case WM_HOOK:
{
DWORD currentTime, startTime;
startTime = GetTickCount ();
while (!recordFound)
{
MSG rawMessage;
while (!PeekMessage (&rawMessage, mainHwnd, WM_INPUT, WM_INPUT, PM_REMOVE))
{
currentTime = GetTickCount ();
if ((currentTime < startTime ? ULONG_MAX - startTime + currentTime :
currentTime - startTime) > maxWaitingTime)
{
WCHAR text[128];
swprintf_s (text, 128, L"Hook TIMED OUT: %X (%d)\n", virtualKeyCode,
keyPressed);
OutputDebugString (text);
return 0;
}
}
}
}
Even though the loop can't go infinite now, I believe this (the missing or delayed Raw Input message
) is one of the biggest problems of the whole concept. Because any waiting for the Raw Input message
introduces a delay of the keyboard input. Therefore the waiting limit should be really low, so the users won't notice any lag. It might be even worth considering using a finer timer, I used the most simple one for the demonstration purposes.
I think, that what we have now is a reasonable core for an application that combines Raw Input and keyboard Hooking. The demo project provided matches what we went through so far. Now I would like to tell you more about some peculiar behaviour you might encounter when you deal with these APIs. It will be mostly things that causes one of the problems we covered earlier, with focus on missing Raw Input messages
. I believe it deserves the attention, because as I mentioned, the missing Raw Input message
can cause keyboard input lag, which is very undesirable. I will show you all of the problematic situations I know of, and propose some approach how to avoid, or at least minimize, the danger it might represent. But I won't include these advanced adjustments in the demo project, because I wanted to keep some balance between clarity and completeness, and I decided this will be the line. I also believe, that since you made it this far, you are capable of implementing the discussed approaches on your own, or even devise better ones, which the rest of us would gladly read in the comments section. But if you are interested in any of these techniques and can't figure it out yourself, please feel free to ask in the comments section about the details, or sample code, and I will try to help you out. I will also include a link to my shortcut managing application (with sources) once I finish it, where all of these additional measures are covered.
Let's begin with something we've already stumbled upon. The inconvenience when Windows produce "Ctrl" + "Alt" Hook messages
, but only "Alt" Raw Input message
. You can go back and take a look at the sequence of messages we are getting in this case.
Usually the "Alt" Raw Input message
arrives correctly, so we can examine the Raw Input message
and get one additional detail from its flags. The said detail is whether the "Alt" key was the right version of the key, i.e. the one, that can in fact be "AltGr". If it is, we will push into our decision buffer not only the decision for the "Alt" key, but first also a fake decision for the "Ctrl" key. That way, the incoming "Ctrl" key Hook message
will be able to find a matching record in the buffer and won't delay the input. As I mentioned earlier, this behaviour only occurs within some locale, so you can employ this workaround just for specific locales using the GetKeyboardLayout
function.
Speaking of locale dependent problems, within some locales you might get really odd behaviour. For example, you might get this message sequence for a single "Win" key stroke (for me on Czech QWERTZ keyboard layout):
Raw Input: 5C (1)
Hook: 5C (1)
Raw Input: 5C (0)
Hook: 5C (0)
Hook TIMED OUT: 11 (0)
You might be wondering what is that "Ctrl" Hook message
doing there, at least I am. Even more so, when you inspect the details of the message. It turns out, that the "Ctrl" Hook message
has its flags set to indicate that the key is being released and that the key was up prior to the event (according to the Previous Key-State Flag
). In other words, the "Ctrl" key is being released when it was released already. This can happen with various keys (not just the "Win" key) on the keyboard, but as far as I know, the improper Hook message
is always the "Ctrl" being released while already up. Since I've never encountered a situation, where this flags setting occurs with the proper "Ctrl" key press (I've noticed them with regular keystrokes of some special keys), I believe you can monitor your Hook messages
for this pattern and don't wait for a Raw Input message
, should such message arrive.
There are situations where Windows will generate multiple keyboard Hook messages
for a single keyboard event, and only one Raw Input message
. Every multiplied Hook message
will try to wait for its Raw Input message
(that won't ever come), which will cause a delay. It often happens when an application enters a menu (be it either menu bar or context menu), or some applications can induce this behaviour more unpredictably. For example Firefox does this when I type too fast. The sequence of the messages can look like this:
Hook: 27 (1)
Raw Input WAITING: 27 (1)
Hook: 27 (1)
Raw Input WAITING: 27 (0)
Hook: 27 (0)
Hook TIMED OUT: 27 (0)
Hook: 27 (0)
Hook TIMED OUT: 27 (0)
As you can see, Windows delivered two Hook messages
for every Raw Input message
, but that's not a rule, there can be three or even more Hook messages
for every single Raw Input message
. I currently approach the issue with the following idea.
The basis is to remember what was the last Hook message
the application received. When new Hook message
arrives, we can check whether it is identical message to the previous one (virtual key code and whether the key is being pressed match). If it is, we won't wait for the Raw Input message
. But we should still check the decision buffer if there is a matching Raw Input message
. Because for example when some key is being held, there will be a meaningful sequence of same Hook messages
with matching Raw Input messages
, and we shouldn't disregard those.
One thing to keep in mind with this approach is that you should apply the same decision (whether to block the input with the Hook) to every repetitive Hook message
. Say you would let the first Hook event through and block all the identical following events, thinking that the first one should be enough for the active window to process the input (for example keystroke in the menu bar). As it turns out, it's not. The application won't carry out the keystroke if you won't pass the following events as well as the first one.
If you want to be more selective when to use this approach, you can check whether the application is currently in the menu mode using the GetGUIThreadInfo
function. Although I would recommend using it all the time, just because there are applications, that can cause this behaviour even while not being in menu mode.
So far, we've used a standard keyboard Hook (WH_KEYBOARD
) to block the original input when we wanted. There is one limitation of this setup. There are some keys that are handled by the system regardless of the standard Hook blocking them. For example shortcuts like "Win" + "d", "Alt" + "Tab", or even some single special keys like "Calculator" button (B7 for my keyboard). Even if you stop propagation of the Hook message
, Windows will launch the Calculator when you press it.
If you want to be able to block such key events, you will have to incorporate the Low Level keyboard Hook (WH_KEYBOARD_LL
). When we were setting things up, we figured out that it's not possible to combine Low Level Hook and Raw Input API, so there is no way (I believe there isn't, if you know any, please let me know in the comments section or via the e-mail) how to block these special keys and still be able to identify the keyboard which was used. But at least they can be used side by side, so you can have standard Hook with Raw Input handling most of the events, and the Low Level Hook handling these special keys, which won't be distinguishable by keyboard identification.
One last thing that I stumbled upon is the "Num Lock" key (I didn't notice it with the other locking keys). Even if I block this key with the Low Level Hook, it will still affect the keyboard state, as the normal "Num Lock" keypress would (only it won't change the LED indicator on the keyboard). This can be circumvented by checking the keyboard state when "Num Lock" key is being released (in standard keyboard Hook), and if need be synthesize a fake "Num Lock" key press that will switch the keyboard to previous state.
I hope this article helped you understand how to combine the Raw Input and keyboard Hooking APIs.
If I should summarize my thoughts about the whole approach, I find it quite useful. It is capable of solving the problem, even though there is a couple of unresolved issues. Since Windows API doesn't provide an easier way how to approach the problem, I think the issues are reasonably manageable. But if you need to implement 100% robust solution, you will probably have to use a custom driver approach.
If you have some thoughts, questions, or anything else to add, please feel free to share them in the comments section.
- January 27, 2014: First version of the article posted.