first commit
This commit is contained in:
commit
761b95080f
404 files changed
+312608
No files matched your search
@@ -0,0 +1,88 @@
|
||||
using anjianjingling.Common;
|
||||
using anjianjingling.Native;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace anjianjingling.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统级键盘模拟(SendInput):单键、组合键、Unicode 文本。
|
||||
/// </summary>
|
||||
public sealed class KeyboardInput
|
||||
{
|
||||
private static void SendKey(Win32.KEYBDINPUT ki)
|
||||
{
|
||||
var input = new Win32.INPUT
|
||||
{
|
||||
type = Win32.INPUT_KEYBOARD,
|
||||
u = new Win32.INPUTUNION { ki = ki }
|
||||
};
|
||||
Win32.SendInput(1, new[] { input }, Marshal.SizeOf<Win32.INPUT>());
|
||||
}
|
||||
|
||||
/// <summary>发送一次按键(按下并弹起)。</summary>
|
||||
public void KeyPress(string keyName)
|
||||
{
|
||||
ushort vk = ResolveOrThrow(keyName);
|
||||
KeyDownVk(vk);
|
||||
Thread.Sleep(30);
|
||||
KeyUpVk(vk);
|
||||
}
|
||||
|
||||
public void KeyDown(string keyName) => KeyDownVk(ResolveOrThrow(keyName));
|
||||
|
||||
public void KeyUp(string keyName) => KeyUpVk(ResolveOrThrow(keyName));
|
||||
|
||||
private static void KeyDownVk(ushort vk)
|
||||
{
|
||||
ushort scan = (ushort)Win32.MapVirtualKey(vk, Win32.MAPVK_VK_TO_VSC);
|
||||
uint flags = Win32.KEYEVENTF_SCANCODE;
|
||||
if (VirtualKeys.IsExtended(vk)) flags |= Win32.KEYEVENTF_EXTENDEDKEY;
|
||||
SendKey(new Win32.KEYBDINPUT { wVk = vk, wScan = scan, dwFlags = flags });
|
||||
}
|
||||
|
||||
private static void KeyUpVk(ushort vk)
|
||||
{
|
||||
ushort scan = (ushort)Win32.MapVirtualKey(vk, Win32.MAPVK_VK_TO_VSC);
|
||||
uint flags = Win32.KEYEVENTF_SCANCODE | Win32.KEYEVENTF_KEYUP;
|
||||
if (VirtualKeys.IsExtended(vk)) flags |= Win32.KEYEVENTF_EXTENDEDKEY;
|
||||
SendKey(new Win32.KEYBDINPUT { wVk = vk, wScan = scan, dwFlags = flags });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组合键,如 "Ctrl+A"、"Ctrl+Shift+Esc":修饰键依次按下,主键按下弹起,修饰键逆序弹起。
|
||||
/// </summary>
|
||||
public void HotKey(string expression)
|
||||
{
|
||||
var parts = expression.Split('+', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 0) throw new ArgumentException($"无法解析组合键:{expression}");
|
||||
|
||||
var vks = parts.Select(ResolveOrThrow).ToArray();
|
||||
for (int i = 0; i < vks.Length - 1; i++) KeyDownVk(vks[i]);
|
||||
Thread.Sleep(40);
|
||||
KeyDownVk(vks[^1]);
|
||||
Thread.Sleep(40);
|
||||
KeyUpVk(vks[^1]);
|
||||
for (int i = vks.Length - 2; i >= 0; i--)
|
||||
{
|
||||
Thread.Sleep(20);
|
||||
KeyUpVk(vks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输入整段文本(含中文),使用 KEYEVENTF_UNICODE,不依赖键盘布局。
|
||||
/// </summary>
|
||||
public void TypeText(string text)
|
||||
{
|
||||
foreach (char c in text)
|
||||
{
|
||||
SendKey(new Win32.KEYBDINPUT { wScan = c, dwFlags = Win32.KEYEVENTF_UNICODE });
|
||||
SendKey(new Win32.KEYBDINPUT { wScan = c, dwFlags = Win32.KEYEVENTF_UNICODE | Win32.KEYEVENTF_KEYUP });
|
||||
}
|
||||
}
|
||||
|
||||
private static ushort ResolveOrThrow(string name)
|
||||
=> VirtualKeys.Resolve(name)
|
||||
?? throw new ArgumentException($"未知按键名:\"{name}\"(可用如 A、F5、ENTER、SPACE、方向键等)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using anjianjingling.Native;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace anjianjingling.Input
|
||||
{
|
||||
public enum MouseButton { Left, Right, Middle }
|
||||
|
||||
/// <summary>
|
||||
/// 系统级鼠标模拟(SendInput)。
|
||||
/// </summary>
|
||||
public sealed class MouseInput
|
||||
{
|
||||
private static readonly Random Rng = new();
|
||||
|
||||
/// <summary>虚拟桌面(含多显示器)尺寸与原点。</summary>
|
||||
private static Rectangle VirtualScreen()
|
||||
{
|
||||
int x = Win32.GetSystemMetrics(Win32.SM_XVIRTUALSCREEN);
|
||||
int y = Win32.GetSystemMetrics(Win32.SM_YVIRTUALSCREEN);
|
||||
int w = Win32.GetSystemMetrics(Win32.SM_CXVIRTUALSCREEN);
|
||||
int h = Win32.GetSystemMetrics(Win32.SM_CYVIRTUALSCREEN);
|
||||
return new Rectangle(x, y, w, h);
|
||||
}
|
||||
|
||||
/// <summary>把屏幕物理坐标换算为 SendInput 绝对坐标(0..65535,含虚拟桌面偏移)。</summary>
|
||||
private static void ToAbsolute(int x, int y, out int ax, out int ay)
|
||||
{
|
||||
var vs = VirtualScreen();
|
||||
ax = (int)((x - vs.Left) * 65535.0 / (vs.Width - 1));
|
||||
ay = (int)((y - vs.Top) * 65535.0 / (vs.Height - 1));
|
||||
}
|
||||
|
||||
private static void SendSingle(Win32.MOUSEINPUT mi)
|
||||
{
|
||||
var input = new Win32.INPUT
|
||||
{
|
||||
type = Win32.INPUT_MOUSE,
|
||||
u = new Win32.INPUTUNION { mi = mi }
|
||||
};
|
||||
Win32.SendInput(1, new[] { input }, Marshal.SizeOf<Win32.INPUT>());
|
||||
}
|
||||
|
||||
/// <summary>移动鼠标到指定屏幕坐标(瞬时)。</summary>
|
||||
public void MoveTo(int x, int y)
|
||||
{
|
||||
ToAbsolute(x, y, out int ax, out int ay);
|
||||
SendSingle(new Win32.MOUSEINPUT
|
||||
{
|
||||
dx = ax,
|
||||
dy = ay,
|
||||
dwFlags = Win32.MOUSEEVENTF_MOVE | Win32.MOUSEEVENTF_ABSOLUTE | Win32.MOUSEEVENTF_VIRTUALDESK
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>相对移动。</summary>
|
||||
public void MoveR(int dx, int dy)
|
||||
{
|
||||
var p = Position;
|
||||
MoveTo(p.X + dx, p.Y + dy);
|
||||
}
|
||||
|
||||
/// <summary>带随机偏移的移动(拟人化)。</summary>
|
||||
public void MoveToJitter(int x, int y, int jitterPx = 3)
|
||||
{
|
||||
MoveTo(x + Rng.Next(-jitterPx, jitterPx + 1),
|
||||
y + Rng.Next(-jitterPx, jitterPx + 1));
|
||||
}
|
||||
|
||||
public void ButtonDown(MouseButton button)
|
||||
=> SendSingle(new Win32.MOUSEINPUT { dwFlags = button switch
|
||||
{
|
||||
MouseButton.Left => Win32.MOUSEEVENTF_LEFTDOWN,
|
||||
MouseButton.Right => Win32.MOUSEEVENTF_RIGHTDOWN,
|
||||
MouseButton.Middle => Win32.MOUSEEVENTF_MIDDLEDOWN,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(button))
|
||||
} });
|
||||
|
||||
public void ButtonUp(MouseButton button)
|
||||
=> SendSingle(new Win32.MOUSEINPUT { dwFlags = button switch
|
||||
{
|
||||
MouseButton.Left => Win32.MOUSEEVENTF_LEFTUP,
|
||||
MouseButton.Right => Win32.MOUSEEVENTF_RIGHTUP,
|
||||
MouseButton.Middle => Win32.MOUSEEVENTF_MIDDLEUP,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(button))
|
||||
} });
|
||||
|
||||
/// <summary>在当前位置点击;jitter=true 时落点带 ±3px 随机偏移(仅当指定坐标时)。</summary>
|
||||
public void Click(MouseButton button, int? x = null, int? y = null, bool jitter = false)
|
||||
{
|
||||
if (x.HasValue && y.HasValue)
|
||||
{
|
||||
if (jitter) MoveToJitter(x.Value, y.Value);
|
||||
else MoveTo(x.Value, y.Value);
|
||||
Thread.Sleep(60 + Rng.Next(0, 50));
|
||||
}
|
||||
ButtonDown(button);
|
||||
Thread.Sleep(40 + Rng.Next(0, 40));
|
||||
ButtonUp(button);
|
||||
}
|
||||
|
||||
/// <summary>双击(左键)。</summary>
|
||||
public void DoubleClick(int? x = null, int? y = null, bool jitter = false)
|
||||
{
|
||||
Click(MouseButton.Left, x, y, jitter);
|
||||
Thread.Sleep(50 + Rng.Next(0, 60));
|
||||
Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
/// <summary>滚轮:正值向上滚,负值向下滚,单位为格(每格 120)。</summary>
|
||||
public void Wheel(int delta)
|
||||
{
|
||||
SendSingle(new Win32.MOUSEINPUT
|
||||
{
|
||||
mouseData = (uint)(delta * Win32.WHEEL_DELTA),
|
||||
dwFlags = Win32.MOUSEEVENTF_WHEEL
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>从一点拖拽到另一点,按 duration 插值移动,模拟人手。</summary>
|
||||
public void Drag(int x1, int y1, int x2, int y2, int durationMs = 400)
|
||||
{
|
||||
MoveTo(x1, y1);
|
||||
Thread.Sleep(100);
|
||||
ButtonDown(MouseButton.Left);
|
||||
|
||||
int steps = Math.Max(5, durationMs / 16);
|
||||
for (int i = 1; i <= steps; i++)
|
||||
{
|
||||
double t = (double)i / steps;
|
||||
// 轻微非线性,模拟加减速
|
||||
double e = t * t * (3 - 2 * t);
|
||||
int cx = (int)(x1 + (x2 - x1) * e);
|
||||
int cy = (int)(y1 + (y2 - y1) * e);
|
||||
MoveTo(cx, cy);
|
||||
Thread.Sleep(16);
|
||||
}
|
||||
Thread.Sleep(80);
|
||||
ButtonUp(MouseButton.Left);
|
||||
}
|
||||
|
||||
/// <summary>当前鼠标屏幕坐标。</summary>
|
||||
public Point Position
|
||||
{
|
||||
get
|
||||
{
|
||||
Win32.GetCursorPos(out var p);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user