QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接

2023-11-05

QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接

读取外设信息

鼠标、键盘、触屏等外部输入设备是以Plugin的方式加载的。
在QGuiApplication初始化时从argv和环境变量QT_QPA_GENERIC_PLUGINS读取所有的插件信息,里面就包括鼠标、键盘等。

void QGuiApplicationPrivate::init()
{
    ...
    QList<QByteArray> pluginList;
    ...
        for (int i=1; i<argc; i++) {
        ...
            if (strcmp(arg, "-plugin") == 0) {
                if (++i < argc)
                    pluginList << argv[i];
    ...
     
    QByteArray envPlugins = qgetenv("QT_QPA_GENERIC_PLUGINS");
    if (!envPlugins.isEmpty())
        pluginList += envPlugins.split(',');
    ...
    
    init_plugins(pluginList);
   ...
}

建立外设连接

init_plugins

获取到设备列表后,调用init_plugins来初始化所有插件。

static void init_plugins(const QList<QByteArray> &pluginList)
{
    for (int i = 0; i < pluginList.count(); ++i) {
        QByteArray pluginSpec = pluginList.at(i);
        int colonPos = pluginSpec.indexOf(':');
        QObject *plugin;
        if (colonPos < 0)
            plugin = QGenericPluginFactory::create(QLatin1String(pluginSpec), QString());
        else
            plugin = QGenericPluginFactory::create(QLatin1String(pluginSpec.mid(0, colonPos)),
                                                   QLatin1String(pluginSpec.mid(colonPos+1)));
        if (plugin)
            QGuiApplicationPrivate::generic_plugin_list.append(plugin);
        else
            qWarning("No such plugin for spec \"%s\"", pluginSpec.constData());
    }
}

其中干活的是QGenericPluginFactory::create,不过这个函数也不是根据设备信息来创建逻辑设备的:

QObject *QGenericPluginFactory::create(const QString& key, const QString &specification)
{
    return qLoadPlugin<QObject, QGenericPlugin>(loader(), key.toLower(), specification);
}

来看下qLoadPlugin这个模板函数:

template <class PluginInterface, class FactoryInterface, typename ...Args>
PluginInterface *qLoadPlugin(const QFactoryLoader *loader, const QString &key, Args &&...args)
{
    const int index = loader->indexOf(key);
    if (index != -1) {
        QObject *factoryObject = loader->instance(index);
        if (FactoryInterface *factory = qobject_cast<FactoryInterface *>(factoryObject))
            if (PluginInterface *result = factory->create(key, std::forward<Args>(args)...))
                return result;
    }
    return nullptr;
}

把模板参数填上,关键语句就是:

const int index = loader()->indexof(key);
QGenericPlugin * factory = loader()->instance(key);
QObject *result = factory->create(key, specification);

QEvdevMousePlugin

Qt内置了好几个QGenericPlugin,比如:QEvdevMousePlugin、QEvdevKeyboardPlugin、QEvdevTouchScreenPlugin等,从名称可以看出来他们是干啥的了。
QEvdevMousePlugin::create()创建鼠标设备:

QObject* QEvdevMousePlugin::create(const QString &key,
                                   const QString &specification)
{
    if (!key.compare(QLatin1String("EvdevMouse"), Qt::CaseInsensitive))
        return new QEvdevMouseManager(key, specification);
    return 0;
}

QEvdevMouseManager

创建了QEvdevMouseManager,这个类很重要,它负责管理所有的鼠标设备,包括热插拔的,我们看他的构造函数,构造函数比较长,我们分三段看,第一段,添加当前设备,先通过spec参数解析,解析的结果为空再启用设备发现:

QEvdevMouseManager::QEvdevMouseManager(const QString &key, const QString &specification, QObject *parent)
    : QObject(parent), m_x(0), m_y(0), m_xoffset(0), m_yoffset(0)
{
    ...
    auto parsed = QEvdevUtil::parseSpecification(spec);
	...
    for (const QString &device : qAsConst(parsed.devices))
        addMouse(device);

    if (parsed.devices.isEmpty()) {
        if (auto deviceDiscovery = QDeviceDiscovery::create(QDeviceDiscovery::Device_Mouse | QDeviceDiscovery::Device_Touchpad, this)) {
            // scan and add already connected keyboards
            const QStringList devices = deviceDiscovery->scanConnectedDevices();
            for (const QString &device : devices)
                addMouse(device);

第二段,处理设备热插拔,发现新设备插入调用addMouse添加,发现设备移除调用removeMouse:

            connect(deviceDiscovery, &QDeviceDiscovery::deviceDetected,
                    this, &QEvdevMouseManager::addMouse);
            connect(deviceDiscovery, &QDeviceDiscovery::deviceRemoved,
                    this, &QEvdevMouseManager::removeMouse);
        }
    }

第三段,处理光标:

    QInputDeviceManager *manager = QGuiApplicationPrivate::inputDeviceManager();
    connect(manager, &QInputDeviceManager::cursorPositionChangeRequested, [this](const QPoint &pos) {
        m_x = pos.x();
        m_y = pos.y();
        clampPosition();
    });
}

接下来重点看,拿到设备后如何关联到qt中,来看addMouse:

void QEvdevMouseManager::addMouse(const QString &deviceNode)
{
    auto handler = QEvdevMouseHandler::create(deviceNode, m_spec);
    if (handler) {
        connect(handler.get(), &QEvdevMouseHandler::handleMouseEvent,
                this, &QEvdevMouseManager::handleMouseEvent);
        connect(handler.get(), &QEvdevMouseHandler::handleWheelEvent,
                this, &QEvdevMouseManager::handleWheelEvent);
        m_mice.add(deviceNode, std::move(handler));
        updateDeviceCount();
    } 
}

这里创建了MouseHandler,并连接了handleMouseEvent来处理鼠标事件,连接了handleWheelEvent来处理滚轮事件。

QEvdevMouseHandler

先看下QEvdevMouseHandler::create:

std::unique_ptr<QEvdevMouseHandler> QEvdevMouseHandler::create(const QString &device, const QString &specification)
{
	...
    int fd = qt_safe_open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
    if (fd >= 0) {
        ::ioctl(fd, EVIOCGRAB, grab);
        return std::unique_ptr<QEvdevMouseHandler>(new QEvdevMouseHandler(device, fd, abs, compression, jitterLimit));
    } 
}

QEvdevMouseHandler::create打开了设备,将文件描述符传给了QEvdevMouseHandler的构造函数。
看下QEvdevMouseHandler构造函数:

QEvdevMouseHandler::QEvdevMouseHandler(const QString &device, int fd...)
    : m_device(device), m_fd(fd)...
{
    ...
    m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
    connect(m_notify, &QSocketNotifier::activated,
            this, &QEvdevMouseHandler::readMouseData);
}

快要接近真相了,这里创建了QSocketNotifier来监听打开的鼠标设备并连接了activated信号,当设备有数据可读时,就会发出activated信号,接着就到readMouseData,这个槽函数比较长,感兴趣的小伙伴可以去看下,这里贴上部分代码:

void QEvdevMouseHandler::readMouseData()
{
	struct ::input_event buffer[32];
    int result = QT_READ(m_fd, reinterpret_cast<char *>(buffer) + n, sizeof(buffer) - n);
    for (int i = 0; i < n; ++i) {
        struct ::input_event *data = &buffer[i];
        if (data->type == EV_ABS) {
        } else if (data->type == EV_REL) {
            if (data->code == REL_X) {
                ....
            } else if (data->code == ABS_WHEEL) { // vertical scroll
                delta.setY(120 * data->value);
                emit handleWheelEvent(delta);
            } else if (data->code == ABS_THROTTLE) { // horizontal scroll
                ...
            }
        } else  if(data->type == EV_KEY && data->code >= BTN_LEFT && data->code <= BTN_JOYSTICK) {
            Qt::MouseButton button = Qt::NoButton;
            ...
            switch (data->code) {
            case 0x110: button = Qt::LeftButton; break;    // BTN_LEFT
         ...
         sendMouseEvent();
}

上面基本上就是最底层的处理了,读取鼠标设备的数据,分析数据得到坐标和状态,再发出鼠标事件信号和滚轮事件信号。
到这一步,鼠标数据的处理已经看到了,但是鼠标数据如何转化为QMouseEvent的呢?还记得前面

connect(handler.get(), &QEvdevMouseHandler::handleMouseEvent, this, &QEvdevMouseManager::handleMouseEvent);

QEvdevMouseHandler的handleMouseEvent如下:

void QEvdevMouseManager::handleMouseEvent(int x, int y, bool abs, Qt::MouseButtons buttons,
                                          Qt::MouseButton button, QEvent::Type type)
{
    ...
    QWindowSystemInterface::handleMouseEvent(0, pos, pos, buttons, button, type, QGuiApplicationPrivate::inputDeviceManager()->keyboardModifiers());
}

调用了QWindowSystemInterface::handleMouseEvent:

QWindowSystemInterface

QT_DEFINE_QPA_EVENT_HANDLER(void, handleMouseEvent, QWindow *window, ulong timestamp,
                            const QPointF &local, const QPointF &global, Qt::MouseButtons state,
                            Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods,
                            Qt::MouseEventSource source)
{
    Q_ASSERT_X(type != QEvent::MouseButtonDblClick && type != QEvent::NonClientAreaMouseButtonDblClick,
               "QWindowSystemInterface::handleMouseEvent",
               "QTBUG-71263: Native double clicks are not implemented.");
    auto localPos = QHighDpi::fromNativeLocalPosition(local, window);
    auto globalPos = QHighDpi::fromNativePixels(global, window);

    QWindowSystemInterfacePrivate::MouseEvent *e =
        new QWindowSystemInterfacePrivate::MouseEvent(window, timestamp, localPos, globalPos,
                                                      state, mods, button, type, source);
    QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
}

根据入参传入的鼠标数据信息构造了QMouseEvent,然后通过handleWindowSystemEvent进行处理,根据同步或异步来决定是直接处理还是放到消息队列。

总结

到这里结束,概括起来就是,将设备的文件描述符传给QSocketNotifier来侦听设备,有数据时读取数据,再分析数据得到鼠标坐标和状态,发出相应的信号,最后由handleWindowSystemEvent来将鼠标事件直接处理或者放入消息队列。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接 的相关文章

  • PHP 无法打开流:是一个目录

    非常简单的 PHP 脚本 我在我亲自设置的 Ubuntu Web 服务器上的 EE 模板中运行 我知道这与权限有关 并且我已经将我尝试写入的目录的所有者更改为 Apache 用户 我得到的错误是 遇到 PHP 错误 严重性 警告 消息 fi
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • ftrace:仅打印trace_printk()的输出

    是否可以只转储trace printk 输出于trace文件 我的意思是过滤掉函数跟踪器 或任何其他跟踪器 中的所有函数 一般来说 您可以在选项目录中关闭选项 sys kernel debug tracing options Use ls显
  • 是否可以创建一个脚本来保存和恢复权限?

    我正在使用 Linux 系统 需要对一组嵌套文件和目录进行一些权限实验 我想知道是否没有某种方法可以保存文件和目录的权限 而不保存文件本身 换句话说 我想保存权限 编辑一些文件 调整一些权限 然后将权限恢复到目录结构中 将更改的文件保留在适
  • 如何减去两个 gettimeofday 实例?

    我想减去两个 gettimeofday 实例 并以毫秒为单位给出答案 这个想法是 static struct timeval tv gettimeofday tv NULL static struct timeval tv2 gettime
  • 如何在qt中创建正确的退出按钮

    我正在尝试创建一个退出按钮来正确关闭我在 QT 中制作的 GUI 我尝试通过以下方式执行此操作 include
  • 进程退出后 POSIX 名称信号量不会释放

    我正在尝试使用 POSIX 命名信号量进行跨进程同步 我注意到进程死亡或退出后 信号量仍然被系统打开 在进程 打开它 死亡或退出后是否有办法使其关闭 释放 早期的讨论在这里 当将信号量递减至零的进程崩溃时 如何恢复信号量 https sta
  • 如果在等待“read -s”时中断,在子进程中运行 bash 会破坏 tty 的标准输出吗?

    正如 Bakuriu 在评论中指出的那样 这基本上与BASH 输入期间按 Ctrl C 会中断当前终端 https stackoverflow com questions 31808863 bash ctrlc during input b
  • 确定我可以向文件句柄写入多少内容;将数据从一个 FH 复制到另一个 FH

    如何确定是否可以将给定数量的字节写入文件句柄 实际上是套接字 或者 如何 取消读取 我从其他文件句柄读取的数据 我想要类似的东西 n how much can I write w handle n read r handle buf n a
  • NUMA 在虚拟内存中是如何表示的?

    有许多资源 https en wikipedia org wiki Non uniform memory access从硬件角度描述NUMA的架构性能影响 http practical tech com infrastructure num
  • 如何查询X11显示分辨率?

    这似乎是一个简单的问题 但我找不到答案 如何查询 通过 X11 存在哪些监视器及其分辨率 查看显示宏 http tronche com gui x xlib display display macros html and 屏幕宏 http
  • 捕获实时流量时如何开启纳秒精度?

    如何告诉 libpcap v1 6 2 将纳秒值存储在struct pcap pkthdr ts tv usec 而不是微秒值 捕获实时数据包时 Note This question is similar to How to enable
  • 为什么C Clock()返回0

    我有这样的事情 clock t start end start clock something else end clock printf nClock cycles are d d n start end 我总是得到输出 时钟周期是 0
  • Capistrano 3 部署无法连接到 GitHub - 权限被拒绝(公钥)

    我使用 Capistrano v3 和 capistrano symfony gem 设置了以下部署脚本 我正在使用 Ubuntu 14 4 部署到 AWS EC2 实例 我正在连接从 AWS 下载的 pem 文件 我的deploy rb中
  • Mcrt1.o和Scrt1.o有什么用?

    我坚持使用以下两个文件 即 Mcrt1 o 和 Scrt1 o 谁能帮我知道这两个文件的用途 如何使用它 我们以 gcrt1 o 为例 在使用 pg 选项编译进行性能测试时非常有用 谢谢 表格的文件 crt o总是 C 运行时启动代码 大部
  • Awk - 计算两个文件之间的每个唯一值和匹配值

    我有两个文件 首先 我尝试获取第 4 列中每个唯一字段的计数 然后匹配第二个文件的第二列中的唯一字段值 File1 第 4 列的每个唯一值和 File2 第 2 列包含我需要在两个文件之间匹配的值 所以本质上 我试图 gt 如果 file2
  • QWidget::showMinimized() 不起作用

    在 Ubuntu 13 04 上 如果使用QWidget showMinimized 为了最小化窗口 我发现通过单击系统任务栏上的应用程序图标恢复它后 调用QWidget showMinimized 无法工作 connect minimum
  • Gearman,php 扩展问题:使用终端在 .. 中找不到类“GearmanWorker”,但可以在浏览器上使用

    我最近在 ubuntu 10 04 上安装了 gearman 并安装了它的 pecl 扩展 现在 当我在浏览器中运行一个 php 文件时 其中包含 client new GearmanWorker die var Dump client I
  • vagrant ssh -c 并在连接关闭后保持后台进程运行

    我正在编写一个脚本来启动和后台流浪机器内的进程 似乎每次脚本结束和 ssh 会话结束时 后台进程也会结束 这是我正在运行的命令 vagrant ssh c cd vagrant src nohup python hello py gt he
  • Linux 上的 RTLD_LOCAL 和dynamic_cast

    我们有一个由应用程序中的一些共享库构成的插件 我们需要在应用程序运行时更新它 出于性能原因 我们在卸载旧插件之前加载并开始使用新插件 并且只有当所有线程都使用旧插件完成后 我们才卸载它 由于新插件和旧插件的库具有相同的符号 我们dlopen

随机推荐

  • Ubuntu下安装Chrome浏览器

    一 下载包 通过直接下载安装Google Chrome浏览器deb包 打开Ubuntu终端 以下为32位版本 使用下面的命令 wget https dl google com linux direct google chrome stabl
  • 模拟相机视频输入方案-----模拟转MIPI /DVP方案

    技术交流 请加微信video D 概述 由于模拟相机在监控领域的优势 以及模拟相机成本优势 目前模拟相机方案需求还是很多的 具体模拟解码芯片介绍 目前接触的有以下几种 1 Nextchip系列 韩国NEXTCHIP系列 主攻ISP AHD
  • 开发微信公众号支付代码

    一 url传入当前页面url地址或者微信公众平台配置的域名根目录 使用window location href方法获取 二 下面代码请结合微信公众号开发文档 微信公众号开发文档 function test url uni request u
  • 如何在Linux下安装vim编辑器

    目前的Ubuntu版本都安装了vi编辑器 vim编辑器可以看做vi编辑器的升级版 可以识别特殊字符 显示不同颜色 目录 第一步 第二步 第三步 第四步 第一步 在terminal里面输入vi命令后按下tab键可以看到当前vi可以执行的命令
  • 算法的时间及空间复杂度

    简介 java系列技术分享 持续更新中 初衷 一起学习 一起进步 坚持不懈 如果文章内容有误与您的想法不一致 欢迎大家在评论区指正 希望这篇文章对你有所帮助 欢迎点赞 收藏 留言 更多文章请点击 文章目录 一 什么是算法 二 算法初体验 案
  • C++ String替换&分割指定字符串

    C String替换 分割指定字符串 1 C String替换指定字符串 C 的string对象提供了replace方法来实现字符串的替换 本文实现对于将字符串中某个字符串全部替换的功能 string replace all string
  • MOOC清华《程序设计基础》第5章:求n的阶乘(用递推法做)

    使用递推思想 求解正整数的阶乘 本算法的数学模型为 n n 1 n include
  • CCF CSP 202206-3角色授权【70分】

    include
  • ts文件服务器端加密,加密ts文件解密

    EXTM3U EXT X VERSION 3 EXT X MEDIA SEQUENCE 0 EXT X ALLOW CACHE YES EXT X TARGETDURATION 13 EXT X KEY METHOD AES 128 URI
  • Linux动态库(.so)搜索路径

    Linux动态库 so 搜索路径 众所周知 Linux动态库的默认搜索路径是 lib和 usr lib 动态库被创建后 一般都复制到这两个目录中 当程序执行时需要某动态库 并且该 动 态库还未加载到内存中 则系统会自动到这两个默认搜索路径中
  • 网站无法访问的一些问题与解决

    最近阿里云的服务器上跑的博客出现了一些问题 上阿里云官网看了一下 原来是忘记续费了 也没给我发邮件 悄摸的给我停了 续费之后 问题依旧 可以使用xshell5进行远程连接 但是在浏览器上不能进行访问 会显示这个 很尴尬 在本地ping主机是
  • cuda异步并行执行

    异步函数使得主机端与设备端并行执行 控制在设备还没有完成前就被返回给主机线程 包括 kernel启动 以Async为后缀的内存拷贝函数 device到device内存拷贝函数 存储器初始化函数 比如cudaMemset cudaMemset
  • simulink中积分环节、惯性环节、比例环节

    第一步 第二步 第三步 注 适当修改参数即可变成所需环节 如下所示 修改为 或者为 抑或
  • hadoop搭建好,启动服务后,无法从web界面访问50070

    在hadoop完全分布式搭建好以后 从主节点启动正常 使用jps查看启动的进程 正常 在几个从节点上使用jps查看 显示正常 但从web上输入下面网址 http 主节点IP 50070 无法正常连接显示 试了若干网上查到的方法 是通过下面方
  • 怎样选择合适的循环体(do&while、while和for)

    我们都知道 循环体可以有五种 while do while for goto和递归 虽然理论上任何循环都可以用其他四种转换 但是因为goto在安全性以及在功能上能够被取代的特点 所以一般不会用到 而递归的特殊性和编写的困难性使递归的登场次数
  • 今天带你体验79毫秒启动一个SpringBoot项目

    大家好 我是雷小帅 今天来个项目实战 先抛一个问题 大家在自己电脑上启动一个 spring boot 项目需要花费多久 根据项目大小和机器环境 花费几秒到几十秒的人应该都有 最近 spring 官方推出了一项技术可以将项目的启动时间缩短到
  • 荒野行动服务器维护啥时好,荒野行动服务器真的极差

    说起 荒野行动 这个游戏 我想大家都不陌生 毕竟这个是在端游吃鸡出现不到三个月的时间 就由网易出品的一款吃鸡手游 可以说这个是第一款吃鸡手游 我玩了快半年的 荒野行动 了 先不说这个游戏咋样 咱们先说一说官网的态度 当真是不想让我们在继续玩
  • Vue2.0与Vue3.0的区别

    Vue2 0 Vue3 0 双向绑定 利用ES5的ApiObject defineProperty 对数据进行劫持 并结合发布订阅模式的方式实现 利用Es6的Proxy 对数据进行代理的方式实现 根节点 根节点只能是一个 根节点可以是多个
  • nginx之反向代理服务器

    本文摘抄自 深入理解Nginx 模块开发与架构解析 反向代理 reverse proxy 方式是指用代理服务器来接受Internet上的连接请求 然后将请求转发给内部网络中的上游服务器 并将从上游服务器上得到的结果返回给Internet上请
  • QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接

    QGuiApplication底层鼠标处理 一 使用QSocketNotifier建立侦听连接 读取外设信息 建立外设连接 init plugins QEvdevMousePlugin QEvdevMouseManager QEvdevMo