QGuiApplication底层鼠标处理(二)QSocketNotifier在事件循环中触发activated信号
前言
在 QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接中讲到底层鼠标事件的处理是通过将鼠标设备的文件描述符传给QSocketNotifier,然后连接activated信号来读取鼠标数据,最后将鼠标数据处理成QMouseEvent放到消息队列里这一系列过程来实现的。
但是上一节没有讲QSocketNotifier的activated信号是如何触发的?监听鼠标设备是否需要单独的线程去不断的轮询设备?
QSocketNotifier简介
先来看看QSocketNotifier,它是用来监听文件描述符状态的,当监听的状态满足时就会发出activated信号,比如监听鼠标设备文件的READ状态,当鼠标移动时,就会产生鼠标数据,鼠标设备文件描述符就变成有数据可读状态,即READ状态,此时就会发出activated信号。
QSocketNotifier可以监听以下三种状态:
类型 | 描述 |
---|---|
QSocketNotifier::Read | 有数据可读 |
QSocketNotifier::Write | 可以写入数据 |
QSocketNotifier::Exception | 发生异常,QT帮助文档中有说不建议使用这个。 |
一般就使用Read和Write,另外,以下两点需要注意: |
- 一个QSocketNotifier只能监听一个文件描述符的一个状态,如果要同时监听READ和WRITE,需要创建两个QSocketNotifier
- 不要使用两个QSocketNotifier来监听同一个文件描述符的同一个状态,没意义
一般用法是:打开文件得到文件描述符,然后将文件描述符合监听类型传给QSocketNotifier构造函数来创建QSocketNotifier对象,连接QSocketNotifier的activated信号,在槽函数中去读或写数据。
下面来看下QSocketNotifier的activated的信号触发时机。
QSocketNotifier activated信号的触发
将QSocketNotifier加入侦听列表
先看构造函数:
QSocketNotifier::QSocketNotifier(qintptr socket, Type type, QObject *parent)
: QObject(*new QSocketNotifierPrivate, parent)
{
...
d->threadData->eventDispatcher.loadRelaxed()->registerSocketNotifier(this);
}
在构造函数中通过调用线程eventDispatcher的registerSocketNotifier方法,将自己加入到侦听列表里。
先看下QEventDispatcherUNIXPrivate::socketNotifiers的定义:
QHash<int, QSocketNotifierSetUNIX> socketNotifiers;
struct Q_CORE_EXPORT QSocketNotifierSetUNIX final
{
...
QSocketNotifier *notifiers[3];
};
eventDispatcher里的socketNotifiers是个QHash,存储了所有需要侦听的文件描述符Notifiers,每一个Notifiers包括3种状态的QSocketNotifier指针,所以前面说“不要使用两个QSocketNotifier来监听同一个文件描述符的同一个状态”。
看完上面的定义后再来看registerSocketNotifier的源码:
void QEventDispatcherUNIX::registerSocketNotifier(QSocketNotifier *notifier)
{
..
int sockfd = notifier->socket();
QSocketNotifier::Type type = notifier->type();
...
QSocketNotifierSetUNIX &sn_set = d->socketNotifiers[sockfd];
....
sn_set.notifiers[type] = notifier;
}
先根据文件描述符即socket在socketNotifiers定位到对应的QSocketNotifierSetUNIX,再根据type将QSocketNotifier的指针加入到QSocketNotifierSetUNIX的notifiers中。
以上完成了QSocketNotifier的注册。
在processEvent中轮询被侦听设备的状态
看QEventDispatcherUNIX::processEvents源码:
bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
...
d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));
if (include_notifiers)
for (auto it = d->socketNotifiers.cbegin(); it != d->socketNotifiers.cend(); ++it)
d->pollfds.append(qt_make_pollfd(it.key(), it.value().events()));
int nevents = 0;
switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {
...
default:
nevents += d->threadPipe.check(d->pollfds.takeLast());
if (include_notifiers)
nevents += d->activateSocketNotifiers();
break;
}
...
return (nevents > 0);
}
从上面可以看出是通过qt_safe_poll来轮询设备,这里为方便理解,可以简单理解为通过select函数来侦听所有socket的状态,侦听过程并不是一直阻塞下去,是有超时时间的,超时时间内会根据设备实际状态修改pollfds,其中符合条件的socket会被标记,待qt_safe_poll就调用activateSocketNotifiers来处理那些被标记的设备,也就是那些满足侦听状态的文件描述符:
int QEventDispatcherUNIXPrivate::activateSocketNotifiers()
{
markPendingSocketNotifiers();
if (pendingNotifiers.isEmpty())
return 0;
int n_activated = 0;
QEvent event(QEvent::SockAct);
while (!pendingNotifiers.isEmpty()) {
QSocketNotifier *notifier = pendingNotifiers.takeFirst();
QCoreApplication::sendEvent(notifier, &event);
++n_activated;
}
return n_activated;
}
里面有一个比较重要的函数markPendingSocketNotifiers,就是根据修改的pollfds来标记那些设备是符合侦听状态的(比如,侦听a\b\c三个设备的READ状态,qt_safe_poll结束后只有a设备符合READ,那pendingNotifiers里面就只有a设备的那个QSocketNotifier),然后用循环给那些设备对应的QSocketNotifier发SockAct事件。
其实QSocketNotifier对SockAct事件的处理就是发出activated信号:
bool QSocketNotifier::event(QEvent *e)
{
...
if ((e->type() == QEvent::SockAct) || (e->type() == QEvent::SockClose)) {
emit activated(d->sockfd, QPrivateSignal());
return true;
}
...
}
processEvent与事件循环的关系
从上面的代码我们知道QSocketNotifier实际是由eventDispatcher的processEvents来侦听设备并发出activated信号的,但是eventDispatcher的processEvents是什么时候执行的呢?
我们来看3个函数
QCoreApplication::exec
int QGuiApplication::exec()
{
...
return QCoreApplication::exec();
}
里面直接调用QCoreApplication::exec。
QCoreApplication::exec
int QCoreApplication::exec()
{
...
QEventLoop eventLoop;
...
int returnCode = eventLoop.exec();
...
}
通过QEventLoop::exec来开启事件循环。
QEventLoop::exec
int QEventLoop::exec(ProcessEventsFlags flags)
{
...
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
...
}
里面就是while事件循环,循环调用processEvents处理所有事件。
QEventLoop::processEvents
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
...
return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}
调用线程eventDispatcher的processEvents来处理事件。
到这里应该比较明白了,QGuiApplication的exec最终调用的是eventDispatcher的processEvents,而里面就是轮询所有的设备,根据设备状态让QSocketNotifier发出activated信号。
总结
第一篇博客,明白了鼠标设备的各种QMouseEvent是通过连接QSocketNotifier的activated信号去读取鼠标设备文件的数据组装而来;
第二篇博客,明白了QGuiApplication的在事件循环中不断的去检测鼠标设备的状态,检测到有数据可读时让QSocketNotifier发出activated信号。
回到开头的问题:
QSocketNotifier的activated信号是如何触发的?监听鼠标设备是否需要单独的线程去不断的轮询设备?
第一个问题已经回答了,第二个问题,并没有单独的线程去轮询设备,是在主线程通过一小段的超时不断地去查询设备状态来完成的。