Thursday, July 3, 2008

C# simulate mouse and keyboard events

This is achieved by calling windows API functions.
Also see
reference.
using System.Runtime.InteropServices;

public class Form1 : System.Windows.Forms.Form {
public enum VK : ushort
{
SHIFT = 0x10,
CONTROL = 0x11,
MENU = 0x12,
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,
RWIN =0x5C
}

#region Dll Imports

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

[StructLayout(LayoutKind.Sequential)]
struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}

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

[StructLayout(LayoutKind.Explicit)]
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", SetLastError=true)]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

const int INPUT_MOUSE = 0;
const int INPUT_KEYBOARD = 1;
const int INPUT_HARDWARE = 2;
const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
const uint KEYEVENTF_KEYUP = 0x0002;
const uint KEYEVENTF_UNICODE = 0x0004;
const uint KEYEVENTF_SCANCODE = 0x0008;
const uint XBUTTON1 = 0x0001;
const uint XBUTTON2 = 0x0002;
const uint MOUSEEVENTF_MOVE = 0x0001;
const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
const uint MOUSEEVENTF_LEFTUP = 0x0004;
const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
const uint MOUSEEVENTF_RIGHTUP = 0x0010;
const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
const uint MOUSEEVENTF_XDOWN = 0x0080;
const uint MOUSEEVENTF_XUP = 0x0100;
const uint MOUSEEVENTF_WHEEL = 0x0800;
const uint MOUSEEVENTF_VIRTUALDESK = 0x4000;
const uint MOUSEEVENTF_ABSOLUTE = 0x8000;


private MOUSEINPUT createMouseInput(int x, int y, uint data, uint t, uint flag) {
MOUSEINPUT mi = new MOUSEINPUT();
mi.dx = x;
mi.dy = y;
mi.mouseData = data;
mi.time = t;
//mi.dwFlags = MOUSEEVENTF_ABSOLUTE| MOUSEEVENTF_MOVE;
mi.dwFlags = flag;
return mi;
}

private KEYBDINPUT createKeybdInput(short wVK, uint flag)
{
KEYBDINPUT i = new KEYBDINPUT();
i.wVk = (ushort) wVK;
i.wScan = 0;
i.time = 0;
i.dwExtraInfo = IntPtr.Zero;
i.dwFlags = flag;
return i;
}

private short getCVal(char c) {
if (c >= 'a' && c <= 'z') return c - 'a' + 0x61;
else if (c >= '0' && c <= '9') return c - '0' + 0x30;
else if (c == '-') return 0x6D; // Note it's NOT 0x2D as in ASCII code!
else return 0; // default
}

///
/// Each time first move the upper left corner, then move from there.
/// x, y: pixel value of position.
///

private void sim_mov(int x, int y) {
INPUT[] inp = new INPUT[2];
inp[0].type = INPUT_MOUSE;
inp[0].mi = createMouseInput(0, 0, 0, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE);
inp[1].type = INPUT_MOUSE;
inp[1].mi = createMouseInput(x, y, 0, 0, MOUSEEVENTF_MOVE);
SendInput((uint)inp.Length, inp, Marshal.SizeOf(inp[0].GetType()));
}

private void sim_click()
{
INPUT[] inp = new INPUT[2];
inp[0].type = INPUT_MOUSE;
inp[0].mi = createMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTDOWN);
inp[1].type = INPUT_MOUSE;
inp[1].mi = createMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTUP);
SendInput((uint)inp.Length, inp, Marshal.SizeOf(inp[0].GetType()));
}

private void sim_type(string txt)
{
int i, len;
char[] c_array;
short c;
INPUT[] inp;

if (txt == null || txt.Length == 0) return;

c_array = txt.ToCharArray();
len = c_array.Length;
inp = new INPUT[2];

for (i = 0; i < len; i ++)
{
c = getCVal(txt[i]);

inp[0].type = INPUT_KEYBOARD;
inp[0].ki = createKeybdInput(c, 0);
inp[1].type = INPUT_KEYBOARD;
inp[1].ki = createKeybdInput(c, KEYEVENTF_KEYUP);

SendInput((uint)inp.Length, inp, Marshal.SizeOf(inp[0].GetType()));
}
}

private void sim_actions() {
sim_mov(x0, y0);
sim_click();
}

public Form1() {
InitializeComponent();
//sim_actions();
}

[STAThread]
static void Main() {
Application.Run(new Form1());
}
}

4 comments:

Created by: X said...

OK The fallowing C# code works just fine!

// Transmit key -> PressKeyDown('2'); <--- transmitting key 2 works and apps recieve it just fine. keys 2->0 also work fine

public static void PressKeyDown(ushort scanCode)
{
INPUT[] inputs = new INPUT[1];
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki = createKeybdInput(scanCode, 0);
uint intReturn = SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));
if (intReturn != 1)
{
throw new Exception("Could not send key: " + scanCode);
}
}
public static KEYBDINPUT createKeybdInput(ushort wVK, uint flag)
{
KEYBDINPUT i = new KEYBDINPUT();
i.wVk = wVK;
i.wScan = 0;
i.time = 0;
i.dwExtraInfo = IntPtr.Zero;
i.dwFlags = flag;
return i;
}

... here is the problem I can send a key stroke to an app, BUT sending key "1" (not the numeric keypad but the regular numbers) ala PressKeyDown('1') does not work as expected instead SendInput returns that indeed 1 key was pressed, BUT the application is receiving the following 5 key down messages !!!!

Ascii codes of recieved chars
------------------
49
55
57
48
109

characters recieved
-----------
1790m

I can't for the life of me figure out why this is. Can you make a suggestion what could be causing this? I am using Windows vista 32Bit, English US KB layout on a HP laptop nx9420

Created by: X said...

Sigh :( There is nothing wrong with the send input code. grrrrr it was with another part of my application that was feeding redundant data.

I am trying to create a app that will allow me to play world of warcraft using my 360 controller. (spamming keys for 3 hrs in naxx can't be good for carpral tunnel :P) And am using a library that i wrote that loads in key bindings from a xml file for the 360 game pad and gives them friendly names. For example "Jump" -> maps to -> "ButtonA" etc

in that bindings file I had redundant entries for the ButtonA on the game pad that is why i was getting the additional keydown events firing. So nothing to do with the sendinput code at all. /facepalm /smashfaceondesk

Tom said...

So the problem is now cleared? I'm glad to know. Sorry I didn't check comments as I didn't expect people would comment. BTW, sounds cool to apply this on warcraft.

Proton said...

I would like to Point out a small mistake. Though the program works perfectly well. The VK Codes for 'a' - 'z' are from 0x41 - 0x5a so in the getCVal function, you should be adding 0x41 instead of 0x61. Otherwise the program is perfect :D Thanks a lot helped me so so so much!!!

Blog Archive

Followers