目录
一、多模输入概述 1
二、 openharmony多模输入子系统整体架构和流程 2
2.1、整体架构 3
2.2、两个关键的数据流 4
三、 多模输入子系统客户端和服务端启动流程 6
3.1、多模输入子系统客户端启动流程 7
3.2、多模输入子系统服务端启动流程 8
四、多模输入事件分发流程和原则 11
4.1、事件分发流程 11
4.2、事件分发原则 11
4.3、如何确定输入事件分发的目标进程 12
五、案例分析 14
5.1、什么是触摸屏(TP)? 14
5.2、linux input子系统 14
5.3、触摸屏I2C驱动 15
5.4、linux标准事件转换成openharmony标准事件 19
六、多模输入事件注入 21
6.1、什么是注入事件 21
6.3、InjectEvent实现逻辑 22
6.3、Linux uinput机制 29

一、多模输入概述
传统输入方式:用户使用传统的输入方式进行操作的方式。比如按键输入(键盘输入)、触控输入和鼠标输入
多模输入方式:用户通过声音、肢体语言、信息载体(文字、图片、音频、视频)、环境等多个模式与计算机进行交流
顺序多模输入:用户必须在多种输入模式中进行切换,不能够一起使用这些模式
同时多模输入:允许用户一次使用多个模式与系统进行交互。比如在地图应用中,用户可以说“显示从这里到这里显示最快的路线”,同时使用触摸在屏幕地图指示这两个位置。

多模输入的优点
1、丰富的输入方式
2、自然和直观地交互方式
3、提高效率和准确度
4、更全面的输入信息
5、适应不同场景和需求

OpenHarmony多模输入子系统的目标:
OpenHarmony旨在为开发者提供NUI(Natural User Interface)的交互方式,有别于传统操作系统的输入,在OpenHarmony上,我们将多种维度的输入整合在一起,开发者可以借助应用程序框架、系统自带的UI组件或API接口轻松地实现具有多维、自然交互特点的应用程序。
二、openharmony多模输入子系统整体架构和流程
应用如何实现多模输入处理的?
https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/ui/arkts-common-events-focus-event.md (OpenHarmony多模输入事件处理应用相关API)
https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/device/inputdevice-guidelines.md (多模输入设备管理应用层开发)
https://gitee.com/openharmony/ai_intelligent_voice_framework (openharmony语音识别子系统)

例如一款教育应用中, 识别用户语音提问, 追踪手势动作进行操作反馈。
1)语音识别提问

img

2)手势识别

img

2.1、整体架构

img

Linux原生驱动和HDF驱动:openharmony支持两种内核驱动linux和HDF,主要是为了支持传统的输入设备的驱动支持。
输入设备管理模块:管理输入设备的属性信息;封装可监听接口给上层模块监听输入设备外设状态。
输入事件接受模块:接收设备输入事件,如键盘、鼠标、触摸屏、触摸板等设备的事件,支持从Linux驱动和HDF驱动获取事件。
输入事件预处理模块:对输入事件进行转换、归一化和标准化等统一操作。
输入事件分发模块:接收归一化后的输入事件数据,将其分发给相应的处理程序或者组件进行处理。
Inner Sdk和JsKit: Inner Sdk或将事件分发给ArkUI封装事件后转发给应用;或通过Jskit直接分发给应用。

img

2.2、两个关键的数据流
输入设备状态数据流:
输入设备的状态变化:设备插入状态、移除状态等
输入设备的属性信息:设备唯一标识、设备名称、设备支持的插入方式等

输入设备状态数据,经过内核设备驱动上报给多模服务端的输入设备状态管理模块(InputDevice Aware)。在InputDevice Aware对全局输入设备状态进行管理,同时设备状态会被被封装为可监听接口提供给上层业务模块用来监听系统输入外设的状态。

交互输入事件数据流:
描述鼠标、键盘、触摸屏输入事件
鼠标事件:鼠标X/Y坐标、鼠标按钮(鼠标左/中/右)事件等;
键盘事件:按键码、按键事件戳、按键所属设备等信息
触摸事件:事件戳、戳摸位置X/Y坐标
输入事件数据流程:
设备驱动上报给输入事件接受模块:完成输入事件从内核空间到用户空间的转发,将输入事件交付给多模服务端
事件接受模块上报给事件预处理模块:完成对输入事件的标准化
事件预处理模块上报给输入事件分发模块:以系统预设的分发机制和原则完成事件的分发

语音/手势识别事件数据流:

img

语音事件:
用户输入的语音传入给智能语音系统:用户传入的语音转入智能语音系统进行分析。
智能语音子系统根据应用开发者注册的热词进行对应的事件处理函数
42. I2S—音频播放与录音输入 — [野火]STM32库开发实战指南——基于野火霸天虎开发板 文档 (embedfire.com) (音频驱动开发介绍)

