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

本文介绍如何使用Qt在Windows系统上实现全局鼠标事件监听。通过使用Windows低级鼠标钩子,即使应用程序窗口失去焦点也能捕获鼠标事件,并将其转换为Qt的事件格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Qt实现全局鼠标事件监听器-Windows版🍇

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

1、概述🍈

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

2、实现效果🍉

在这里插入图片描述

3、实现方式🍊

  1. 使用SetWindowsHookExW()函数挂钩低级鼠标钩子;
  2. 通过回调函数LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)监听到全局鼠标事件;
  3. wParam参数表示信号类型:
    1. WM_LBUTTONDOWN:鼠标左键按下
    2. WM_LBUTTONUP:鼠标左键抬起
    3. WM_MOUSEMOVE:鼠标移动
    4. WM_MOUSEWHEEL:鼠标滚轮
    5. WM_MOUSEHWHEEL:鼠标的水平滚轮倾斜或旋转(一般没用)
    6. WM_RBUTTONDOWN :鼠标右键按下
    7. WM_RBUTTONUP:鼠标右键抬起
  4. 使用MSLLHOOKSTRUCT * msll = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);将lParam转换为MSLLHOOKSTRUCT结构体的指针,可通过这个结构体获取当前鼠标的坐标或者鼠标滚轮向前还是向后滚动的值。
  5. 然后将获取到的鼠标事件映射为QMouseEvent、QWheelEvent事件,发送给当前程序使用;
  6. 这里我使用的是QMouseEvent、QWheelEvent指针进行发送,由于QMouseEvent、QWheelEvent没有默认无参构造,所以在Linux下不支持使用信号发送QMouseEvent、QWheelEvent变量,所以只能使用指针;
  7. 因为传递的是指针,所以在接收信号的槽函数里使用完后需要Delete,避免内存泄漏;
  8. 简易这个信号只绑定一次,避免多个槽函数里使用同一个指针,一个槽函数释放了另外一个槽函数里出现野指针或者重复释放。
  9. 不使用时需要使用UnhookWindowsHookEx()函数删除 SetWindowsHookEx ()函数在挂钩链中安装的挂钩过程。

4、关键代码🍋

  • 由于使用到了系统API,所以pro文件中需要链接系统库
win32 {
LIBS+= -luser32    # 使用WindowsAPI需要链接库
}
  • 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_win.cpp
#include "globalmouseevent.h"
#if defined(Q_OS_WIN)
#include <QDebug>
#include <QCursor>
#include <QMouseEvent>

#include "Windows.h"


static HHOOK g_hook = nullptr;
/**
 * @brief           处理鼠标事件的回调函数,由于这不是一个成员函数,所以需要通过中间单例类mouseEvent将鼠标信号传递出来
 *                  具体内容看https://learn.microsoft.com/zh-cn/previous-versions/windows/desktop/legacy/ms644986(v=vs.85)
 * @param nCode     挂钩过程用于确定如何处理消息的代码。如果nCode小于零,则挂钩过程必须将消息传递给 CallNextHookEx 函数而不进行进一步处理,并且应返回CallNextHookEx返回的值
 * @param wParam    信号类型:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE、WM_MOUSEWHEEL、WM_MOUSEHWHEEL、WM_RBUTTONDOWN 或WM_RBUTTONUP(鼠标中键点击和拓展按还没找到怎么弄)。
 * @param lParam    MSLLHOOKSTRUCT结构体指针
 * @return
 */
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    QPoint point = QCursor::pos();  // 获取鼠标当前位置
    switch (wParam)
    {
    case WM_LBUTTONDOWN:   // 鼠标左键按下
        emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
        break;
    case WM_MOUSEMOVE:     // 鼠标移动
        emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseMove, point, Qt::NoButton, Qt::NoButton, Qt::NoModifier));
        break;
    case WM_RBUTTONDOWN:   // 鼠标右键按下
        emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier));
        break;
   case WM_RBUTTONUP:     // 鼠标右键抬起
        emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier));
        break;
    case WM_LBUTTONUP:     // 鼠标左键抬起
        emit GlobalMouseEvent::getInstance()->mouseEvent(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
        break;
    case WM_MOUSEWHEEL:    // 鼠标滚轮
    {
        MSLLHOOKSTRUCT * msll = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
//        qDebug() << QString("坐标:(%1, %2)").arg(msll->pt.x).arg(msll->pt.y);     // 获取鼠标坐标
        int delta = GET_WHEEL_DELTA_WPARAM(msll->mouseData);                     // 获取滚轮状态,向前:120,向后-120
        emit GlobalMouseEvent::getInstance()->wheelEvent(new QWheelEvent(point, delta, Qt::MiddleButton, Qt::NoModifier));
        break;
    }
    default:
        break;
    }

    return CallNextHookEx(nullptr, nCode, wParam, lParam);   // 注意这一行一定不能少,否则会出大问题
}


/**
 * @brief  安装全局鼠标事件监听器
 * @return
 */
bool GlobalMouseEvent::installMouseEvent()
{
    if(g_hook) return true;     // 避免重复安装
    /**
     * WH_KEYBOARD_LL 为全局键盘钩子, WH_MOUSE_LL 为全局鼠标钩子
     * 详细说明看官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexw
     */
    g_hook = SetWindowsHookExW(WH_MOUSE_LL, LowLevelMouseProc, GetModuleHandleW(nullptr), 0);
    return g_hook;
}

/**
 * @brief   卸载全局鼠标事件监听器
 * @return
 */
bool GlobalMouseEvent::removeMouseEvent()
{
    if(!g_hook) return true;   // 避免重复卸载
    bool ret = UnhookWindowsHookEx(g_hook);
    if(ret)
    {
        g_hook = nullptr;
        return true;
    }
    return false;
}

#endif

5、源代码🍌

🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mahuifa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值