本文记录的是在银河麒麟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应用层。如下图所示。

Ctrl按键+鼠标滚轮模拟双指触摸缩放功能框架图

其中hwc中的代码逻辑是鼠标滚轮模拟双指触摸缩放功能的具体实现,事件处理流程如下图所示。

鼠标模拟双指缩放功能中,对Ctrl按键事件和鼠标滚轮滚动事件处理逻辑

鼠标模拟双指缩放功能实现完后遇到的问题:

weston应用方式下键盘Ctrl按键按下时滚动鼠标滚轮会导致自动触发Ctrl按键抬起事件,而我们的双指缩放功能的进入退出依赖了Ctrl按键事件,进而导致操作中会快速进入退出双指缩放功能,无法形成连续触摸滑动的双指捏合动作效果。

排查分析过程:

1、首先对比排查了不启动麒麟桌面,以mutter桌面方式启动OpenFDE,发现鼠标滚轮模拟双指触摸缩放功能是正常的。不会频繁收到Ctrl按键up事件。

2、不启动麒麟桌面,以weston桌面方式启动OpenFDE,发现鼠标滚轮模拟双指触摸缩放功能也是正常的。

综上两点初步判断,应该不是weston的问题,可能是麒麟桌面显示服务器程序Xorg的问题,即Xorg可能发送了额外的Ctrl按键up事件。

3、编写一个简单的x11客户端程序,进行进一步的对比排查定位。

x11_event_monitor.c

1#include <X11/Xlib.h> 2#include <stdio.h> 3#include <stdlib.h> 4 5int main() { 6 Display *display; 7 Window window; 8 XEvent event; 9 int screen; 10 // 打开与X服务器的连接 11 display = XOpenDisplay(NULL); 12 if (display == NULL) { 13 fprintf(stderr, "Cannot open display\n"); 14 exit(1); 15 } 16 screen = DefaultScreen(display); 17 // 创建一个窗口 18 window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 640, 480, 1, 19 BlackPixel(display, screen), WhitePixel(display, screen)); 20 // 选择感兴趣的事件类型 21 XSelectInput(display, window, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask); 22 // 显示窗口 23 XMapWindow(display, window); 24 // 事件循环 25 while (1) { 26 XNextEvent(display, &event); 27 // 处理暴露事件(窗口需要重新绘制) 28 if (event.type == Expose) { 29 XFillRectangle(display, window, DefaultGC(display, screen), 20, 20, 10, 10); 30 } 31 // 处理键盘按键按下事件 32 else if (event.type == KeyPress) { 33 printf("Key pressed: %x\n", event.xkey.keycode); 34 } 35 // 处理键盘按键抬起事件 36 else if (event.type == KeyRelease) { 37 printf("Key released: %x\n", event.xkey.keycode); 38 } 39 // 处理鼠标按键按下事件 40 else if (event.type == ButtonPress) { 41 printf("Mouse button pressed: %x at (%d, %d)\n", event.xbutton.button, event.xbutton.x, event.xbutton.y); 42 } 43 // 处理鼠标按键抬起事件 44 else if (event.type == ButtonRelease) { 45 printf("Mouse button released: %x at (%d, %d)\n", event.xbutton.button, event.xbutton.x, event.xbutton.y); 46 } 47 } 48 // 关闭与X服务器的连接 49 XCloseDisplay(display); 50 return 0; 51}

编译 gcc -o x11_event_monitor x11_event_monitor.c

运行 ./x11_event_monitor

测试结果如下图,我们发现同样会收到Ctrl按键up(release)事件。

普通x11客户端测试程序打印结果

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项目源码,编译调试版本并安装。具体编译安装步骤已在另外一篇文档单独记录,请移步查阅。地址:

https://openfde.atlassian.net/wiki/spaces/OpenFDE1/pages/140836873

经过编译安装后,注销重新登录。开始对Xorg进行gdb调试分析。

首先对xserver源码进行了初步的阅读分析,对GetKeyboardEvents进行断点调试。

正常按下抬起Ctrl按键时,调用栈信息打印如下:

