e. 修复Weston应用方式启动OpenFDE,Ctrl按键+鼠标滚轮模拟双指触摸缩放功能无法使用的问题
本文记录的是在银河麒麟V10(SP1)2303版本环境下,排查并修复Weston应用方式启动OpenFDE,Ctrl按键+鼠标滚轮模拟双指触摸缩放功能无法使用的问题的过程。
原理介绍
Xorg 是麒麟系统显示服务端程序,X.Org Server(简称Xorg)是X服务器的一种实现,它是X Window System(X11)中最常用的服务器。X.Org Server 是开源的,并且广泛用于Linux和UNIX系统上。Xorg进程在收到来自鼠标键盘内核驱动上报的事件后,查找当前的焦点目标应用窗口并将鼠标键盘事件通过XCB库(X协议的c语言绑定)发送给对应的焦点应用。这里OpenFDE是基于weston应用窗口运行,weston窗口作为client端实现了X协议,并将Xorg发送过来的鼠标键盘事件通过wayland协议转发给OpenFDE的显示合成器hwc。hwc再将鼠标滚轮事件转换成双指触摸事件并写入OpenFDE启动时创建的wl_touch_events设备节点,Android的EventHub监听此设备节点,当监听收到触摸事件时,将触摸事件通过framework上报给Android应用层。如下图所示。
其中hwc中的代码逻辑是鼠标滚轮模拟双指触摸缩放功能的具体实现,事件处理流程如下图所示。
鼠标模拟双指缩放功能实现完后遇到的问题:
weston应用方式下键盘Ctrl按键按下时滚动鼠标滚轮会导致自动触发Ctrl按键抬起事件,而我们的双指缩放功能的进入退出依赖了Ctrl按键事件,进而导致操作中会快速进入退出双指缩放功能,无法形成连续触摸滑动的双指捏合动作效果。
排查分析过程:
1、首先对比排查了不启动麒麟桌面,以mutter桌面方式启动OpenFDE,发现鼠标滚轮模拟双指触摸缩放功能是正常的。不会频繁收到Ctrl按键up事件。
2、不启动麒麟桌面,以weston桌面方式启动OpenFDE,发现鼠标滚轮模拟双指触摸缩放功能也是正常的。
综上两点初步判断,应该不是weston的问题,可能是麒麟桌面显示服务器程序Xorg的问题,即Xorg可能发送了额外的Ctrl按键up事件。
3、编写一个简单的x11客户端程序,进行进一步的对比排查定位。
x11_event_monitor.c
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
Display *display;
Window window;
XEvent event;
int screen;
// 打开与X服务器的连接
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
screen = DefaultScreen(display);
// 创建一个窗口
window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 640, 480, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
// 选择感兴趣的事件类型
XSelectInput(display, window, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
// 显示窗口
XMapWindow(display, window);
// 事件循环
while (1) {
XNextEvent(display, &event);
// 处理暴露事件(窗口需要重新绘制)
if (event.type == Expose) {
XFillRectangle(display, window, DefaultGC(display, screen), 20, 20, 10, 10);
}
// 处理键盘按键按下事件
else if (event.type == KeyPress) {
printf("Key pressed: %x\n", event.xkey.keycode);
}
// 处理键盘按键抬起事件
else if (event.type == KeyRelease) {
printf("Key released: %x\n", event.xkey.keycode);
}
// 处理鼠标按键按下事件
else if (event.type == ButtonPress) {
printf("Mouse button pressed: %x at (%d, %d)\n", event.xbutton.button, event.xbutton.x, event.xbutton.y);
}
// 处理鼠标按键抬起事件
else if (event.type == ButtonRelease) {
printf("Mouse button released: %x at (%d, %d)\n", event.xbutton.button, event.xbutton.x, event.xbutton.y);
}
}
// 关闭与X服务器的连接
XCloseDisplay(display);
return 0;
}
编译 gcc -o x11_event_monitor x11_event_monitor.c
运行 ./x11_event_monitor
测试结果如下图,我们发现同样会收到Ctrl按键up(release)事件。
4、使用xrecord,通过 XRecord Extension非侵入式的监听全局事件进行对比测试验证。
可编译的代码在 https://github.com/WHLUG/xrecord-example
下载后,执行下面的命令编译运行测试:
sudo apt install libxcb-util-dev libqt5x11extras5-dev libxtst-dev cmake qt5-default
mkdir build
cd build
qmake ..
make
./xrecord-example
完成以后,会弹出一个Qt窗口,可以实时查看全局鼠标和键盘的事件信息。
测试监听全局事件,发现同样会收到Ctrl按键up(release)事件。如下图所示。
综上两点进一步确认了,不是weston的问题,其他x11client端确实都有收到Ctrl按键up事件,怀疑Xorg server端的问题。
5、排查Xorg端:初步使用gdb调试,发现麒麟桌面的Xorg没有开启debug。下一步只能自行编译xserver项目源码,编译调试版本并安装。具体编译安装步骤已在另外一篇文档单独记录,请移步查阅。地址:h. xserver debug版本编译过程说明
经过编译安装后,注销重新登录。开始对Xorg进行gdb调试分析。
首先对xserver源码进行了初步的阅读分析,对GetKeyboardEvents进行断点调试。
正常按下抬起Ctrl按键时,调用栈信息打印如下:
Ctrl按键按下时的调用栈信息:
Thread 18 "InputThread" hit Breakpoint 3, GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=2, key_code=key_code@entry=37) at ../../dix/getevents.c:1067
1067 {
(gdb) bt
#0 GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=2, key_code=key_code@entry=37) at ../../dix/getevents.c:1067
#1 0x0000005579ac9d74 in QueueKeyboardEvents (device=device@entry=0x559f0848b0, type=type@entry=2, keycode=keycode@entry=37) at ../../dix/getevents.c:1051
#2 0x0000005579afab30 in xf86PostKeyEventM (device=device@entry=0x559f0848b0, key_code=key_code@entry=37, is_down=is_down@entry=1) at ../../../../hw/xfree86/common/xf86Xinput.c:1423
#3 0x0000005579afab9c in xf86PostKeyboardEvent (device=0x559f0848b0, key_code=37, is_down=1) at ../../../../hw/xfree86/common/xf86Xinput.c:1432
#4 0x0000007f82381e14 in () at /usr/lib/xorg/modules/input/libinput_drv.so
#5 0x0000007f823829e4 in () at /usr/lib/xorg/modules/input/libinput_drv.so
#6 0x0000005579bfb22c in InputReady (fd=24, xevents=1, data=0x559ee27cf0) at ../../os/inputthread.c:180
#7 0x0000005579bfd940 in ospoll_wait (ospoll=0x559eb9ce30, timeout=timeout@entry=-1) at ../../os/ospoll.c:657
#8 0x0000005579bfb078 in InputThreadDoWork (arg=<optimized out>) at ../../os/inputthread.c:369
#9 0x0000007f957c651c in start_thread (arg=0x7fec380d4f) at pthread_create.c:477
#10 0x0000007f9571d88c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78
Ctrl按键抬起时的调用栈信息:
Thread 18 "InputThread" hit Breakpoint 3, GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=3, key_code=key_code@entry=37) at ../../dix/getevents.c:1067
1067 {
(gdb) bt
#0 GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=3, key_code=key_code@entry=37) at ../../dix/getevents.c:1067
#1 0x0000005579ac9d74 in QueueKeyboardEvents (device=device@entry=0x559f0848b0, type=type@entry=3, keycode=keycode@entry=37) at ../../dix/getevents.c:1051
#2 0x0000005579afab30 in xf86PostKeyEventM (device=device@entry=0x559f0848b0, key_code=key_code@entry=37, is_down=is_down@entry=0) at ../../../../hw/xfree86/common/xf86Xinput.c:1423
#3 0x0000005579afab9c in xf86PostKeyboardEvent (device=0x559f0848b0, key_code=37, is_down=0) at ../../../../hw/xfree86/common/xf86Xinput.c:1432
#4 0x0000007f82381e14 in () at /usr/lib/xorg/modules/input/libinput_drv.so
#5 0x0000007f823829e4 in () at /usr/lib/xorg/modules/input/libinput_drv.so
#6 0x0000005579bfb22c in InputReady (fd=24, xevents=1, data=0x559ee27cf0) at ../../os/inputthread.c:180
#7 0x0000005579bfd940 in ospoll_wait (ospoll=0x559eb9ce30, timeout=timeout@entry=-1) at ../../os/ospoll.c:657
#8 0x0000005579bfb078 in InputThreadDoWork (arg=<optimized out>) at ../../os/inputthread.c:369
#9 0x0000007f957c651c in start_thread (arg=0x7fec380d4f) at pthread_create.c:477
#10 0x0000007f9571d88c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78
按住Ctrl按键,然后滚动鼠标滚轮时的调用栈信息如下:
Thread 1 "X" hit Breakpoint 3, GetKeyboardEvents (events=0x7f94d4f010, pDev=0x559eba1450, type=type@entry=2, key_code=37) at ../../dix/getevents.c:1067
1067 {
(gdb) bt
#0 GetKeyboardEvents (events=0x7f94d4f010, pDev=0x559eba1450, type=type@entry=2, key_code=37) at ../../dix/getevents.c:1067
#1 0x0000005579b439bc in ProcXTestFakeInput (client=0x559f0c84b0) at ../../Xext/xtest.c:423
#2 0x0000005579ab23e0 in Dispatch () at ../../dix/dispatch.c:478
#3 0x0000005579ab650c in dix_main (argc=11, argv=0x7fec380fe8, envp=<optimized out>) at ../../dix/main.c:276
#4 0x0000007f9566cd90 in __libc_start_main (main=
0x5579a9ff30 <main>, argc=11, argv=0x7fec380fe8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=<optimized out>) at ../csu/libc-start.c:308
#5 0x0000005579a9ff68 in _start () at ../../Xext/xtest.c:538
对比上面两种情况下,GetKeyboardEvents的调用栈信息发现,在异常情况下走的是ProcXTestFakeInput流程。会产生额外的模拟输入事件。修改xtest.c文件进行测试验证,在ProcXTestFakeInput函数中,将type为KeyPress和KeyRelease的event直接return掉。
测试操作x11窗口,按住Ctrl按键滚动鼠标滚轮时没有再收到Ctrl按键抬起事件。结果说明xserver确实有向Client端发送额外的模拟输入事件。接下来分析排查xserver处理的键盘模拟输入事件的发送方到底是谁。
继续gdb调试Xorg进程,在ProcXTestFakeInput函数设置断点,按住Ctrl按键滚动鼠标滚轮复现问题。
调试分析结果如下图所示:
这里找到了Ctrl按键up伪事件的来源为imwheel程序。imwheel进程使用XTEST扩展Xorg发送模拟事件。包名为imwheel,程序路径/usr/bin/imwheel。将imwheel进程kill掉测试验证结果:按住Ctrl按键滚动鼠标滚轮x11窗口不再重复收到Ctrl按键抬起事件。测试weston应用方式启动的OpenFDE鼠标滚轮模拟双指触摸缩放功能恢复正常。
6、imwheel分析研究
关于imwheel的源码编译安装,已记录在另外一篇文档。请移步查阅,地址:i. imwheel debug版本编译安装说明
imwheel:是在Linux中支持鼠标非标准按钮的一个程序,用于Linux系统的鼠标滚轮增强工具,它可以重新映射滚轮事件,以提供更好的滚动控制和自定义功能,如缩放、翻页等。事件传递流程如下图所示。
最后通过对imwheel项目源码及配置文件的分析,可以通过imwheel -k -b "0 0 0 0 8 9"命令跳过鼠标滚轮抓取。修改fde_ctrl项目的fde_utils文件,在OpenFDE的启动脚本中增加上面的imwheel设置命令。