18using System.Collections.Generic;
21using System.Runtime.InteropServices;
23using System.Threading;
24using System.Threading.Tasks;
25using System.Windows.Input;
34 internal class HardwareHelper
38 static byte scanCode = 0;
41 private static readonly Random _random =
new Random();
50 internal static int GetTypingDelay(
int ceilingMs)
55 int floorMs = Math.Min(25, ceilingMs);
56 return _random.Next(floorMs, ceilingMs + 1);
65 [DllImport(
"user32.dll")]
66 public static extern short VkKeyScan(
char ch);
76 [DllImport(
"user32.dll")]
77 public static extern void keybd_event(
byte bVk,
byte bScan, uint dwFlags, uint dwExtraInfo);
86 [DllImport(
"user32.dll", EntryPoint =
"SetCursorPos")]
87 [
return: MarshalAs(UnmanagedType.Bool)]
88 private static extern bool SetCursorPos(
int x,
int y);
95 [DllImport(
"user32.dll")]
96 public static extern bool GetCursorPos(out Point p);
102 [DllImport(
"user32.dll")]
103 private static extern uint GetDoubleClickTime();
108 [DllImport(
"user32.dll")]
109 private static extern short GetAsyncKeyState(
int vKey);
111 [DllImport(
"user32.dll")]
112 private static extern int GetSystemMetrics(
int index);
114 private const int SM_CXDOUBLECLK = 36;
115 private const int SM_CYDOUBLECLK = 37;
116 private const int SM_CXDRAG = 68;
117 private const int SM_CYDRAG = 69;
121 private static DateTime _lastPress = DateTime.MinValue;
122 private static Point _lastPressPoint =
new Point(
int.MinValue,
int.MinValue);
130 private static void RecordPress(
int x,
int y)
132 _lastPress = DateTime.UtcNow;
133 _lastPressPoint =
new Point(x, y);
148 private static void SeparateFromLastPress(
int x,
int y)
150 if (
int.MinValue != _lastPressPoint.X)
152 int window = (int)GetDoubleClickTime();
153 int elapsed = (int)(DateTime.UtcNow - _lastPress).TotalMilliseconds;
155 bool nearby = Math.Abs(x - _lastPressPoint.X) <= GetSystemMetrics(SM_CXDOUBLECLK) / 2
156 && Math.Abs(y - _lastPressPoint.Y) <= GetSystemMetrics(SM_CYDOUBLECLK) / 2;
158 if (
true == nearby && elapsed < window)
160 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Holding [{window - elapsed}]ms so this press is not read as a double click with the one before it.",
null, GPALObjectType.None);
161 Thread.Sleep(window - elapsed + 20);
175 [DllImport(
"user32.dll")]
176 static extern void mouse_event(
int dwFlags,
int dx,
int dy,
177 int dwData,
int dwExtraInfo);
183 public enum MouseEventFlags
185 LEFTDOWN = 0x00000002,
187 MIDDLEDOWN = 0x00000020,
188 MIDDLEUP = 0x00000040,
190 ABSOLUTE = 0x00008000,
191 RIGHTDOWN = 0x00000008,
211 internal static void ReleaseStrayModifiers(ModifierKeys wanted)
213 foreach (var held
in new[]
215 (Key: ModifierKeys.Control, Left: GPAL.VK_CONTROL_LEFT, Right: GPAL.VK_CONTROL_RIGHT),
216 (Key: ModifierKeys.Shift, Left: GPAL.VK_SHIFT_LEFT, Right: GPAL.VK_SHIFT_RIGHT),
217 (Key: ModifierKeys.Alt, Left: GPAL.VK_LMENU, Right: GPAL.VK_RMENU),
220 if (0 != (held.Key & wanted))
223 if (0 != (GetAsyncKeyState(held.Left) & 0x8000) || 0 != (GetAsyncKeyState(held.Right) & 0x8000))
225 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{held.Key}] was still held and nothing asked for it, so it is being released before this action.",
null, GPALObjectType.None);
227 ReleaseModifierKeys(held.Key,
true);
245 internal static void DragTravel(
int toX,
int toY,
bool simulate)
247 if (
true == simulate)
248 MoveMouse(toX, toY, 4, 4, 0.5);
251 GetCursorPos(out Point from);
253 int stepX = from.X + Math.Sign(toX - from.X) * (GetSystemMetrics(SM_CXDRAG) + 2);
254 int stepY = from.Y + Math.Sign(toY - from.Y) * (GetSystemMetrics(SM_CYDRAG) + 2);
256 MoveMouse(stepX, stepY);
263 public static void MouseDown(
int x,
int y, ClickType clickType)
265 SeparateFromLastPress(x, y);
269 if (ClickType.LeftClick == clickType)
270 mouse_event((
int)(MouseEventFlags.LEFTDOWN), x, y, 0, 0);
271 else if (ClickType.MiddleClick == clickType)
272 mouse_event((
int)(MouseEventFlags.MIDDLEDOWN), x, y, 0, 0);
273 else if (ClickType.RightClick == clickType)
274 mouse_event((
int)(MouseEventFlags.RIGHTDOWN), x, y, 0, 0);
276 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unknown mouse down clicktype [{clickType}]",
null, GPALObjectType.None);
284 public static void MouseUp(
int x,
int y, ClickType clickType)
287 if (ClickType.LeftClick == clickType)
288 mouse_event((
int)(MouseEventFlags.LEFTUP), x, y, 0, 0);
289 else if (ClickType.MiddleClick == clickType)
290 mouse_event((
int)(MouseEventFlags.MIDDLEUP), x, y, 0, 0);
291 else if (ClickType.RightClick == clickType)
292 mouse_event((
int)(MouseEventFlags.RIGHTUP), x, y, 0, 0);
294 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unknown mouse down clicktype [{clickType}]",
null, GPALObjectType.None);
302 public static void MiddleClick(
int x,
int y)
307 mouse_event((
int)(MouseEventFlags.MIDDLEDOWN), x, y, 0, 0);
309 mouse_event((
int)(MouseEventFlags.MIDDLEUP), x, y, 0, 0);
318 public static void LeftClick(
int x,
int y)
323 mouse_event((
int)(MouseEventFlags.LEFTDOWN), x, y, 0, 0);
325 mouse_event((
int)(MouseEventFlags.LEFTUP), x, y, 0, 0);
334 public static void LeftDoubleClick(
int x,
int y)
337 mouse_event((
int)(MouseEventFlags.LEFTDOWN | MouseEventFlags.LEFTUP), x, y, 0, 0);
339 mouse_event((
int)(MouseEventFlags.LEFTDOWN | MouseEventFlags.LEFTUP), x, y, 0, 0);
348 public static void RightClick(
int x,
int y)
352 mouse_event((
int)MouseEventFlags.RIGHTDOWN, x, y, 0, 0);
355 mouse_event((
int)MouseEventFlags.RIGHTUP, x, y, 0, 0);
364 public static void MoveMouse(
int x,
int y)
376 internal static void PressModifierKey(ModifierKeys modifierKeys,
bool dontPubolish =
false)
378 if (
false == dontPubolish)
379 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Pressed [{Browser.Browser.GetModifierKeys(modifierKeys)}]", modifierKeys, GPALObjectType.Other);
385 if (0 != (ModifierKeys.Control & modifierKeys))
387 keybd_event(GPAL.VK_CONTROL_LEFT, 0, 0, 0);
388 keybd_event(GPAL.VK_CONTROL_RIGHT, 0, GPAL.KEYEVENTF_EXTENDEDKEY, 0);
391 if (0 != (ModifierKeys.Shift & modifierKeys))
393 keybd_event(GPAL.VK_SHIFT_LEFT, 0, 0, 0);
394 keybd_event(GPAL.VK_SHIFT_RIGHT, 0, 0, 0);
397 if (0 != (ModifierKeys.Alt & modifierKeys))
399 keybd_event(GPAL.VK_LMENU, 0, 0, 0);
400 keybd_event(GPAL.VK_RMENU, 0, GPAL.KEYEVENTF_EXTENDEDKEY, 0);
403 if (0 != (ModifierKeys.Windows & modifierKeys))
405 keybd_event(GPAL.VK_LWIN, 0, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
406 keybd_event(GPAL.VK_RWIN, 0, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
409 if (0 != (ModifierKeys.Application & modifierKeys))
411 keybd_event(GPAL.VK_APPS, 0, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
414 if (0 != (ModifierKeys.ScrollLock & modifierKeys))
416 keybd_event(GPAL.VK_SCROLL, 0, 0, 0);
425 internal static void ReleaseModifierKeys(ModifierKeys modifierKeys,
bool dontPublish =
false)
427 if (
false == dontPublish)
428 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Released [{Browser.Browser.GetModifierKeys(modifierKeys)}]", modifierKeys, GPALObjectType.Other);
431 if (0 != (ModifierKeys.Alt & modifierKeys))
433 keybd_event(GPAL.VK_RMENU, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
435 keybd_event(GPAL.VK_LMENU, 0, GPAL.KEYEVENTF_KEYUP, 0);
438 if (0 != (ModifierKeys.Control & modifierKeys))
440 keybd_event(GPAL.VK_CONTROL_RIGHT, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
442 keybd_event(GPAL.VK_CONTROL_LEFT, 0, GPAL.KEYEVENTF_KEYUP, 0);
445 if (0 != (ModifierKeys.Shift & modifierKeys))
447 keybd_event(GPAL.VK_SHIFT_RIGHT, 0, GPAL.KEYEVENTF_KEYUP, 0);
449 keybd_event(GPAL.VK_SHIFT_LEFT, 0, GPAL.KEYEVENTF_KEYUP, 0);
452 if (0 != (ModifierKeys.Windows & modifierKeys))
454 keybd_event(GPAL.VK_RWIN, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
456 keybd_event(GPAL.VK_LWIN, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
459 if (0 != (ModifierKeys.Application & modifierKeys))
461 keybd_event(GPAL.VK_APPS, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
464 if (0 != (ModifierKeys.ScrollLock & modifierKeys))
466 keybd_event(GPAL.VK_SCROLL, 0, GPAL.KEYEVENTF_KEYUP, 0);
475 public static bool SendKey(
byte keyToSend, ModifierKeys modifierKeys = ModifierKeys.NONE)
478 if (ModifierKeys.NONE != modifierKeys)
479 PressModifierKey(modifierKeys);
483 keybd_event(keyToSend, scanCode, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
484 Thread.Sleep(_random.Next(10, 30));
485 keybd_event(keyToSend, scanCode, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
489 if (ModifierKeys.NONE != modifierKeys)
490 ReleaseModifierKeys(modifierKeys);
501 public static void SendString(
string stringToSend,
int delayMs = 0)
504 foreach (
char c
in stringToSend)
506 if (
false == first && 0 < delayMs)
507 Thread.Sleep(GetTypingDelay(delayMs));
508 SendChar(c, ModifierKeys.NONE,
true);
518 public static void SendChar(
char charToSend, ModifierKeys modifierKeys = ModifierKeys.NONE,
bool dontPublish =
false)
520 short vkKeyScan = HardwareHelper.VkKeyScan(charToSend);
521 System.Windows.Forms.Keys vkCode = (System.Windows.Forms.Keys)(vkKeyScan & 0xff);
523 var shift = (vkKeyScan & 0x100) > 0;
529 keybd_event(GPAL.VK_SHIFT_LEFT, 0, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
532 if (ModifierKeys.NONE != modifierKeys)
533 PressModifierKey(modifierKeys, dontPublish);
537 keybd_event((
byte)vkCode, (
byte)vkKeyScan, GPAL.KEYEVENTF_EXTENDEDKEY | 0, 0);
538 Thread.Sleep(_random.Next(10, 30));
539 keybd_event((
byte)vkCode, (
byte)vkKeyScan, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
543 if (ModifierKeys.NONE != modifierKeys)
544 ReleaseModifierKeys(modifierKeys, dontPublish);
548 keybd_event(GPAL.VK_SHIFT_LEFT, 0, GPAL.KEYEVENTF_EXTENDEDKEY | GPAL.KEYEVENTF_KEYUP, 0);
559 public static void HardwareClick(
int x,
int y, ClickType clickType)
563 case ClickType.LeftClick:
566 case ClickType.LeftDoubleClick:
567 LeftDoubleClick(x, y);
569 case ClickType.MiddleClick:
572 case ClickType.RightClick:
581 #region Improved WindMouse - Less Bounce, More Natural (C# 8 Compatible)
591 public static void MoveMouse(
int targetX,
int targetY,
int randomnessX = 4,
int randomnessY = 4,
double speed = 1.0)
593 Point current =
new Point();
594 GetCursorPos(out current);
596 Random rnd =
new Random();
599 targetX = Math.Max(0, targetX + rnd.Next(-randomnessX, randomnessX + 1));
600 targetY = Math.Max(0, targetY + rnd.Next(-randomnessY, randomnessY + 1));
602 double adjustedSpeed = Math.Max(0.4, speed);
605 current.X, current.Y,
609 minWait: 7.0 / adjustedSpeed,
610 maxWait: 16.0 / adjustedSpeed,
611 maxStep: 38.0 * adjustedSpeed,
629 private static void ImprovedWindMouse(
double xs,
double ys,
double xe,
double ye,
630 double gravity,
double wind,
double minWait,
double maxWait,
631 double maxStep,
double targetArea)
633 double dist, windX = 0, windY = 0, veloX = 0, veloY = 0;
634 int oldX, oldY, newX = (int)Math.Round(xs), newY = (int)Math.Round(ys);
636 double waitDiff = maxWait - minWait;
637 double sqrt2 = Math.Sqrt(2.0);
638 double sqrt3 = Math.Sqrt(3.0);
639 double sqrt5 = Math.Sqrt(5.0);
641 Random rnd =
new Random();
643 dist = Hypot(xe - xs, ye - ys);
647 wind = Math.Min(wind, dist * 0.8);
649 if (dist >= targetArea)
652 windX = windX / sqrt3 + (rnd.NextDouble() * wind * 2 - wind) / sqrt5;
653 windY = windY / sqrt3 + (rnd.NextDouble() * wind * 2 - wind) / sqrt5;
658 windX = windX / sqrt2 * 0.75;
659 windY = windY / sqrt2 * 0.75;
662 maxStep = 3.0 + rnd.NextDouble() * 2;
664 maxStep = maxStep / sqrt5 * 0.85;
668 double pull = gravity * (dist > 1.0 ? 1.0 : dist);
669 veloX += windX + pull * (xe - xs) / dist;
670 veloY += windY + pull * (ye - ys) / dist;
673 double mag = Hypot(veloX, veloY);
676 double stepSize = maxStep * 0.55 + rnd.NextDouble() * (maxStep * 0.45);
677 veloX = (veloX / mag) * stepSize;
678 veloY = (veloY / mag) * stepSize;
682 if (dist < targetArea * 0.6)
688 oldX = (int)Math.Round(xs);
689 oldY = (int)Math.Round(ys);
692 dist = Hypot(xe - xs, ye - ys);
694 newX = (int)Math.Round(xs);
695 newY = (int)Math.Round(ys);
697 if (oldX != newX || oldY != newY)
698 SetCursorPos(newX, newY);
700 double step = Hypot(xs - oldX, ys - oldY);
701 int wait = (int)Math.Round(waitDiff * (step / maxStep) + minWait);
702 Thread.Sleep(Math.Max(1, wait));
706 int finalX = (int)Math.Round(xe);
707 int finalY = (int)Math.Round(ye);
709 if (Math.Abs(newX - finalX) > 1 || Math.Abs(newY - finalY) > 1)
711 while (Math.Abs(newX - finalX) > 0 || Math.Abs(newY - finalY) > 0)
713 int dx = Math.Sign(finalX - newX) * rnd.Next(1, 3);
714 int dy = Math.Sign(finalY - newY) * rnd.Next(1, 3);
716 newX = Math.Max(finalX - 3, Math.Min(finalX + 3, newX + dx));
717 newY = Math.Max(finalY - 3, Math.Min(finalY + 3, newY + dy));
719 SetCursorPos(newX, newY);
720 Thread.Sleep(rnd.Next(8, 18));
724 SetCursorPos(finalX, finalY);
733 private static double Hypot(
double dx,
double dy)
735 return Math.Sqrt(dx * dx + dy * dy);
739 #region VKCODE to ASCII
743 internal static class KeyboardUtils
755 [DllImport(
"user32.dll")]
756 private static extern int ToAscii(uint uVirtKey, uint uScanCode,
byte[] lpKeyState,
757 [Out] StringBuilder lpChar, uint uFlags);
764 [DllImport(
"user32.dll")]
765 private static extern int GetKeyboardState(
byte[] lpKeyState);
773 [DllImport(
"user32.dll")]
774 private static extern uint MapVirtualKey(uint uCode, uint uMapType);
781 internal static string VkCodeToAscii(uint vkCode)
784 uint scanCode = MapVirtualKey(vkCode, 0);
787 byte[] keyState =
new byte[256];
788 if (GetKeyboardState(keyState) == 0)
797 StringBuilder charBuffer =
new StringBuilder(2);
800 int result = ToAscii(vkCode, scanCode, keyState, charBuffer, 0);
805 return charBuffer[0].ToString();
807 else if (result == 2)
810 return charBuffer[0].ToString();
819 #endregion VKCODE to ASCII