模拟按键 —— 鼠标

背景

之前写自动化脚本的时候总是遇到一些很尴尬的问题:

  • 跑脚本时模拟鼠标按键时,光标是真实的跑到了那个位置的,也就是说跑脚本的时候会很影响电脑的正常使用,导致不得不开一个虚拟机专门跑。

  • 另外因为光标只有一个所以很难实现多线程去同时操作多个窗口,当线程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;
}

效果图

我的代码里创建了两个线程,分别是点击 “关于” 按钮然后关闭,点击 “选项” 按钮然后关闭。可以看到无论怎么移动鼠标都不会影响模拟按键的准确性:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值