Qt实现全局鼠标事件监听器-Linux

news2024/9/22 4:20:39

Qt实现全局鼠标事件监听器-Linux版🦑

var code = “bc8d4eb4-a9df-48e9-8028-bbe1ae7fbd05”

文章目录

  • Qt实现全局鼠标事件监听器-Linux版🦑
    • 1、概述🦞
    • 2、实现效果🍰
    • 3、实现方式🦀
    • 4、关键代码🍦
    • 5、源代码🍭

更多精彩内容
👉个人内容分类汇总 👈
👉Qt自定义模块、工具👈

1、概述🦞

  • Qt版本:V5.12.5
  • 兼容系统:
    • Windows:这里测试了Windows10,其它的版本没有测试;
    • Linux:这里测试了ubuntu18.04、20.04,其它的没有测试(ubuntu自带的截图软件没有这个功能);
    • Mac:等啥时候我有了Mac电脑再说。
  1. 有时候我们想获取到【系统全局鼠标事件】,使用Qt的鼠标事件、事件过滤器之类的都无法实现,因为当鼠标移出当前窗口或者当前窗口失去焦点、窗口最小化了就无法获取到鼠标事件了;
  2. 而Linux下想要监听到全局鼠标事件就需要使用到X11或者xcb的API来实现;
  3. 在这个类中通过X11的API监听到全局鼠标事件(我没有使用Xcb);
  4. 然后将监听到的鼠标事件映射为QMouseEvent事件,便于在Qt里面使用。

2、实现效果🍰

在这里插入图片描述

3、实现方式🦀

  1. 使用XRecordEnableContext()函数绑定用于监听全局鼠标事件的回调函数;
  2. 由于XRecordEnableContext会一直阻塞,所以需要在子线程中调用;
  3. 通过回调函数void callback(XPointer ptr, XRecordInterceptData* data)监听到全局鼠标事件;
  4. 使用xEvent * event = reinterpret_cast<xEvent*>(data->data);XRecordInterceptData::data转换为xEvent结构体的指针,可通过这个结构体获取当前鼠标的坐标或者鼠标滚轮向前还是向后滚动的值。
  5. 然后将获取到的鼠标事件映射为QMouseEvent、QWheelEvent事件,发送给当前程序使用;
  6. 这里我使用的是QMouseEvent、QWheelEvent指针进行发送,由于QMouseEvent、QWheelEvent没有默认无参构造,所以在Linux下不支持使用信号发送QMouseEvent、QWheelEvent变量,所以只能使用指针;
  7. 因为传递的是指针,所以在接收信号的槽函数里使用完后需要Delete,避免内存泄漏;
  8. 简易这个信号只绑定一次,避免多个槽函数里使用同一个指针,一个槽函数释放了另外一个槽函数里出现野指针或者重复释放。
  9. 不使用时需要使用XRecordDisableContext()、XRecordFreeContext()函数来关闭监听。

4、关键代码🍦

  • 由于使用到了系统API,所以pro文件中需要链接系统库
unix:!macx{
LIBS += -lX11 -lXtst      # linux获取鼠标、键盘事件信息需要用到xlib,Xtst 可以安装sudo apt install libxtst-dev
}
  • globalmouseevent.h
/******************************************************************************
 * @文件名     mouseevent.h
 * @功能       全局鼠标事件监听类
 *
 * @开发者     mhf
 * @邮箱       1603291350@qq.com
 * @时间       2022/12/07
 * @备注
 *****************************************************************************/
#ifndef MOUSEEVENT_H
#define MOUSEEVENT_H

#include <QObject>

class QMouseEvent;
class QWheelEvent;

/**
 *  全局鼠标事件单例信号类
 */
class GlobalMouseEvent : public QObject
{
    Q_OBJECT
public:
    static GlobalMouseEvent* getInstance()
    {
        static GlobalMouseEvent mouseEvent;
        return &mouseEvent;
    }

    static bool installMouseEvent();      // 安装全局鼠标事件监听器
    static bool removeMouseEvent();       // 卸载全局鼠标事件监听器

signals:
    /**
     * @brief 由于传递的是指针,为了保证不会出现内存泄露,需要在槽函数中delete。
     *        建议此信号只绑定一次,因为如果绑定多次可能会出现一个槽函数里把信号delete了,另外一个槽函数还在使用,出现野指针,或者多个槽函数多次delete
     */
    void mouseEvent(QMouseEvent* event);
    void wheelEvent(QWheelEvent* event);

private:
    GlobalMouseEvent(){}
};
#endif // MOUSEEVENT_H

  • globalmouseevent_x11.cpp
