OpenHarmony 源码解析之多模输入子系统(事件派发流程) 原创 精华
作者:孙仲毅
1 简介
多模输入系统主要用于接收按键,触摸等输入事件,并且会对这些原始输入事件进行处理,之后再对这些事件进行派发。同时多模输入系统还提供了注入事件的接口,应用可以通过调用这个接口产生输入事件,然后将该输入事件注入到输入系统中进行处理。
1.1 OpenHarmony 架构图
1.2 系统框架
- 多模输入系统主要是由
InputManagerService
,InputEventHub
,InputEventDistributer
来负责处理的。 InputManagerService
会启动InputEventHub
,并且会通过创建子线程的方式来创建InputEventDistributer
。- 当底层传来按键或触摸事件的时候,
InputEventHub
就会进行读取,并且会对这些原始的输入事件进行处理,处理完后会交给InputEventDistributer
进行派发。 InputEventDistributer
又会通过InputEventClientProxy
进行IPC
交互的方式发给应用端。
2 基础知识
2.1 代码结构
通过每个目录下的.gn文件可以看到每个目录下的模块都对应动态库
\interfaces\native\innerkits\event
下的文件编出来的是mmi_event.so
\interfaces\native\innerkits\napi
下的文件编出来的是injecteventhandler.so
\interfaces\native\innerkits\proxy
下的文件编出来的是libmultimodalinput_proxy.so
\service
下的文件编出来的是libmultimodalinput_service.so
\uinput
下的文件编出来的是mmi_uinject.so
3 事件注入接口
多模输入目前提供的接口为事件注入接口,该接口目前仅对系统应用开放。
3.1 JS接口
InJectEventHandler
是处理注入事件类。
接口名 | 描述 |
---|---|
function injectEventSync(keyEvent: KeyEvent) | 注入按键事件的JS层接口 |
\applications\standard\systemui\navigationBar\src\main\js\default\pages\backKey\backKey.js
可以从openharmony systemui
的navigationbar
的源码中看到, 当点击navigationbar
的back
键的时候,就会调用js
的接口函数injectEventSync
,并传入三个参数,其中:
isPress
: 按键的状态,true
表示down
, false
表示up
keyCode
: 键值码,2表示back
事件
keyDownDuration
: 按键按下到抬起之间的时长,单位ms
,1表示1ms
3.2 C++接口
接口名 | 描述 |
---|---|
bool InjectEvent(const sptr<MultimodalEvent> event) | 注入按键事件的C++层接口 |
3.3 系统内部接口
在\interfaces\native\innerkits\events\include
下的头文件都定义了各自对内部系统调用的口。
KeyEvent的主要接口
接口名 | 描 |
---|---|
getKeyCode() | 获取当前按键类事件的keycode值。 |
getMaxKeyCode() | 获取当前定义的按键事件的最大keycode值。 |
getKeyDownDuration() | 获取当前按键被按下的持续时长。 |
isKeyDown() | 获取当前按键事件是否是按下状态。 |
KeyBoardEvent的主要接口
接口名 | 描述 |
---|---|
enableIme() | 启动输入法编辑器。 |
disableIme() | 关闭输入法编辑器。 |
isHandledByIme() | 判断输入法编辑器是否在使用。 |
isNoncharacterKeyPressed(int keycode) | 判定输入的单个NoncharacterKey是否处于按下状态。 |
isNoncharacterKeyPressed(int keycode1, int keycode2) | 判定输入的两个NoncharacterKey是否都处于按下状态。 |
isNoncharacterKeyPressed(int keycode1, int keycode2, int keycode3) | 判定输入的三个NoncharacterKey是否都处于按下状态。 |
getUnicode() | 获取按键对应的Unicode码。 |
ManipulationEvent的主要接口
接口名 | 描述 |
---|---|
getPointerCount() | 获取一次事件中触控或轨迹追踪的指针数量。 |
getPointerId(int index) | 获取一次事件中,指针的唯一标识Id。 |
setScreenOffset(float offsetX, float offsetY) | 设置相对屏幕坐标原点的偏移位置。 |
getPointerPosition(int index) | 获取一次事件中触控或轨迹追踪的某个指针相对于偏移位置的坐标。 |
getPointerScreenPosition(int index) | 获取一次事件中触控或轨迹追踪的某个指针相对屏幕坐标原点的坐标。 |
getRadius(int index) | 返回指定index手指与屏幕接触的半径值。 |
getForce(int index) | 获取指定index手指触控的压力值。 |
getStartTime() | 获取操作开始时间。 |
getPhase() | 获取事件所属阶段。 |
MmiPoint的主要接口
接口名 | 描述 |
---|---|
MmiPoint(float px, float py) | 创建一个只包含x和y坐标的MmiPoint对象。 |
MmiPoint(float px, float py, float pz) | 创建一个包含x,y和z坐标的MmiPoint对象。 |
getX() | 获取x坐标值。 |
getY() | 获取y坐标值。 |
getZ() | 获取z坐标值。 |
toString() | 返回包含x、y、z坐标值信息的字符串 |
MouseEvent的主要接口
接口名 | 描述 |
---|---|
getAction() | 获取鼠标设备产生事件的行为。 |
getActionButton() | 获取状态发生变化的鼠标按键。 |
getPressedButtons() | 获取所有按下状态的鼠标按键。 |
getCursor() | 获取鼠标指针的位置。 |
getCursorDelta(int axis) | 获取鼠标指针位置相对上次的变化值。 |
setCursorOffset(float offsetX, float offsetY) | 设置相对屏幕的偏移位置信息。 |
getScrollingDelta(int axis) | 获取滚轮的滚动值。 |
MultimodalEvent的主要接口
接口名 | 描述 |
---|---|
getDeviceId() | 获取输入设备所在的承载设备id,如当同时有两个鼠标连接到一个机器上,该机器为这两个鼠标的承载设备。 |
getInputDeviceId() | 获取产生当前事件的输入设备id,该id是该输入设备的唯一标识,如两个鼠标同时输入时,它们会分别产生输入事件,且从事件中获取到的deviceid是不同的,开发者可以将此id用来区分实际的输入设备源。 |
getSourceDevice() | 获取产生当前事件的输入设备类型。 |
getOccurredTime() | 获取产生当前事件的时间。 |
getUuid() | 获取事件的UUID。 |
isSameEvent(UUID id) | 判断当前事件与传入id的事件是否为同一事件。 |
isHighLevelInput() | 判断当前事件是否是一个高级事件 |
getHighLevelEvent() | 获取当前的高级事件 |
StylusEvent的主要接口
接口名 | 描述 |
---|---|
getAction() | 获取触笔设备产生事件的行为。 |
getButtons() | 获取触笔设备的按键。 |
TouchEvent的主要接口
接口名 | 描述 |
---|---|
getAction() | 获取触屏设备产生事件的行为。 |
getIndex() | 获取点击index手指 |
getForcePrecision() | 获取手指按压的压力值 |
getMaxForce() | 获取手指按压的最大压力值 |
getTapCount() | 获取点击的数量 |
getPhase() | 获取事件所属阶段 |
3.4 InjectEvent的实现逻辑
\foundation\multimodalinput\input\interfaces\native\innerkits\napi\src\key_event_handler.cpp
在key_event_handler.cpp
中实现了InjectEventSync
这个接口。通过NAPI
获得应用端的isPressed
,KeyDownDuration
,KeyCode
这三个数值,并将这三个参数放入到KeyProperty
这个结构体中。然后调用KeyEvent
的Initialize
,将KeyProperty
封装到KeyEvent
中最后再调用InjectManager
的InjectEvent
。
\foundation\multimodalinput\input\interfaces\native\innerkits\proxy\src\inject_manager.cpp
foundation\multimodalinput\input\interfaces\native\innerkits\proxy\include\inject_manager.h
multimodalInputService_
->InjectEvent
其实是一个IPC
进程间调用,这会调用到客户端的MultimodalInputServiceProxy
的InjectEvent
。
foundation\multimodalinput\input\interfaces\native\innerkits\proxy\src\multimodal_input_service_proxy.cpp
在MultimodalInputServiceProxy::InjectEvent
会通过SendRequest
向服务端MultimodalInputServiceStub
发送数据。
foundation\multimodalinput\input\service\src\multimodal_input_service_stub.cpp
通过sendRequest
将数据发送之后,服务端的MultimodalInputServiceStub
的OnRemoteRequest
就会被调用,最终会调用MultimodaInputService
的InjectEvent
。
\foundation\multimodalinput\input\service\src\multimodal_input_service.cpp
MultimodaInputService
的InjectEvent
实际上会调用KeyboardInject的InjectKeyEvent
,从函数的实现来看,目前只使用了KeyboardInject
,也就是说目前只支持键盘事件的注入。
\foundation\multimodalinput\input\uinput\keyboard_inject.cpp
在InjectKeyEvent
中会通过InjectInputEvent
的WaitFunc
将事件注入继续注入。
\foundation\multimodalinput\input\uinput\inject_thread.cpp
在WaitFunc
中会将injectInputEvent
放入到injectQueue
这个队列中,这个队列是用来存放injectInputEvent
的,并且通过notify_one
来唤醒InjectThread
,由于目前只支持键盘类型事件的注入,所有只会调用g_pKeyboard->EmitEven()
,g_pKeyboard
是VirtualKeyboard
的对象,VirtualKeyboard
又继承自VirtualDevice
,因此最终会调用VirtualKeyboard
的EmitEvent
。
foundation\multimodalinput\input\uinput\virtual_device.cpp
在该函数中会将这个注入事件写入到文件描述符为fd_
的设备文件中,从SetUp
的函数中可以看出实际是写入到/dev/uinput
这个设备文件中。
到此多模输入系统接口的介绍以及InjectEvent
整个注入事件的流程就结束了。
4 事件派发流程
4.1 事件派发流程图
4.2 源码分析
下面就对多模输入系统事件派发流程的源码进行分析。
InputManagerService
\foundation\graphic\wms\services\wms\wms.cpp
InputManagerService
的启动是在WMS
的main
函数中通过InputManagerService::GetInstance()->Run()
执行的。
\foundation\graphic\wms\services\ims\input_manager_service.cpp
在InputManagerService::Run()
中首先会创建InputEventHub
的对象并通过RegisterReadCallbac
k来注册InputEventHub
的回调,然后通过SetUp
来启动InputEventHub
,InputEventHub
主要是用于对底层原始输入事件的读取和处理,该函数的最后会创建distributerThread
子线程,用于对输入事件的派发。
InputEventHub
\foundation\graphic\wms\services\ims\input_event_hub.cpp
在这个函数中InputEventHub
主要的工作就是通过调用驱动层的OpenInputDevice
来打开输入设备,并且会将EventCallback
的回调函数通过驱动层的RegisterReportCallback
进行注册。当底层有事件传递上来,EventCallback
就会被调用。OpenInputDevice
和RegisterReportCallback
具体实现分别是在drivers/peripheral/input/hal/src/input_manager.c
和drivers/peripheral/input/hal/src/input_reporter.c
中。
\foundation\graphic\wms\services\ims\input_event_hub.cpp
当底层有输入事件上来的话,EventCallback
就会被调用,在这个函数里会通过EventPackage->type来判断输入事件的类型,其中
EV_REL
是相对坐标的输入事件,比如轨迹球,鼠标事件EV_ABS
是绝对坐标的输入事件,比如触屏触摸事件EV_KEY
是按键输入事件,比如设备上的物理按键的点击事件EV_SYN
是Motion
的一系列动作结束标志位
如果是鼠标事件,会将相对坐标值放入到data.x
和data.y
中,如果是触屏触摸事件,会将在触屏上触摸的坐标位置放入到data.x
和data.y
中,如果是按键事件会将按键的点击状态放入到data.state
中。
处理完输入事件后,会将数据放入到data
中,并通过readCallback
传给InputManagerService
进行处理,之后就会调用InputManagerService::ReadCallback
。
\foundation\graphic\wms\services\ims\input_manager_service.cpp
ReadCallback
这个函数首先会判断eventQueue
这个事件队列里事件数量是否达到最大数量,如果达到最大数量该线程就一直等待,否则就会把该事件放到eventQueue
这个事件队列里,并且同时也会发出nonEmpty
的signal
, 来让Distribute
中的线程停止等待。
Distribute
函数中,当eventQueu
e队列里没有事件的时候,就会一直等待,当有事件来的时候就会停止线程等待,然后会遍历整个eventQueue
这个队列,把每个事件获取出来后放入到events
这个数组中,并做为参数放入到InputEventDistributer::Distribute
中进行事件的派发。
InputEventDistributer
\foundation\graphic\wms\services\ims\input_event_distributer.cpp
这个函数比较简单,主要就是遍历所有的InputEventClientProxy
, 并且调用各自的onRawEvent
进行实际的派发工作。
InputEventClientProxy
\foundation\graphic\wms\services\ims\input_event_client_proxy.cpp
这个函数主要就是通过ipc的交互方式把输入事件传给应用端。
到此整个多模输入系统的事件派发流程就结束了。
5 总结
通过本文的学习可以了解多模输入系统事件派发的流程,以及多模输入系统的接口和注入事件的流程,结合以上的源码分析会对多模输入子系统会有更深入的理解。
更多原创内容请关注:开鸿 HarmonyOS 学院
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
好帖,支持一波。
相当详细,非常有用
厉害厉害,学习了
我之前读鸿蒙 Input 代码的时候流程总是跟断掉, 一直搞不清楚完整逻辑~
还是大佬厉害, 从底到上的流程都讲解的好清楚~
非常好用,学习了
写的很详细,收藏了
开鸿是鸿蒙生态中的一股新势力咩?
关于OpenHarmony的文章不多啊,果断收藏
不错,收藏了。
看到了熟悉的代码了,之前工作就是负责这一块,深入了解之后,才发现每个子系统的开发都不是轻而易举的事情。感谢在默默背后付出的大佬们
看完后对多模输入子系统有了更深入的理解
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
哈哈哈,你猜
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~
还有更多OpenHarmony源码解析,小伙伴们记得关注、点赞、收藏哦~