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

8 篇文章 1 订阅

读取外设信息

鼠标、键盘、触屏等外部输入设备是以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来将鼠标事件直接处理或者放入消息队列。

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 很容易,只需要在Spring Boot项目中使用@EnableAutoConfiguration注解,然后使用@EnableWebMvc注解来激活Web MVC配置,最后使用@ServletComponentScan注解扫描子服务的Servlet,然后就可以通过指定的端口监听该服务了。 ### 回答2: 在Spring Boot中实现一个子服务监听一个端口的步骤如下: 1. 首先,创建一个Spring Boot项目,并添加相关的依赖,包括`spring-boot-starter-web`和`spring-boot-starter-tomcat`。 2. 在`application.properties`或`application.yml`配置文件中,设置该子服务的端口号。例如,使用`server.port=8081`来设置端口号为8081。 3. 创建一个新的Controller类,用于处理该子服务的请求。例如,创建一个名为`SubServiceController`的类,并添加相关的请求处理方法。 ```java @RestController @RequestMapping("/sub-service") public class SubServiceController { @GetMapping("/example") public String handleRequest() { return "This is a response from the sub-service!"; } } ``` 4. 运行Spring Boot应用程序,并访问`http://localhost:8081/sub-service/example`,应该能够看到返回的响应信息。 以上就是实现一个子服务侦听一个端口的简单步骤。可以根据需求进一步扩展该子服务,添加更多的请求处理方法和业务逻辑。 ### 回答3: 在Spring Boot中实现一个子服务侦听一个端口可以通过以下步骤完成: 1. 创建一个新的Spring Boot项目,并将其作为父项目。 2. 在父项目中创建一个模块作为子服务,可以通过Maven或Gradle进行管理。 3. 在子服务模块的pom.xml或build.gradle文件中添加Spring Boot的依赖。 4. 创建一个新的Spring Boot应用程序类作为子服务的入口点。 5. 在子服务应用程序类中使用`@SpringBootApplication`注解标记该类作为Spring Boot应用程序的入口。 6. 在子服务应用程序类中定义一个`@RestController`类或者`@Controller`类用于处理HTTP请求。 7. 在定义的控制器类中添加相应的请求处理方法,并使用`@RequestMapping`或其他相关注解进行映射。 8. 在子服务应用程序类中声明并初始化一个`EmbeddedServletContainerCustomizer` Bean对象,并实现接口中的`customize`方法。 9. 在`customize`方法中通过调用`container.setPort`方法设置子服务的监听端口。 10. 启动父项目,并访问子服务的URL加上设置的端口号即可访问该子服务。 以上是一个实现简单子服务侦听一个端口的步骤,根据具体需求,你还可以根据需要添加相关的配置和功能。希望可以对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值