ARM+linux平台实现Qt/e自定义键盘
发布日期:2011-04-26
Qt是诺基亚开发的一个跨平台的C++图形用户界面应用程序框架,Qt/e是面向嵌入式系统的Qt版本。Qt/e是Client/Server结构的,不仅继承了Qt在X WindowSystem上的强大功能,而且在底层摒弃了XIib,仅采用帧缓存作为底层图形接口加快了显示速度。同时,将外部输入设备抽象为keyboard和mouse输入事件,底层接口支持键盘、鼠标、触摸屏以及用户自定义的设备。输入设备是嵌入式系统进行友好人机交互时不可或缺的设备,如鼠标、小键盘、触摸屏等,要让Qt/e支持特定的嵌入式输入设备,需要对设备驱动和Qt/e的事件驱动非常熟悉,虽然关于键盘驱动方面已有很多的介绍,但都没有结合Qt的事件驱动原理深入分析键盘的工作流程。本文主要从自己设计的键盘出发深入分析了Qt/e的事件驱动原理和键盘事件中插件的加载流程,并给出了详细的源代码分析,最后实现了该插件的详细设计,对基于嵌入式Qt的工程开发有一定的参考价值。
一、Qt/e的实现结构
Qt/e不像Qt构建在X Windows之上,而是构建在Linux的Framebuffer之上,这样就可以把需要显示的内容直接写入framebuffer,不仅省略了X Windows所带来的系统开销,而且直接写framebuffer,加快了显示的速度。但就是这一个改变,导致在Qt/E中多出了Server这么一层,这一层负责监听系统事件,尤其是键盘和鼠标事件屏幕输出、管理region、管理顶层窗口、管理光标和屏幕保护程序等等诸多功能。
二、Qt/e事件驱动
当Server收到一个event的时候,它需要判断应该发送给那一个窗口,这时候它就会从QWSWindow列表中去查找,然后根据这个窗口去找对应的client application,然后用一个QWSEvent对象来封装这个event,通过socket机制发送给具体的client application。如果当前系统安装了一个输入法,那么每一次键盘事件产生的时候,都会去调用输入法的相应方法。
鼠标事件的处理和键盘事件的处理也符合上面的流程。鼠标驱动由一个QWSMouseHandler对象封装,键盘驱动由一个QWSKeyboardHandler封装。这两个驱动程序对象都会通过Qt的plugin机制加载。具体的鼠标和键盘事件发
生之后,都会封装成为一个QWSEvent对象并发送给具体的client。具体的实现流程如下:
1.在Start application时,带GUI的main函数都会创建一个QApplication的实例(在src/gui/kernel/qapplication.cpp中),在其构造过程中会调用qt_init来解析命令行参数,从而调用QWSServer::startup(flags)。应用命令行如加入了”-qws”选项,就会以server(GuiSever)方式运行。QWSServer的具体的实例就是在QWSServer::startup(flags)中创建的。
2.在QWSServer的构造函数中会调用QWSServer Private::initServer完成初始化的工作,其中包括各个硬件接口的初始化,如鼠标,键盘等外设。其中openKeyboard就是用来初始化键盘接口的。它负责解析环境变量QWS_KEYBOARD的设定,从中取得键盘设备的名称和driver handler的类型,并最终调用QkbdDriverFactory::create函数载入与之对应的键盘处理插件。
3.插件加载成功后,QWSKeyboardHandler派生的键盘处理类就可以响应键盘事件,封装成为一个QWSEvent对象后并发送给具体的client。
三、Qt插件机制
插件是提供特定接口的动态库,是一个独立文件中的独立模块,可被多个程序访问。
Qt有两种与插件有关的API。一种用来扩展Qt本身的功能,如自定义数据库驱动,图像格式,文本编解码,自定义分格等,称为Higher-Level API。另一种用于应用程序的功能扩展,称为Lower-Level API。前一种是建立在后一
种的基础之上的。这里讨论的是后一种,即用来扩展应用程序的Lower-level API。
Qt插件按编译生成方式的不同又分为静态插件和动态插件,这里主要讨论动态插件的创建和加载使用。
使应用程序支持扩展插件主要包括以下几个步骤:
1.定义一个接口集(只有纯虚函数的类),用来与应用程序交流。
2.用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统。
3.在应用程序中用QPluginLoader来装载插件。
4.用宏qobject_cast()来确定一个插件是否实现了接口。
四、s3c2410键盘驱动设计
硬件环境为samsung S3c2410,自定义小键盘为ZLG7289,共17个键。当有键按下时产生一个中断,然后通过串行传送数据。下面结合Qt/e事件驱动原理介绍本系统的键盘驱动结构,具体结构如下:
static struct file_operations s3c2410_fops={
open:s3c2410_kbd_open,//打开
read:s3c2410_kbd_read,//读取
release:s3c2410_kbd_release,//释放
poll:s3c2410_kbd_poll,//轮询
};
1.模块的加载与卸载
由于裁剪内核较为烦琐,本设计将键盘驱动以模块的方式加载到系统,更加提高了自定义键盘驱动的灵活性。
模块的加载:module_init(s3c2410_kbd_init);
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
模块的卸载:module_exit(s3c2410_kbd_exit);
当通过rmmod命令卸载模块时,模块的卸载函数就会自动被内核执行,完成与模块加载函数相反的功能。
2.中断函数和读操作函数的实现
当完成系统的初始化操作以后,系统等待按键中断的到来,一旦事件发生,系统自动调用当前中断处理函数s3c2410_isr_kbd()。其实现如下:
static void s3c2410_isr_kbd(int irq,void*dev_id,
struct pt_regs*reg)
{
disable_irq(IRQ_KBC_INT);//关中断
{读数据}
wake_up_interruptible(&KBC_wq);//唤醒阻塞程序
enable_irq(IRQ_KBC_INT);//开中断
}
而s3c2410_kbd_read()函数主要调用copy_to_user(buffer,(char*)&kbd_ret,sizeof(KBD_RET))把数据从内核拷贝到应用层。
3.s3c2410_kbd_poll函数实现
s3c2410_kbd_poll函数是根据Qt/e事件驱动原理设计的,poll结构在kernel中是通过poll_wait(filp,&(kbddev.wq),wait)来达到阻塞的目的,当没按键时,上层自动进入阻塞状态,当有按键时当前循环队列中有数据,kbddev.head与kbddev.tail不等,返回POLLIN|POLLRDNORM,就触发信号。
static unsigned int s3c2410_kbd_poll(struct file
*filp,struct poll_table_struct*wait)
{
poll_wait(filp,&(kbddev.wq),wait);
return(kbddev.head==kbddev.tail)?0:
(POLLIN|POLLRDNORM);
}
五、自定义键盘插件的实现
写一个插件的步骤:
1.声明插件类,该类从QObject和该插件希望实现的接口继承而来。
2.用宏Q_INTERFACES()将该接口告诉Qt元对象系统。
3.用宏Q_EXPORT_PLUGIN2()导出插件。
4.用适当的.pro文件构建插件。
程序要能感知插件,需要程序和插件共同遵守某种规则。于是定义一个共同的接口,对于我们要自定义的小键盘插件而言,键盘接口类QT已经为我们写好了统一的一个接口,现在我们要做的就是实现自定义的小键盘插件,重点是创建两个类:键盘处理类(Handler)和键盘插件类(Plugin)。键盘处理类(Handler)是基于QObject和QWSKeyboardHandler派生的,键盘插件类(Plugin)是基于QKbdDriverPlugin派生的。Handler类的主要作用是完成对底层键盘设备的打开、读取等操作,并将读取到的键值映射为Qt支持的键值。一个关键的函数readKpdData()就是用来把我们的硬件值(hardcode)转换为Qt库中定义的键值。
键盘处理类KeypadHandler声明如下代码所示:
class KeypadHandler:public QObject,public
QWSKeyboardHandler
{
Q_OBJECT
public:
KeypadHandler(const QString&device=QString("/
dev/mcu/kbd"));
~KeypadHandler();
private:
QSocketNotifier*m_notifier;
int KbdFd;
private slots:
void readKpdData();
};
当环境变量QWS_KEYBOARD的设定后,在QWSServer的构造函数中就会解析该环境变量,并调用KeypadHandler类的构造函数初始化键盘接口。
KeypadHandler::KeypadHandler(const QString&
device)
{
qDebug("button pressed\n");
this->KbdFd=open(device.toLocal8Bit().constDat
a(),O_RDONLY,0);
if(KbdFd>=0)
{
printf("%s opened as keyboard input.\n",
device.toLocal8Bit().constData());
this->m_notifier=new QSocketNotifier(KbdFd,
QSocketNotifier::Read,this);
connect(this->m_notifier,SIGNAL(activated(int)),
this,SLOT(readKpdData()));
}
else
{
qWarning("Cannot open%s for keyboard input\n",
device.toLocal8Bit().constData());
return;
}
}
通过KeypadHandler的实现可以看到,完成设备的打开以后,系统会注册一个信号槽。信号的发送者是QSocketNotifier,QSocketNotifier类是基于阻塞模式实现的。它的实现原理是在初始化过程中调用poll函数来达到阻塞,通过前面对poll的分析可以看到,唤醒poll等待队列的时机是当中断处理程序将当前数据读取到缓冲中之后,系统会自动将当前阻塞队列唤醒,接收此信号的处理函数为readKpdData(),此函数用来实现从设备上读取扫描码,通过唯一的扫描码来调用processKeyEvent()通知Qt/e上层处理接口,processKeyEvent()根据传递的参数来通知上层函数所需要显示的数据或需要触发的功能键。当按下某个键以后,我们则可以在基于QT/e的应用程序中看到输入的数据。
程序实现如下:
void
QWSSKBKeyboardHandler::readKeyboardData()
{
InputData event;
int n=read(KbdFd,&event,sizeof(InputData));
if(n!=sizeof(InputData))
{
qDebug("key pressed:n=%d\n",n);
return;
}
{根据读取到的InputData中的值按软件需求进行相应赋值}
this->processKeyEvent(unicode,key_code
modifiers,event.value!=0,false);
}
Plugin类的主要作用是与外部接口(因为在一个动态链接库中,Plugin类是导出的)交互,在该类中创建一个我们自己定义的键盘处理类对象。
六、结语
目前,在嵌入式领域Linux操作系统和Qt/e图形界面库逐渐成为主流。本文依据在嵌入式软件开发中普遍遇到的键盘输入处理问题,深入分析了Qt/e的事件驱动原理和键盘事件中插件的加载流程,并给出了实现自定义键盘插件的
详细设计,对Qt/e的输入设备的实现有一定的应用价值和参考价值。