《Symbian OS Internal》:窗口服务器 (一)

窗口服务器
道格拉斯.肥瑟

451
窗口服务器(或称WSERV)与symbian OS的几乎所有的部分协同工作,从核心到应用,只有通信子系统完全例外。
窗口服务器的两个主要的职责是屏幕管理和事件管理。
WSERV从内核接收到事件,然后将它们传递到窗口服务器的客户端(通常是应用程序)。
另一方面,窗口服务器从客户端接收命令,然后相应的更新屏幕。
关于这两个关键职责的讨论,将组成这一章的骨架。

WSERV在系统启动时开始,并且运行在系统的整个生命周期中。
它是一个标准的系统服务,派生自CServer2(或CPolicyServer,Symbian OS v9以上)成为CWindowServer。

在这一章,我同样会谈及动画Dll(anim DLLs),他是WSERV的一个插件。
我会讨论什么是anim DLL,如何创建一个anim DLL以及它怎样和事件互动。
为了阐明这些,我会开发一个简单的手写识别系统。

同样我会深度的讨论窗口 - 不同类型的窗口,窗口类在服务器和客户端,窗口对象怎样链接在一起(窗口树),
窗口拥有的不同区域,不同的窗口效果及客户如何绘制到窗口。
但是首先,我会将WSERV当做内核事件处理者。

11.1 内核事件处理者
在系统启动过程中,WSERV调用UserSvr::CaptureEventHook(),来告诉内核WSERV希望成为内核事件处理者。
但要实际接收事件,WSERV还必须调用UserSvr::RequestEvent(TRawEventBuf& aBuf,TRequestStatus& aStatus),
传入活动对象CRawEventReceiver的请求状态,只要内核有一个等待事件CRawEventReceiver就会运行。
以这种方式,内核将他所有的事件(例如:数字和按键事件)传递到WSERV,由WSERV进一步处理这些事件。

WSERV不仅仅是传递事件到其客户的简单管道。
他对事件进行大量的处理,丢弃其中的一些事件,处理另一些,创建新的事件并决定发送事件到哪个客户。
在内核与WSERV之间传递的事件类型同WSERV到他客户之间传递的并不完全相同。
内核不是唯一的事件源头,客户能产生,同样DLL和WSERV本身也能产生。

同传递事件到客户一样,WSERV也能传递他们到DLL。

11.2 不同类型的事件
在这一部分,我会列出SWERV处理的不同事件的列表,大体描述每个类型的作用。我会同时描述 从内核到WSERV 及 WSERV到客户的事件组。
图11.1给出了事件在系统中的路径概观。他显示了3个不同的线路:内核、WSERV及WSERV的客户 - 这些线路的边界被分割线隔开。
三个小盒子代表进入WSERV内部的处理。

虽然图11.1不能给出完整的图像,他应该足以给你一个映象,事件如何穿过WSERV。

11.2.1 从内核到WSERV的事件
从内核到WSERV之间的事件在TRawEvent::TType类中列出。其中的一些事件与触摸事件相关。在下表中骂我只列出了触摸事件的一个代表性的子集。

原始事件
事件   目的
ENone   未使用
EPointerMove  指针或笔移动位置。这可能是一个移动或拖动事件
EpointerSwitchOn 数字转换器被触发,导致设备启动
EKeyDown  一个按键按下
EKeyUp   一个按键松开
ERedraw   模拟器得到一个Win32重绘事件
ESwitchOn  设备刚七佛那个完成
EActive   模拟器窗口得到焦点
EInactive  模拟器窗口失去焦点
EUpdateModifiers 编辑键设置改变。当模拟器得到焦点时发送
EButton1Down  笔或鼠标1键按下
EButton1Up  笔或鼠标1键弹起
ESwitchOff  设备准备关闭
ECaseOpen  翻盖被打开
ECaseClose  翻盖被关上

我会讨论按键和触摸事件发生了什么,在下一章。下表显示了WSERV如何响应系统中其他的原始事件:

WSERV事件
事件   响应
ERedraw   在模拟器上,屏幕驱动组件管理着一张位图,这张位图包含了显示的完整拷贝。
   WSERV调用CFbsScreenDevice::Update(const TRegion& aRegion),传递完整的屏幕区域,
   这个函数从位图更新屏幕。
