dotnet X11 的多屏觸控行為測試

lindexi發表於2024-07-31

故事的背景是我在給 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部落格

具體輸入行為測試

我使用了相同的物理面積的物體觸控螢幕,兩個螢幕分別是 3840x21602400x2160 解析度

觸控左邊 3840x2160 螢幕,獲取到的 TouchMajorValuatorValue 是大概 100 的值。觸控右邊 2400x2160 螢幕,獲取到的 TouchMajorValuatorValue 是大概 160 的值

分別求畫素大小:

  • 左邊螢幕: 100/18950*3840=20.2638522427
  • 右邊螢幕: 160/18950*2400=20.2638522427

程式碼

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快

先建立一個空資料夾,接著使用命令列 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 資料夾,即可獲取到原始碼

更多技術部落格,請參閱 部落格導航

相關文章