#include "globalmouseevent.h"
#if defined(Q_OS_LINUX)
#include <QDebug>
#include <QCursor>
#include <QMouseEvent>
#include <QtConcurrent>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/record.h>     //  如果找不到可以安装sudo apt-get install xorg-dev
#include <X11/Xlibint.h>


#if 0     // 方法1:这种方法可以获取全局鼠标事件,但是会截断鼠标事件,导致其他所有程序都无法获取到鼠标事件
void  sleepMsec(int msec)
{
    QEventLoop loop;		//定义一个新的事件循环
    QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
    loop.exec();			//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
}

void MouseEventX11()
{
    XEvent xevent;
    int grb;
    Display* display = XOpenDisplay(NULL);      // 首先连接到显示服务器
    if(!display) return ;

    unsigned int t_new=0,t_prev=0,t_diff=0;
    int scr = DefaultScreen(display);           // 获取默认屏幕编号
    Window window = RootWindow(display, scr);   // 获取根窗口

    while(1)
    {
        XGrabPointer(display,
                     window,
                     true,
                     PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
                     GrabModeAsync,
                     GrabModeAsync,
                     None,
                     None,
                     CurrentTime);
        XAllowEvents(display,AsyncPointer, CurrentTime);
        XNextEvent(display, &xevent);

        qDebug() << Button1Mask <<" " <<Button2Mask<<" " <<Button2Mask<<" " <<Button3Mask<<" " <<Button4Mask<<" " <<Button5Mask;
        qDebug() << Button1 <<" " <<Button2<<" " <<Button2<<" " <<Button3<<" " <<Button4<<" " <<Button5;
        switch (xevent.type) {
        case MotionNotify:
        {
            qDebug() << "运动事件";
            break;
        }
        case ButtonPress:
        {
            qDebug() << xevent.xbutton.button;
            switch (xevent.xbutton.button)
            {
                case 1:
                qDebug() << QString("左键单击:[%1, %2]").arg(xevent.xbutton.x_root).arg(xevent.xbutton.y_root);
                t_prev=t_new;
                break;
                case 2:
                qDebug() << "单击鼠标中键";
                break;
                case 3:
                qDebug() << "单击鼠标右键";
                break;
                case 4:
                qDebug() << "向上滚动";
                break;
                case 5:
                qDebug() << "向下滚动";
                break;
            }
            break;
          }
        case ButtonRelease:
        {
            switch (xevent.xbutton.button)
            {
                case 1:
                qDebug() << QString("左键释放:[%1, %2]").arg(xevent.xbutton.x_root).arg(xevent.xbutton.y_root);
                t_prev=t_new;
                break;
                case 2:
                qDebug() << "释放鼠标中键";
                break;
                case 3:
                qDebug() << "释放鼠标右键";
                break;
                case 4:
                qDebug() << "向上滚动";
                break;
                case 5:
                qDebug() << "向下滚动";
                break;
            }
            break;
          }
        }
        sleepMsec(1);
    }
    XUngrabPointer(display,CurrentTime);
}
#else

// 使用static修饰全局函数和全局变量:只能在本源文件使用
static XRecordContext g_context = 0;
static Display* g_display = nullptr;

static bool init()
{
    g_display =XOpenDisplay(nullptr);           // 打开与控制显示器的X服务器的连接,详细说明看【https://tronche.com/gui/x/xlib/display/opening.html】
    if(!g_display)
    {
        qWarning() << "连接X服务失败!";
        return false;
    }
    XRecordClientSpec clients = XRecordAllClients;     // 初始化 XRecordCreateContext 所需的 XRecordClientSpec 参数,XRecordAllClients 的意思是 "记录所有 X Client" 的事件
    XRecordRange*range = XRecordAllocRange();          // 创建 XRecordRange 变量,用于控制记录事件的范围
    if (!range)
    {
        qDebug() << "无法分配XRecordRange";
        return false;
    }

    // 会监听到 first - last之间并包含first和last的所有类型的事件
    memset(range, 0, sizeof(XRecordRange));
    range->device_events.first = ButtonPress;
    range->device_events.last  = MotionNotify;
    // 根据上面的记录客户端类型和记录事件范围来创建 “记录上下文”
    // 然后把 XRecordContext 传递给 XRecordEnableContext 函数来开启事件记录循环
    g_context = XRecordCreateContext(g_display, 0, &clients, 1,&range, 1);
    XFree(range);
    if(g_context == 0)
    {
        qWarning() << "创建事件记录上下文失败!";
        return false;
    }
    XSync(g_display, false);              // XSync 的作用就是把上面的X 代码立即发给 X Server,这样 X Server 接受到事件以后会立即发送给 XRecord 的 Client 连接  True

    return true;
}

