Qt實(shí)現(xiàn)全局鼠標(biāo)事件監(jiān)聽器-Linux版??
更多精彩內(nèi)容 |
---|
??個(gè)人內(nèi)容分類匯總 ?? |
??Qt自定義模塊、工具?? |
1、概述??
- Qt版本:V5.12.5
- 兼容系統(tǒng):
- Windows:這里測(cè)試了Windows10,其它的版本沒有測(cè)試;
- Linux:這里測(cè)試了ubuntu18.04、20.04,其它的沒有測(cè)試;
- Mac:等啥時(shí)候我有了Mac電腦再說。
- 有時(shí)候我們想獲取到【系統(tǒng)全局鼠標(biāo)事件】,使用Qt的鼠標(biāo)事件、事件過濾器之類的都無法實(shí)現(xiàn),因?yàn)楫?dāng)鼠標(biāo)移出當(dāng)前窗口或者當(dāng)前窗口失去焦點(diǎn)、窗口最小化了就無法獲取到鼠標(biāo)事件了;
- 而Linux下想要監(jiān)聽到全局鼠標(biāo)事件就需要使用到X11或者xcb的API來實(shí)現(xiàn);
- 在這個(gè)類中通過X11的API監(jiān)聽到全局鼠標(biāo)事件(我沒有使用Xcb);
- 然后將監(jiān)聽到的鼠標(biāo)事件映射為QMouseEvent事件,便于在Qt里面使用。
2、實(shí)現(xiàn)效果??
文章來源:http://www.zghlxwxcb.cn/news/detail-509223.html
3、實(shí)現(xiàn)方式??
- 使用
XRecordEnableContext()
函數(shù)綁定用于監(jiān)聽全局鼠標(biāo)事件的回調(diào)函數(shù);- 由于XRecordEnableContext會(huì)一直阻塞,所以需要在子線程中調(diào)用;
- 通過回調(diào)函數(shù)
void callback(XPointer ptr, XRecordInterceptData* data)
監(jiān)聽到全局鼠標(biāo)事件;- 使用
xEvent * event = reinterpret_cast<xEvent*>(data->data);
將XRecordInterceptData::data
轉(zhuǎn)換為xEvent結(jié)構(gòu)體的指針,可通過這個(gè)結(jié)構(gòu)體獲取當(dāng)前鼠標(biāo)的坐標(biāo)或者鼠標(biāo)滾輪向前還是向后滾動(dòng)的值。- 然后將獲取到的鼠標(biāo)事件映射為QMouseEvent、QWheelEvent事件,發(fā)送給當(dāng)前程序使用;
- 這里我使用的是QMouseEvent、QWheelEvent指針進(jìn)行發(fā)送,由于QMouseEvent、QWheelEvent沒有默認(rèn)無參構(gòu)造,所以在Linux下不支持使用信號(hào)發(fā)送QMouseEvent、QWheelEvent變量,所以只能使用指針;
- 因?yàn)閭鬟f的是指針,所以在接收信號(hào)的槽函數(shù)里使用完后需要Delete,避免內(nèi)存泄漏;
- 簡(jiǎn)易這個(gè)信號(hào)只綁定一次,避免多個(gè)槽函數(shù)里使用同一個(gè)指針,一個(gè)槽函數(shù)釋放了另外一個(gè)槽函數(shù)里出現(xiàn)野指針或者重復(fù)釋放。
- 不使用時(shí)需要使用
XRecordDisableContext()、XRecordFreeContext()
函數(shù)來關(guān)閉監(jiān)聽。
4、關(guān)鍵代碼??
- 由于使用到了系統(tǒng)API,所以pro文件中需要鏈接系統(tǒng)庫
unix:!macx{
LIBS += -lX11 -lXtst # linux獲取鼠標(biāo)、鍵盤事件信息需要用到xlib,Xtst 可以安裝sudo apt install libxtst-dev
}
- globalmouseevent.h
/******************************************************************************
* @文件名 mouseevent.h
* @功能 全局鼠標(biāo)事件監(jiān)聽類
*
* @開發(fā)者 mhf
* @郵箱 1603291350@qq.com
* @時(shí)間 2022/12/07
* @備注
*****************************************************************************/
#ifndef MOUSEEVENT_H
#define MOUSEEVENT_H
#include <QObject>
class QMouseEvent;
class QWheelEvent;
/**
* 全局鼠標(biāo)事件單例信號(hào)類
*/
class GlobalMouseEvent : public QObject
{
Q_OBJECT
public:
static GlobalMouseEvent* getInstance()
{
static GlobalMouseEvent mouseEvent;
return &mouseEvent;
}
static bool installMouseEvent(); // 安裝全局鼠標(biāo)事件監(jiān)聽器
static bool removeMouseEvent(); // 卸載全局鼠標(biāo)事件監(jiān)聽器
signals:
/**
* @brief 由于傳遞的是指針,為了保證不會(huì)出現(xiàn)內(nèi)存泄露,需要在槽函數(shù)中delete。
* 建議此信號(hào)只綁定一次,因?yàn)槿绻壎ǘ啻慰赡軙?huì)出現(xiàn)一個(gè)槽函數(shù)里把信號(hào)delete了,另外一個(gè)槽函數(shù)還在使用,出現(xiàn)野指針,或者多個(gè)槽函數(shù)多次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:這種方法可以獲取全局鼠標(biāo)事件,但是會(huì)截?cái)嗍髽?biāo)事件,導(dǎo)致其他所有程序都無法獲取到鼠標(biāo)事件
void sleepMsec(int msec)
{
QEventLoop loop; //定義一個(gè)新的事件循環(huán)
QTimer::singleShot(msec, &loop, SLOT(quit()));//創(chuàng)建單次定時(shí)器,槽函數(shù)為事件循環(huán)的退出函數(shù)
loop.exec(); //事件循環(huán)開始執(zhí)行,程序會(huì)卡在這里,直到定時(shí)時(shí)間到,本循環(huán)被退出
}
void MouseEventX11()
{
XEvent xevent;
int grb;
Display* display = XOpenDisplay(NULL); // 首先連接到顯示服務(wù)器
if(!display) return ;
unsigned int t_new=0,t_prev=0,t_diff=0;
int scr = DefaultScreen(display); // 獲取默認(rèn)屏幕編號(hào)
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() << "運(yùn)動(dòng)事件";
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() << "單擊鼠標(biāo)中鍵";
break;
case 3:
qDebug() << "單擊鼠標(biāo)右鍵";
break;
case 4:
qDebug() << "向上滾動(dòng)";
break;
case 5:
qDebug() << "向下滾動(dòng)";
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() << "釋放鼠標(biāo)中鍵";
break;
case 3:
qDebug() << "釋放鼠標(biāo)右鍵";
break;
case 4:
qDebug() << "向上滾動(dòng)";
break;
case 5:
qDebug() << "向下滾動(dòng)";
break;
}
break;
}
}
sleepMsec(1);
}
XUngrabPointer(display,CurrentTime);
}
#else
// 使用static修飾全局函數(shù)和全局變量:只能在本源文件使用
static XRecordContext g_context = 0;
static Display* g_display = nullptr;
static bool init()
{
g_display =XOpenDisplay(nullptr); // 打開與控制顯示器的X服務(wù)器的連接,詳細(xì)說明看【https://tronche.com/gui/x/xlib/display/opening.html】
if(!g_display)
{
qWarning() << "連接X服務(wù)失??!";
return false;
}
XRecordClientSpec clients = XRecordAllClients; // 初始化 XRecordCreateContext 所需的 XRecordClientSpec 參數(shù),XRecordAllClients 的意思是 "記錄所有 X Client" 的事件
XRecordRange*range = XRecordAllocRange(); // 創(chuàng)建 XRecordRange 變量,用于控制記錄事件的范圍
if (!range)
{
qDebug() << "無法分配XRecordRange";
return false;
}
// 會(huì)監(jiān)聽到 first - last之間并包含first和last的所有類型的事件
memset(range, 0, sizeof(XRecordRange));
range->device_events.first = ButtonPress;
range->device_events.last = MotionNotify;
// 根據(jù)上面的記錄客戶端類型和記錄事件范圍來創(chuàng)建 “記錄上下文”
// 然后把 XRecordContext 傳遞給 XRecordEnableContext 函數(shù)來開啟事件記錄循環(huán)
g_context = XRecordCreateContext(g_display, 0, &clients, 1,&range, 1);
XFree(range);
if(g_context == 0)
{
qWarning() << "創(chuàng)建事件記錄上下文失?。?;
return false;
}
XSync(g_display, false); // XSync 的作用就是把上面的X 代碼立即發(fā)給 X Server,這樣 X Server 接受到事件以后會(huì)立即發(fā)送給 XRecord 的 Client 連接 True
return true;
}
/**
* @brief 處理鼠標(biāo)事件的回調(diào)函數(shù),將X11鼠標(biāo)事件轉(zhuǎn)換為Qt鼠標(biāo)事件,通過單例類MouseEvent發(fā)送出去
* @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("鼠標(biāo)坐標(biāo):[%1, %2]").arg(event->u.keyButtonPointer.rootX).arg(event->u.keyButtonPointer.rootY); // 獲取鼠標(biāo)坐標(biāo)
switch (event->u.u.type) // 動(dòng)作類型
{
case ButtonPress: //鼠標(biāo)按下
{
QPoint point = QCursor::pos(); // 獲取鼠標(biāo)當(dāng)前位置
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: // 向前滾動(dòng)
{
emit GlobalMouseEvent::getInstance()->wheelEvent(new QWheelEvent(point, 120, Qt::MiddleButton, Qt::NoModifier));
break;
}
case Button5: // 向后滾動(dòng)
{
emit GlobalMouseEvent::getInstance()->wheelEvent(new QWheelEvent(point, -120, Qt::MiddleButton, Qt::NoModifier));
break;
}
default:
{
qDebug() << QString("未定義的按鍵:%1").arg(event->u.u.detail); // 比如很多鼠標(biāo)邊上會(huì)多幾個(gè)鍵
break;
}
}
break;
}
case MotionNotify: // 鼠標(biāo)移動(dòng)
{
emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseMove, QCursor::pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier));
}
case ButtonRelease: // 鼠標(biāo)釋放
{
QPoint point = QCursor::pos(); // 獲取鼠標(biāo)當(dāng)前位置
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: // 向前滾動(dòng)
{
break;
}
case Button5: // 向后滾動(dòng)
{
break;
}
default:
{
// qDebug() << QString("未定義的按鍵:%1").arg(event->u.u.detail); // 比如很多鼠標(biāo)邊上會(huì)多幾個(gè)鍵
}
}
break;
}
default:
break;
}
}
XRecordFreeData(data);
}
/**
* 調(diào)用 XRecordEnableContext 函數(shù)建立 XRecord 上下文
* X Server 事件一旦發(fā)生就傳遞給事件處理回調(diào)函數(shù)
* XRecordEnableContext 函數(shù)一旦調(diào)用就開始進(jìn)入堵塞時(shí)的事件循環(huán),直到線程或所屬進(jìn)程結(jié)束
*/
static void enableContext()
{
Status ret = XRecordEnableContext(g_display, g_context, callback, nullptr);
XCloseDisplay(g_display); // 關(guān)閉連接
g_display = nullptr;
qDebug() << QString("退出事件監(jiān)聽:%1").arg(ret);
}
#endif
/**
* @brief 安裝全局鼠標(biāo)事件監(jiān)聽器
* @return true:安裝成功 false:失敗
*/
bool GlobalMouseEvent::installMouseEvent()
{
bool ret = init();
if(!ret) return false;
QtConcurrent::run(enableContext); // 由于XRecordEnableContext會(huì)一直阻塞,所以需要在線程中調(diào)用
return true;
}
/**
* @brief 卸載全局鼠標(biāo)事件監(jiān)聽器,注意:如果不卸載事件監(jiān)聽則導(dǎo)致子線程會(huì)一直存在,程序無法正常退出
* @return true:卸載成功 false:失敗
*/
bool GlobalMouseEvent::removeMouseEvent()
{
if(g_context == 0) return false;
Display* display = XOpenDisplay(nullptr); // 這里需要單獨(dú)建立一個(gè)連接來關(guān)閉監(jiān)聽,否則XRecordEnableContext不會(huì)退出
if(!display)
{
qWarning() << "連接X服務(wù)失??!";
return false;
}
XRecordDisableContext(display, g_context);
XFlush(display);
XSync(display, false);
XRecordFreeContext(display, g_context); // 釋放監(jiān)聽上下文,否則XRecordEnableContext不會(huì)退出
g_context = 0;
XCloseDisplay(display);
return true;
}
#endif
5、源代碼??
- gitee
- github
- 全局鼠標(biāo)鍵盤事件監(jiān)聽器倉庫github
- 全局鼠標(biāo)鍵盤事件監(jiān)聽器倉庫gitee
- CSDN
- 可以使用命令
git clone https://gitee.com/mahuifa/QtGlobalEvent.git
直接下載倉庫,然后引用到自己的程序中。
????????????????????????文章來源地址http://www.zghlxwxcb.cn/news/detail-509223.html
到了這里,關(guān)于Qt實(shí)現(xiàn)全局鼠標(biāo)事件監(jiān)聽器-Linux的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!