Chinaunix首页 | 论坛 | 博客
  • 博客访问: 134019
  • 博文数量: 48
  • 博客积分: 15
  • 博客等级: 民兵
  • 技术积分: 38
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-22 17:03
文章分类
文章存档

2016年(1)

2015年(6)

2014年(12)

2013年(27)

2012年(2)

我的朋友

分类: 嵌入式

2014-08-20 19:15:05

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) |
给主人留下些什么吧!~~