-
-
Save obviliontsk/90403a0fea8c24258570f3a577704864 to your computer and use it in GitHub Desktop.
Simulate keyboard and mouse input in WPF (similar to sendkeys in winform)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.ComponentModel; | |
using System.Drawing; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
using System.Windows.Input; | |
namespace RoslynScriptContext; | |
/// <summary> | |
/// Native methods | |
/// </summary> | |
public static partial 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 KeyeventfUnicode = 0x0004; | |
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; | |
}; | |
[StructLayout(LayoutKind.Sequential)] | |
internal struct POINT | |
{ | |
public readonly int X; | |
public readonly int Y; | |
} | |
[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 | |
[LibraryImport("user32.dll")] | |
internal static partial int GetSystemMetrics(int nIndex); | |
// DllImport CharSet = CharSet.Auto -> Unicode / Utf.16 for windows, so 'W' | |
[LibraryImport("user32.dll", EntryPoint = "MapVirtualKeyW")] | |
internal static partial int MapVirtualKey(int nVirtKey, int nMapType); | |
[LibraryImport("user32.dll", SetLastError = true)] | |
internal static partial int SendInput(int nInputs, ref INPUT mi, int cbSize); | |
[LibraryImport("user32.dll", EntryPoint = "BlockInput")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static partial bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt); | |
[LibraryImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
internal static partial bool GetCursorPos(out POINT lpPoint); | |
#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 at specific coordinates. | |
/// </summary> | |
/// <param name="x">X coordinate</param> | |
/// <param name="y">Y coordinate</param> | |
/// <param name="mouseButton">The mouse button to click.</param> | |
public static void ClickAt(int x, int y, MouseButton mouseButton) | |
{ | |
NativeMethods.GetCursorPos(out var startPoint); | |
MoveTo(new Point(x, y)); | |
Click(mouseButton); | |
// return to start position | |
MoveTo(new Point(startPoint.X, startPoint.Y)); | |
} | |
/// <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> | |
/// Clicks a mouse button N times. | |
/// </summary> | |
/// <param name="mouseButton">The mouse button to click.</param> | |
/// <param name="clicksAmount">Clicks amount, default = 1</param> | |
/// <param name="clickDelayMs">Delay between clicks in milliseconds, default = 0</param> | |
/// <exception cref="ArgumentOutOfRangeException">clicksAmount less than 1 or clickDelayMs less than 0</exception> | |
public static void NClick(MouseButton mouseButton, int clicksAmount = 1, int clickDelayMs = 0) | |
{ | |
if (clicksAmount < 1) throw new ArgumentOutOfRangeException(nameof(clicksAmount), clicksAmount, "Less than one"); | |
if (clickDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(clickDelayMs), clickDelayMs, "Less than zero"); | |
for (int i = 0; i < clicksAmount; i++) | |
{ | |
Click(mouseButton); | |
if (clickDelayMs > 0) Thread.Sleep(clickDelayMs); | |
} | |
} | |
/// <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> | |
/// Moves the mouse pointer to the specified screen coordinates. | |
/// </summary> | |
/// <param name="x">X coordinate</param> | |
/// <param name="y">Y coordinate</param> | |
public static void MoveTo(int x, int y) | |
{ | |
Point toPoint = new Point(x, y); | |
MoveTo(toPoint); | |
} | |
/// <summary> | |
/// Resets the system mouse to a clean state. | |
/// </summary> | |
public static void Reset() | |
{ | |
// Maybe better to reset cursor position explicitly, only when needed? | |
//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> | |
/// Resets the mouse position to 0,0. | |
/// </summary> | |
public static void ResetPosition() => MoveTo(0, 0); | |
/// <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> | |
private static void SendMouseInput(int x, int y, int data, NativeMethods.SendMouseInputFlags flags) | |
{ | |
int intFlags = (int)flags; | |
if ((intFlags & (int)NativeMethods.SendMouseInputFlags.Absolute) != 0) | |
{ | |
// Absolute position requires normalized coordinates. | |
NormalizeCoordinates(ref x, ref y); | |
intFlags |= NativeMethods.MouseeventfVirtualdesk; | |
} | |
var mi = new NativeMethods.INPUT | |
{ | |
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 corresponds 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. | |
/// To send input with hotkeys use BlockInput method (app will need admin rights) to block all users inputs | |
/// and reset inputs state, so they won't interfere | |
/// <code> | |
/** | |
NativeMethods.BlockInput(true); | |
Keyboard.Reset(); | |
Keyboard.Type("Hello world"); | |
Keyboard.Press(Key.LeftShift); | |
Keyboard.Type("hello, capitalized world"); | |
Keyboard.Release(Key.LeftShift); | |
NativeMethods.BlockInput(false); | |
*/ | |
/// </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> | |
/// Presses down a key. | |
/// </summary> | |
/// <param name="input">The key to press.</param> | |
public static void Press(char input) | |
{ | |
SendKeyboardInput(input, true); | |
} | |
/// <summary> | |
/// Releases a key. | |
/// </summary> | |
/// <param name="input">The key to release.</param> | |
public static void Release(char input) | |
{ | |
SendKeyboardInput(input, 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 equivalent to typing. | |
/// </summary> | |
/// <param name="key">The key to press.</param> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0</exception> | |
public static void Type(Key key, int releaseDelayMs = 0) | |
{ | |
if (releaseDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(releaseDelayMs), releaseDelayMs, "Less than zero"); | |
Press(key); | |
if (releaseDelayMs > 0) | |
Thread.Sleep(releaseDelayMs); | |
Release(key); | |
} | |
/// <summary> | |
/// Performs a press-and-release operation for the specified key specific amount of times. | |
/// </summary> | |
/// <param name="key">The key to press.</param> | |
/// <param name="amountToType"></param> | |
/// <param name="inputDelayMs">Delay after releasing key</param> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or amountToType less than 1 or inputDelayMs less than 0</exception> | |
public static void TypeKeyNTimes(Key key, int amountToType = 1, int inputDelayMs = 0, int releaseDelayMs = 0) | |
{ | |
if (amountToType < 1) throw new ArgumentOutOfRangeException(nameof(amountToType), amountToType, "Less than one"); | |
if (inputDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(inputDelayMs), releaseDelayMs, "Less than zero"); | |
for (int i = 0; i < amountToType; i++) | |
{ | |
Type(key, releaseDelayMs); | |
if (inputDelayMs > 0) | |
Thread.Sleep(releaseDelayMs); | |
} | |
} | |
/// <summary> | |
/// Types the specified text. | |
/// </summary> | |
/// <param name="text">The text to type.</param> | |
/// <param name="inputDelayMs">Delay between typing each key</param> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or inputDelayMs less than 0</exception> | |
public static void Type(string text, int inputDelayMs = 0, int releaseDelayMs = 0) | |
{ | |
int delay = 0; | |
if (text.Length > 1) | |
delay = inputDelayMs; | |
foreach (char c in text) | |
{ | |
Type(c, releaseDelayMs); | |
if (delay > 0) Thread.Sleep(delay); | |
} | |
} | |
/// <summary> | |
/// Types the specified char. | |
/// </summary> | |
/// <param name="input">The char to type.</param> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
/// <exception cref="ArgumentOutOfRangeException">releaseDelayMs less than 0 or inputDelayMs less than 0</exception> | |
public static void Type(char input, int releaseDelayMs = 0) | |
{ | |
if (releaseDelayMs < 0) throw new ArgumentOutOfRangeException(nameof(releaseDelayMs), releaseDelayMs, "Less than zero"); | |
Press(input); | |
if (releaseDelayMs > 0) Thread.Sleep(releaseDelayMs); | |
Release(input); | |
} | |
/// <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> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
public static void Type(Key key, Key[] modifierKeys, int releaseDelayMs = 0) | |
{ | |
foreach (Key modifierKey in modifierKeys) | |
{ | |
if (modifierKey == Key.None) | |
continue; | |
Press(modifierKey); | |
} | |
Type(key, releaseDelayMs); | |
foreach (Key modifierKey in modifierKeys.Reverse()) | |
{ | |
if (modifierKey == Key.None) | |
continue; | |
Release(modifierKey); | |
} | |
} | |
/// <summary> | |
/// Types a key while a set of modifier keys are being pressed. Modifer keys | |
/// are pressed in the order Ctrl->Shift->Alt->Win. | |
/// </summary> | |
/// <param name="key">Key to type.</param> | |
/// <param name="modifierKeys">Set of ModifierKeys enum flags to hold down with key is typed. </param> | |
/// <param name="releaseDelayMs">Delay before key release</param> | |
public static void Type(Key key, ModifierKeys modifierKeys, int releaseDelayMs = 0) | |
{ | |
var modifierKeysArray = ModifierKeysToArrayConverter(modifierKeys); | |
Type(key, modifierKeysArray, releaseDelayMs); | |
} | |
#endregion | |
#region Private Members | |
/// <summary> | |
/// Helper method to convert ModifierKeys enum flags to Key array | |
/// </summary> | |
/// <param name="modifierKeys">ModifierKeys enum flags</param> | |
private static Key[] ModifierKeysToArrayConverter(ModifierKeys modifierKeys) | |
{ | |
Key[] keys = new Key[4]; | |
if (modifierKeys.HasFlag(ModifierKeys.Control)) | |
keys.SetValue(Key.LeftCtrl, 0); | |
if (modifierKeys.HasFlag(ModifierKeys.Shift)) | |
keys.SetValue(Key.LeftShift, 1); | |
if (modifierKeys.HasFlag(ModifierKeys.Alt)) | |
keys.SetValue(Key.LeftAlt, 2); | |
if (modifierKeys.HasFlag(ModifierKeys.Windows)) | |
keys.SetValue(Key.LWin, 3); | |
return keys; | |
} | |
/// <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> | |
private static void SendKeyboardInput(Key key, bool press) | |
{ | |
NativeMethods.INPUT ki = new NativeMethods.INPUT | |
{ | |
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()); | |
} | |
} | |
/// <summary> | |
/// Injects keyboard input into the system. | |
/// </summary> | |
/// <param name="input">Indicates the character pressed or released..</param> | |
/// <param name="press">True to inject a key press, false to inject a key release.</param> | |
private static void SendKeyboardInput(char input, bool press) | |
{ | |
NativeMethods.INPUT ki = new NativeMethods.INPUT | |
{ | |
type = NativeMethods.InputKeyboard | |
}; | |
ki.union.keyboardInput.wVk = 0; | |
ki.union.keyboardInput.wScan = (short) input; | |
int dwFlags = 0; | |
dwFlags |= NativeMethods.KeyeventfUnicode; | |
if (!press) | |
{ | |
dwFlags |= NativeMethods.KeyeventfKeyup; | |
} | |
ki.union.keyboardInput.dwFlags = dwFlags; | |
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 = { | |
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 | |
} |
Send unicode char input implemented, added methods:
Type(char input, int releaseDelayMs = 0)
Press(char input)
Release(char input)
SendKeyboardInput(char input, bool press)
Changed Type(string text...) method, now don't need to Press shift key. Also now can type non-Key characters.
VkKeyScan method removed.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
NativeMethods.BlockInput now public
Mouse:
Added method MoveTo(int x, int y)