/**
 * @brief      处理鼠标事件的回调函数,将X11鼠标事件转换为Qt鼠标事件,通过单例类MouseEvent发送出去
 * @param ptr
 * @param data
 */
static void callback(XPointer ptr, XRecordInterceptData* data)
{
    Q_UNUSED(ptr)

    if (data->category == XRecordFromServer)
    {
        xEvent * event  = reinterpret_cast<xEvent*>(data->data);
//      qDebug() << QString("鼠标坐标:[%1, %2]").arg(event->u.keyButtonPointer.rootX).arg(event->u.keyButtonPointer.rootY);   // 获取鼠标坐标
        switch (event->u.u.type)            // 动作类型
        {
        case ButtonPress:                   //鼠标按下
        {
            QPoint point = QCursor::pos();  // 获取鼠标当前位置
            switch (event->u.u.detail)      // 按键类型
            {
            case Button1:     // 左键按下
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
                break;
            }
            case Button2:     // 中键按下
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier));
                break;
            }
            case Button3:     // 右键按下
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier));
                break;
            }
            case Button4:     // 向前滚动
            {
                emit GlobalMouseEvent::getInstance()->wheelEvent(new QWheelEvent(point, 120, Qt::MiddleButton, Qt::NoModifier));
                break;
            }
            case Button5:     // 向后滚动
            {
                emit GlobalMouseEvent::getInstance()->wheelEvent(new QWheelEvent(point, -120, Qt::MiddleButton, Qt::NoModifier));
                break;
            }
            default:
            {
                qDebug() << QString("未定义的按键:%1").arg(event->u.u.detail);   // 比如很多鼠标边上会多几个键
                break;
            }
            }
            break;
        }
        case MotionNotify:                              // 鼠标移动
        {
            emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseMove, QCursor::pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier));
        }
        case ButtonRelease:                             // 鼠标释放
        {
            QPoint point = QCursor::pos();  // 获取鼠标当前位置
            switch (event->u.u.detail)      // 按键类型
            {
            case Button1:   // 左键释放
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
                break;
            }
            case Button2:   // 中键释放
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier));
                break;
            }
            case Button3:   // 右键释放
            {
                emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier));
                break;
            }
            case Button4:   // 向前滚动
            {
                break;
            }
            case Button5:   // 向后滚动
            {
                break;
            }
            default:
            {
//                qDebug() << QString("未定义的按键:%1").arg(event->u.u.detail);   // 比如很多鼠标边上会多几个键
            }
            }
            break;
        }
        default:
            break;
        }
    }

    XRecordFreeData(data);
}

/**
 * 调用 XRecordEnableContext 函数建立 XRecord 上下文
 * X Server 事件一旦发生就传递给事件处理回调函数
 * XRecordEnableContext 函数一旦调用就开始进入堵塞时的事件循环,直到线程或所属进程结束
 */
static void enableContext()
{
    Status ret = XRecordEnableContext(g_display, g_context,  callback, nullptr);
    XCloseDisplay(g_display);    // 关闭连接
    g_display = nullptr;
    qDebug() << QString("退出事件监听:%1").arg(ret);
}
#endif

/**
 * @brief  安装全局鼠标事件监听器
 * @return true:安装成功 false:失败
 */
bool GlobalMouseEvent::installMouseEvent()
{
    bool ret = init();
    if(!ret) return false;
    QtConcurrent::run(enableContext);   // 由于XRecordEnableContext会一直阻塞,所以需要在线程中调用
    return true;
}

/**
 * @brief   卸载全局鼠标事件监听器,注意:如果不卸载事件监听则导致子线程会一直存在,程序无法正常退出
 * @return  true:卸载成功 false:失败
 */
