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