Qt实现全局鼠标事件监听器-Linux
创始人
2024-04-22 06:42:53
0

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,其它的没有测试;
    • 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(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 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include      //  如果找不到可以安装sudo apt-get install xorg-dev
#include #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 <<" " <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 连接  Truereturn 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(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直接下载仓库,然后引用到自己的程序中。

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

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...