1Ctrl按键按下时的调用栈信息: 2Thread 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 31067 { 4(gdb) bt 5#0 GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=2, key_code=key_code@entry=37) at ../../dix/getevents.c:1067 6#1 0x0000005579ac9d74 in QueueKeyboardEvents (device=device@entry=0x559f0848b0, type=type@entry=2, keycode=keycode@entry=37) at ../../dix/getevents.c:1051 7#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 8#3 0x0000005579afab9c in xf86PostKeyboardEvent (device=0x559f0848b0, key_code=37, is_down=1) at ../../../../hw/xfree86/common/xf86Xinput.c:1432 9#4 0x0000007f82381e14 in () at /usr/lib/xorg/modules/input/libinput_drv.so 10#5 0x0000007f823829e4 in () at /usr/lib/xorg/modules/input/libinput_drv.so 11#6 0x0000005579bfb22c in InputReady (fd=24, xevents=1, data=0x559ee27cf0) at ../../os/inputthread.c:180 12#7 0x0000005579bfd940 in ospoll_wait (ospoll=0x559eb9ce30, timeout=timeout@entry=-1) at ../../os/ospoll.c:657 13#8 0x0000005579bfb078 in InputThreadDoWork (arg=<optimized out>) at ../../os/inputthread.c:369 14#9 0x0000007f957c651c in start_thread (arg=0x7fec380d4f) at pthread_create.c:477 15#10 0x0000007f9571d88c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78 16 17Ctrl按键抬起时的调用栈信息: 18Thread 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 191067 { 20(gdb) bt 21#0 GetKeyboardEvents (events=0x7f95242010, pDev=pDev@entry=0x559f0848b0, type=type@entry=3, key_code=key_code@entry=37) at ../../dix/getevents.c:1067 22#1 0x0000005579ac9d74 in QueueKeyboardEvents (device=device@entry=0x559f0848b0, type=type@entry=3, keycode=keycode@entry=37) at ../../dix/getevents.c:1051 23#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 24#3 0x0000005579afab9c in xf86PostKeyboardEvent (device=0x559f0848b0, key_code=37, is_down=0) at ../../../../hw/xfree86/common/xf86Xinput.c:1432 25#4 0x0000007f82381e14 in () at /usr/lib/xorg/modules/input/libinput_drv.so 26#5 0x0000007f823829e4 in () at /usr/lib/xorg/modules/input/libinput_drv.so 27#6 0x0000005579bfb22c in InputReady (fd=24, xevents=1, data=0x559ee27cf0) at ../../os/inputthread.c:180 28#7 0x0000005579bfd940 in ospoll_wait (ospoll=0x559eb9ce30, timeout=timeout@entry=-1) at ../../os/ospoll.c:657 29#8 0x0000005579bfb078 in InputThreadDoWork (arg=<optimized out>) at ../../os/inputthread.c:369 30#9 0x0000007f957c651c in start_thread (arg=0x7fec380d4f) at pthread_create.c:477 31#10 0x0000007f9571d88c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78

按住Ctrl按键,然后滚动鼠标滚轮时的调用栈信息如下:

1Thread 1 "X" hit Breakpoint 3, GetKeyboardEvents (events=0x7f94d4f010, pDev=0x559eba1450, type=type@entry=2, key_code=37) at ../../dix/getevents.c:1067 21067 { 3(gdb) bt 4#0 GetKeyboardEvents (events=0x7f94d4f010, pDev=0x559eba1450, type=type@entry=2, key_code=37) at ../../dix/getevents.c:1067 5#1 0x0000005579b439bc in ProcXTestFakeInput (client=0x559f0c84b0) at ../../Xext/xtest.c:423 6#2 0x0000005579ab23e0 in Dispatch () at ../../dix/dispatch.c:478 7#3 0x0000005579ab650c in dix_main (argc=11, argv=0x7fec380fe8, envp=<optimized out>) at ../../dix/main.c:276 8#4 0x0000007f9566cd90 in __libc_start_main (main= 9 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 10#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的源码编译安装,已记录在另外一篇文档。请移步查阅,地址:

https://openfde.atlassian.net/wiki/spaces/OpenFDE1/pages/140836889

imwheel:是在Linux中支持鼠标非标准按钮的一个程序,用于Linux系统的鼠标滚轮增强工具,它可以重新映射滚轮事件,以提供更好的滚动控制和自定义功能,如缩放、翻页等。事件传递流程如下图所示。

最后通过对imwheel项目源码及配置文件的分析,可以通过imwheel -k -b "0 0 0 0 8 9"命令跳过鼠标滚轮抓取。修改fde_ctrl项目的fde_utils文件,在OpenFDE的启动脚本中增加上面的imwheel设置命令。