OS:linux
Arch:Mips
Input:remoter、touch screen
Qt source:qt-embedded-linux-opensource-src-4.5.0
难点分析:
1、如《Virt FrameBuffer开发小结 》所描述般,输入设备遥控器、触摸屏等设备驱动不是按照Linux的内核驱动规范来写的,而Qt消息的获取过程则满足Linux的文件读写操作标准,是正统的open、close、read、write。解决方法参照virt framebuffer的做法:建立一个虚拟的输入设备virtinput,当底层设备发生中断时,先读取码值,然后write(virtInputFd, &input_event, sizeof(input_event))将码值填送到虚拟设备中;Qt则read(virtInputFd, &input_event, sizeof(input_event))将码值读出来,进行分析处理。大体思路就这样,稍后分析一下Qt关于消息机制,事实其机制是非常典型的,在我们原来平台上也是类似的。
2、Qt4是基于LPGL协议的,不能随便修改Qt source中源文件。一旦修改了其中的库文件,就要公开自己的商业非开源代码。为了避免这种情况,我们使用这样的技巧:在Qt中我们open的依然是“标准设备”(例如usb键盘),但实际上open的是我们的遥控器、触摸屏等输入设备,所以需要在我们的遥控器驱动中,将码值一一转为usb键盘的标准键码值,用switch语句,效率还是很高的。
源码编译配置相关:
./configure -arch mipsel -fast -qt-kbd-usb -no-kbd-tty
大体选项就这些,其他一些优化裁剪的自己看着办,记住如果启用usb键盘,tty键盘是要disable的。一开始我在这里吃了亏,搞了很久,usb键盘都没反应,最后还是看keyboard工厂那部分代码才反应过来。
运行时的环境变量设置:
设置Qt动态库路径、fonts路径等等,具体的我也记不了那么多,网上很多这方面的资料。
如果如难点2那样,用我们的虚拟输入设备来替代usb键盘,则还需要
export QWS_KEYBOARD='usb:/dev/virtput'
字符串中usb是指要启用的键盘类型,/dev/virtput是虚拟输入设备的节点名称,这样Qt上层以为read的是usb键盘消息,但真正的数据是从虚拟输入设备virtinput读取的,而virtinput的数据又是由遥控器送过来的。听起来非常啰嗦且效率低下,但很多时候要兼顾风险、市场效率、软件协议的,技术只是一小部分。出色的工程师或许很偏激,但我明显不是。
以下代码是QWSKeyboardHandler *QKbdDriverFactory::create(const QString& key, const QString& device)摘下来的:
# ifndef QT_NO_QWS_KBD_TTY if (driver == QLatin1String("tty") || driver.isEmpty()) return new QWSTtyKeyboardHandler(device); # endif # ifndef QT_NO_QWS_KBD_USB if (driver == QLatin1String("usb")) return new QWSUsbKeyboardHandler(device); /* 从中可以看出:如果没有disable tty设备的话,那么tty kbd会先于usb kbd初始化,return new QWSTtyKeyboardHandler(device)会导致直接返回。很明显,这部分代码架构用到了工厂模式,封装性非常强,值得体会学习。 */
|
我们再来看qkbdusb_qws.cpp中的代码:
QWSUsbKbPrivate::QWSUsbKbPrivate(QWSPC101KeyboardHandler *h, const QString &device) : handler(h) { #ifdef QT_QWS_ZYLONITE shift = FALSE; #endif fd = ::open(device.isEmpty()?"/dev/input/event1":device.toLocal8Bit(),O_RDONLY, 0); if (fd >= 0) { QSocketNotifier *notifier; notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, SIGNAL(activated(int)),this, SLOT(readKeyboardData())); } } /* **当前对象继承了QWSPC101KeyboardHandler。
**语句fd = ::open(device.isEmpty()?"/dev/input/event1":device.toLocal8Bit(),O_RDONLY, 0)的device.isEmpty()是用来判断环境变量QWS_KEYBOARD有没有被export,如果没有,则使用是默认的设备节点/dev/input/event1。
**语句connect(notifier, SIGNAL(activated(int)),this,SLOT(readKeyboardData()))要理解的话,最好先理解一下qt的信号/槽机制原理,这里就不详述了。简单的来说:对象notifier的信号activated(int)和当前对象this的槽readKeyboardData()联系起来,这样当对象notifier的信号发射后,当前对象this的readKeyboardData()就会立即被执行。
**readKeyboardData()无非是read(fd, &event, sizeof(input_event)),将键值从设备的buffer中提取出来,转换成qt中的命令字,然后handler->processKeyEvent进行处理。
**至于qt如何维护readKeyboardData操作的,我们可以追寻QSocketNotifier并结合connect来分析。
*/ |
QSocketNotifier::QSocketNotifier(int socket, Type type, QObject *parent) : QObject(parent) { if (socket < 0) qWarning("QSocketNotifier: Invalid socket specified"); sockfd = socket; sntype = type; snenabled = true;
Q_D(QObject); if (!d->threadData->eventDispatcher) { qWarning("QSocketNotifier: Can only be used with threads started with QThread"); } else { d->threadData->eventDispatcher->registerSocketNotifier(this); } //从这里往registerSocketNotifier追寻如下,我们知道,这是类似于UNIX上高级IO方法select/poll,不断轮询直到文件句柄的标志位发生变化(这些变化应该对应于输入设备驱动的poll函数的mask),从而emit activated(sockfd) }
void QEventDispatcherGlib::registerSocketNotifier(QSocketNotifier *notifier) { GPollFDWithQSocketNotifier *p = new GPollFDWithQSocketNotifier; p->pollfd.fd = sockfd; switch (type) { case QSocketNotifier::Read: p->pollfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; break; case QSocketNotifier::Write: p->pollfd.events = G_IO_OUT | G_IO_ERR; break; case QSocketNotifier::Exception: p->pollfd.events = G_IO_PRI | G_IO_ERR; break; } p->socketNotifier = notifier; d->socketNotifierSource->pollfds.append(p); g_source_add_poll(&d->socketNotifierSource->source, &p->pollfd); }
bool QSocketNotifier::event(QEvent *e) { // Emits the activated() signal when a QEvent::SockAct is // received. if (e->type() == QEvent::ThreadChange) { if (snenabled) { QMetaObject::invokeMethod(this, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, snenabled)); setEnabled(false); } } QObject::event(e); // will activate filters if (e->type() == QEvent::SockAct) { emit activated(sockfd); //注意这里,发射信号,会导致相绑定的槽会立即被执行
return true; } return false; } /* **虽然我们不能从代码级上明白QSocketNotifier如何触发event,从而发射activated(sockfd)信号的,但是大体还是能了解其流程。
**结合文档前面的一些注释: Once you have opened a device using a low-level (usually platform-specific) API, you can create a socket notifier to monitor the file descriptor. The socket notifier is enabled by default, i.e. it emits the activated() signal whenever a socket event corresponding to its type occurs. Connect the activated() signal to the slot you want to be called when an event corresponding to your socket notifier's type occurs.
**如果要透彻理解QSocketNotifier如何触发event的,可以看下socketNotifierSourceDispatch、registerSocketNotifier的实现以及它们的关联。 */ |
我们可以总结出:当设备产生中断时,系统会进入中断处理例程,将码值读取出来,但是并不立即通知应用程序产生新的消息了,而是将码值送入某个buffer中;而应用程序总是运行一个后台线程,不断轮询直到文件句柄标志位发生变化(通过select/poll实现),然后应用程序就知道有新的信息到达了,接着读取该buffer,如果从中提取到消息,则进行分派处理。
阅读(2303) | 评论(0) | 转发(0) |