img

interfaces提供了对外了一些接口
frameworks封装了各种接口给其他子系统和应用来调用,也就是客户端代码
service实现了上述这些接口,并且实现了事件派发处理的核心逻辑
这两个部分运行在不同进程中,根据具体接口,通过socket或者binder ipc机制进行通信
uinput实现了事件注入的功能

三、多模输入子系统客户端和服务端启动流程

img

3.1、多模输入子系统客户端启动流程

img

说明:
 1) Ability生命周期函数OnStart()中会去创建WindowImpl实例,WindowImpl::Create()中调用InputTransferStation::AddInputWindow()创建InputEventListener并注册
 到InputManagerImpl中。后续收到多模服务端发送来的输入事件之后,会通过回调InputEventListener的接口函数,把事件上报到窗口管理,窗口管理再把事件进一步上报给ArkUI。
 2) InputManagerImpl::SetWindowInputEventConsumer()方法中会去初始化多模Socket客户端,用于接收多模服务端发来的输入事件。
3.2、多模输入子系统服务端启动流程

img

说明:
1)MMIService::OnThread()中会起循环,等待并处理epoll事件。接收到libinput相关的epoll事件后,调用LibinputAdapter::EventDispatch()处理input事件。
2)InputEventHandler::BuildInputHandlerChain()会创建IInputEventHandler对象链,用于处理libinput上报的input事件。类图如下:

  1. InputEventHandler::OnEvent(void event)调用EventNormalizeHandler::HandleEvent(libinput_event event)开始按顺序处理输入事件。
  2. EventNormalizeHandler把libinput_event标准化成各种InputEvent(KeyEvent、PointerEvent、AxisEvent),并传递给下一级 EventFilterHandler处理
  3. EventFilterHandler会过滤一些事件,否则继续往下传递
  4. EventInterceptorHandler事件拦截器,拦截成功不会继续往下传
  5. KeyCommandHandler根据配置文件,对一些特殊按键,拉起特定应用界面,或者对电源键,音量键做特殊处理,否则继续往下传递
  6. KeySubscriberHandler应用订阅的组合按键(应用通过inputConsumer.on接口订阅)处理,否则继续往下传递
  7. EventMonitorHandler事件跟踪器,把事件分发给跟踪者并继续往下传(对事件进行监控事件)
  8. EventDispatchHandler通过socket把事件派发给应用
    四、多模输入事件分发流程和原则
    事件分发:即当一个事件发生后,系统需要将这个事件传递给一个具体的应用的具体组件去处理。这个事件传递的过程就是分发过程。

4.1、事件分发流程

img

说明:
MMIService收到libinput驱动上报的input事件后,会调用InputEventHandler::OnEvent来处理输入事件。最终EventDispatchHandler通过socket把事件派发给目标应用进程。
4.2、事件分发原则
多模输入事件分发原则
鼠标/触摸屏事件分发原则
鼠标/触摸屏坐标指向哪个目标,输入事件就分发给对应的目标。

鼠标/触摸屏事件分发特殊场景说明:
如没有鼠标上的按钮按下,当前鼠标指向哪个目标,鼠标输入事件就分发给坐标锁定的目标。
如果有鼠标上的按钮按下,以第一个按钮按下时刻鼠标坐标锁定的目标作为分发目标,直到所有的按钮都抬起。
触摸屏输入时,将第一个手指按下锁定的目标作为输入事件分发目标,直到所有的手指都抬起。
按键事件分发原则
按键事件分发以当前用户可视界面中的焦点作为分发标的,当前界面焦点在哪个目标上,按键事件就分发给对应的目标。
输入事件分发模块对于事件预处理说明:
输入事件分发过程会优先经过输入事件拦截模块:当有拦截器注册时,输入事件会终止继续上报,相应的拦截器会拦截输入事件。
当没有拦截器注册时,输入事件会上报给输入事件监听模块,系统级应用(如:系统设置、桌面)通过监听输入事件,支持系统级特性(如:状态栏隐藏/消失等)。
事件监听模块对事件的监听不会阻断事件继续上报;支持事件监听的同时,输入事件还会继续上报。
对于按键事件会上报给订阅按键分发模块处理,分发给对应的应用处理,事件分发流程结束。其他触摸屏事件和鼠标事件不会经过订阅按键分发模块,会继续上报给应用窗口处理。
4.3、如何确定输入事件分发的目标进程
多模服务端InputWindowsManager类中有如下成员变量。
DisplayGroupInfo displayGroupInfo_;
std::map<int32_t, WindowInfo> touchItemDownInfos_;
DisplayGroupInfo中包含了当前获焦的窗口id,以z轴排序的窗口信息列表,物理屏幕信息列表等。displayGroupInfo_信息由窗口管理服务调用。
MMI::InputManager::GetInstance()->UpdateDisplayInfo(displayGroupInfo_)接口设置。
struct DisplayGroupInfo {
int32_t width; //Width of the logical display
int32_t height; //Height of the logical display
int32_t focusWindowId; //ID of the focus window
//List of window information of the logical display arranged in Z order, with the top window at the top
std::vector windowsInfo;
std::vector displaysInfo; //Physical screen information list
};
以键盘按键事件为例。
收到libinput上报的输入事件之后,最终走到EventDispatchHandler::DispatchKeyEventPid(UDSServer& udsServer, std::shared_ptr key)函数。
简化的调用流程如下:
EventDispatchHandler::DispatchKeyEventPid() => InputWindowsManager::UpdateTarget() => InputWindowsManager::GetPidAndUpdateTarget()

