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

这篇具有很好参考价值的文章主要介绍了Qt实现全局鼠标事件监听器-Linux。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

更多精彩内容
👉个人内容分类汇总 👈
👉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、实现效果🍰

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

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直接下载仓库,然后引用到自己的程序中。

🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆🍆文章来源地址https://www.toymoban.com/news/detail-509223.html

到了这里,关于Qt实现全局鼠标事件监听器-Linux的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • laravel框架 - 事件与监听器

    在 appProviders 下的 EventServiceProvider.php 中添加我们定义的事件与监听器 使用 event:generate 命令即可。 这个命令会生成在 EventServiceProvider 中列出的所有事件和监听器。当然,已经存在的事件和监听器将保持不变:  在Applisteners目录下,打开你新创建的 监听器 文件 SendMessage.php

    2024年02月07日
    浏览(49)
  • Spring高手之路7——事件机制与监听器的全面探索

      观察者模式是一种行为设计模式,它定义了对象之间的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。在这个模式中,改变状态的对象被称为主题,依赖的对象被称为观察者。 举个实际的例子: 事件源(Event Source) :可以视

    2024年02月11日
    浏览(43)
  • Unity UGUI的EventTrigger (事件监听器)组件的介绍及使用

    EventTrigger是Unity UGUI中的一个组件,用于监听和响应UI元素的各种事件,例如点击、拖拽、进入、离开等。通过EventTrigger组件,我们可以方便地为UI元素添加各种交互行为。 EventTrigger组件通过监听UI元素上的事件,并在事件触发时执行相应的回调函数。每个UI元素可以添加多个

    2024年02月15日
    浏览(52)
  • Listener监听器,实现一个显示在线用户人数

    目录 Listener监听器,实现一个显示在线用户人数 每博一文案 1. Listener 监听器的概述 2. Listener 监听器在 Servlet 的体系结构 3. 编写一个 Listener 监听器的过程 3.1 ServletContextListener 4. 其他Listener 监听器的使用: 4.1 ServletRequestListener 4.2 HttpSessionListener 4.3 HttpSessionAttributeListener 4.4 H

    2024年02月03日
    浏览(43)
  • web3j的基础用法-6合约的监听器事件Event和过滤器EthFilter,以及NullPointed,调用失败导致的bug解决

    本篇以Uniswap为例(https://uniswap.org/) 合约地址 :0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 (Uni) 监听合约Tranfer事件 调用代码 核心代码实现在这里 之前实验全量区块,导致请求多次失败,是由于个人RPC节点的请求和数据有限,为了测试出结果,从13763721L block到当前,结果毫秒级返

    2024年02月11日
    浏览(50)
  • 207、SpringBoot 整合 RabbitMQ 实现消息的发送 与 接收(监听器)

    1、ContentUtil 先定义常量 2、RabbitMQConfig 创建队列的两种方式之一: 配置式: 在容器中配置 org.springframework.amqp.core.Queue 类型的Bean,RabbitMQ将会自动为该Bean创建对应的队列。 就是在配置类中创建一个生成消息队列的@Bean。 问题: 用 @Configuration 注解声明为配置类,但是项目启动

    2024年02月06日
    浏览(56)
  • HttpSessionListener监听器和HttpSessionAttributeListener监听器

    1.作用:监听Session创建或销毁,即生命周期监听 2.相关方法: 3.使用场景: 和前面的ServletContextListener等一样,可以用于监控用户上线和离线 4.代码 HttpSessionListener监听器 实现类 HttpSessionAttributeListener监听器 1.作用:监听Session属性的变化,使用少 2.相关方法: 3.代码 监听器 实

    2024年02月04日
    浏览(50)
  • Listener监听器----HttpServletRequest对象的生命周期监听器

    一、HttpServletRequest对象的生命周期监听器         ServletRequestListener接口定义了ServletRequest(是HttpServletRequest接口的父接口类型)对象生命周期的监听行为。 void requestInitialized(ServletRequestEvent sre)         HttpServletRequest对象创建后会触发该监听器方法,并将已创建HttpServletR

    2024年01月23日
    浏览(61)
  • camunda执行监听器和任务监听器有什么区别

    Camunda的执行监听器和任务监听器是用于添加自定义逻辑的监听器,它们的区别在于作用对象和触发事件的不同。 执行监听器是与BPMN流程中的各种流程元素(例如开始事件、用户任务、服务任务、网关等)相关联的。执行监听器可以在流程元素执行前、执行后或抛出异常时添

    2024年02月04日
    浏览(56)
  • 消息监听器和消息监听容器

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 消息监听器顾名思义用来接收消息,它是使用消息监听容器的必须条件。目前有8个消息监听器: 使用自动提交或容器管理的提交方法之一,处理从 Kafka 消费者 p

    2024年02月07日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包