OpenHarmony源码解析之多模输入子系统(一) 原创 精华
作者:吴文璐
1 简介
多模输入子系统是 OpenHarmony 输入事件管理框架。多模输入服务接收多种类型输入设备(触摸屏、鼠标、键盘、触摸板等)的输入事件,通过归一/标准化处理后,分发给多模客户端(应用,系统服务)。多模输入还提供事件注入接口,该接口目前仅对系统应用开放。
多模输入子系统分为框架部分和服务部分:框架部分封装了各种接口给其他子系统和应用来调用;服务部分实现了这些接口,并且实现了事件派发处理的核心逻辑。这两个部分运行在不同进程中,根据具体接口,通过socket或者binder ipc机制进行通信。
1.1 主要模块交互图
1.2 代码目录
/foundation/multimodalinput/input
├── frameworks # napi接口代码,客户端实现代码
├── interfaces # 对外接口存放目录
│ └── native # 对外native层接口存放目录
│ └── innerkits # 对系统内部子系统提供native层接口存放目录
├── service # 服务端代码
├── sa_profile # 服务启动配置文件
├── tools # 输入事件注入工具
├── uinput # 输入事件注入模块
├── util # socket相关工具类
2 多模客户端启动流程
2.1 时序图
说明:
-
Ability生命周期函数OnStart()中会去创建WindowImpl实例,WindowImpl::Create()中调用InputTransferStation::AddInputWindow()创建InputEventListener并注册到InputManagerImpl中。后续收到多模服务端发送来的输入事件之后会通过回调InputEventListener的接口函数,把事件上报到窗口管理,窗口管理再把事件进一步上报给ArkUI。
-
InputManagerImpl::SetWindowInputEventConsumer()方法中会去初始化多模Socket客户端,用于接收多模服务端发来的输入事件。
2.2 ArkUI何时注册的窗口管理输入事件回调?
AceAbility::OnStart()方法中先调用基类Ability::OnStart()方法走完上述时序图的流程,然后调用如下代码段,创建AceWindowListener,并调用WindowImpl::SetInputEventConsumer()注册输入事件回调。
OHOS::sptr<OHOS::Rosen::Window> window = Ability::GetWindow();
std::shared_ptr<AceAbility> self = std::static_pointer_cast<AceAbility>(shared_from_this());
OHOS::sptr<AceWindowListener> aceWindowListener = new AceWindowListener(self);
// register surface change callback and window mode change callback
window->RegisterWindowChangeListener(aceWindowListener);
// register drag event callback
window->RegisterDragListener(aceWindowListener);
// register Occupied Area callback
window->RegisterOccupiedAreaChangeListener(aceWindowListener);
// register ace ability handler callback
window->SetAceAbilityHandler(aceWindowListener);
// register input consumer callback
std::shared_ptr<AceWindowListener> aceInputConsumer = std::make_shared<AceWindowListener>(self);
window->SetInputEventConsumer(aceInputConsumer);
3.多模输入服务
3.1 多模服务初始化流程
说明:
- MMIService::OnThread()中会起循环,等待并处理epoll事件。接收到libinput相关的epoll事件后,调用LibinputAdapter::EventDispatch()处理input事件。
void MMIService::OnThread()
{
SetThreadName(std::string("mmi_service"));
uint64_t tid = GetThisThreadId();
delegateTasks_.SetWorkerThreadId(tid);
MMI_HILOGI("Main worker thread start. tid:%{public}" PRId64 "", tid);
#ifdef OHOS_RSS_CLIENT
tid_.store(tid);
#endif
libinputAdapter_.RetriggerHotplugEvents();
libinputAdapter_.ProcessPendingEvents();
while (state_ == ServiceRunningState::STATE_RUNNING) {
epoll_event ev[MAX_EVENT_SIZE] = {};
int32_t timeout = TimerMgr->CalcNextDelay();
MMI_HILOGD("timeout:%{public}d", timeout);
int32_t count = EpollWait(ev[0], MAX_EVENT_SIZE, timeout, mmiFd_);
for (int32_t i = 0; i < count && state_ == ServiceRunningState::STATE_RUNNING; i++) {
auto mmiEd = reinterpret_cast<mmi_epoll_event*>(ev[i].data.ptr);
CHKPC(mmiEd);
if (mmiEd->event_type == EPOLL_EVENT_INPUT) {
libinputAdapter_.EventDispatch(ev[i]);//处理input事件
} else if (mmiEd->event_type == EPOLL_EVENT_SOCKET) {
OnEpollEvent(ev[i]);
} else if (mmiEd->event_type == EPOLL_EVENT_SIGNAL) {
OnSignalEvent(mmiEd->fd);
} else if (mmiEd->event_type == EPOLL_EVENT_ETASK) {
OnDelegateTask(ev[i]);
} else {
MMI_HILOGW("Unknown epoll event type:%{public}d", mmiEd->event_type);
}
}
TimerMgr->ProcessTimers();
if (state_ != ServiceRunningState::STATE_RUNNING) {
break;
}
}
MMI_HILOGI("Main worker thread stop. tid:%{public}" PRId64 "", tid);
}
- InputEventHandler::BuildInputHandlerChain()会创建IInputEventHandler对象链,用于处理libinput上报的input事件。类图如下:
InputEventHandler::OnEvent(void event)调用
EventNormalizeHandler::HandleEvent(libinput_event event)开始按顺序处理输入事件。 - EventNormalizeHandler把libinput_event标准化成各种InputEvent(KeyEvent,PointerEvent,AxisEvent),并传递给下一级 EventFilterHandler处理
- EventFilterHandler会过滤一些事件,否则继续往下传递
- EventInterceptorHandler事件拦截器,拦截成功不会继续往下传
- KeyCommandHandler根据配置文件,对一些特殊按键,拉起特定应用界面,或者对电源键,音量键做特殊处理,否则继续往下传递
- KeySubscriberHandler应用订阅的组合按键(应用通过inputConsumer.on接口订阅)处理,否则继续往下传递
- EventMonitorHandler事件跟踪器,把事件分发给跟踪者并继续往下传
- EventDispatchHandler通过socket把事件派发给应用
4. 多模输入touch事件派发流程
说明:
MMIService收到libinput上报的input事件后,会调用InputEventHandler::OnEvent来处理输入事件。最终EventDispatchHandler通过socket把事件派发给目标应用进程。
5.如何确定输入事件派发的目标进程?
多模服务端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<WindowInfo> windowsInfo;
std::vector<DisplayInfo> displaysInfo; //Physical screen information list
};
以键盘按键事件为例。
收到libinput上报的输入事件之后,最终走到EventDispatchHandler::DispatchKeyEventPid(UDSServer& udsServer, std::shared_ptr<KeyEvent> key)函数,
简化的调用流程如下:
EventDispatchHandler::DispatchKeyEventPid() =>
InputWindowsManager::UpdateTarget() =>
InputWindowsManager::GetPidAndUpdateTarget()
int32_t InputWindowsManager::GetPidAndUpdateTarget(std::shared_ptr<InputEvent> inputEvent)
{
CALL_DEBUG_ENTER;
CHKPR(inputEvent, INVALID_PID);
const int32_t focusWindowId = displayGroupInfo_.focusWindowId;
WindowInfo* windowInfo = nullptr;
for (auto &item : displayGroupInfo_.windowsInfo) {
if (item.id == focusWindowId) {
windowInfo = &item;
break;
}
}
CHKPR(windowInfo, INVALID_PID);
inputEvent->SetTargetWindowId(windowInfo->id);
inputEvent->SetAgentWindowId(windowInfo->agentWindowId);
MMI_HILOGD("focusWindowId:%{public}d, pid:%{public}d", focusWindowId, windowInfo->pid);
return windowInfo->pid;
}
InputWindowsManager::GetPidAndUpdateTarget()函数中把当前获焦windowId信息设置到InputEvent中,并且返回目标窗口所在进程pid,有了目标进程pid,就可以获取到目标进程对应的socket会话的服务端fd,把事件派发给目标进程。
touch事件目标窗口信息的获取和按键事件不同,感兴趣的可以自己查看代码。
6.总结
本篇文章基于社区weekly_20230207的代码,对多模输入客户端注册监听流程和多模服务端事件派发流程作了简单介绍。相信大家通过本文,对多模输入子系统能有一个大致了解。
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。
图中流程整理的相当清晰
不错不错,挺好的
各个模块交互展示的很直观