做了一段时间linux下与QT事件相关的工作,经常会遇到X11,总是苦于无法完全理解其与linux以及QT事件之间的关系,所以用两篇文章来简单总结下linux中的图形管理和QT事件与X11的关系。
<1> linux中的图形管理 |
1. Qt中的事件
参考自《C++ GUI Qt 4编程》中第7章给出的Qt事件定义及说明:
Qt的事件(event)是由窗口系统或者Qt自身产生,用以响应所发生的各类事情。当用户按下或者松开键盘或者鼠标上的按键时,就可以产生一个键盘或者鼠标事件;当某个窗口第一次显示的时候,就会产生一个绘制事件,用来告知窗口需要重新绘制它本身,从而使得该窗口可见。大多数事件是作为用户动作的响应而产生的,但也有一些例外,比如timer事件,则是由系统独立产生的。
上面提到的Qt事件的组成,也即按照事件产生的来源进行分类。
窗口系统产生 |
在类unix系统中,即X11产生的事件,X server将系统消息放入XEvent队列,并通过socket通知Qt应用程序,Qt事件循环的时候读取这些时间,并转为QEvent。 |
Qt自身产生 |
Qt内部自定义的事件,如timer,通过postEvent将事件加入事件队列,等待被分派处理;或者直接sendEvent,直接进行分派处理。 |
我们可以通过Qt中提供了5个级别的事件处理和事件过滤方法,重新定义不同控件(widget)的事件响应。级别越大,可控制的widget越多,权限也就越大。从最高级别到最低级别的事件过滤方法,
[1] 派生QApplication并且重新实现notify:Qt调用QApplication::notify来处理一个事件。 [2] 在QApplication对象中安装事件过滤器:在qApp(QApplication的单例对象)中安装事件过滤器(qApp->installEventFilter),应用程序中所有对象的所有事件都将在发送到其它事件过滤器之前,先发送给自定义的eventFilter函数。 [3] 在QObject中安装事件过滤器:在widget对象中安装事件过滤器(widget-> installEventFilter),用户目标对象的所有事件都会首先发送给这个监视对象的eventFilter函数。 [4] 重新实现QObject::event():所有事件在到达特定的事件处理器之前都将通过event函数。 [5] 重新实现特殊的事件处理器:如mousePressEvent、keyPressEvent等。 |
Qt内部的事件处理及过滤流程,可以从这5个级别中清晰地看出,如果还有疑问,可以review下Qt的源代码中QApplication::notify(QObject *receiver, Qevent *e)函数。
2. Qt与X11事件
阅读Qt源代码中与X11事件相关的部分是件相当花费时间和精力的事情,毕竟接触GUI编程的时间很短,所有对于X server如何通过socket通知Qt应用程序?Qt如何将XEvent转换成QEvent?Qt的事件(QPaintEvent)如何响应给X server?以及Qt多线程下的事件循环机制等这些问题将不做细究。
我们从一个Hello Qt开始吧!
1 #include <QApplication> 2 #include <QLabel> 3 4 int main(int argc, char* argv[]) 5 { 6 QApplication app(argc, argv); 7 QLabel* label = new QLabel(“Hello Qt!”); 8 label->show(); 9 10 return app.exec(); 11 }
每一个Qt的应用程序都要以QApplication::exec()函数结尾,它将开始事件循环(event loop),即主线程的事件循环。这里需要说明下,该事件循环只是一个局部event loop,QDialog::exec()、Qmenu::exec()或者其它线程的exec()都是可以中断QApplication::exec()的。
根据Qt源代码中,事件循环的大致流程如下,
在整个流程中,都需要判断是否有中断退出信号。另外,一个XEvent可能会被翻译(转换)为多个QEvent,比如一个enter事件,XEvent只包含事件的数据,转换为QEvent时,则需要绑定给多个widget(childWidgets、parentWidgets),所以将产生多个QEvent。
在Qt源代码之外,有两种方法可以截获并处理XEvent。其实在事件循环过程中的QEventDispatcherX11::processEvents函数中,存在两个级别的事件处理和过滤过程,第一个就是QApplication::x11EventFilter,可以通过重载此函数,截获所有的XEvent;第二个是QWidget::x11Event函数,通过重载此函数,发送到该widget的XEvent将首先被此函数截获。