Skip to content

Instantly share code, notes, and snippets.

@DrustZ
Created May 15, 2019 23:44
Show Gist options
  • Save DrustZ/640912b9d5cb745a3a56971c9bd58ac7 to your computer and use it in GitHub Desktop.
Save DrustZ/640912b9d5cb745a3a56971c9bd58ac7 to your computer and use it in GitHub Desktop.
Simulate keyboard and mouse input in WPF (similar to sendkeys in winform)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Windows.Input;
namespace Demo
{
/// <summary>
/// Native methods
/// </summary>
internal static class NativeMethods
{
//User32 wrappers cover API's used for Mouse input
#region User32
// Two special bitmasks we define to be able to grab
// shift and character information out of a VKey.
internal const int VKeyShiftMask = 0x0100;
internal const int VKeyCharMask = 0x00FF;
// Various Win32 constants
internal const int KeyeventfExtendedkey = 0x0001;
internal const int KeyeventfKeyup = 0x0002;
internal const int KeyeventfScancode = 0x0008;
internal const int MouseeventfVirtualdesk = 0x4000;
internal const int SMXvirtualscreen = 76;
internal const int SMYvirtualscreen = 77;
internal const int SMCxvirtualscreen = 78;
internal const int SMCyvirtualscreen = 79;
internal const int XButton1 = 0x0001;
internal const int XButton2 = 0x0002;
internal const int WheelDelta = 120;
internal const int InputMouse = 0;
internal const int InputKeyboard = 1;
// Various Win32 data structures
[StructLayout(LayoutKind.Sequential)]
internal struct INPUT
{
internal int type;
internal INPUTUNION union;
};
[StructLayout(LayoutKind.Explicit)]
internal struct INPUTUNION
{
[FieldOffset(0)]
internal MOUSEINPUT mouseInput;
[FieldOffset(0)]
internal KEYBDINPUT keyboardInput;
};
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[Flags]
internal enum SendMouseInputFlags
{
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800,
Absolute = 0x8000,
};
// Importing various Win32 APIs that we need for input
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
internal static extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int MapVirtualKey(int nVirtKey, int nMapType);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int SendInput(int nInputs, ref INPUT mi, int cbSize);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern short VkKeyScan(char ch);
#endregion
}
/// <summary>
/// Exposes a simple interface to common mouse operations, allowing the user to simulate mouse input.
/// </summary>
/// <example>The following code moves to screen coordinate 100,100 and left clicks.
/// <code>
/**
Mouse.MoveTo(new Point(100, 100));
Mouse.Click(MouseButton.Left);
*/
/// </code>
/// </example>
public static class Mouse
{
/// <summary>
/// Clicks a mouse button.
/// </summary>
/// <param name="mouseButton">The mouse button to click.</param>
public static void Click(MouseButton mouseButton)
{
Down(mouseButton);
Up(mouseButton);
}
/// <summary>
/// Double-clicks a mouse button.
/// </summary>
/// <param name="mouseButton">The mouse button to click.</param>
public static void DoubleClick(MouseButton mouseButton)
{
Click(mouseButton);
Click(mouseButton);
}
/// <summary>
/// Performs a mouse-down operation for a specified mouse button.
/// </summary>
/// <param name="mouseButton">The mouse button to use.</param>
public static void Down(MouseButton mouseButton)
{
switch (mouseButton)
{
case MouseButton.Left:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.LeftDown);
break;
case MouseButton.Right:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.RightDown);
break;
case MouseButton.Middle:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.MiddleDown);
break;
case MouseButton.XButton1:
SendMouseInput(0, 0, NativeMethods.XButton1, NativeMethods.SendMouseInputFlags.XDown);
break;
case MouseButton.XButton2:
SendMouseInput(0, 0, NativeMethods.XButton2, NativeMethods.SendMouseInputFlags.XDown);
break;
default:
throw new InvalidOperationException("Unsupported MouseButton input.");
}
}
/// <summary>
/// Moves the mouse pointer to the specified screen coordinates.
/// </summary>
/// <param name="point">The screen coordinates to move to.</param>
public static void MoveTo(Point point)
{
SendMouseInput(point.X, point.Y, 0, NativeMethods.SendMouseInputFlags.Move | NativeMethods.SendMouseInputFlags.Absolute);
}
/// <summary>
/// Resets the system mouse to a clean state.
/// </summary>
public static void Reset()
{
MoveTo(new Point(0, 0));
if (System.Windows.Input.Mouse.LeftButton == MouseButtonState.Pressed)
{
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.LeftUp);
}
if (System.Windows.Input.Mouse.MiddleButton == MouseButtonState.Pressed)
{
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.MiddleUp);
}
if (System.Windows.Input.Mouse.RightButton == MouseButtonState.Pressed)
{
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.RightUp);
}
if (System.Windows.Input.Mouse.XButton1 == MouseButtonState.Pressed)
{
SendMouseInput(0, 0, NativeMethods.XButton1, NativeMethods.SendMouseInputFlags.XUp);
}
if (System.Windows.Input.Mouse.XButton2 == MouseButtonState.Pressed)
{
SendMouseInput(0, 0, NativeMethods.XButton2, NativeMethods.SendMouseInputFlags.XUp);
}
}
/// <summary>
/// Simulates scrolling of the mouse wheel up or down.
/// </summary>
/// <param name="lines">The number of lines to scroll. Use positive numbers to scroll up and negative numbers to scroll down.</param>
public static void Scroll(double lines)
{
int amount = (int)(NativeMethods.WheelDelta * lines);
SendMouseInput(0, 0, amount, NativeMethods.SendMouseInputFlags.Wheel);
}
/// <summary>
/// Performs a mouse-up operation for a specified mouse button.
/// </summary>
/// <param name="mouseButton">The mouse button to use.</param>
public static void Up(MouseButton mouseButton)
{
switch (mouseButton)
{
case MouseButton.Left:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.LeftUp);
break;
case MouseButton.Right:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.RightUp);
break;
case MouseButton.Middle:
SendMouseInput(0, 0, 0, NativeMethods.SendMouseInputFlags.MiddleUp);
break;
case MouseButton.XButton1:
SendMouseInput(0, 0, NativeMethods.XButton1, NativeMethods.SendMouseInputFlags.XUp);
break;
case MouseButton.XButton2:
SendMouseInput(0, 0, NativeMethods.XButton2, NativeMethods.SendMouseInputFlags.XUp);
break;
default:
throw new InvalidOperationException("Unsupported MouseButton input.");
}
}
/// <summary>
/// Sends mouse input.
/// </summary>
/// <param name="x">x coordinate</param>
/// <param name="y">y coordinate</param>
/// <param name="data">scroll wheel amount</param>
/// <param name="flags">SendMouseInputFlags flags</param>
[PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
private static void SendMouseInput(int x, int y, int data, NativeMethods.SendMouseInputFlags flags)
{
PermissionSet permissions = new PermissionSet(PermissionState.Unrestricted);
permissions.Demand();
int intflags = (int)flags;
if ((intflags & (int)NativeMethods.SendMouseInputFlags.Absolute) != 0)
{
// Absolute position requires normalized coordinates.
NormalizeCoordinates(ref x, ref y);
intflags |= NativeMethods.MouseeventfVirtualdesk;
}
NativeMethods.INPUT mi = new NativeMethods.INPUT();
mi.type = NativeMethods.InputMouse;
mi.union.mouseInput.dx = x;
mi.union.mouseInput.dy = y;
mi.union.mouseInput.mouseData = data;
mi.union.mouseInput.dwFlags = intflags;
mi.union.mouseInput.time = 0;
mi.union.mouseInput.dwExtraInfo = new IntPtr(0);
if (NativeMethods.SendInput(1, ref mi, Marshal.SizeOf(mi)) == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
private static void NormalizeCoordinates(ref int x, ref int y)
{
int vScreenWidth = NativeMethods.GetSystemMetrics(NativeMethods.SMCxvirtualscreen);
int vScreenHeight = NativeMethods.GetSystemMetrics(NativeMethods.SMCyvirtualscreen);
int vScreenLeft = NativeMethods.GetSystemMetrics(NativeMethods.SMXvirtualscreen);
int vScreenTop = NativeMethods.GetSystemMetrics(NativeMethods.SMYvirtualscreen);
// Absolute input requires that input is in 'normalized' coords - with the entire
// desktop being (0,0)...(65536,65536). Need to convert input x,y coords to this
// first.
//
// In this normalized world, any pixel on the screen corresponds to a block of values
// of normalized coords - eg. on a 1024x768 screen,
// y pixel 0 corresponds to range 0 to 85.333,
// y pixel 1 corresponds to range 85.333 to 170.666,
// y pixel 2 correpsonds to range 170.666 to 256 - and so on.
// Doing basic scaling math - (x-top)*65536/Width - gets us the start of the range.
// However, because int math is used, this can end up being rounded into the wrong
// pixel. For example, if we wanted pixel 1, we'd get 85.333, but that comes out as
// 85 as an int, which falls into pixel 0's range - and that's where the pointer goes.
// To avoid this, we add on half-a-"screen pixel"'s worth of normalized coords - to
// push us into the middle of any given pixel's range - that's the 65536/(Width*2)
// part of the formula. So now pixel 1 maps to 85+42 = 127 - which is comfortably
// in the middle of that pixel's block.
// The key ting here is that unlike points in coordinate geometry, pixels take up
// space, so are often better treated like rectangles - and if you want to target
// a particular pixel, target its rectangle's midpoint, not its edge.
x = ((x - vScreenLeft) * 65536) / vScreenWidth + 65536 / (vScreenWidth * 2);
y = ((y - vScreenTop) * 65536) / vScreenHeight + 65536 / (vScreenHeight * 2);
}
}
/// <summary>
/// Exposes a simple interface to common keyboard operations, allowing the user to simulate keyboard input.
/// </summary>
/// <example>
/// The following code types "Hello world" with the specified casing,
/// and then types "hello, capitalized world" which will be in all caps because
/// the left shift key is being held down.
/// <code>
/**
Keyboard.Type("Hello world");
Keyboard.Press(Key.LeftShift);
Keyboard.Type("hello, capitalized world");
Keyboard.Release(Key.LeftShift);
*/
/// </code>
/// </example>
public static class Keyboard
{
#region Public Members
/// <summary>
/// Presses down a key.
/// </summary>
/// <param name="key">The key to press.</param>
public static void Press(Key key)
{
SendKeyboardInput(key, true);
}
/// <summary>
/// Releases a key.
/// </summary>
/// <param name="key">The key to release.</param>
public static void Release(Key key)
{
SendKeyboardInput(key, false);
}
/// <summary>
/// Resets the system keyboard to a clean state.
/// </summary>
public static void Reset()
{
foreach (Key key in Enum.GetValues(typeof(Key)))
{
if (key != Key.None && (System.Windows.Input.Keyboard.GetKeyStates(key) & KeyStates.Down) > 0)
{
Release(key);
}
}
}
/// <summary>
/// Performs a press-and-release operation for the specified key, which is effectively equivallent to typing.
/// </summary>
/// <param name="key">The key to press.</param>
public static void Type(Key key)
{
Press(key);
Release(key);
}
/// <summary>
/// Types the specified text.
/// </summary>
/// <param name="text">The text to type.</param>
public static void Type(string text)
{
foreach (char c in text)
{
// We get the vKey value for the character via a Win32 API. We then use bit masks to pull the
// upper and lower bytes to get the shift state and key information. We then use WPF KeyInterop
// to go from the vKey key info into a System.Windows.Input.Key data structure. This work is
// necessary because Key doesn't distinguish between upper and lower case, so we have to wrap
// the key type inside a shift press/release if necessary.
int vKeyValue = NativeMethods.VkKeyScan(c);
bool keyIsShifted = (vKeyValue & NativeMethods.VKeyShiftMask) == NativeMethods.VKeyShiftMask;
Key key = KeyInterop.KeyFromVirtualKey(vKeyValue & NativeMethods.VKeyCharMask);
if (keyIsShifted)
{
Type(key, new Key[] { Key.LeftShift });
}
else
{
Type(key);
}
}
}
#endregion
#region Private Members
/// <summary>
/// Types a key while a set of modifier keys are being pressed. Modifer keys
/// are pressed in the order specified and released in reverse order.
/// </summary>
/// <param name="key">Key to type.</param>
/// <param name="modifierKeys">Set of keys to hold down with key is typed.</param>
private static void Type(Key key, Key[] modifierKeys)
{
foreach (Key modiferKey in modifierKeys)
{
Press(modiferKey);
}
Type(key);
foreach (Key modifierKey in modifierKeys.Reverse())
{
Release(modifierKey);
}
}
/// <summary>
/// Injects keyboard input into the system.
/// </summary>
/// <param name="key">Indicates the key pressed or released. Can be one of the constants defined in the Key enum.</param>
/// <param name="press">True to inject a key press, false to inject a key release.</param>
[PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
private static void SendKeyboardInput(Key key, bool press)
{
PermissionSet permissions = new PermissionSet(PermissionState.Unrestricted);
permissions.Demand();
NativeMethods.INPUT ki = new NativeMethods.INPUT();
ki.type = NativeMethods.InputKeyboard;
ki.union.keyboardInput.wVk = (short)KeyInterop.VirtualKeyFromKey(key);
ki.union.keyboardInput.wScan = (short)NativeMethods.MapVirtualKey(ki.union.keyboardInput.wVk, 0);
int dwFlags = 0;
if (ki.union.keyboardInput.wScan > 0)
{
dwFlags |= NativeMethods.KeyeventfScancode;
}
if (!press)
{
dwFlags |= NativeMethods.KeyeventfKeyup;
}
ki.union.keyboardInput.dwFlags = dwFlags;
if (ExtendedKeys.Contains(key))
{
ki.union.keyboardInput.dwFlags |= NativeMethods.KeyeventfExtendedkey;
}
ki.union.keyboardInput.time = 0;
ki.union.keyboardInput.dwExtraInfo = new IntPtr(0);
if (NativeMethods.SendInput(1, ref ki, Marshal.SizeOf(ki)) == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
// From the SDK:
// The extended-key flag indicates whether the keystroke message originated from one of
// the additional keys on the enhanced keyboard. The extended keys consist of the ALT and
// CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP,
// PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK
// key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in
// the numeric keypad. The extended-key flag is set if the key is an extended key.
//
// - docs appear to be incorrect. Use of Spy++ indicates that break is not an extended key.
// Also, menu key and windows keys also appear to be extended.
private static readonly Key[] ExtendedKeys = new Key[] {
Key.RightAlt,
Key.RightCtrl,
Key.NumLock,
Key.Insert,
Key.Delete,
Key.Home,
Key.End,
Key.Prior,
Key.Next,
Key.Up,
Key.Down,
Key.Left,
Key.Right,
Key.Apps,
Key.RWin,
Key.LWin };
// Note that there are no distinct values for the following keys:
// numpad divide
// numpad enter
#endregion
}
}
@Amnetik
Copy link

Amnetik commented Sep 12, 2022

Works well, thanks for sharing.

@Cyp9715
Copy link

Cyp9715 commented Apr 18, 2023

Thank you so much for providing the code :)

@obviliontsk
Copy link

obviliontsk commented May 9, 2023

Thx for the code.
Updated it in my fork for .Net 7, extended, added methods to send char 'directly' with UNICODE flag.

@sfalkorr
Copy link

Good health and long life to you, dude, you helped me out.

@Rankors
Copy link

Rankors commented Jan 25, 2024

Hello everyone, this code can solve my problem, I have a program that puts webview2 on my desktop wallpaper, but it doesn't respond to the mouse?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment