Thursday, April 09, 2009

.Net – Sending keys to the keyboard buffer

How do you send a bunch of keys to the keyboard buffer? Or in other words how do you emulate a user typing away at the keyboard through your application?

Well the simplest answer is that you should use the SendKeys class in the System.Windows.Forms namespace.

System.Windows.Forms.SendKeys.Send("Hello World");
SendKeys will work fine and dandy as long as you are running within a WinForms application. If you try and use SendKeys from a console application, or a dll (which you wish to keep generic so that it could be used in any type of application), then it will throw an exception with the following message:

SendKeys cannot run inside this application because the application is not handl ing Windows messages. Either change the application to handle messages, or use the SendKeys.SendWait method.

The alternative is to use the SendWait method, but that one expects you to have an active application waiting to accept the input. (Otherwise your app will hang, waiting for an active app to accept the keys).

What I needed was a method to send keys to the keyboard buffer and then forget about it. Just like Send works, only it had to be able to work without being run from within an active windows application.

The only way to get this functionality is to use PInvoke and call the WINApi method “SendInput”.

Here is my SendKeys class with a Send method that sets up the WIN API structures and calls the SendInput method.

public class SendKeys
{
/// <summary>
/// Sends the text to the keyboard buffer
/// </summary>
/// <param name="text"></param>
public static void Send(string text)
{
   if (string.IsNullOrEmpty(text))
       return;

   uint numCharsToSend = (uint)(text.Length * 2); //keydown keyup
   SendInputWin32.INPUT[] structInputArray = new SendInputWin32.INPUT[numCharsToSend];
   int index = 0;
   foreach (char key in text.ToCharArray())
   {
       //key down
       SendInputWin32.INPUT structInputKeyDown = SendInputWin32.CreateNewINPUT(SendInputWin32.INPUT_KEYBOARD);
       structInputKeyDown.ki.dwFlags = SendInputWin32.KEYEVENTF_UNICODE;
       structInputKeyDown.ki.wScan = (ushort)key;
       structInputArray[index++] = structInputKeyDown;
  
       //key up
       SendInputWin32.INPUT structInputKeyUp = SendInputWin32.CreateNewINPUT(SendInputWin32.INPUT_KEYBOARD);
       structInputKeyUp.ki.dwFlags = SendInputWin32.KEYEVENTF_UNICODE | SendInputWin32.KEYEVENTF_KEYUP;
       structInputKeyUp.ki.wScan = (ushort)key;
       structInputArray[index++] = structInputKeyUp;
   }
   int sizeOfINPUT = Marshal.SizeOf(structInputArray[0]);
   SendInputWin32.SendInput((numCharsToSend), ref structInputArray[0], sizeOfINPUT);
}
}

And here is the code that provides all the constants, structures, and the PInvoke required to call SendInput.

public class SendInputWin32
{
public const ushort INPUT_MOUSE = 0x000;
public const ushort INPUT_KEYBOARD = 0x0001;
public const ushort INPUT_HARDWARE = 0x0002;

public const ushort KEYEVENTF_KEYUP = 0x0002;
public const ushort KEYEVENTF_KEYDOWN = 0x0000;
public const ushort KEYEVENTF_SCANCODE = 0x0008;
public const ushort KEYEVENTF_UNICODE = 0x0004;

public const ushort XBUTTON1 = 0x0001;
public const ushort XBUTTON2 = 0x0002;
public const ushort MOUSEEVENTF_MOVE = 0x0001;
public const ushort MOUSEEVENTF_LEFTDOWN = 0x0002;
public const ushort MOUSEEVENTF_LEFTUP = 0x0004;
public const ushort MOUSEEVENTF_RIGHTDOWN = 0x0008;
public const ushort MOUSEEVENTF_RIGHTUP = 0x0010;
public const ushort MOUSEEVENTF_MIDDLEDOWN = 0x0020;
public const ushort MOUSEEVENTF_MIDDLEUP = 0x0040;
public const ushort MOUSEEVENTF_XDOWN = 0x0080;
public const ushort MOUSEEVENTF_XUP = 0x0100;
public const ushort MOUSEEVENTF_WHEEL = 0x0800;
public const ushort MOUSEEVENTF_VIRTUALDESK = 0x4000;
public const ushort MOUSEEVENTF_ABSOLUTE = 0x8000;

public enum VK : ushort
{
   SHIFT = 0x10,
   CONTROL = 0x11, //ctrl key
   MENU = 0x12,    //alt key
   ESCAPE = 0x1B,
   BACK = 0x08,
   TAB = 0x09,
   RETURN = 0x0D,
   PRIOR = 0x21,
   NEXT = 0x22,
   END = 0x23,
   HOME = 0x24,
   LEFT = 0x25,
   UP = 0x26,
   RIGHT = 0x27,
   DOWN = 0x28,
   SELECT = 0x29,
   PRINT = 0x2A,
   EXECUTE = 0x2B,
   SNAPSHOT = 0x2C,
   INSERT = 0x2D,
   DELETE = 0x2E,
   HELP = 0x2F,
   NUMPAD0 = 0x60,
   NUMPAD1 = 0x61,
   NUMPAD2 = 0x62,
   NUMPAD3 = 0x63,
   NUMPAD4 = 0x64,
   NUMPAD5 = 0x65,
   NUMPAD6 = 0x66,
   NUMPAD7 = 0x67,
   NUMPAD8 = 0x68,
   NUMPAD9 = 0x69,
   MULTIPLY = 0x6A,
   ADD = 0x6B,
   SEPARATOR = 0x6C,
   SUBTRACT = 0x6D,
   DECIMAL = 0x6E,
   DIVIDE = 0x6F,
   F1 = 0x70,
   F2 = 0x71,
   F3 = 0x72,
   F4 = 0x73,
   F5 = 0x74,
   F6 = 0x75,
   F7 = 0x76,
   F8 = 0x77,
   F9 = 0x78,
   F10 = 0x79,
   F11 = 0x7A,
   F12 = 0x7B,
   OEM_1 = 0xBA,   // ',:' for US
   OEM_PLUS = 0xBB,   // '+' any country
   OEM_COMMA = 0xBC,   // ',' any country
   OEM_MINUS = 0xBD,   // '-' any country
   OEM_PERIOD = 0xBE,   // '.' any country
   OEM_2 = 0xBF,   // '/?' for US
   OEM_3 = 0xC0,   // '`~' for US
   MEDIA_NEXT_TRACK = 0xB0,
   MEDIA_PREV_TRACK = 0xB1,
   MEDIA_STOP = 0xB2,
   MEDIA_PLAY_PAUSE = 0xB3,
   LWIN = 0x5B,        //windows key
   RWIN = 0x5C         //windows key
}

[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
   public int dx;
   public int dy;
   public uint mouseData;
   public uint dwFlags;
   public uint time;
   public IntPtr dwExtraInfo;
}

[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
   public ushort wVk;
   public ushort wScan;
   /// <summary>
   /// Set flags such as KEYEVENTF_KEYUP, UNICODE, etc
   /// Flags should be combined using bitwise |
   /// </summary>
   public uint dwFlags;
   public uint time;
   public IntPtr dwExtraInfo;
}

[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
   public uint uMsg;
   public ushort wParamL;
   public ushort wParamH;
}

[StructLayout(LayoutKind.Explicit)]
public struct INPUT
{
   [FieldOffset(0)]
   public int type;
   [FieldOffset(4)] //*
   public MOUSEINPUT mi;
   [FieldOffset(4)] //*
   public KEYBDINPUT ki;
   [FieldOffset(4)] //*
   public HARDWAREINPUT hi;
}


[DllImport("user32.dll")]
public static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

public static INPUT CreateNewINPUT(ushort type)
{
   SendInputWin32.INPUT structInputKeyDown = new SendInputWin32.INPUT();
   structInputKeyDown.type = type;
   structInputKeyDown.ki.wScan = 0;
   structInputKeyDown.ki.time = 0;
   structInputKeyDown.ki.dwFlags = 0;
   structInputKeyDown.ki.dwExtraInfo = IntPtr.Zero;
   return structInputKeyDown;
}
}

