Using the Keyboard Driver

Overview

The keyboard driver allows you to:

  • Press and release any regular keys keys plus any eight modifier keys to the driver.

The driver appears equivalent to the OS as a physical keyboard. The OS will apply the keystrokes to the active application.

If this is your first attempt at sending data to the drivers, we suggest you try the mouse driver first to get the idea of how it works. They keyboard driver is much more complicated.

The Keyboard Reader Utility

While developing code that uses the keyboard driver, use the reader utility to consume data from the keyboard driver. This is easier than using Notepad or Wordpad or a game since the reader has a simple known name that you will need to search for to make the app active before sending keystrokes to it. The keyboard reader doesn’t actually connect to the driver. It’s just a simple textbox to receive the text from the driver. The OS will determine whether to put the text into the textbox in this app (it will if it is the active app).

The Keyboard Driver Sender Utility

Use the SDK Keyboard Driver Sender to keys and modifiers to the keyboard driver. The keyboard driver will appear to Windows as if it is an actual physical keyboard. Be sure to press the ‘Connect to Keyboard Driver’ before sending data to the driver.

Here we have sent ‘Hello’ from the Keyboard Sender utility to the Keyboard Reader utility.

Modifiers

A modifier modifies how a Key is used. There are eight modifiers, and each modifier represents a bit array contained in a single byte. So if you wanted to press the Left Control Key, you would set bit zero to 1. And later to release it, you would set bit zero to 0. You can have more than one modifier pressed at the same time, like LCTRL-LSHIFT. If no modifiers are pressed, all bits are zero. Here are the modifiers:

  • Bit 0 = LCTRL: Left Control Key
  • Bit 1 = LSHIFT: Left Shift Key
  • Bit 2 = LALT: Left Alt Key
  • Bit 3 = LWIN: Left Control Key
  • Bit 4 = RCTRL: Right Control Key
  • Bit 5 = RSHIFT: Right Shift Key
  • Bit 6 = RALT: Right Alt Key
  • Bit 7 = RWIN: Right Windows Key

Keys

Everything that are not Modifiers are ‘Keys’. This includes the usual 0-9, a-z, Insert, Delete, Home, Esc, F1 etc. The HID keyboard spec allows you to press up to five of these keys simultaneously. Try it on your physical keyboard: you can only press 5 simultaneously and have those recognized by the OS. The driver follows this spec, so you can have multiple keys pressed at the same time.

In a crazy example, you could have all the modifiers pressed, as well as five keys pressed at the same time. I wouldn’t recommend this, but it could be done. This would be the only way to do it, as you don’t have enough fingers to do it on a physical keyboard.

We’ll telling the driver which keys are pressed (not modifiers) by sending keycodes. These are not the same as ascii codes. They are defined in page 64 o fthe HID Keyboard Usage specs in the sdk/delphi/common/hut1_12v2.pdf. Here’s the beginning of the keycode definitions. So to send an ‘a’ ,we send keycode = 4. These are also defined in the uKeyboardUtils.pas in the Delphi sample code.

  • 0 = nothing
  • 1 = nothing
  • 2 = nothing
  • 3 = nothing
  • 4 = a
  • 5 = b
  • 6 = c
  • ..
  • 115 = F24 (yes, F24!)

Sending Data to the Driver

First, iterate through all drivers until you find ‘Tetherscript Virtual Keyboard’ and connect to that driver. Then send a packed record as a ‘Feature Report’ to the driver. More info on feature reports below:

https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/

Joystick driver reports are based sdk/delphi/common/hut1_12v2.pdf

Delphi

Here’s the keyboard driver report Delphi data format. In our Delphi source we call this a Feature, but really it is a standard HID Input Report. This is just the terminology left over from Delphi’s JCL and JVCL libraries:

type
PTSetFeatureKeyboard = ^TSetFeatureKeyboard;
TSetFeatureKeyboard = packed record
  ReportID: Byte;
  CommandCode: Byte;
  timeout: Longword;  //you must ping the driver every (timeout / 5) seconds or the driver will reset itself and release all keypresses
  modifier: byte;
  padding: byte;
  key0: byte;
  key1: byte;
  key2: byte;
  key3: byte;
  key4: byte;
  key5: byte;
end;

C#

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SetFeatureKeyboard
{
  public Byte ReportID;
  public Byte CommandCode;
  public uint Timeout;       //you must ping the driver every (timeout / 5) seconds or the driver will reset itself and release all keypresses
  public Byte Modifier;
  public Byte Padding;
  public Byte Key0;
  public Byte Key1;
  public Byte Key2;
  public Byte Key3;
  public Byte Key4;
  public Byte Key5;
}

c

The Delphi and C# data structure corresponds to the following c struct (this is the actual receiving input report struct from the driver source):

