这是我在学习 CPF 和 Avalonia 过程中,编写的 X11 触摸测试程序所测试到的一些行为
前置博客: dotnet 学习 CPF 框架笔记 了解 X11 里如何获取触摸信息
X11 触摸测试程序
测试程序开源代码路径: https://github.com/dotnet-campus/ManipulationDemo/tree/master/ManipulationDemoCpfX11
此测试程序基于 CPF 的源代码进行编写
XI_Leave 行为
以下是我测试到的 XI_Leave 的行为逻辑
当存在别的窗口在当前的窗口之上时,触摸先进入当前的窗口,让当前的进程收到了 X11 的 Down 事件。接着在不断移动,不断收到 Move 事件。当移动到别的窗口之上时,将可以收到 XiEventType.XI_Leave 类型的事件。接着在别的窗口移动过程中,继续收到 Move 事件。抬手时,可以收到 End 事件
本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 43711cd55b54616e0d75a70d61dec5591151ad2b
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 43711cd55b54616e0d75a70d61dec5591151ad2b
获取代码之后,进入 BujeeberehemnaNurgacolarje 文件夹,即可获取到源代码
触摸宽度高度
在我的一台设备里面,使用的是统信 UOS 系统,复现出丢失触摸宽度高度信息
执行名为 CaijawhejiJoballbarwi 的测试程序,可以看到如下控制台输出。此 CaijawhejiJoballbarwi 测试程序代码可在后文获取到
WH=1920,1080
ABS_MT_TOUCH_MAJOR=282 Name=Abs MT Touch Major ABS_MT_TOUCH_MINOR=283 Name=Abs MT Touch Minor Abs_MT_Pressure=287 Name=Abs MT Pressure
XIDeviceInfo [0] 2 XIMasterPointer
XIDeviceInfo [1] 3 XIMasterKeyboard
XiValuatorClassInfo Label=140(Rel X) Value=427; Max=-1.00; Min=-1.00; Resolution=1)
XiValuatorClassInfo Label=141(Rel Y) Value=736; Max=-1.00; Min=-1.00; Resolution=1)
XiValuatorClassInfo Label=293(Rel Vert Wheel) Value=853; Max=-1.00; Min=-1.00; Resolution=1)
Can't find TouchMajorAtom 丢失触摸宽度高度
从以上的输出日志可以看到,只是拿到了 XiValuatorClassInfo 为 Rel X
和 Rel Y
等信息,而没有触摸宽度高度需要的 Abs MT Touch Major
和 Abs MT Touch Minor
信息
此时尝试触摸一下屏幕,从 CaijawhejiJoballbarwi 程序里面的 XNextEvent 收到的 GenericEvent 里,取出的 XIDeviceEvent 事件参数里面的 valuators 集合里面,也是只有 Rel X
和 Rel Y
等信息,没有 Abs MT Touch Major
和 Abs MT Touch Minor
信息
此时执行 xinput list 可见如下输出信息
$ xinput list
⎡ Virtual core pointer id=2 [master pointer (3)]
⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
在触摸没有宽度高度信息时,使用 xinput list 2
所见也是没有 Abs MT Touch Major
和 Abs MT Touch Minor
信息,只有 Rel X
和 Rel Y
等信息
$ xinput list 2
Virtual core pointer id=2 [master pointer (3)]
Reporting 6 classes:
Class originated from: 18. Type: XIButtonClass
Buttons supported: 13
Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" "Button Side" "Button Extra" "Button Unknown" "Button Unknown" "Button Unknown" "Button Unknown"
Button state:
Class originated from: 18. Type: XIValuatorClass
Detail for Valuator 0:
Label: Rel X
Range: -1.000000 - -1.000000
Resolution: 1 units/m
Mode: relative
Class originated from: 18. Type: XIValuatorClass
Detail for Valuator 1:
Label: Rel Y
Range: -1.000000 - -1.000000
Resolution: 1 units/m
Mode: relative
Class originated from: 18. Type: XIValuatorClass
Detail for Valuator 2:
Label: Rel Vert Wheel
Range: -1.000000 - -1.000000
Resolution: 1 units/m
Mode: relative
Class originated from: 18. Type: XIScrollClass
Scroll info for Valuator 2
type: 1 (vertical)
increment: -1.000000
flags: 0x2 ( preferred )
Class originated from: 10. Type: XITouchClass
Touch mode: direct
Max number of touches: 50
如此可以证明这是从 X11 底层就没有拿到触摸的宽度高度信息,和任何上层 UI 框架都没有关系,和应用程序本身没有关系
如果此时触摸一下触摸屏,则再次执行 CaijawhejiJoballbarwi 测试程序,可以获取到触摸宽度高度信息
WH=1920,1080
ABS_MT_TOUCH_MAJOR=282 Name=Abs MT Touch Major ABS_MT_TOUCH_MINOR=283 Name=Abs MT Touch Minor Abs_MT_Pressure=287 Name=Abs MT Pressure
XIDeviceInfo [0] DeviceId=2 Name=559AACBA03D0(Virtual core pointer) Use=XIMasterPointer Attachment=3
XIDeviceInfo [1] DeviceId=3 Name=559AACBA03F0(Virtual core keyboard) Use=XIMasterKeyboard Attachment=2
XiValuatorClassInfo Label=285(Abs MT Position X) Value=7157; Max=32767.00; Min=0.00; Resolution=38000)
XiValuatorClassInfo Label=286(Abs MT Position Y) Value=19969; Max=32767.00; Min=0.00; Resolution=66000)
PressureAtom Value=1075; Max=4095.00; Min=0.00; Resolution=0
TouchMajorAtom Value=87; Max=18950.00; Min=0.00; Resolution=10000
TouchMinorAtom Value=40; Max=10660.00; Min=0.00; Resolution=10000
XiValuatorClassInfo Label=284(Abs MT Orientation) Value=-32; Max=15707.00; Min=-15707.00; Resolution=0)
再次执行 xinput list 2
命令,也是可以看到触摸宽度高度信息
$ xinput list 2
Virtual core pointer id=2 [master pointer (3)]
Reporting 8 classes:
Class originated from: 10. Type: XIButtonClass
Buttons supported: 13
Button labels: "Button Unknown" "Button Unknown" "Button Unknown" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" "Button Side" "Button Extra" "Button Unknown" "Button Unknown" "Button Unknown" "Button Unknown"
Button state:
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 0:
Label: Abs MT Position X
Range: 0.000000 - 32767.000000
Resolution: 38000 units/m
Mode: absolute
Current value: 741.000000
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 1:
Label: Abs MT Position Y
Range: 0.000000 - 32767.000000
Resolution: 66000 units/m
Mode: absolute
Current value: 31492.000000
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 2:
Label: Abs MT Pressure
Range: 0.000000 - 4095.000000
Resolution: 0 units/m
Mode: absolute
Current value: 139.000000
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 3:
Label: Abs MT Touch Major
Range: 0.000000 - 18950.000000
Resolution: 10000 units/m
Mode: absolute
Current value: 37.000000
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 4:
Label: Abs MT Touch Minor
Range: 0.000000 - 10660.000000
Resolution: 10000 units/m
Mode: absolute
Current value: 11.000000
Class originated from: 10. Type: XIValuatorClass
Detail for Valuator 5:
Label: Abs MT Orientation
Range: -15707.000000 - 15707.000000
Resolution: 0 units/m
Mode: absolute
Current value: -32.000000
Class originated from: 10. Type: XITouchClass
Touch mode: direct
Max number of touches: 50
此问题复现步骤:
- 挂机,不要碰屏幕,等待一段时间,也许是半个小时以上
- 使用 SSH 远程、或键盘在终端,启动 CaijawhejiJoballbarwi 测试程序或空 Avalonia 程序。即一定不要用触摸双击打开
- 此时可见 CaijawhejiJoballbarwi 测试程序和 xinput 都报告没有触摸宽度高度信息
如果此时触摸了一下触摸屏,则再次启动 CaijawhejiJoballbarwi 测试程序或使用 xinput 都能拿到触摸宽度高度信息
预计是一段时间没有碰触摸屏,导致触摸进入某个状态
本文以上代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin c64e19bfab4a85720d9a0692f1de3c960d6d8ce1
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin c64e19bfab4a85720d9a0692f1de3c960d6d8ce1
获取代码之后,进入 X11/CaijawhejiJoballbarwi 文件夹,即可获取到源代码
这里必须额外说明的是,本文以上的 CaijawhejiJoballbarwi 测试程序没有处理 XI_DeviceChanged 事件,但在本文描述的情况下,即使带上 XI_DeviceChanged 处理也不能解决问题
为什么带上 XI_DeviceChanged 更新 Valuator 内容也不能处理?因为从 XInput 2 收到的事件 XIDeviceEvent 的 valuators 就没有带上 Abs MT Touch Major 和 Abs MT Touch Minor 的内容
如以下我的控制台所示,先收到 XI_DeviceChanged 事件,最后我使用 XIQueryDevice 将 XIMasterPointer 设备的 XiValuatorClassInfo 进行输出,日志内容如下
设备变更
XIMasterPointer Deviceid=2
XiValuatorClassInfo Label=285(Abs MT Position X) Num=0 Value=20159; Max=32767.00; Min=0.00; Resolution=38000)
XiValuatorClassInfo Label=286(Abs MT Position Y) Num=1 Value=14308; Max=32767.00; Min=0.00; Resolution=66000)
XiValuatorClassInfo Label=287(Abs MT Pressure) Num=2 Value=296; Max=4095.00; Min=0.00; Resolution=0)
XiValuatorClassInfo Label=282(Abs MT Touch Major) Num=3 Value=153; Max=18950.00; Min=0.00; Resolution=10000)
XiValuatorClassInfo Label=283(Abs MT Touch Minor) Num=4 Value=132; Max=10660.00; Min=0.00; Resolution=10000)
XiValuatorClassInfo Label=284(Abs MT Orientation) Num=5 Value=-32; Max=15707.00; Min=-15707.00; Resolution=0)
通过以上日志信息可以看到 Abs MT Touch Major 对应的 Num 刚好是 3 的值。正好在丢失触摸宽度高度信息的时候的 XiValuatorClassInfo 只有 Rel X
和 Rel Y
和 Rel Vert Wheel
的值。于是我就通过读取 XIDeviceEvent 的 valuators 的数量来确定是否出错
读取 XIDeviceEvent 的 valuators 的数量的代码是从 Avalonia 里面抄的,代码如下
XIDeviceEvent* ev = ...
Valuators = new Dictionary<int, double>();
var values = ev->valuators.Values;
if(ev->valuators.Mask != null)
for (var c = 0; c < ev->valuators.MaskLen * 8; c++)
if (XIMaskIsSet(ev->valuators.Mask, c))
Valuators[c] = *values++;
出现问题时的 Valuators 字典只有三个元素,也就是 Num 刚好是 3 的 Abs MT Touch Major 是不存在的,丢失触摸宽度高度信息。尝试从 Valuators 字典获取 TouchMajorXIValuatorClassInfo 的 Number 对应信息,也是获取不到,获取的代码如下
Valuators.TryGetValue(TouchMajorXIValuatorClassInfo.Number, out var touchMajorValue)
如此证明了从 XIDeviceEvent 的 valuators 里面就不存在 Touch Major 信息,丢失触摸宽度高度
这就是为什么即使在 XI_DeviceChanged 事件里面更新 valuators 信息也是不能修复此问题的原因,核心原因不在于 XI_DeviceChanged 事件里面拿不到更新的 XiValuatorClassInfo 信息,而是在于收到的 XIDeviceEvent 参数里面的 valuators 没有宽度高度信息
我尝试追踪代码,根据阅读代码,可以看到在 XIQueryDevice 里面发送消息给到 XServer 端,收到消息再返回。以下是在 XIQueryDevice.c 的 XIQueryDevice 方法的部分代码
GetReq(XIQueryDevice, req);
req->reqType = extinfo->codes->major_opcode;
req->ReqType = X_XIQueryDevice;
req->deviceid = deviceid;
_XRead(dpy, buf, reply.length * 4);
ptr += copy_classes(lib, (xXIAnyInfo*)ptr, &nclasses);
如此可以看到 XIQueryDevice 没有进行额外的处理,继续阅读在 XExtInt.c 的 copy_classes 方法,可以发现里面只是做拷贝,证明信息是从 XServer 存放的
继续追到 XServer 里面,又发现实际实现是从 xf86-input-evdev 过来的数据,再继续我就跟丢了
多次调用 XIQueryDevice 方法,第二次将会卡住
我尝试使用如下代码执行多次对 XIQueryDevice 方法的调用:
unsafe
{
var devices = (XIDeviceInfo*) XIQueryDevice(display,
(int) XiPredefinedDeviceId.XIAllMasterDevices, out int num);
... // 忽略其他代码
}
Task.Run(() =>
{
Console.ReadLine();
// 重新获取触摸宽度高度是可以获取到的
Console.WriteLine("重新获取");
unsafe
{
var stopwatch = Stopwatch.StartNew();
var devices = (XIDeviceInfo*) XIQueryDevice(display,
(int) XiPredefinedDeviceId.XIAllMasterDevices, out int num);
stopwatch.Stop();
// 统计到的耗时是 XIQueryDevice 耗时=7297ms
// 似乎没有点击屏幕时,是不会完成 XIQueryDevice 方法的
Console.WriteLine($"XIQueryDevice 耗时={stopwatch.ElapsedMilliseconds}ms");
... // 忽略其他代码
}
}
运行程序,按下回车,在触摸/鼠标点击屏幕之前 XIQueryDevice 方法不会返回
似乎没有点击屏幕时,是不会完成 XIQueryDevice 方法的
这与上一次的 XIQueryDevice 是否调用 XIFreeDeviceInfo 进行释放没有关系,即使调用了 XIFreeDeviceInfo 依然也会让第二次 XIQueryDevice 需要等待鼠标或触摸点击屏幕才能返回
以上代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin e6eee366c29e877bd8537766fe58262023d5db08
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin e6eee366c29e877bd8537766fe58262023d5db08
获取代码之后,进入 X11/CaijawhejiJoballbarwi 文件夹,即可获取到源代码
参考文档
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/%E8%AE%B0-X11-%E9%87%8C%E9%9D%A2%E8%A7%A6%E6%91%B8%E7%9A%84%E4%B8%80%E4%BA%9B%E8%A1%8C%E4%B8%BA.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利