赛扬自助机系统扫码大小异常问题
问题概述
赛扬自助机上的易联众三代扫码机器扫描门诊就诊指引单时大小存在异常
window下输入框正常现象如下图:
nfs下wine异常现象如下图:
经验证问题复现环境为:
软件环境:amd架构,nfs-5.0-U212版本
硬件环境:cpu J1900
wine版本:上游wine-8.6
问题分析
从问题截图猜测该问题大致原因可能为:
- 猜测1:驱动不正确导致机器未能正确发送事件至应用
- 猜测2:窗管未能正常发送事件至目标窗口
- 猜测3:wine自身存在问题导致显示错误
分析验证
验证准备
为了方便验证首先需要排除是该应用问题还是通用输入框问题,简单实现了一个输入框窗口,简单代码如下:
1
2
3
4
5
6
7
8
9
10HWND hwnd = CreateWindow("MyClass", "输入框示例", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
NULL, NULL, hInstance, NULL);
// 创建输入框
HWND hEdit = CreateWindow("EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 50, 50, 200, 25,
hwnd, NULL, hInstance, NULL);
// 显示窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);运行后如下图,发现wine下的输入框在该硬件上现象保持一致,存在同样大小写丢失问题
猜测1
介绍一个观察linux系统下硬件事件工具evtest
- evtest:evtest是打印evdev内核事件的工具,它直接从内核设备读取并打印设备描述的带有值和符号名的事件,可以用来调试鼠标、键盘、触摸板等输入设备evtest通常用于在X.org中调试输入设备的问题,evtest提供了内核的处理信息,根据这些信息。可以判断是内核问题还是X.org问题 输出数据中,“type”是input类型,可以是“EV KEY”、“EV SW”、“EV SND”、“EV LED”或数值
value可以是十进制也可以是十六进制,或者是查询的kev/开关/声音/LED的常量名。
- evtest:evtest是打印evdev内核事件的工具,它直接从内核设备读取并打印设备描述的带有值和符号名的事件,可以用来调试鼠标、键盘、触摸板等输入设备evtest通常用于在X.org中调试输入设备的问题,evtest提供了内核的处理信息,根据这些信息。可以判断是内核问题还是X.org问题 输出数据中,“type”是input类型,可以是“EV KEY”、“EV SW”、“EV SND”、“EV LED”或数值
通过evtest监听指定的硬件发送至内核的事件如图:
其中value值是按下与松开code值代表按键码,从evtest内核监听看”j”的按下与松开事件与”C”按下与松开事件一致,证明驱动正确的发送事件至内核。
猜测2:
介绍一个观察linux窗口事件的工具xev与查看linux窗口信息工具xwininfo
- xwininfo:列出窗口的基本几何信息和状态,简单用法是在终端里面执行命令
xwininfo
之后, 此时系统鼠标光标被xwininfo
捕获,光标变成十字星形状,移动鼠标点击要查看的窗口的任意区域,执行xwininfo
的终端输出了该窗口的这些 X11 属性. - xev:xev是一个X Window System的实用程序,用于查看键盘和鼠标的输入事件。
- xwininfo:列出窗口的基本几何信息和状态,简单用法是在终端里面执行命令
通过xwindow获取目标窗口window id再通过xev监听系统发送至窗口消息,如下图:
其中XmbLookupString与XLookupString获取的值均为”J”,证明窗管发送事件无误
猜测3:
- 开启wine下关于键盘输入的日志
WINEDEBUG=+timestamp,+pid,+tid,+key,+keyboard,+imm WINEPREFIX=”/opt/winux/hotpot/appstore/drawbloodsef-win-u-1.0nfs1” /opt/winux/wine-all/wine8.6/bin/wine /home/xm174/桌面/demo.exe &>~/222.txt
- 过滤KeyRelease|KeyPress事件
grep -a -E “KeyRelease|KeyPress” ~/222.txt
- 发现当前应用在按下“J”时,已经提前将Shift_L释放,其异常总体流程为:
- KeyPress(Shift_L)->KeyPress (J)->KeyRelease(Shift_L)->KeyRelease(j)
- 正常的“P”大写按键流程为:
- KeyPress(Shift_L)->KeyPress(P)->KeyRelease(Shift_L)->KeyRelease”p”
通过3,4发现按键事件存在异常,通过The Input Method Protocol文档中发现如果使用xim协议注册了xic,就会先将事件提供至输入法框架,如下图:
开启输入法日志,查看接收的事件变化
fcitx5 -r –verbose *=5
如下图,其中
XIM Key Event: 2
为按下事件,发现当前系统中输入法是正常发送shift+j
事件至应用中的,但应用日志存在大量KeyRelease(Shift_L)事件,将KeyPress(Shift_L)事件提前释放查看wine下的事件代码,在
/dlls/winex11.drv/event.c
的process_events
函数中处理窗口事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38while (XCheckIfEvent( display, &event, filter, (char *)arg ))
{
count++;
if (XFilterEvent( &event, None ))
{
//continue;
/*
* SCIM on linux filters key events strangely. It does not filter the
* KeyPress events for these keys however it does filter the
* KeyRelease events. This causes wine to become very confused as
* to the keyboard state.
*
* We need to let those KeyRelease events be processed so that the
* keyboard state is correct.
*/
if (event.type == KeyRelease)
{
KeySym keysym = 0;
XKeyEvent *keyevent = &event.xkey;
XLookupString(keyevent, NULL, 0, &keysym, NULL);
if (!(keysym == XK_Shift_L ||
keysym == XK_Shift_R ||
keysym == XK_Control_L ||
keysym == XK_Control_R ||
keysym == XK_Alt_R ||
keysym == XK_Alt_L ||
keysym == XK_Meta_R ||
keysym == XK_Meta_L))
continue; /* not a key we care about, ignore it */
}
else
continue; /* filtered, ignore it */
}
get_event_data( &event );
if (prev_event.type) action = merge_events( &prev_event, &event );
...
}发现代码中通过“XFilterEvent”过滤事件时,未将KeyRelease事件全部过滤,XK_Control_L也会进入函数正常处理,如第五点中的图中所示,程序如果使用xim并且注册xic,会将输入法需要的按键事件(XK_Control_L释放消息)通过XFilterEvent与XNextEvent发送至输入法再转发至应用。通过wine下的注释得知,此过滤操作是特殊处理输入法框架SCIM,因为其框架未能正常处理KeyRelease消息需要wine中特殊处理完成。
分析结论
通过上述分析与验证代码,可以初步猜测在nfs系统上如果存在输入法并在较慢芯片硬件上,快速触发按键释放消息,会导致wine应用接收消息错乱,导致应用未能正确处理按键按下释放消息。
解决方案
- 方案1: 卸载输入法,将按键消息直接通过窗管发送至应用,即XFilterEvent不会过滤窗管发送至应用的KeyRelease消息,经验证可以解决大小写问题。
- 方案2:读取系统中环境变量XMODIFIERS,若其设置为SCIM,则进入此判断循环,若不是则不进入,大致代码如下:修改后效果如下:
1
2
3
4
5
6
7
8
9
10
11
12char* env = getenv("XMODIFIERS");
while (XCheckIfEvent( display, &event, filter, (char *)arg ))
{
count++;
if (XFilterEvent( &event, None ))
{
if(NULL == env || strcmp(env, "@im=SCIM")){
continue;
}
}
‘’‘‘’‘
}
小结
这个问题通过对于x11窗口分析的几个工具与wine中的日志进行分析定位,该问题是需要开发者熟悉输入法与wine中的交互关系,理清其中消息处理步骤,才能把握问题根本原因。