typedef struct _HIDMINI_CONTROL_INFO {
  UCHAR ReportId;
  UCHAR CommandCode;
  UINT32 timeout; //you must ping the driver every (timeout / 5) seconds or the driver will reset itself and release all keypresses
  BYTE modifier = 0;
  BYTE padding = 0;
  BYTE key0 = 0;
  BYTE key1 = 0;
  BYTE key2 = 0;
  BYTE key3 = 0;
  BYTE key4 = 0;
  BYTE key5 = 0;
} HIDMINI_CONTROL_INFO, * PHIDMINI_CONTROL_INFO;

  • ReportID: is always 1. Just leave it as 1.
  • CommandCode: This is 1, 2 or 3. Set it to 1 to release all keypresses. Set it to 2 to send modifier and key-state data to the driver. Set it to 3 to ping the driver.
  • timeout: You must ping the driver every timeout x milliseconds or the driver will reset itself and release all keys pressed. This is super-important. It is very easy during dev to forget to release a key, or crash which maybe leaves a key pressed. This will ensure that if the pinging stops (due to crash or you shutting down app), that the keypresses will all be released. If you didn’t have this feature, you would be rebooting a lot, because that is the only way to fix it. Setting timeout to 5000 (approx one second) should be enough. Be sure to put your pinger code in a separate thread so the pinging isn’t blocked for some reason.
  • modifier: This is a single byte that represents which, if any of the modifiers are pressed. A value of zero means no modifier is pressed. Or all the bits together to calculate the byte value.
    • Bit 0 = LCTRL: Left Control Key
    • Bit 1 = LSHIFT: Left Shift Key
    • Bit 2 = LALT: Left Alt Key
    • Bit 3 = LWIN: Left Control Key
    • Bit 4 = RCTRL: Right Control Key
    • Bit 5 = RSHIFT: Right Shift Key
    • Bit 6 = RALT: Right Alt Key
    • Bit 7 = RWIN: Right Windows Key
  • padding: Always set padding to zero.
  • key0: This is the keycode that is currently pressed in key slot zero. We can have up to five keys pressed simultaneously. If the value here is zero, no key is pressed in this slot.
  • key1: This is the keycode that is currently pressed in key slot one.
  • key2: This is the keycode that is currently pressed in key slot two.
  • key3: This is the keycode that is currently pressed in key slot three.
  • key4: This is the keycode that is currently pressed in key slot four.

Handling Stuck Keyboard Buttons

As an example, you may press keyboard ‘a’ and maybe forgot to release it, or due to your app crashing, that the button remains pressed. There are three ways to unstuck a key or modifier.

  1. Clear the modifier or button by setting its value to zero.
  2. Send a reset command to the driver. This releases all modifiers and key presses.
  3. Be rescued by the built-in driver ping feature.
  4. Reboot.

Resetting the Driver

To reset the driver, send the report with a CommandCode = 1 and any values for modifier and key slots. The driver will release all modifier and button keypresses.

You can also just send the the modifier and key slot release values with a CommandCode = 1. It’s up to you. Both methods are equivalent.

The Pinger

You can avoid the worst effects of stuck keypresses (having to reboot) by pinging the driver every x ms. When you ping, or send keypress data, you can set a timeout (5000 = approx 1 second). If the timeout has elapsed without a ping being received by the driver, the driver will automatically release all keypresses. It is best to set the pinger on a thread in your app so it can’t be blocked, otherwise your keypresses may become ignored by the driver. So ping, and keep on pinging throughout the life of your app.

It is common to accidentally not release a keypress. To the OS, this is the same as you physically pressing the key on the keyboard and keeping your finger on it. If the app were to crash, this key would remain pressed and create a mess in whatever app you have active. However if the pinging stops, they driver will release the keypresses in timeout ms. Problem solved.

So be ready to handle these stuck keys. Getting familiar again with CTRL-TAB and ALT-F4 is a good idea just in case you need to shut down your app quickly.

Keys are Sent to the Active Application

Just as when you press keys on the physical keyboard, the OS sends these keystrokes to whatever app is active (or in focus). The keyboard driver does the same thing since the OS thinks it is a physical keyboard, so your app should have a way to make a target app active before you send keystrokes to it. The Delphi key sender utility has some code that does this. You’ll need to have a short delay (100 ms?) after you make an app active before you send keystrokes to it, otherwise the keystrokes may be lost.

Your First Hello-Driver App – a Suggestion

  1. Start the Keyboard Reader utility.
  2. In your app, iterate through the drivers and find and connect to the keyboard driver.
  3. In your app, send data with a command code of 2, modifier = 0, key0 = 4. Pause 100 ms. Then send data with command code of 2, modifier of 0, key0 = 0. That presses ‘a’ and releases it. Sweet victory!