January 21, 2005

Emulating keystrokes in Windows

One of the core functions in Shoot, a voice command program I wrote a couple of years ago, is the ability to emulate key presses programmatically.

There is a fairly straightforward way to do this in Windows 2000/XP, via the SendInput Win32 function. In Windows 98/ME things are somewhat more complicated: SendInput inserts the keystrokes in the Windows event queue, but DirectInput bypasses it and hooks directly into the keyboard driver.

As a result, any keystroke sent via SendInput is completely ignored by DirectInput applications. Pretty much every modern Windows-based game uses DirectInput, the main target for Shoot. The solution is to implement a Virtual Device Driver (VxD) that inserts the keystrokes directly into the keyboard driver buffer, and that is what I ended up doing for Shoot.

Here's how to use the SendInput function from C#. I stripped all the Win98/ME-related code. Not only would it make the code below significantly more complex, but it's probably useless by now. Who's using Win 98, anyway? Not even Microsoft supports it anymore :)

[StructLayout(LayoutKind.Sequential)]
private struct KEYBOARD_INPUT {
public uint type;
public ushort vk;
public ushort scanCode;
public uint flags;
public uint time;
public uint extrainfo;
public uint padding1;
public uint padding2;
}

[DllImport("User32.dll")]
private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst=1)] KEYBOARD_INPUT[] input, int structSize);

void press(int scanCode)
{
  sendKey(scanCode, true);
}

void release(int scanCode)
{
  sendKey(scanCode, false);
}

private void sendKey(int scanCode, bool press)
{
   KEYBOARD_INPUT[] input = new KEYBOARD_INPUT[1];
   input[0] = new KEYBOARD_INPUT();
   input[0].type = INPUT_KEYBOARD;
   input[0].flags = KEY_SCANCODE;

   if ((scanCode & 0xFF00) == 0xE000) { // extended key?
     input[0].flags |= KEY_EXTENDED;
   }

   if (press) { // press?
     input[0].scanCode = (ushort) (scanCode & 0xFF);
   }
   else { // release?
     input[0].scanCode = scanCode;
     input[0].flags |= KEY_UP;
   }

   uint result = SendInput(1, input, Marshal.SizeOf(input[0]));

   if (result != 1) {
     throw new Exception("Could not send key: " + scanCode);
   }
}

5 Comments:

Anonymous Anonymous said...

Ive worked a lot with this method, trying to use the SendInput and another one for a program a I am working on, with little success at all. I have another method which works on all but DirectX.

I cannot get this one to work anywhere though. Can you send a compilable version of this. and explain more about how you worked with the driver for a solution, as I saw that mentioned elsewhere as well.

James falazar@yahoo.com

May 10, 2005 1:29 PM  
Anonymous Anonymous said...

is that possible to put your vxd source inline ?

September 20, 2005 2:35 AM  
Anonymous Anonymous said...

Hi, Could you please send me the values for INPUT_KEYBOARD, KEY_EXTENDED, KEY_UP?

Thanks,

Emil.Aguinaldo@AP.EmersonProcess.Com

October 07, 2005 6:26 AM  
Blogger tom2 said...

It's
const uint KEY_SCANCODE = 0x0008;
and NOT 0x0004!!!!!!

Oh darn, I only wasted something like 5 hours because of this...

April 14, 2008 1:25 PM  
Blogger suspectx86 said...

I worked out the code like this, but everytime I send a key with the Press and Release Method...

I get an exception error:
Could not send key: 65

static void SendKey_A()
{
press(65);
release(65);
}

[StructLayout(LayoutKind.Sequential)]
private struct KEYBOARD_INPUT
{
public uint type;
public ushort vk;
public int scanCode;
public uint flags;
public uint time;
public uint extrainfo;
public uint padding1;
public uint padding2;
}

[DllImport("User32.dll")]
private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] KEYBOARD_INPUT[] input, int structSize);

static void press(int scanCode)
{
sendKey(scanCode, true);
}

static void release(int scanCode)
{
sendKey(scanCode, false);
}

public static void sendKey(int scanCode, bool press)
{
const uint INPUT_KEYBOARD = 1;
const int KEY_EXTENDED = 0x0001;
const uint KEY_UP = 0x0002;
const uint KEY_SCANCODE = 0x0008;
KEYBOARD_INPUT[] input = new KEYBOARD_INPUT[1];
input[0] = new KEYBOARD_INPUT();
input[0].type = INPUT_KEYBOARD;
input[0].flags = KEY_SCANCODE;

if ((scanCode & 0xFF00) == 0xE000)
{ // extended key?
input[0].flags |= KEY_EXTENDED;
}

if (press)
{ // press?
input[0].scanCode = (ushort)(scanCode & 0xFF);
}
else
{ // release?
input[0].scanCode = scanCode;
input[0].flags |= KEY_UP;
}

uint result = SendInput(1, input, Marshal.SizeOf(input[0]));

if (result != 1)
{
throw new Exception("Could not send key: " + scanCode);
}
}
}
}

October 18, 2009 10:42 PM  

Post a Comment

<< Home