OpenHarmony开发者论坛
标题:
【SUBJECT技术】(MMI, ArkUI, LibInput)实现“输入事件收集、打包、接收以及转换完全日志”的方法
[打印本页]
作者:
wangyeyu
时间:
2023-12-8 16:53
标题:
【SUBJECT技术】(MMI, ArkUI, LibInput)实现“输入事件收集、打包、接收以及转换完全日志”的方法
[md]# 实现“输入事件收集、打包、接收以及转换完全日志”的方法
## 1.问题背景
输入事件是开源系统提供人机交互的纽带。从Linux libinput上报由用户操作产生的各种事件。输入设备包括触摸屏、手势、键盘、鼠标、触摸板、手写笔、游戏手柄、音量电源键等各种设备。MultimodalInput即多模输入子系统(简称MMI),它进行收集,初步转换成自己对应的事件类型。然后经由拦截、消费、监听、订阅等节点逐级对事件打包上报。凡是注册了多模输入的光标事件PointerEvent、按键事件KeyEvent或者轴类事件AxisEvent,都会在输入事件产生后收到回调处理。典型的就是上报给ArkUI子系统,相应地ArkUI收到输入事件会将多模子系统的事件转换成自己的事件类型。
在整个过程中,输入事件的缺失、冗余、转换错误都将导致用户失败的体验。整个排查过程就是层层、处处设置日志进行分析,而每个开发者的思路和对模块的了解程度不尽相同,所以排查过程比较困难。
于是想从Linux输入事件的收集、MMI打包、ARKUI接收以及转换等关键节点输出完全、可阅读的信息,帮助开发者迅速定位问题。
## 2.特别改进点
* 之前每处会打印多条,并且各处所关注的参数不尽相同,改进后一条输入事件打印一条完全日志。包含来自 libinput模块所有以libinput_ 开头的函数。
* 之前多处打印日志,改进后分别在收集、打包、接收和转换节点输出。
如蓝色框所示分别表示几个节点时的日志:
![log-string.PNG](
https://forums-obs.openharmony.c ... zhxkjhh7fh7gsha.png
"log-string.PNG")
* 之前输出枚举数值,改进后调用那些能够输出枚举名称的函数,例如事件类型用EventTypeToString() 和GetEventType()代替之前的GetEventType()。代码为:
```cpp
{
std::string logPkt;
logPkt += ",EventType:" + std::string(inpEvt->EventTypeToString(inpEvt->GetEventType())) +
std::to_string(inpEvt->GetEventType());
}
```
* 之前deviceID不知道是什么设备,改进后将设备名称和 deviceID结合输出。
如红框所示设备名称与DeviceId相结合:
![log-deviceId.PNG](
https://forums-obs.openharmony.c ... 0zwtz9t4e21491h.png
"log-deviceId.PNG")
* 之前不能够输出枚举名称的即未实现枚举值到名称转换的switch case 函数,采用 magic_enum::enum_name() 可以输出枚举值名称,并且无须添加switch case函数。
枚举值到枚举名称的转换,如代码所示:
```cpp
{
std::string logPkt;
logPkt +=",action(mmi-ark):["+std::string(evt.pointerEvent->DumpPointerAction())+ std::to_string(evt.pointerEvent->GetPointerAction())+
"-"+magic_enum::enum_name(evt.action).data()+ std::to_string(static_cast<int32_t>(evt.action))+"]";
}
```
* 之前转换前后的参数分属在不同日志条目中,现在整合在一块。例如鼠标按下动作在多模MMI中枚举值为2,名称为down,在ArkUI中枚举值是0,名称为DOWN, 打印的日志为 action(mmi-ark):[down2-DOWN0],如红框所示表示了两个模块的不同枚举转换值:
![log-enumstring.PNG](
https://forums-obs.openharmony.c ... edd7dz74e7mem1s.png
"log-enumstring.PNG")
* 一条输入事件打印一条完全日志时,有大部分的参数是相同的,字符串会很长。怎么把焦点放在变化的参数上,选择包含'__LINE__'的日志,并且删除'/'前的日志前缀,按照逗号分隔符将相邻日志的重复参数(字段)去掉,后面有实现的方法说明。
## 3.日志编写及实现
### 3.1.收集 Linux上报的输入事件
在多模输入子系统中,处理事件函数的入口进行事件收集,传入的是libinput_event* event。
```
void EventNormalizeHandler::HandleEvent(libinput_event* event)
{
// ......
PrintState_LibinputEvent(event); // sstate1-COL
// ......
}
```
收集时的日志实现
```
int32_t EventNormalizeHandler:
rintState_LibinputEvent(libinput_event *event) // wyyxiu sstate1-COL
{
//......
std::string logPkt;
logPkt = "sstate1-COL,";
switch (type) {
case LIBINPUT_EVENT_DEVICE_ADDED: {
//......
logPkt += ",sysName:" + std::string(ssysName) + ",udevTags:" + std::to_string(udevTags) +
",bus:" + std::to_string(bus) + ",phys:" + std::string(sphys) + ",uniq:" + std::string(suniq);
logPkt += ",deviceId:" + std::string(sname) + std::string(ssysName);
//......
MMI_HILOGI("$%{public}d__LINE__ %{public}s", __LINE__, logPkt.c_str());
break;
}
case LIBINPUT_EVENT_DEVICE_REMOVED: {
logPkt += "[EVENT_DEVICE_REMOVED]" + std::to_string(type) + ",deviceId:" + std::string(sname) + std::string(ssysName);
MMI_HILOGI("$%{public}d__LINE__ %{public}s", __LINE__, logPkt.c_str());
break;
}
case LIBINPUT_EVENT_KEYBOARD_KEY: {
// ......
logPkt += "[EVENT_KEYBOARD_KEY]" + std::to_string(type) + " time:" + std::to_string(time) +
",deviceId:" + std::string(sname) + std::string(ssysName) + ",keyCode:" +
std::to_string(keyCode) + ",keyAction:" + std::to_string(keyAction);
MMI_HILOGI("$%{public}d__LINE__ %{public}s", __LINE__, logPkt.c_str());
break;
}
case LIBINPUT_EVENT_POINTER_MOTION:
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
case LIBINPUT_EVENT_POINTER_BUTTON:
case LIBINPUT_EVENT_POINTER_BUTTON_TOUCHPAD:
case LIBINPUT_EVENT_POINTER_AXIS:
case LIBINPUT_EVENT_POINTER_TAP:
case LIBINPUT_EVENT_POINTER_MOTION_TOUCHPAD: {
//......
logPkt += "[EVENT_POINTER]" + std::to_string(type) + ",deviceId:" + std::string(sname) + std::string(ssysName) +
",dxUnacc:" + std::to_string(static_cast<int32_t>(dxUnacc)) +
",dyUnacc:" + std::to_string(static_cast<int32_t>(dyUnacc)) + ",button:" + std::to_string(button) +
",state:" + std::to_string(state) + ",source:" + std::to_string(source) + ",hasVerticalAxis:" + std::to_string(hasVerticalAxis) +
",axisVerValue:" + std::to_string(static_cast<int32_t>(axisVerValue)) + ",hasHorizontalAxis:" + std::to_string(hasHorizontalAxis) +
",axisHorValue:" + std::to_string(static_cast<int32_t>(axisHorValue)) +
",dx:" + std::to_string(static_cast<int32_t>(dx)) + ",dy:" + std::to_string(dy) + ",fingerCount:" + std::to_string(fingerCount);
MMI_HILOGI("$%{public}d__LINE__ %{public}s", __LINE__, logPkt.c_str());
break;
}
case LIBINPUT_EVENT_TOUCHPAD_DOWN:
case LIBINPUT_EVENT_TOUCHPAD_UP:
case LIBINPUT_EVENT_TOUCHPAD_MOTION: {
// ......
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
case LIBINPUT_EVENT_GESTURE_SWIPE_END:
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
case LIBINPUT_EVENT_GESTURE_PINCH_END: {
// ......
break;
}
case LIBINPUT_EVENT_TOUCH_DOWN:
case LIBINPUT_EVENT_TOUCH_UP:
case LIBINPUT_EVENT_TOUCH_MOTION: {
// ......
break;
}
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
case LIBINPUT_EVENT_TABLET_TOOL_TIP: {
// ......
break;
}
case LIBINPUT_EVENT_JOYSTICK_BUTTON: {
// ......
break;
}
case LIBINPUT_EVENT_JOYSTICK_AXIS: {
// ......
break;
}
case LIBINPUT_EVENT_SWITCH_TOGGLE: {
// ......
break;
}
default: {
MMI_HILOGW("[default] This device does not support :%{public}d", type);
break;
}
}
return RET_OK;
}
```
### 3.2.事件派发打包前的输入事件
在多模输入子系统中,输入事件数据转换(事件打包)函数的末尾打印事件,传入的是KeyEvent、SwitchEvent、PointerEvent。以打印按键事件为例说明:
```
int32_t InputEventDataTransformation::KeyEventToNetPacket(
const std::shared_ptr<KeyEvent> key, NetPacket &pkt)
{
//......
std::string logPkt;
if (PrintState_InputEvent(key, logPkt)) {
PrintState_KeyEvent(key, logPkt);// sstate2-PKG
}
return RET_OK;
}
```
打包时的日志实现
```
bool InputEventDataTransformation:
rintState_InputEvent(const std::shared_ptr<InputEvent> inpEvt, std::string &logPkt)
{
static int32_t lastEvtId = -1;
if (lastEvtId == inpEvt->GetId()) {
return false;
}
lastEvtId = inpEvt->GetId();
logPkt ="sstate2-PKG,[InputEvent] mmi_Id:"+std::to_string(inpEvt->GetId())+
",ActionTime:"+std::to_string(inpEvt->GetActionTime())+
",EventType:"+std::string(inpEvt->EventTypeToString(inpEvt->GetEventType()))+std::to_string(inpEvt->GetEventType())+
//",ActionStartTime:"+std::to_string(inpEvt->GetActionStartTime())+
",Action:"+std::to_string(inpEvt->GetAction())+
//",SensorInputTime:"+std::to_string(inpEvt->GetSensorInputTime())+
",DeviceId:"+std::to_string(inpEvt->GetDeviceId())+",TargetDisplayId:"+std::to_string(inpEvt->GetTargetDisplayId())+
",TargetWindowId:"+std::to_string(inpEvt->GetTargetWindowId())+
",AgentWindowId:"+std::to_string(inpEvt->GetAgentWindowId())+",Flag:"+std::to_string(inpEvt->GetFlag());
return true;
}
```
```
void InputEventDataTransformation:
rintState_KeyEvent(const std::shared_ptr<KeyEvent> evt, std::string &logLkt)
{
logLkt +=",[KeyEvent] Action:"+std::string(evt->ActionToString(evt->GetKeyAction()))+std::to_string(evt->GetKeyAction())+
",Code:"+std::string(evt->KeyCodeToString(evt->GetKeyCode()))+std::to_string(evt->GetKeyCode()) +
",Intention:"+std::to_string(evt->GetKeyIntention());
auto keys = evt->GetKeyItems();
int32_t size = static_cast<int32_t>(keys.size());
logLkt += ",keys.size:"+std::to_string(size);
for (const auto &item : keys) {
logLkt += ",Code:"+std::to_string(item.GetKeyCode())+ ",DownTime:"+std::to_string(item.GetDownTime())+
",DeviceId:"+std::to_string(item.GetDeviceId())+ ",IsPressed:"+std::to_string(item.IsPressed())+
",Unicode:"+std::to_string(item.GetUnicode());
}
logLkt += ",NUMFunctionKey:"+std::to_string(evt->GetFunctionKey(KeyEvent::NUM_LOCK_FUNCTION_KEY))+
",CAPSFunctionKey:"+std::to_string(evt->GetFunctionKey(KeyEvent::CAPS_LOCK_FUNCTION_KEY))+
",SCROLLFunctionKey:"+std::to_string(evt->GetFunctionKey(KeyEvent::SCROLL_LOCK_FUNCTION_KEY));
MMI_HILOGI("$%{public}d__LINE__ %{public}s",__LINE__, logLkt.c_str());
}
```
### 3.3.ArkUI接收和转换时的输入事件
典型接收输入事件客户端是ArkUI,以鼠标事件为例。它在处理来自多模输入(MMI)的鼠标事件,首先转换成自己的事件类型如MouseEvent,然后进行后续ACE-ENGING的回调处理。需要在事件转换后和回调处理前进行日志记录,调用PrintState_MouseEvent(event)。
```
void AceViewOhos:
rocessMouseEvent(const std::shared_ptr<MMI:
ointerEvent>& pointerEvent)
{
MouseEvent event;
if (pointerEvent) {
auto container = Platform::AceContainer::GetContainer(instanceId_);
CHECK_NULL_VOID(container);
ConvertMouseEvent(pointerEvent, event, container->IsScenceBoardWindow());
}
CHECK_NULL_VOID(mouseEventCallback_);
PrintState_MouseEvent(event);// sstate3-ARK
mouseEventCallback_(event, markProcess);
}
```
转换时的日志实现
```
#include "magic_enum.h" // sstate3-ARK
void PrintState_MouseEvent(const MouseEvent& evt)
{
MMI:
ointerEvent:
ointerItem item;
evt.pointerEvent->GetPointerItem(evt.id, item);
std::set<int32_t> pressedSet = evt.pointerEvent->GetPressedButtons();
std::string logPkt;
logPkt = "sstate3-ARK,[MouseEvent] mmi_Id:"+std::to_string(evt.pointerEvent->GetId())+
//",time:"+std::to_string(evt.time.time_since_epoch().count()/1000)+
",deviceId:"+std::to_string(evt.deviceId)+
",action(mmi-ark):["+std::string(evt.pointerEvent->DumpPointerAction())+std::to_string(evt.pointerEvent->GetPointerAction())+
"-"+magic_enum::enum_name(evt.action).data()+std::to_string(static_cast<int32_t>(evt.action))+"]"+
//",pullAction:"+std::to_string(static_cast<int32_t>(evt.pullAction))+
",buttonId(mmi-ark):["+std::to_string(evt.pointerEvent->GetButtonId())+
"-"+magic_enum::enum_name(evt.button).data()+std::to_string(static_cast<int32_t>(evt.button))+"]"+
",sourceType(mmi-ark):["+std::string(evt.pointerEvent->DumpSourceType())+std::to_string(evt.pointerEvent->GetSourceType())+
"-"+magic_enum::enum_name(evt.sourceType).data()+std::to_string(static_cast<int32_t>(evt.sourceType))+"]"+
",pressedBtns_size(mmi):"+std::to_string(pressedSet.size());
if (pressedSet.size() > 0) {
logPkt += ",btnId(mmi-ark):[";
for (int32_t btnId : pressedSet) {
logPkt += " "+std::to_string(btnId);
}
logPkt += "-l1r2m4&"+std::to_string(evt.pressedButtons)+"]";
}
logPkt += ",pointerId(id):"+std::to_string(evt.id)+
",windowX(x):"+std::to_string(static_cast<int32_t>(evt.x))+
",windowY(y):"+std::to_string(static_cast<int32_t>(evt.y))+
",z(ark):"+std::to_string(static_cast<int32_t>(evt.z))+
",displayX(screenX):"+std::to_string(static_cast<int32_t>(evt.screenX))+
",displayY(screenY):"+std::to_string(static_cast<int32_t>(evt.screenY))+
",deltaX:"+std::to_string(static_cast<int32_t>(evt.deltaX))+
",deltaY:"+std::to_string(static_cast<int32_t>(evt.deltaY))+
",deltaZ:"+std::to_string(static_cast<int32_t>(evt.deltaZ))+
",scrollX:"+std::to_string(static_cast<int32_t>(evt.scrollX))+
",scrollY:"+std::to_string(static_cast<int32_t>(evt.scrollY))+
",scrollZ:"+std::to_string(static_cast<int32_t>(evt.scrollZ))+
",targetDisplayId:"+std::to_string(evt.targetDisplayId);
for (size_t i=0; i<evt.enhanceData.size(); i++) {
logPkt += ",enhanceData:"+std::to_string(evt.enhanceData
);
}
LOGI("$%{public}d__LINE__ %{public}s",__LINE__, logPkt.c_str());
}
```
### 3.4.参考日志
```
08-07 18:17:02.487 372 892 I C02800/EventNormalizeHandler: in PrintState_LibinputEvent, $743__LINE__ sstate1-COL,[EVENT_TOUCH]502 time:3245578489,x:271,y:715,toolx:0,tooly:0,longAxis:0,shortAxis:0,pressure:0,toolRectWidth:0,toolRectHeight:0,seatSlot:0,toolType:-1
08-07 18:17:02.489 372 892 I C02800/PACKAGE: in PrintState_PointerEvent, $573__LINE__ state2-PKG,[InputEvent] mmi_Id:99,ActionTime:3245578489,EventType:pointer131072,Action:0,DeviceId:8,TargetDisplayId:0,TargetWindowId:1,AgentWindowId:1,Flag:0,[PointerEvent] ActionTime:3245578489,Action:move3,PointerId:0,SourceType:touch-screen2,ButtonId:-1,FingerCount:0,Axes:0,pressedBtns_size:0,pointerIds.size:1,item-pointerId:0,IsPressed:1,DisplayX:305,DisplayY:715,Width:0,Height:0,TiltX:0,TiltY:0,ToolDisplayX:0,ToolDisplayY:0,ToolWindowX:0,ToolWindowY:0,ToolWidth:0,ToolHeight:0,Pressure:0,ToolType:0,LongAxis:0,ShortAxis:0,DeviceId:8,RawDx:0,RawDy:0,pressedKeys.size:0,buffer.size:0
08-07 18:17:02.490 1090 1090 I C03900/Ace: [mmi_event_convertor.cpp(PrintState_TouchEvent)-(0)] $113__LINE__ sstate4-ARK,[TouchEvent] mmi_Id:98,deviceId:8,action(mmi-ark):[move3-2],sourceType(mmi-ark):[touch-screen2-2],pointerIds.size:1,size:0,mmi_pointerId(id):0,mmi_windowX(x):305,mmi_windowY(y):763,mmi_displayX(screenX):305,mmi_displayY(screenY):763,IsPressed:1,mmi_pressure(force):0,tiltX:0,tiltY:0,toolType(mmi-ark):[0-1],targetDisplayId:0
```
## 4.枚举值到枚举名称的转换方法
现代C++的枚举静态反射(到字符串、从字符串、迭代),适用于任何枚举类型,无需任何宏或模板代码
### 4.1.原理简述
枚举值转换成字符串这一步,是是利用了函数模板和 __PRETTY_FUNCTION__ 组合使用获得,也就是对 __PRETTY_FUNCTION__ 进行截取得到的字符串。
__PRETTY_FUNCTION__ 在预编译阶段会替换成带有参数的函数名,比如 constexpr auto magic_enum::detail::n() [with E = WeekDay; E e = WD_MONDAY] 从中截取出 WD_MONDAY 就可以了。
### 4.2.局限性
为了实现从字符串到枚举值的转换,这个库的内部定义了一个整数范围,默认从-128到128,用于遍历查找字符串对应的枚举值是多少,并且在代码中加了 static_assert 来判断范围,如果超过了这个范围就会报编译错误
### 4.3.参考链接
* 推荐一个C++枚举转字符串的开源项目
https://blog.csdn.net/albertsh/article/details/125955230
* magic_enum:C++ enum静态反射库
https://zhuanlan.zhihu.com/p/464758750
* magic_enum 开源库链接
https://github.com/neargye/magic_enum
## 5.缩短日志长度的方法
把焦点放在变化的参数上,删除重复参数的py逻辑实现,缺失的函数需要自己补齐。
```py
def read_log_file(path):
"""
# 读日志文件到字符串列表
"""
file = open(path, 'r', encoding='utf-8',errors='ignore')
file_line = file.readlines()
return file_line
def abbreviating_log():
# 读取日志
log_lines = read_log_file('logfile.txt')
if len(log_lines) == 0:
print('Error: Log file is empty!')
return
# 抽取包含 '__LINE__' 关键字的日志,并且删除'/'前的日志前缀
log_lines = preprocess_log_file(log_lines)
file_line = log_lines.copy()
i = 0
# 删除相邻且行号相同日志中不变的参数
while i <len(file_line):
left_subcurrent_string = get_left_string(log_lines
, '__LINE__')
right_subcurrent_string = get_right_string(log_lines
, '__LINE__')
if right_subcurrent_string == '':
i += 1
continue
for index in range(i-1, -1, -1):
left_sub_pre_string = get_left_string(log_lines[index], '__LINE__')
right_sub_pre_string = get_right_string(log_lines[index], '__LINE__')
if left_sub_pre_string == left_subcurrent_string:
current_items = re.split(r'[,]', right_subcurrent_string)
pre_items = re.split(r'[,]', right_sub_pre_string)
for k in range(len(current_items)-1, -1, -1):
if current_items[k] in pre_items:
current_items.pop(k)
if len(current_items) > 0:
file_line
= left_subcurrent_string + '__LINE__'
for item in current_items:
file_line
+= ',' + item
file_line
+= '\n'
else:
file_line
= ''
break
i += 1
# 保存只有变化参数的日志
save_file(file_line, 'log_abbreviating.txt')
```
## 6.附件
### 6.1.打桩代码
![dumpInputEvent.rar](
https://forums-obs.openharmony.cn/forum/
"dumpInputEvent.rar")
[/md]
作者:
wangyeyu
时间:
2023-12-8 16:56
压缩包为打桩代码以及三方文件magic_enum
欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/)
Powered by Discuz! X3.5