故事的背景是我在給 Avalonia 加上觸控尺寸的支援時,程式碼審查過程中大佬提出了在多屏上的 X11 行為問題,為此我找了兩個觸控式螢幕進行測試 X11 的多屏觸控行為。由於我的裝置有限,本文只記錄我所測試到的行為
給 Avalonia 加上觸控尺寸支援的功能的程式碼: https://github.com/AvaloniaUI/Avalonia/pull/16498
基礎環境
本次測試是我在麒麟 Kylin 系統搭配 CVT 廠商的雙屏進行測試
在我的裝置上使用 cat /etc/.kyinfo
獲取麒麟系統的版本的輸出資訊如下
[dist]
name=Kylin
milestone=Desktop-V10-SP1-General-Release-TSM-lindexi-20230217
arch=arm64
beta=False
time=2023-02-17 19:01:29
根據 定昌電子 記錄的文件,這裡的 Desktop V10 SP1 General Release 版本就是銀河麒麟桌面作業系統V10 SP1版本
執行 uname -r
的輸出如下
>$ uname -r
5.4.18-53sy01-generic
在麒麟系統上執行 cat /etc/debian_version
獲取 debian 版本號,輸出資訊如下
>$ cat /etc/debian_version
bullseye/sid
bullseye 是 debian 11 的釋出代號,詳細請看 https://www.debian.org/releases/bullseye/
獲取基礎資訊
輸入 xrandr 獲取螢幕資訊,可見內容如下
Screen 0: minimum 320 x 200, current 6240 x 2160, maximum 16384 x 16384
HDMI-A-0 disconnected (normal left inverted right x axis y axis)
HDMI-A-1 disconnected (normal left inverted right x axis y axis)
HDMI-A-2 connected primary 3840x2160+0+0 (normal left inverted right x axis y axis) 708mm x 398mm
3840x2160 60.00*+ 50.00 59.94 30.00 25.00 24.00 29.97 23.98
1920x1200 60.00
1920x1080 60.00 60.00 50.00 59.94 24.00 23.98
1600x1200 60.00
1680x1050 59.88
1280x1024 60.02
1440x900 60.00
1280x960 60.00
1280x800 59.91
1280x720 60.00 50.00 59.94
1024x768 60.00
800x600 60.32 56.25
720x576 50.00
720x480 60.00 59.94
640x480 60.00 59.94
720x400 70.08
HDMI-A-3 connected 2400x2160+3840+0 (normal left inverted right x axis y axis) 708mm x 398mm
2400x2160 59.99*+
3840x2160 60.00 50.00 59.94 30.00 25.00 24.00 29.97 23.98
1920x1200 59.99
1920x1080 60.00 60.00 50.00 59.94 24.00 23.98
1600x1200 59.99
1680x1050 59.88
1280x1024 60.02
1440x900 59.99
1200x1080 59.90
1280x960 60.00
1280x800 59.91
1280x720 60.00 50.00 59.94
1024x768 60.00
800x600 60.32 56.25
720x576 50.00
720x480 60.00 59.94
640x480 60.00 59.94
720x400 70.08
以上程式碼的 Screen 0: minimum 320 x 200, current 6240 x 2160, maximum 16384 x 16384
中的 current 6240 x 2160
就是對應 XDisplayWidth 和 XDisplayHeight 所獲取的值,如以下程式碼所示
var display = XOpenDisplay(IntPtr.Zero);
var screen = XDefaultScreen(display);
var xDisplayWidth = XDisplayWidth(display, screen);
var xDisplayHeight = XDisplayHeight(display, screen);
var width = xDisplayWidth;
var height = xDisplayHeight;
Console.WriteLine($"WH={width},{height}");
在我的裝置上執行以上程式碼,可以看到如下輸出
WH=6240,2160
由此可見 Screen 的總寬度是多個觸控式螢幕的外接矩形,如下字元圖所示
<----- XDisplayWidth ------->
↑┌---------------------------┐
││┌-------┐ │
│││ │ │
H││ HDMI-1│ │
E││ │ │
I││ │ ┌-------┐│
G│└-------┘ │ ││
H│ │ HDMI-2││
T│ │ ││
││ │ ││
││ └-------┘│
↓└---------------------------┘
觸控寬度高度
使用如下程式碼查詢主裝置獲取的資訊如下
var devices = (XIDeviceInfo*) XIQueryDevice(display,
(int) XiPredefinedDeviceId.XIAllMasterDevices, out int num);
XIDeviceInfo? pointerDevice = default;
for (var c = 0; c < num; c++)
{
Console.WriteLine($"XIDeviceInfo [{c}] {devices[c].Deviceid} {devices[c].Use}");
if (devices[c].Use == XiDeviceType.XIMasterPointer)
{
pointerDevice = devices[c];
break;
}
}
if (pointerDevice != null)
{
var multiTouchEventTypes = new List<XiEventType>
{
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd,
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease,
XiEventType.XI_Leave,
XiEventType.XI_Enter,
};
XiSelectEvents(display, handle, new Dictionary<int, List<XiEventType>> { [pointerDevice.Value.Deviceid] = multiTouchEventTypes });
for (int i = 0; i < pointerDevice.Value.NumClasses; i++)
{
var xiAnyClassInfo = pointerDevice.Value.Classes[i];
if (xiAnyClassInfo->Type == XiDeviceClass.XIValuatorClass)
{
valuators.Add(*((XIValuatorClassInfo**) pointerDevice.Value.Classes)[i]);
}
else if (xiAnyClassInfo->Type == XiDeviceClass.XIScrollClass)
{
scrollers.Add(*((XIScrollClassInfo**) pointerDevice.Value.Classes)[i]);
}
}
foreach (var xiValuatorClassInfo in valuators)
{
if (xiValuatorClassInfo.Label == touchMajorAtom)
{
Console.WriteLine($"TouchMajorAtom Max={xiValuatorClassInfo.Max:0.00}; Min={xiValuatorClassInfo.Min:0.00}; Resolution={xiValuatorClassInfo.Resolution}");
}
else if (xiValuatorClassInfo.Label == touchMinorAtom)
{
Console.WriteLine($"TouchMinorAtom Max={xiValuatorClassInfo.Max:0.00}; Min={xiValuatorClassInfo.Min:0.00}; Resolution={xiValuatorClassInfo.Resolution}");
}
}
}
在我的裝置上執行以上程式碼的輸出資訊如下
TouchMajorAtom Max=18950.00; Min=0.00; Resolution=10000
TouchMinorAtom Max=10660.00; Min=0.00; Resolution=10000
以上的 Max=18950.00
所獲取的值是其物理尺寸,即 1.89 米寬度。這裡的 Resolution 是一個比例,計算公式如下
TouchMajorAtomMax/Resolution = 18950.00/10000 = 1.895 米
由於 CVT 的裝置報告的邏輯值和物理值都是相同的最大值最小值,因此以上程式碼我不確定拿到的是邏輯值還是物理值
透過以上程式碼也可以看到,我無法直接獲取到正確的多屏不同尺寸的裝置的多個螢幕的物理尺寸。這是因為我無法直接知道輸入的是哪個螢幕以及其比例值
但畫素值倒是很好獲取到,只需獲取到其觸控點報告的 TouchMajor 值,與 TouchMajorXIValuatorClassInfo 的最大值和最小值相比,壓縮到 [0-1] 範圍內,再乘以螢幕畫素,即可獲取到其畫素值
(TouchMajorValuatorValue - TouchMajorXIValuatorClassInfo.Min)/(TouchMajorXIValuatorClassInfo.Max - TouchMajorXIValuatorClassInfo.Min) * Monitor.Width
以上虛擬碼的 Monitor.Width
指的是對應螢幕的畫素寬度。由於 Min 常是 0 因此在計算中常被忽略
但值得一提的是在 X11 裡面,根據 https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html 文件,所獲取的是橢圓長軸,將其當成觸控寬度是不準確的
校準螢幕
在我的裝置上,發現觸控式螢幕的觸控輸入和對應的螢幕顯示沒有對齊,需要根據以下大佬們的部落格進行修復
- Linux處理多觸屏的終極解決方案 香風家的火柴盒
- 【圖形顯示】擴充套件屏模式,觸控點較準不準確_90-touchscreen-map-CSDN部落格
具體輸入行為測試
我使用了相同的物理面積的物體觸控螢幕,兩個螢幕分別是 3840x2160
和 2400x2160
解析度
觸控左邊 3840x2160
螢幕,獲取到的 TouchMajorValuatorValue 是大概 100 的值。觸控右邊 2400x2160
螢幕,獲取到的 TouchMajorValuatorValue 是大概 160 的值
分別求畫素大小:
- 左邊螢幕:
100/18950*3840=20.2638522427
- 右邊螢幕:
160/18950*2400=20.2638522427
程式碼
本文程式碼放在 github 和 gitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快
先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin dedfc0ec3a3c8d04e7bec5276fe5bcaa926fe6e9
以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin dedfc0ec3a3c8d04e7bec5276fe5bcaa926fe6e9
獲取程式碼之後,進入 X11/DacemciwarjurqofuKearwihiyi 資料夾,即可獲取到原始碼
更多技術部落格,請參閱 部落格導航