2023年6月26日发(作者:)
Qt消息传递机制分析及输入设备驱动开发OS:linuxArch:MipsInput:remoter、touch screenQt 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")||y())
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_中的代码:
QWSUsbKbPrivate:QWSUsbKbPrivate(QWSPC101KeyboardHandler*h,const
QString&device):handler(h)
{
#ifdef QT_QWS_ZYLONITE shift=FALSE;
#endif fd=:open(y()?"/dev/input/event1":l8Bit(),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(y()?"/dev/input/event1":l8Bit(),O_RDONLY,0)的y()是用来判断环境变量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; =sockfd;
switch(type){
case QSocketNotifier:Read:
=G_IO_IN|G_IO_HUP|G_IO_ERR;
break;
case QSocketNotifier:Write:
=G_IO_OUT|G_IO_ERR;
break;
case QSocketNotifier:Exception:
=G_IO_PRI|G_IO_ERR;
break;
}
p-socketNotifier=notifier;
(p);
g_source_add_poll(&d-socketNotifierSource-source,&p-pollfd);
}
bool QSocketNotifier:event(QEvent*e)
{
//Emits the activated()signal when aQEvent: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 adevice using alow-level(usually platform-specific)API,you can create asocket notifier to monitor the file socket notifier is enabled by default, emits the
activated()signal whenever asocket event corresponding to its type
t 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,如果从中提取到消息,则进行分派处理。
MSN空间完美搬家到新浪博客!
发布者:admin,转转请注明出处:http://www.yc00.com/news/1687756294a39978.html
评论列表(0条)