img


InputWindowsManager::GetPidAndUpdateTarget()函数中把当前获焦windowId信息设置到InputEvent中,并且返回目标窗口所在进程pid,有了目标进程pid,
就可以获取到目标进程对应的socket会话的服务端fd,把事件派发给目标进程。
五、案例分析
下面,我们通过一个触摸屏触摸的案例,来讲解openharmony中的linux中的事件如何从底层硬件一直上传到上层应用的过程
RK3568触摸屏为电容屏,I2C接口,电容屏芯片使用的是7寸GT911芯片
5.1、什么是触摸屏(TP)?
30. 电容触摸屏—触摸画板 — [野火]STM32库开发实战指南——基于野火霸天虎开发板 文档 (embedfire.com)
触摸面板通过双面胶粘在显示屏上,他们在硬件上没有关联,通常情况下我们会设置触摸面板的"分辨率"与显示屏的分辨率一致。触摸芯片自动完成触摸信息的采集和处理,
我们要做的就是配置触摸芯片、读取触点坐标。

img

触摸芯片有四个引脚连接到了芯片,其中scl、sda是I2C通信引脚。RSTN是触摸的复位引脚,配置触摸芯片时也会用到。
INT是中断引脚,默认高电平。发生触摸后该引脚会发送一个低电平的脉冲信号。
5.2、linux input子系统
https://kernel.org/doc/html/v4.12/input/index.html (linux input子系统官网介绍)
https://blog.csdn.net/weixin_45499326/article/details/131130227 (比较详细的linux input子系统博客)
5.3、触摸屏I2C驱动
https://blog.csdn.net/kuniqiw/article/details/112390863 (gt911驱动编写介绍)
https://zhuanlan.zhihu.com/p/663966271 (linux多点触摸屏驱动介绍)
5.3.1、probe函数

img


Struct goodix_ts_data结构体
完成上述初始化后,会调用另外一些芯片设置函数
1)goodix_get_gpio_config:获取触摸芯片rst和int使用的GPIO
2)goodix_reset:复位触摸芯片
3)复位触摸芯片goodix_i2c_test:测试I2C,尝试与触摸芯片通信。
4)goodix_read_version:读取触摸芯片版本,后边会根据触摸芯片版本来初始化触摸芯片。
5)goodix_get_chip_data:根据goodix_read_version函数获取的触摸芯片型号指定触摸的一些配置参数。
完成上述的配置就完成了可以和gt911进行通信了。下面讲解中断相关的处理
5.3.2、中断处理

img

当从底层硬件收到中断时,会调用irq handler来接受中断并调用goodix_process_events处理底层数据,读取设备状态并上报事件

img

goodix_process_events调用goodix_ts_read_input_report从gt911获取当前触摸点的数量和坐标数据(这里是通过轮询的方式等待触摸芯片把数据准备好)
Gt911对9个触摸点和9个以下的触摸点进行单独处理上报事件,这里用goodix_ts_report_touch_9b来讲解

img

最后调用input_mt_sync_frame和input_sync把上面多个触摸点的统一成input_event事件进行上报。
产生事件后,会在input core和对应的input handler的处理下在对应的input下面产生event。
调试输入系统时,直接执行类似下面的命令,然后操作对应的输入设备即可读出数据:
xdd /dev/input/event4

img

这里用标注的一行进行解释:
前面五行是序号和事件发生的事件信息
0300表示事件类型EV_ABS为绝对坐标事件
3500表示事件代码0x0035表示ABS_MT_POSITION_X表示上报X轴坐标位置
1901 0000 表示值也就是表示上报的坐标是0x00000119
https://kernel.org/doc/html/v4.12/input/multi-touch-protocol.html (多触摸协议)

