Keyboard Hooking

Hi everyone, Kernel Hotkey library and driver have been updated.

Last time I've provided a version of the driver/library which is now obsolete, I've built a new version of the driver that is best designed and a new version of the library to support it. The old driver and library are incompatible with the new model.

Today I've opt to start with two very basic samples, with them I'll explain a new feature of the driver/library: multiple attachments to a single device.


The first sample we'll analize is a C# app that simple maps the x key to the y key.


    1 using System;

    2 using System.Diagnostics;

    3 using KernelHotkey;

    4 

    5 namespace xy

    6 {

    7     class Program

    8     {

    9         static void Main(string[] args)

   10         {

   11             Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

   12 

   13             Keyboard[] keyboards = new Keyboard[10];

   14             Keyboard kbd;

   15             Keyboard.Stroke keystroke;

   16 

   17             for (int i = 0; i < 10; i++)

   18             {

   19                 keyboards[i] = new Keyboard(i);

   20                 keyboards[i].Filter = Keyboard.Filters.MAKE | Keyboard.Filters.BREAK; // filtering only key-down and key-up

   21             }

   22 

   23             while (true)

   24             {

   25                 kbd = Keyboard.Wait(keyboards);

   26                 if (kbd == null) break;

   27 

   28                 keystroke = kbd.Read();

   29                 if (keystroke.code == 0x2D) keystroke.code = 0x15; // x becomes y

   30                 kbd.Write(keystroke);

   31             }

   32         }

   33     }

   34 }


Lets review this tiny code sample to see how the Kernel Hotkey library is used.


Line 3: The KernelHotkey namespace is imported to avoid excessive typing.


Line 11: The priority class of the current process is raised to high. This is done because this app is dealing with an input device of vital importance, without that the keyboard processing will start to share cpu time as a common application, which is wrong given that if some another user land app hangs, our keyboard may hang too and the system may become very slow. Setting the priority class to high will avoid all this problems.


Line 13: An empty array of 10 elements of the class Keyboard is declared. The Keyboard class is the one which provides a link with a real device. An array of 10 elements is declared because this app will attach to all keyboards connected to the system and do the same thing to each one. Up to 10 keyboards can be handled and each keyboard connected to the system receives an ID ranging from 0 to 9. If you have a PS/2 keyboard connect to your system it will aways receive the 0 ID, USB keyboards receives higher IDs but if there isn't any PS/2 keyboard attached an USB keyboard may get the unused 0 ID.


Line 14: A Keyboard variable is declared, this one will hold the instance that received a notification of arriving keystrokes to be read.


Line 15: A Keyboard.Stroke variable is declared. Instances of this class contains keystroke information like key scan-code and state.


Line 17: An initialization loop is started. Inside this loop the 10 instances corresponding to all possible keyboards in the system are created. It doesn't matter whether there's only one keyboard connected, if we attach another one while the app is still running it will start being hooked right away. Inside this loop the Filter property of each Keyboard instance is set. Without this nothing will be filtered, the app will never get any notification of an arriving keystroke. We must define what is to be filtered. This sample filters only key-down and key-up events, this means that common keys will notify the app but special keys will not. To accept some special keys like Delete more flags must be set.


Line 23: The bulk of the app is inside this loop. Here every keystroke caught is sent back to the system, only x keystrokes are mutated to y.


Here's a depiction of what's going on:


x-y


As can be seen, when x is typed on the keyboard it arrives the app before getting to the Windows subsystem. The app mutates x to y and then send it to the subsystem. When it doesn't receive a x it simply send what has arrived back.


The following Visual Basic sample does nearly the same thing, the difference is that it maps y to z.


    1 Module Program

    2 

    3     Sub Main()

    4         Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High

    5 

    6         Dim keyboards(9), kbd As Keyboard

    7         Dim keystroke As Keyboard.Stroke

    8 

    9         For i As Integer = 0 To 9

   10             keyboards(i) = New Keyboard(i)

   11             keyboards(i).Filter = Keyboard.Filters.MAKE Or Keyboard.Filters.BREAK 'filtering only key-down and key-up

   12         Next

   13 

   14         Do

   15             kbd = Keyboard.Wait(keyboards)

   16             If kbd Is Nothing Then Exit Do

   17 

   18             keystroke = kbd.Read()

   19             If keystroke.code = &H15 Then keystroke.code = &H2C 'y becomes z

   20             kbd.Write(keystroke)

   21         Loop

   22 

   23     End Sub

   24 

   25 End Module


Here's the depiction of what's going on:


y-z


Samples download


Now, what if both apps are executed?

Lets analize the case where we execute the x-y app and then the y-z app:


Here's a depiction of what happens when x is typed:


x-y-z


Now x becomes z. As can be seen the apps are piled up, the one that was first started get a higher filtration precedence, so x becomes y which then becomes z. That's the way multiple attachments to a single device works.

What if y is typed?


y-y-z


The x-y app doesn't mutate y keys, so y is just handed on to the next lower precedence hook. The end result is that y becomes z.


And what if y-z is executed first then x-y? This is what happens:


x-x-y


y-z-z


The end result is that the expected functionality of both apps is working, x becomes y when x is typed and y becomes z when y is typed. It's working now because no mutated output from a higher precedence hook is being mutated again by a lower precedence hook. In each case just a single hook really works at a time.

What shall be learned from here is that when building multiple hooks to be executed at the same time, it's aways better to build then mutually exclusive, so that they do not interfere on each other.


Precedence is an integer Keyboard property that can be set at any time, what means it's possible to change the order of the hooks in the filtration pile dynamically. On this two samples the Precedence isn't set explicitly so that it receives the default value of 0 on both samples, and what decides which one gets a higher precedence is the order of attachment to the keyboard, which happens at instantiation.


That's all folks, see ya!

Thanks

daniloz3 to make me wonder about this hook to help him conquer the Ragnarok. I thank daniloz3 too for showing me a brave new world. I'll never forget :) :D :P

Fernando for paying me a feijoada when my debit card stopped working. I'll never forget.

Comments