背景
之前写自动化脚本的时候总是遇到一些很尴尬的问题:
-
跑脚本时模拟鼠标按键时,光标是真实的跑到了那个位置的,也就是说跑脚本的时候会很影响电脑的正常使用,导致不得不开一个虚拟机专门跑。
-
另外因为光标只有一个所以很难实现多线程去同时操作多个窗口,当线程1 模拟鼠标但还没有结束时,线程2 已经开始执行模拟操作,这就导致了线程1 的模拟操作被终止了,被迫之下只能开多个虚拟机(但实在太占用性能🙄)
解决方法
针对第一个问题,可以在模拟鼠标移动之前先记录下鼠标当前的位置,当模拟完成后再将鼠标位置复原,从而营造一种鼠标没有移动的 “假象”。使用这种方法时如果用户的鼠标再高速移动,可能会造成模拟点击的位置有一点点偏差(以我单身20年手速,巅峰状态大概会偏上十几个像素 👍),解决办法就是在执行模拟操作的时候锁住鼠标,不让用户乱动,因为代码执行的速度是很快的,所以鼠标被锁的几毫秒以人的反应速度是感知不到的。
针对第二个问题,可以设置一个 “当前是否有鼠标模拟操作在执行” 的标志 ,根据这个变量的值确定线程是执行还是阻塞。
代码
#include <windows.h>
#include <iostream>
using namespace std;
/*
typedef struct INPUT {
DWORD type; // 输入事件类型(鼠标/键盘/硬件)
union {
MOUSEINPUT mi; // 鼠标事件信息
KEYBDINPUT ki; // 键盘事件信息
HARDWAREINPUT hi; // 硬件事件信息
} DUMMYUNIONNAME;
} ;
typedef struct MOUSEINPUT {
LONG dx; // x 坐标的绝对位置
LONG dy; // y 坐标的绝对位置
DWORD mouseData; // 滚轮移动的数量(正:向前滚,负:向后滚)
DWORD dwFlags; // 执行的操作类型
DWORD time; // 事件的时间戳
ULONG_PTR dwExtraInfo; // 与鼠标事件关联的附加值
} ;
typedef struct KEYBDINPUT {
WORD wVk; // 按键代码
WORD wScan; // 密钥的硬件扫描码
DWORD dwFlags; // 执行操作的类型
DWORD time; // 事件的时间戳
ULONG_PTR dwExtraInfo; // 与击键相关联的附加值
} ;
typedef struct HARDWAREINPUT {
DWORD uMsg; // 由输入硬件生成的消息
WORD wParamL; // 第一个参数的地位
WORD wParamH; // 第一个参数的高位
} ;
*/
int cx_screen = GetSystemMetrics(SM_CXSCREEN);
int cy_screen = GetSystemMetrics(SM_CYSCREEN);
VOID init_input_mouse(INPUT &input, DWORD type, LONG x, LONG y, DWORD mouseData, DWORD dwFlags, DWORD time, ULONG_PTR dwExtraInfo) {
input.type = type;
input.mi.dx = 65535 * x / cx_screen;
input.mi.dy = 65535 * y / cy_screen;
input.mi.mouseData = mouseData;
input.mi.dwFlags = dwFlags;
input.mi.time = time;
input.mi.dwExtraInfo = dwExtraInfo;
SendInput(1, &input, sizeof(INPUT));
}
VOID mouse_right(INPUT &input,LONG x,LONG y) {
// get current mouse location
POINT point = { 0,0 };
GetCursorPos(&point);
// lock mouse
RECT rect;
rect.left = x;
rect.right = x;
rect.top = y;
rect.bottom = y;
ClipCursor(&rect);
// mouse simulation
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0, 0);
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_RIGHTDOWN, 0, 0);
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_RIGHTUP, 0, 0);
// unlock mouse
ClipCursor(NULL);
// resume mouse location
SetCursorPos(point.x, point.y);
}
VOID mouse_left(INPUT& input, LONG x, LONG y) {
// get current mouse location
POINT point = { 0,0 };
GetCursorPos(&point);
// lock mouse
RECT rect;
rect.left = x;
rect.right = x;
rect.top = y;
rect.bottom = y;
ClipCursor(&rect);
// mouse simulation
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0, 0);
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN, 0, 0);
init_input_mouse(input, INPUT_MOUSE, x, y, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTUP, 0, 0);
// unlock mouse
ClipCursor(NULL);
// resume mouse location
SetCursorPos(point.x, point.y);
}
BOOL isBasy = FALSE;
VOID WINAPI thread_click_guayu() {
// init variables
HWND hwnd_main = NULL, hwnd_guanyu = NULL;
RECT rect_main, rect_guanyu;
INPUT input;
// get main hwnd
hwnd_main = FindWindow(NULL, L"LordPE吾爱破解专用版 ");
if (hwnd_main == NULL) {
cout << "FindWindow failed." << endl;
goto end;
}
// click "关于" button , and close dialog box window
while (1) {
if (isBasy == TRUE)
continue;
isBasy = TRUE;
// get main window location
if (GetWindowRect(hwnd_main, &rect_main) == 0) {
cout << "GetWindowRect failed." << endl;
goto end;
}
// activate main window
SetForegroundWindow(hwnd_main);
// click "关于" button
mouse_left(input, rect_main.left + 565, rect_main.top + 220);
Sleep(1000);
// close "关于" window
hwnd_guanyu = FindWindow(NULL, L"[ 关于 ]");
if (hwnd_guanyu == NULL) {
cout << "FindWindow failed." << endl;
goto end;
}
if (GetWindowRect(hwnd_guanyu, &rect_guanyu) == 0) {
cout << "GetWindowRect failed." << endl;
goto end;
}
SetForegroundWindow(hwnd_guanyu);
mouse_left(input, rect_guanyu.left + 317, rect_guanyu.top + 11);
Sleep(100);
isBasy = FALSE;
Sleep(3000);
}
end:
if (hwnd_main)
CloseHandle(hwnd_main);
if (hwnd_guanyu)
CloseHandle(hwnd_guanyu);
}
VOID WINAPI thread_click_xuanxiang() {
// init variables
HWND hwnd_main = NULL, hwnd_xuanxiang = NULL;
RECT rect_main, rect_guanyu;
INPUT input;
// get main hwnd
hwnd_main = FindWindow(NULL, L"LordPE吾爱破解专用版 ");
if (hwnd_main == NULL) {
cout << "FindWindow failed." << endl;
goto end;
}
// click "选项" button , and close dialog box window
while (1) {
if (isBasy == TRUE)
continue;
isBasy = TRUE;
// get main window location
if (GetWindowRect(hwnd_main, &rect_main) == 0) {
cout << "GetWindowRect failed." << endl;
goto end;
}
// activate main window
SetForegroundWindow(hwnd_main);
// click "选项" button
mouse_left(input, rect_main.left + 565, rect_main.top + 175);
Sleep(1000);
// close "选项" window
hwnd_xuanxiang = FindWindow(NULL, L"[ 选项 ]");
if (hwnd_xuanxiang == NULL) {
cout << "FindWindow failed." << endl;
goto end;
}
if (GetWindowRect(hwnd_xuanxiang, &rect_guanyu) == 0) {
cout << "GetWindowRect failed." << endl;
goto end;
}
SetForegroundWindow(hwnd_xuanxiang);
mouse_left(input, rect_guanyu.left + 466, rect_guanyu.top + 42);
Sleep(100);
isBasy = FALSE;
Sleep(3000);
}
end:
if (hwnd_main)
CloseHandle(hwnd_main);
if (hwnd_xuanxiang)
CloseHandle(hwnd_xuanxiang);
}
int main()
{
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread_click_guayu, 0, 0, 0);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread_click_xuanxiang, 0, 0, 0);
getchar();
return 0;
}
效果图
我的代码里创建了两个线程,分别是点击 “关于” 按钮然后关闭,点击 “选项” 按钮然后关闭。可以看到无论怎么移动鼠标都不会影响模拟按键的准确性: