Wednesday, September 10, 2008

Win32 SendInput API With C#

In a recent project, I came across the task of simulating user input using C#. There are several way of doing what I was attempting, but nothing seemed reliable when interacting with another thread. Enter: my old friend SendInput, part of the user32 library. I searched high and low for a wrapper class that would save me the several hours of implementation and testing I saw rising in my future, but to no avail! You mean to tell me no one has been kind enough to write a wrapper and share it with the "special needs" programmers of the world?

*sigh* alright, well here it is.

I wrote my wrapper around the idea of a virtual keyboard and mouse. Though it's by no means complete and comprehensive, it does work. If there is obvious interest in it, I'll spend a day making it all pretty and robust. Let me know. ;).

File: VirtualInput.cs
File: VirtualInputExample.cs

Below is the source for VirtualInputExample.cs to illustrate how much more fun it is to work with a wrapper class instead of the unmanaged SendInput directly.

Please note that I've left out the include statements here, in the example .cs file the includes are actually there. Just note if you are copy/pasting this, you need to include System.Diagnostics and VirtualInput. Also, you will need a reference to System.Drawing in your project in order to use VirtualInput.cs (datatype Point).

// create a virtual mouse and keyboard
VirtualKeyboard vk = new VirtualKeyboard();
VirtualMouse vm = new VirtualMouse();

// find the Notepad process(s)
Process[] pNotepad = Process.GetProcessesByName("notepad");

// no notepads?
if (pNotepad.Length <= 0)
throw new Exception("Could not find Notepad.exe");

// grab window handle off of the notepad process
IntPtr hNotepad = pNotepad[0].MainWindowHandle;

Console.WriteLine("Press enter to test.");
Console.Read();

// click the notepad, bring it to front, you can also do this by:
// VirtualInput.UnmanagedCode.SetForegroundWindow(hWnd)
vm.ClickClient(hNotepad, VirtualMouse.MOUSE_CLICK_TYPE.LBUTTON);

/* take note, we send in uppercase characters, but they get typed as lowercase
* because all VirtualKeys (VKs) are lowercase! */
vk.Keystroke("THIS IS ALL LOWERCASE!");
vk.Keystroke(Constants.VK.RETURN);

/* note, this is how you would type in uppercase ... makes sense no?
* as an aside: one might extend the VirtualInput class to have a method
* UppercaseKeystroke that would send Shift down, keystroke, shift up,
* as I do here */
vk.KeyDown(Constants.VK.SHIFT);
vk.Keystroke("this is all uppercase!");
vk.KeyUp(Constants.VK.SHIFT);

/* think outside of the box, and you can really do quite a bit combining these
* objects. here I'll right-click the client bringing up the context menu, then
* press Down 5 times to end up highlighting the "Select All" menu item before
* pressing enter. This is like pressing CNTL-A */
vm.ClickClient(hNotepad, VirtualMouse.MOUSE_CLICK_TYPE.RBUTTON);
for (int i = 0; i<6; i++)
vk.Keystroke(Constants.VK.DOWN);
vk.Keystroke(Constants.VK.RETURN);

/* speaking of hotkeys (CNTL-A), how about an example of that?
* here I'll send CNTL-X which is Cut. Try pasting (CNTL-V) after the cut is done
* if you want to confirm everything was typed, selected, then cut */
vk.KeyDown(Constants.VK.CONTROL);
vk.Keystroke('x');
vk.KeyUp(Constants.VK.CONTROL);

3 comments:

Shane said...

I've been struggling getting the shift key to work on some code I've put together. I'd like to download your code, but its not available anymore. Can you resubmit it there?

Unknown said...

I've released a similar project to Codeplex called Windows Input Simulator which is included in several projects now. It would be neat if you're interested in reviewing the code and/or contributing.

Unknown said...

Hey Mike, I have been checking out thecode in codeplex and was wondering if you use the same way in that code as you do here to target the destination app. I think you are using clickwindow? I have not been able to identify how you are doing it in the virtual keyboard and when I use the InputSimulator code in my own project, I am unable to taget an external app without losing focus.

Any hints?

k.