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电脑再说。
- 有时候我们想获取到【系统全局鼠标事件】,使用Qt的鼠标事件、事件过滤器之类的都无法实现,因为当鼠标移出当前窗口或者当前窗口失去焦点、窗口最小化了就无法获取到鼠标事件了;
- 而Linux下想要监听到全局鼠标事件就需要使用到X11或者xcb的API来实现;
- 在这个类中通过X11的API监听到全局鼠标事件(我没有使用Xcb);
- 然后将监听到的鼠标事件映射为QMouseEvent事件,便于在Qt里面使用。
2、实现效果🍰
3、实现方式🦀
- 使用
XRecordEnableContext()
函数绑定用于监听全局鼠标事件的回调函数;- 由于XRecordEnableContext会一直阻塞,所以需要在子线程中调用;
- 通过回调函数
void callback(XPointer ptr, XRecordInterceptData* data)
监听到全局鼠标事件;- 使用
xEvent * event = reinterpret_cast<xEvent*>(data->data);
将XRecordInterceptData::data
转换为xEvent结构体的指针,可通过这个结构体获取当前鼠标的坐标或者鼠标滚轮向前还是向后滚动的值。- 然后将获取到的鼠标事件映射为QMouseEvent、QWheelEvent事件,发送给当前程序使用;
- 这里我使用的是QMouseEvent、QWheelEvent指针进行发送,由于QMouseEvent、QWheelEvent没有默认无参构造,所以在Linux下不支持使用信号发送QMouseEvent、QWheelEvent变量,所以只能使用指针;
- 因为传递的是指针,所以在接收信号的槽函数里使用完后需要Delete,避免内存泄漏;
- 简易这个信号只绑定一次,避免多个槽函数里使用同一个指针,一个槽函数释放了另外一个槽函数里出现野指针或者重复释放。
- 不使用时需要使用
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
直接下载仓库,然后引用到自己的程序中。
🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