ESwitchOn  停止键盘重复计时器。如果设置了密码窗口则提示。调用UserSvr::WsSwitchOnScreen()启动屏幕硬件。
   发送事件到请求此通知的客户。
EInactive  停止按键自动重复
EUpdateModifiers 重设修改键的状态
ESwitchOff  如果一个客户注册了switch-off事件,WSERV发送一个事件到该客户。
   否则,它调用UserHal::SwitchOff关闭设备。
EKeyRepeat  不处理
ECaseOpen  和ESwitchOn相同,但不会启动屏幕设备
ECaseClose  发送一个事件到注册switch-off事件的客户。(如果该客户不存在,也不会关机)

11.2.2 从WSERV到其客户端的事件
在下面两张表中,我列出了WSERV发送到客户端的事件。我会在这一章的后面讨论按键和触摸事件。
第一张表包含了WSERV发送到相关客户的事件(不符按他们什么时候发生):

非客户注册事件
事件    含义
EEventNull   忽略
EEventKey   一个字符事件
EEventKeyUp   按键弹起事件
EEventKeyDown   按键按下事件
EEventPointer   一个弹起、按下、拖动或移动指针事件
EEventPointerEnter  指针移到窗口上
EEventPointerExit  指针移出特定窗口
EEventPointerBufferReady 一个BUFFER包含指针拖动或移动事件准备发送
EEventDragDrop   一个特殊类型的指针事件。这个事件能被客户注册,以接收UI组件的拖动和释放
EEventFocusLost   窗口刚刚失去焦点
EEventFocusGained  窗口刚刚获得焦点
EEventPassword   当密码窗口显示时,发送给密码窗口的所有者
EEventMessageReady  从另一个应用程序发出的消息到达
EEventMarkInvalid  内部使用,不发送到客户端
EEventKeyRepeat   不发送到客户,发送到按键点击创建者

在下一个表,我列出WSERV仅发送给注册事件的客户 - 这些事件大多发送到多于一个客户:

客户注册的事件
事件    含义
EEventModifiersChanged  一个或多个修改键改变状态
EEventSwitchOn   机器刚启动完成(这个事件不是产生于一个手机,而是产生于,比如,Psion Series 5 PDA)
EEventWindowGroupsChanged 当一组窗口被销毁或取名时发送
EEventErrorMessage  发送一个错误,比如内存不足,发生在WSERV(细节请参看TEventCode ,C++组件参考部分文档)
EEventSwitchOff   发送到客户处理关机
EEventKeySwitchOff  如果OFF键按下,发送给客户处理关机
EEventScreenDeviceChanged 当屏幕尺寸变化时发送
EEventFocusGroupChanged  当焦点窗口组变化时发送
EEventCaseOpened  当翻盖打开时发送
EEventCaseClosed  当翻盖关闭时发送到客户处理关机
EEventWindowGroupListChanged 当窗口组次序变化时发送

11.3 WSERV 如何处理事件
WSERV以多个步骤处理事件:其中一些步骤对所有事件通用,特别是WSERV为客户创建事件队列的方式。其他步骤与特定类型的事件相关。
比如,指针事件和按键事件都必须进行特殊处理。