5.4、linux标准事件转换成openharmony标准事件
libinput库负责将linux底层的事件转换成其内部事件
https://wayland.freedesktop.org/libinput/doc/latest/api/ (libinput库api)
libinput’s internal architecture — libinput 1.25.0 documentation (wayland.freedesktop.org)(libinput实现机制)

Mmi service如何监控libinput事件的

img

六、多模输入事件注入
6.1、什么是注入事件
注入事件:向应用程序注入模拟的输入事件,以模仿用户的操作或行为。通过事件注入,可以在软件系统中生产虚拟的事件,如按键、鼠标点击、触摸手势等,使其被系统或应用程序接收和处理,从而触发相应的操作和反应。
事件注入的用途:
自动化测试、用户界面交互模拟、远程控制。

openharmony多模输入系统提供了注入事件的接口,应用可以通过调用这个接口产生输入事件,然后将该输入事件注入到输入系统中进行处理。
注意: 多模输入目前提供的接口为按键事件注入接口,该接口仅对系统应用开放。

6.2、openharmony事件注入使用说明
BACK按键的事件注入。
当系统应用需要返回上一层时,通过注入接口将BACK键注入到多模服务,多模服务再上传到应用,以实现返回到上一层级目录的效果。使用方式如下所示:

img

6.3、InjectEvent实现逻辑

img

客户端传入KeyCode和KeyAction构造出KeyEvent,然后调用InputManager的SimulateKeyEvent
/foundation/multimodalinput/input/frameworks/proxy/events/src/input_manager.cpp

InputManager的SimulateKeyEvent调用InputManagerImpl的SimulateKeyEvent
/foundation/multimodalinput/input/frameworks/proxy/event_handler/src/input_manager_impl.cpp

InputManagerImpl的SimulateKeyEvent调用MutimodalEventHandler的InjectEvent
/foundation/multimodalinput/input/frameworks/proxy/event_handler/src/multimodal_event_handler.cpp

MutilmodalInputConnMgr->InjectEvent其实是一个IPC进程间调用,这会调用到客户端的MultimodalInputConnectProxy的InjectKeyEvent。

/foundation/multimodalinput/input/service/connect_manager/src/multimodal_input_connect_proxy.cpp

在MultimodalInputConnectProxy::InjectEvent会通过SendRequest向服务端MultimodalInputConnectStub发送数据。

/foundation/multimodalinput/input/service/connect_manager/src/multimodal_input_connect_stub.cpp

通过sendRequest将数据发送之后,服务端的MultimodalInputConnectStub的OnRemoteRequest就会被调用,然后调用StubInjectKeyEvent最终会调用MultimodaInputService的InjectEvent
/foundation/multimodalinput/input/service/module_loader/src/mmi_service.cpp

MultimodaInputService的InjectKeyEvent实际上会调用KeyboardInject的InjectKeyEvent(这里也就是具体的事件注入的相关逻辑)
/foundation/multimodalinput/input/uinput/keyboard_inject.cpp

在InjectKeyEvent中会通过injectThread的WaitFunc将注入事件(InjectInputEvent)继续向下注入。
/openhar/foundation/multimodalinput/input/uinput/inject_thread.cpp

在WaitFunc中会将injectInputEvent放入到injectQueue这个队列中,这个队列是用来存放injectInputEvent的,并且通过notify_one来唤醒InjectThread,由于目前只支持键盘类型事件的注入, 所有只会调用g_pKeyboard->EmitEvent(),g_pKeyboard是VirtualKeyboard的对象,VirtualKeyboard又继承自VirtualDevice,因此最终会调用VirtualKeyboard的EmitEvent。

在该函数中会将这个注入事件写入到文件描述符为fd_的设备文件中,从SetUp的函数中可以看出实际是写入到/dev/uinput这个设备文件中。
到此多模输入系统的整个注入事件的流程就结束了。
6.3、Linux uinput机制
事件注入在 Linux 中可以通过 uinput 内核模块来实现,该模块允许用户空间程序创建和管理虚拟输入设备。这些虚拟设备的事件可以被注入到系统中,就像它们来自真实的硬件设备一样。
https://kernel.org/doc/html/v4.12/input/uinput.html (linux uinput官方文档)
事件注入实现 (介绍如何实现事件注入)
Android/Linux Input子系统与uinput用户层事件注入 · 张福文的个人电子书 (gitbooks.io)
Linux /dev/uinput_uinput 触控手势-CSDN博客 (介绍了uinput的具体用处和开发)
linux下如何模拟按键输入和模拟鼠标_linux hid gadget模拟键鼠-CSDN博客 (linux下如何模拟相关事件)
事件响应工具evtest--用于调试诊断输入设备相关问题 | 统信软件-知识分享平台 (uniontech.com) (如何调试输入设备相关问题)

简单的C语言实例说明uinput交互模拟键盘事件:模拟按下然后释放’y’键

img

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