-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSimulation.cs
More file actions
525 lines (464 loc) · 18.2 KB
/
Copy pathSimulation.cs
File metadata and controls
525 lines (464 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
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;
///
///
/// The code yoinked from: https://gist.github.com/DrustZ/640912b9d5cb745a3a56971c9bd58ac7
///
/// All credit goes to: DrustZ, https://github.com/DrustZ
///
///
namespace Simulation
{
/// <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
}
}