bool GlobalMouseEvent::removeMouseEvent()
{
    if(g_context == 0) return false;

    Display* display = XOpenDisplay(nullptr);         // 这里需要单独建立一个连接来关闭监听,否则XRecordEnableContext不会退出
    if(!display)
    {
        qWarning() << "连接X服务失败!";
        return false;
    }
    XRecordDisableContext(display, g_context);
    XFlush(display);
    XSync(display, false);

    XRecordFreeContext(display, g_context);           // 释放监听上下文,否则XRecordEnableContext不会退出
    g_context = 0;
    XCloseDisplay(display);
    return true;
}


#endif

5、源代码🍭

  • gitee
  • github
  • 全局鼠标键盘事件监听器仓库github
  • 全局鼠标键盘事件监听器仓库gitee
  • CSDN
  • 可以使用命令git clone https://gitee.com/mahuifa/QtGlobalEvent.git直接下载仓库,然后引用到自己的程序中。

🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/79863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringMVC基础篇:第一个MVC程序和细节分析

文章整理自孙哥说SpringMVC&#xff0c;相关课程联系孙哥学习谢谢。第一章&#xff1a;编码开发一&#xff1a;思路分析二&#xff1a;SpringMVC程序编码三&#xff1a;控制器提供多个服务方法四&#xff1a;注意事项第二章&#xff1a;细节分析一&#xff1a;控制器创建次数二…

C++ 【set、map模拟实现】

目录 set概念 set基本使用 map概念 map的使用 map统计次数 operator[] operator[]底层如何实现&#xff1f; set和map迭代器封装 红黑树迭代器基本结构 operator operator-- operator[] 源代码链接 map、set底层都使用平衡搜索树(即红黑树)&#xff0c;容器中的元素…

HanLP 基于朴素贝叶斯 训练 文本分类

一、HanLP 朴素贝叶斯分类器 HanLP 针对文本分类算法已经帮我们实现 朴素贝叶斯法 &#xff0c;用户可以无需关心内部细节&#xff0c;HanLP 也提供了相关自定义训练接口&#xff0c;前提需要将数据集根据分类放到不同的目录中&#xff0c;例如&#xff1a; 官方给出了相关性能…

HanLP 基于SVM支持向量机 训练 文本分类

一、HanLP 基于SVM支持向量机分类器 上篇文章通过朴素贝叶斯文本分类器&#xff0c;训练测试了 搜狗文本分类语料库迷你版 &#xff0c;本篇继续测试SVM支持向量机分类器。 由于HanLP 官方给出的 SVM 分类器依赖了第三方库&#xff0c;没有集成在主项目中&#xff0c;需要拉取…

问题解决(1)——VS中scanf报错怎么解决

目录 方法一&#xff1a; 方法二&#xff1a; 方法三&#xff1a; 各位好&#xff0c;博主新建了个公众号《自学编程村》&#xff0c;拉到底部即可看到&#xff0c;有情趣可以关注看看哈哈&#xff0c;关注后还可以加博主wx呦~~~&#xff08;公众号拉到底部就能看到呦~~&am…

Redis【13】-修改数据库后,如何保证Redis与数据库的数据一致性

一、需求起因 在高并发的业务场景下&#xff0c;数据库大多数情况都是用户并发访问最薄弱的环节。所以&#xff0c;就需要使用redis做一个缓冲操作&#xff0c;让请求先访问到redis&#xff0c;而不是直接访问MySQL等数据库。 这个业务场景&#xff0c;主要是解决读数据从Redi…

ARM 代码重定位实战

前言 任务 在 SRAM 中将代码从 0xd0020010 重定位到 0xd0024000。任务解释&#xff1a;本来代码是运行在0xd0020010的&#xff0c;但是因为一些原因我们又希望代码实际是在0xd0024000位置运行 的。这时候就需要重定位了。注解&#xff1a;本练习对代码本身运行无实际意义&…

你都工作两年半了,还不会RabbitMQ?

What is rabbitMQ &#xff1f; RabbitMQ 是一个由 Erlang 语言开发的 AMQP(高级消息队列协议) 的开源实现。 RabbitMQ 是轻量级且易于部署的&#xff0c;能支持多种消息协议。 RabbitMQ 可以部署在分布式和联合配置中&#xff0c;以满足高规模、高可用性的需求。 具体特点包括…