WSERV要进行的第一阶段是处理指针事件 - 这是对指针事件的主要处理,导致以后接收到标准类型的指针事件。WSERV在这里做了些事情:
1。过滤掉基于手写笔设备的模拟器的WIN32移动事件
2。内核发给WSERV的坐标是相对于上个位置的,为虚拟指针光标,转换他们到绝对坐标。
3。对于真机,WSERV旋转坐标到当前屏幕的旋转。(屏幕先赚允许一个坐标系中屏幕的地址,从屏幕的物理坐标旋转0,90,180,270度
4。移动坐标到当前屏幕的原点和尺寸
5。如果指针事件被限制到显示的子矩形中,若他们在这个区域之外,WSERV将限制其坐标

在模拟器上考虑选转时无意义的,因为fascia位图旋转,内核从WIN32接收到的坐标已经被旋转。

我们从SYMBIAN OS 8.1开始支持屏幕的原点和尺寸,一个用户界面设计者能允许不同应用程序使用不同的坐标系统访问屏幕的像素点。

在某些环境下,WSERV关掉其心跳计时器 - 这一般发生在一个客户调用RWsSession::PrepareForSwitchOff()时,这样处理器能够关闭以节省电力。
作为对每一个内核事件的回应,WSERV将其计时器重新打开(如果其关闭)。

下一步,WSERV传递事件到任何注册对事件感兴趣的anim DLL那里,anim DLL 调用MAnimGeneralFunction::GetRawEvent(ETrue)来进行注册。
为了发送事件到anim DLL,WSERV调用MEventHandle::OfferRawEvent()。anim DLL能够消费事件,这样WSERV就不必进一步处理,为了达到这个目的,它从
OfferRawEvent函数返回ETrue,否则他应该返回EFalse。

从这一点以后,WSERV对于不同类型的事件,进行不同的处理。除按键和指针以外的所有事件,都列举在上面的“WSERV事件”表中。

11.4 处理按键事件
有三种内核事件与按键直接相关:EKeyDown EKeyUp和EUpdateModifiers。要处理这些按键事件,WSERV使用一个CKeyTranslator派生对象的实例。
这个对象懂得字符映射和修改键,他的主要目的是告诉WSERV一个特定按键应该映射到哪一个字符。比如:按下“a”键会导致“a”或“A”,
这就需要CKeyTranslator识别shift键的状态,然后决定应该选择哪一个。

EUpdateModifiers事件直接被传入到CKeyTranslator对象中。内核在模拟器中产生这个事件,当模拟器窗口从另一个窗口程序获得焦点时。
事件传递的数据告诉我们当前所有修改键的状态,让模拟器能够考虑,当其他程序拥有焦点时,用户对修改键状态做出的任何改变。

11.4.1 按键弹起和按下
WSERV如此处理每一个弹起和按下事件:
1。在日志中记录事件,如果日志开启
2。告诉键盘重复计时器对象这个事件
3。传递事件到键盘转换对象
4。检查任何修改件的变化
5。将弹起/按下事件放入队列
6。进行进一步处理来创建字符事件(如果有的话)

键盘重复计时器对象,控制按键的自动重复。WSERV只从内核接受单个的弹起和按下事件,而不管这个键按了多长时间。
如果一个按键映射到一个字符,wWSERV启动一个计时器,每次那个计时器到达时,WSERV将为客户队列产生另一个字符实例。
如果客户及时的回应此事件,他会从那个字符得到很多事件,除了他们中的第一个外,计时器会产生其他所有事件。

当一个新的按键按下事件发生,WSERV必须通知计时器,这样它能够退出当前的重复
- 这是需要的,因为任何按键按下都应该改变计时器所产生的字符。
类似的,当一个按键弹起事件发生,WSERV将通知计时器,这样他就能停止重复,如果按键弹起事件来自当前重复的字符。

接着,WSERV调用键盘转换对象,使用函数TBool TranslateKey(TUint aScanCode, TBool aKeyUp,const CCaptureKeys,TKeyData &aKeyData)
如你所见,WSERV传递按键事件的扫描码,一个布尔值指明按键是一个弹起还是按下事件,及当前捕获的按键列表。
按键转换对象返回一个TBool指明按键是否映射到一个字符事件,如果是,按键转换器还要返回下列按键事件的详情到TKeyData对象中:
1。字符的代码
2。当前所有修改键的状态
3。按键是否被捕获

如果这个按键被捕获,按键转换器还要返回:
1。一个指明那个窗口捕获这个对象的句柄
2。另一个句柄,WSERV自己用它来捕获按键。

WSERV对于按键或热键的捕获是在系统范围的。热键包括 提高或降低对比度,切换或打开关闭背光,你可以查看THotKey枚举的完整列表。

客户可以请求事件,让他们知道某些修改键改变了状态。当此事发生,WSERV检查所有客户请求,来查看是否有任何关于发生特定变化的请求消息。
对于每一个请求,WSERV将一个事件放入相关客户的队列。
WSERV必须决定发送弹起或按下事件到哪一个客户。通常他选择当前拥有窗口的客户
- 唯一的例外是,如果一个特定的客户已经请求捕获这个特定按键的弹起和按下事件。
WSERV同样会发送这个事件到按键点击插件,如果这个事件与一个声音相关。

WSERV处理那些按键弹起和按下事件,及那些由按键转换器对象决定引发的字符事件。这个过程相当复杂,我在下一节将描述它。

11.4.2 字符事件
WSERV处理字符事件的主要步骤是:
1。调用按键点击插件
2。处理捕获按键(包括WSERV捕获按键)
3。决定谁应该接收这个事件
4。检查事件是否捕获长按
5。检查是否重复计时器应该启动
6。将事件放入队列

首先,WSERV发送事件到按键点击插件,如果有的话。
按键转换对象已经返回了字符事件是否应该被捕获,所以WSERV检查这个,并相应的为事件设置目标。
如果按键没有被捕获,WSERV发送自己二个事件到当前的焦点窗口。如果事件被捕获,但WSERV当前正显示密码窗口,
WSERV只在当捕获者同密码窗口在同一窗口组时,才发送此事件。

如果字符事件时WSERV的捕获键,那么WSERV会自己捕获它。这种情况下,WSERV会立即处理事件,并不会继续发送给客户。
如果按键是以下按键,重复计时器被启动。
1。长按捕获按键
2。当前没有重复按键,且这个按键允许自动重复

长安捕获石我们在Symbian Os V7.0加入的特征之一。它允许一个按键的一次长按同轻击做不同处理。
由同一个物理按键产生的长按键事件和段按键事件,可以在目标客户 和 产生字符代码方面不同。
这个特征允许快速按下一个号码键,输入到拨出号码对话框,而长按相同的键则跳到联系人程序中以某个字符开头的第一个联系人处。

11.5 处理点击事件
指针事件的处理要比处理按键事件复杂得多。这是因为,WSERV要根据点击的确切位置,计算发送事件到哪个窗口。
指针的拖动和捕获也会影响他。

指针事件的一般顺序开始于一个指针按下事件,接着又0到多个指针拖动事件,结束于指针弹起事件。
有可能所有这些事件在他们发生的位置都会传递到屏幕的可见窗口中。客户可以通过两个特征来区别这个行为:抓取和捕获。

如果窗口接收到一个按下事件,并且设置为抓取指针事件,WSERV将发送所有的抓取事件和接下来的弹起时间到那个窗口,即使实际上发生在另一个窗体上。
如果一个窗口被捕获,并且按下事件在他后面的一个窗口上,那么正在捕获的窗口将接收到这个事件,如果那个窗口也被抓取,那么他也会接收到随后的抓取和弹起事件。

事实上,大部分窗口会抓取指针事件,且某些窗口也会捕获他们。捕获允许对话框防止指针事件发送到他后面的窗口上。
WSERV在处理指针事件时,采取以下主要步骤:
1。计算指针事件所在的实际窗口
2。决定是否另一个窗口的抓取或捕获,意味着他应该得到指针事件
3。如果当前窗口发生变化,将进入和退出事件放入队列
4。将指针事件告诉按键点击插件
5。对于移动和拖动事件,检查窗口看他是否希望这个事件
6。如果窗口请求他,保存移动和拖动事件到一个指针BUFFER
7。如果窗口有虚拟键盘并且事件发生在其中一个虚拟键上,转换此事件到按键事件
8。检查事件,看他是否应该是一个双击事件,是否一个拖动事件也时需要的

WSERV 计算指针事件发生的实际窗口,以递归的方式分析窗口树。起始于顶级客户窗口,他找到在那个级别最前端的窗口,包含用户在其上点击的点。
然后他继续检查这个窗口的每一个子窗口,继续这一操作,直到不再有子窗口或者没有一个子窗口包含此点。
然后,WSERV再次以相同的方式分析窗口,但这一次他为每一个窗口检查捕获标志,确定是否应该捕获这个事件。

当WSERV增加一个移动或拖动事件到客户队列,他检查当前在客户队列尾部的事件,如果这是一个除坐标事件外的相同的事件,
WSERV会仅仅用新的事件替换旧的。这意味着客户不会在笔或鼠标移动时,得到非常细粒度的信息。
这没有问题,实际上对于大多数程序都很有益处,除了绘图程序不理想外。所以,对于那样的程序,
WSERV替代的保存所有内核发过来的事件在一个BUFFER中,客户将一次得到一整块的事件。

11.6 客户队列
WSERV使用客户队列来保存事件,当这些事件在等待发送给客户时。对于每个客户,有三种不同的队列,每一个保存一类不同的事件:
1。重绘事件
2。优先按键事件
3。所有其他事件(主队列)

我们为OPL程序语言设计了优先按键事件,目前也只有这样的程序使用它们。当一个OPL程序在运行,用户可以按下ctrl+esc,这将立即终止程序。
这是因为这个键是通过优先键队列发送,所以可以忽略所有其他事件。

更普遍的,我们有三个队列,这样客户可以用不同的活动对象优先级处理不同事件。一般来说,一个客户希望在重绘事件之前,
接收队列中已有的指针和按键事件,所以为重绘队列设置的活动对象的优先级要低于主队列。

当WSERV有一个要发给客户的事件,他就将事件放入队列,并将客户为该队列提供的请求状态设为完成,这样客户就知道最少有一个事件在等待。
但是系统可能很忙,WSERV可能在客户有机会要求一个事件之前,产生很多事件。这意味着WSERV必须处理队列溢出问题。

11.6.1 优先按键队列溢出
我们设计此队列来接受一个按键点击指示程序关闭;这意味着在该队列中我们对那个键的多次出现并不感兴趣。
所以该队列仅保存与特殊状态相关的最后一次按键 - 如果在旧事件发出之前来了新事件,旧的将被覆盖。

11.6.2 重绘队列溢出
重绘队列是一个数组,这个数组列出所有需要重绘的客户窗口。数组从头到尾排序以使第一个要重绘的窗口排在最前。
如果有足够的内存,这个数组将无限的扩张 - 除了每一个窗口只能在其中出现一次。

如果任何时候数组不能被扩展了,那么重绘队列将设置标志标明数组未完成,当数组为空,标志被设置,WSERV扫描所有客户窗口,查找谁需要一次重绘。
只有当他扫描完所有的窗口,才会清除此标志。

11.6.3 事件队列溢出
WSERV使用了很多策略来避免此队列溢出造成的后果。但是,他们不简单 - 在一些极端的情况下,可能会出现事件丢失。
然而,就我们所知,在实际中并为出现过,或者曾经出现过,也没有任何副作用!

事件队列是一个全局的堆单元 - 整个系统只有一个事件队列。WSERV增长和收缩这个单元,当客户的数量改变时。
队列的尺寸是大约 48 + 2 * (客户数量)个条目,每个条目为40字节,即TWsEvent的尺寸。

这个堆单元分为很多部分,一个客户被分配一个部分。
WSERV也有自由区修改堆单元中每个客户部分的大小,相应的增长或收缩其他客户的部分。
一个特定的客户部分可以有 2 到 32 个条目。

如果WSERV需要将事件加入队列,但在客户部分没有空间,显然他会首先尝试扩张客户部分到32个条目的最大尺寸。
(如果客户部分已经有32个条目了,WSERV将尝试清理客户队列 - 也就是找到可以被删除的事件。)
为此,WSERV将尝试寻找其他有空间的客户,收缩那些部分。如果这样做失败了,WSERV会尝试从其他客户的队列中清理一个事件。
焦点客户是这一操作的最后一个。 - WSERV只有在其他客户部分没有事件可清理的情况下,才会清理当前焦点客户。
如果清理失败,这个事件最终将被丢弃。

为了从客户队列清理事件,WSERV会尝试一些不同的策略,包括:
1。删除一对相关的指针弹起和按下事件。(如果相关的UP事件还没有被接收,他会先删DOWN事件,并延迟删除下一个符合的UP事件。
2。从非焦点客户队列删除按键的UP 或 DOWN 事件
3。在焦点队列里删除匹配的按键UP和DOWN成对事件(多数程序忽略此事件)
4。合并两个修改键事件并删除其中之一
5。删除匹配的焦点失去和获得成对事件
6。删除重复开机事件
7。删除这些事件:按键事件,指针进入和离开事件,拖动和释放事件,指针BUFFER准备事件 和随后的指针事件:拖动,移动。按键重复和开机。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值