Once you have the code implemented, all you need to do to send a set of characters to the keyboard is to call “SendKeys.Send(“Hello World”);”

Notes: This code was created on a 32bit machine. On a 64 bit machine, your offsets for the structures might change – do your research on this. Specifically, look at the “INPUT” structure. The offsets are set to 4bytes. This might have to be changed to 8bytes on a 64bit machine. If you test this out – let everyone know – by leaving a comment.

On a Windows Vista machine – you might have issues with the User Account Control (UAC). Generally speaking, one app should be able to speak to another app that has the same or a lower security level.

The above method that I have described, has one more advantage – it will allow you to send keys to games that are being run using DirectX (DirectInput).

The reason I needed such a method was that I was creating an application that would run in the background which would speak to a bar-code reader. Everytime a bar-code would be read, the application would log the bar-code information in a database. In addition the application would send the barcode information to other applications using SendKeys. The effect of this, was that I could open any other application (NotePad, Internet Explorer forms, etc), and everytime I scanned a bar-code, the barcode text would appear in these other applications. In essence, the application made the bar-code appear as though it was connected to the keyboard. (Coming in another post – how to speak to a barcode reader over a serial port).

Note 2: Testing the code:

here is how (using a console app):

public static void Main()

{

//run as console app and then select different apps to see the code being pasted to the window.

//eg. notepad, textboxes, etc.

do

{

SendKeys.Send("Hello World ");

System.Threading.Thread.Sleep(1000);

} while (true);

//press ctrl+break to exit

Console.ReadLine();

}

References: http://www.pinvoke.net/default.aspx/user32.SendInput

9 comments:

Zen said...

If you change the FieldOffset from 4 to 8, then it works with x64.

Jagz said...

Really good post. I want to use it to send keys to notepad window or Quake3. It is not clear how to do that. Kindly explain ?

Douglas said...
This comment has been removed by the author.
Douglas said...

I added your code to see keyboard buffer input in action but for whatever reason it does nothing.

I added...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
System.Threading.Thread.Sleep(2000);
SendKeys.Send("a");
System.Threading.Thread.Sleep(3000);
}
}
}
Am I missing something conceptually?

Raj M. Rao said...

Added code that explains how to use the code.

Douglas said...

Awesome! Thank you very much. This was the perfect example I was looking for.

eljuancho said...

I Love you man, you made my life easier just today.

Cory Mathewson said...

I've implemented your code and it is very cool!
However, I'm try to send a F2 key using your code. When I send "F2" it just shows up as text "F2" but doesnt act as a function key.

Aaron said...

I am using this code as a DLL for my VB 2012 project. It works to some extent, but I cannot get any special characters (i.e. Control key) to pass properly. I need to send a Control+V paste command to an app when the user initiates by pressing a different hothey. The function does calculation on a value then (supposed to) paste the result directly to the external app. Instead of pasting the result I get "^v". Am I using the syntax wrong?