ADI Blackfin DSP处理器-BF533的开发详解29:TOUCH_LINE(屏幕画线)(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件设计原理图 功能介绍 代码实现了读取触摸屏坐标&#xff0c;并将触摸屏坐标换算为液晶屏的显示坐标&#xff0c;将像素点显示到触摸坐标的位…

学习Python中turtle模块的基本用法(4:绘制科赫曲线和谢尔宾斯基三角形)

科赫曲线和谢尔宾斯基三角形是常见的分形图形&#xff08;详细介绍见参考文献1&#xff09;&#xff0c;本文使用turtle库绘制这两类图形。 科赫曲线 科赫曲线的详细介绍见参考文献2&#xff0c;其中的绘图思路是“画正三角形&#xff0c;并把每一边三等分,取三等分后的一边中…

【LeetCode】Day194-超级丑数

题目 313. 超级丑数【中等】 题解 之前做过丑数&#xff0c;规定丑数是质因数只包含2,3,5的正整数&#xff0c;而这道题丑数升级为超级丑数&#xff0c;规定为包含的质因数是在primes数组中的正整数 丑数的题解用动态规划&#xff0c;那么超级丑数也可以利用相同的方法解答…

CSS -- CSS元素显示模式总结(块元素,行内元素,行内块元素)

文章目录CSS 的元素显示模式1 什么是元素显示模式2 块元素3 行内元素4 行内块元素5 元素的显示模式总结CSS 的元素显示模式 1 什么是元素显示模式 作用&#xff1a;网页的标签非常多&#xff0c;在不同地方会用到不同类型的标签&#xff0c;了解他们的特点可以更好的布局我们…

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-(系统+LW)

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff…

开源即巅峰,《Java程序性能优化实战》GitHub三小时标星已超34k

蓦然回首自己做开发已经十年了&#xff0c;这十年中我获得了很多&#xff0c;技术能力、培训、出国、大公司的经历&#xff0c;还有很多很好的朋友。但再仔细一想&#xff0c;这十年中我至少浪费了五年时间&#xff0c;这五年可以足够让自己成长为一个优秀的程序员&#xff0c;…

项目设置分页条件查询接口

一、分页 1、HospPlusConfig中配置分页插件1 /** 2 * 分页插件 3 */ 4 Bean 5 public PaginationInterceptor paginationInterceptor() { 6 return new PaginationInterceptor(); 7 }2、分页Controller方法 HospitalSetController中添加分页方法1 ApiOperation(value "分…

Python学习基础笔记四十二——序列化模块

1、序列化的概念&#xff1a; 序列&#xff1a;就是字符串。 序列化&#xff1a;将原本的字典、列表等内容转换成一个字符串数据类型的过程就叫做序列化。 反序列化&#xff1a;从字符串到数据类型的过程。 2、序列化的目的&#xff1a; 1、以某种存储形式使自定义的数据持…

servlet+Mysql实现的校园论坛管理系统(功能包含登录,首页帖子查看、发帖、个人帖子删除编辑、帖子评论回复、用户管理等)

博客目录servletMysql实现的校园论坛管理系统实现功能截图系统功能使用技术代码完整源码servletMysql实现的校园论坛管理系统 本系统是一个简单的校园论坛系统&#xff0c;学生可以在线发帖并进行帖子评论回复&#xff0c;同同时管理员可以对用户进行管理。 (文末查看完整源码…

win11: cmake+glfw+imgui

下载源码&#xff1a;imgui github地址 将需要的文件拖拽入项目外部库的imgui文件夹 backends文件夹里选择与环境适配的文件&#xff0c;我这里用了glfw和opengl3 目录结构&#xff1a; CMakeLists.txt cmake_minimum_required(VERSION 3.24) project(proforlearn) set(CM…

基于java+springmvc+mybatis+jsp+mysql的实验室计算机故障报修系统

项目介绍 本系统采用java语言开发&#xff0c;后端采用ssm框架&#xff0c;前端采用jsp技术&#xff0c;数据库采用mysql进行数据存储。 前端页面&#xff1a; 功能&#xff1a;首页、设备信息、公告资讯、个人中心、后台管理、联系客服 管理员后台页面&#xff1a; 功能&…

电子学会2020年12月青少年软件编程(图形化)等级考试试卷(二级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题【该题由测评师线下评分】&#xff08;共2题&#xff0c;共30分&#xff09; 青少年软件…