模拟按键 —— 鼠标

标签: 模拟按键

背景

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

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

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

效果图

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

版权声明:本文为Simon798原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Simon798/article/details/107864678

智能推荐

CentOS学习之路1-wget下载安装配置

参考1: https://blog.csdn.net/zhaoyanjun6/article/details/79108129 参考2: http://www.souvc.com/?p=1569 CentOS学习之路1-wget下载安装配置 1.wget的安装与基本使用 安装wget yum 安装软件 默认安装保存在/var/cache/yum ,用于所有用户使用。 帮助命令 基本用法 例子:下载...

深入浅出Spring的IOC容器,对Spring的IOC容器源码进行深入理解

文章目录 DispatcherServlet整体继承图 入口:DispatcherServlet.init() HttpServletBean.init() FrameworkServlet.initServletBean() 首先大家,去看Spring的源码入口,第一个就是DispatcherServlet DispatcherServlet整体继承图 入口:DispatcherServlet....

laravel框架的课堂知识点概总

1. MVC 1.1 概念理解 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑 MVC 是一种使用 MVC(Model View Controller ...

Unity人物角色动画系统学习总结

使用动画系统控制人物行走、转向、翻墙、滑行、拾取木头 混合树用来混合多个动画 MatchTarget用来匹配翻墙贴合墙上的某一点,人物以此为支点翻墙跳跃 IK动画类似于MatchTarget,控制两只手上的两个点来指定手的旋转和位置,使得拾取木头时更逼真 创建AnimatorController: 首先创建一个混合树,然后双击 可以看到该混合树有五种状态机,分别是Idle、WalkForward、...

Composer 安装 ThinkPHP6 问题

Composer 安装 ThinkPHP6 问题 先说说问题 一.运行环境要求 二.配置 参考: ThinkPHP6.0完全开发手册 先说说问题 执行ThinkPHP6的安装命令 遇到问题汇总如下: 看提示是要更新版本,执行命令更新。 更新之后,再次安装ThinkPHP,之后遇到如下问题。 尝试了很多方法,依然不能解决。其中包括使用https://packagist.phpcomposer.com...

猜你喜欢

Spring Boot 整合JDBC

今天主要讲解一下SpringBoot如何整合JDBC,没啥理论好说的,直接上代码,看项目整体结构 看一下对应的pom.xml 定义User.java 定义数据源配置,这里使用druid,所以需要写一个配置类 上面指定druid的属性配置,和用户登录的账号信息以及对应的过滤规则: 下面定义数据访问接口和对应的实现: 数据访问层很简单,直接注入JdbcTemplate模板即可,下面再看对应的servi...

html鼠标悬停显示样式

1.显示小手:     在style中添加cursor:pointer 实现鼠标悬停变成小手样式     实例:         其他参数: cursor语法: cursor : auto | crosshair | default | hand | move | help | wait | tex...

Yupoo(又拍网)的系统架构

Yupoo!(又拍网) 是目前国内最大的图片服务提供商,整个网站构建于大量的开源软件之上。以下为其使用到的开源软件信息: 操作系统:CentOS、MacOSX、Ubuntu 服务器:Apache、Nginx、Squid 数据库:MySQLmochiweb、MySQLdb 服务器监控:Cacti、Nagios、 开发语言:PHP、Python、Erlang、Java、Lua 分布式计算:Hadoop...

创建一个Servlet项目流程(入门)

版本 IDEA 2020.2 JDK1.8 apache-tomcat-9.0.36 项目流程 一、IDEA中新建JaveEE项目 项目起名,选择项目存放地址,点击finish创建成功 进入项目后,右键选择项目,选择add Framework Support 选择Web Application,点击OK 此时项目文件夹 在WEB-INF下创建两个目录classes和lib 按ctrl+alt+sh...

Docker部署SpringCloud ELK+RabbitMQ日志

Docker部署SpringCloud ELK+RabbitMQ日志  Im_Coder 原文:https://www.jianshu.com/p/f773f23096a9 一、效果图 image.png 二、ELK是什么? ELK由ElasticSearch、Logstash和Kiabana三个开源工具组成。 其中Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,索...