Files
2026-10-24 13:02:19 +08:00

152 lines
5.4 KiB
C#

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;
}
}
}
}