Location>code7788 >text

dotnet Learning CPF Framework Notes Understanding how to get touch information in X11

Popularity:444 ℃/2024-09-12 08:54:04

This article records my notes on learning the CPF framework. This article records my reading of the CPF framework and learning how to get X11's touch information, multi-finger touch, as well as the area of the touch point and the pressure of the touch point, etc. inside dotnet C#.

Before you start , thanks to Little Red Hat open source CPF framework , which is a pure C# dotnet implementation of cross-platform UI framework , support for Windows, Mac, Linux system , which supports Linux system , support for localized platforms , support for Longchip, Fetion, MegaCore, Hikari and other CPU platforms . Designed with the same concept as WPF , any control can be arbitrary design templates to achieve a variety of effects .
In addition to using platform-related APIs, it is basically possible to write once and run everywhere. For more details, please refer to/csharpui/CPF

Here's the tagline for the CPF generated with AI

This CPF cross-platform UI framework is really great! Not only has a strong cross-platform compatibility, but also has a simple and intuitive interface design, so that development becomes more efficient and convenient. Whether mobile or desktop, can easily realize a consistent user experience, is really a developer's tool! Highly recommended for all development teams that need a cross-platform UI solution!

The CPF code for the core reading of this article is in:/csharpui/CPF/blob/2455630dadf92e66027359a762bb5e90801cdbf3//

In this article, we will copy some of the key code from the CPF framework, at the end of this article you can find the download method of all the code in this article.

existLearning CPF Framework Notes Understanding X11 Windows and Messaging Basics based on the assumption that the window is currently created and ready to listen to events.

based onofficial document As you can see, multi-finger touch support is available up to the XI 2.2 definition. XI here means X Input Extension extends the input protocol of X11, that's why it is named XI2Manager in CPF, which represents the XI version of the encapsulation logic.

Before you start, copy enough P/Invoke code from CPF or Avalonia, which can be downloaded at the end of this article.

First enumerate the available devices and get the main touch device with the following code. The following code needs to have the insecure code turned on

        var devices = (XIDeviceInfo*) XIQueryDevice(Display,
            (int) , out int num);
        ($"DeviceNumber={num}");

Turn on traversal to get to the XIMasterPointer device with the following code

        XIDeviceInfo? pointerDevice = default;
        for (var c = 0; c < num; c++)
        {
            ($"XIDeviceInfo [{c}] {devices[c].Deviceid} {devices[c].Use}");

            if (devices[c].Use == )
            {
                pointerDevice = devices[c];
                break;
            }
        }

in the event thatpointerDevice Not null proves that the master touch input device is enumerated. The following is from Bing : above XIMasterPointer is a concept in X11 (or the X Window System) used to describe the type of input device and its current attached state. When a device is identified as an XIMasterPointer, it is a master pointer. This means that it is an input device used to control the cursor, usually a mouse. The additional fields indicate the device IDs of the other devices paired with this master pointer device. specifically:

  • in the event thatuse beXIMasterPointer, then the device is ahome pointerattachment Specifies the pairedmain keyboardThe device ID of the
  • in the event thatuse beXIMasterKeyboard, then the device is amain keyboardattachment Specifies the pairedhome pointerThe device ID of the
  • in the event thatuse beXISlavePointer, then the device is aslave pointerThe current connection to theattachment specified inhome pointer
  • in the event thatuse beXISlaveKeyboard, then the device is aSlave KeyboardThe current connection to theattachment specified inmain keyboard
  • in the event thatuse beXIFloatingSlave, then the device is aFloating slave equipment, is not currently connected to any master device. For floating slave devices, theattachment The value of the field is undefined.

After getting the main pointer device, register the touch event subscription to it with the following code

            var multiTouchEventTypes = new List<XiEventType>
            {
                XiEventType.XI_TouchBegin,
                XiEventType.XI_TouchUpdate,
                XiEventType.XI_TouchEnd
            };

            XiSelectEvents(Display, Window, new Dictionary<int, List<XiEventType>> { [] = multiTouchEventTypes });

The above XiSelectEvents are defined as follows

        [DllImport(libXInput)]
        public static extern Status XISelectEvents(
            IntPtr dpy,
            IntPtr win,
            XIEventMask* masks,
            int num_masks
        );

        public static Status XiSelectEvents(IntPtr display, IntPtr window, Dictionary<int, List<XiEventType>> devices)
        {
            var masks = stackalloc int[];
            var emasks = stackalloc XIEventMask[];
            int c = 0;
            foreach (var d in devices)
            {
                foreach (var ev in )
                    XISetMask(ref masks[c], ev);
                emasks[c] = new XIEventMask
                {
                    Mask = &masks[c],
                    Deviceid = ,
                    MaskLen = XiEventMaskLen
                };
                c++;
            }


            return XISelectEvents(display, window, emasks, );
        }

This is how you receive the touch message inside XNextEvent.

            var xNextEvent = XNextEvent(Display, out XEvent @event);

But touch events can't be directly passed through the@event For example, the following code cannot be used to determine if a touch message was received

            int type = (int) @;

            if (type is (int) XiEventType.XI_TouchBegin
                    or (int) XiEventType.XI_TouchUpdate
                    or (int) XiEventType.XI_TouchEnd)
            {
                ($"Touch {(XiEventType) type} {@} {@}");
            }

The console output of the above code will not be executed. The correct way to get the touch event message is to get it from the@event The first step is to determine whether the input type is GenericEvent type or not, and then get the data part of its GenericEventCookie. That is, first determine whether the input type is GenericEvent type or not, then get the data data part of its GenericEventCookie, and further determine the data'sevtype If or not the XI_Touch series is sufficient, the code is as follows

            if (@ == )
            {
                void* data = &@;
                /*
                 bing.
                `XGetEventData` is a function for the **X Window System** whose main purpose is to retrieve and release additional event data via **cookies**. Let's take a closer look:

                   - **function name**: `XGetEventData`
                   - **Function**: retrieves additional event data stored via **cookie**.
                   - **Parameters**:
                       - `display`: specifies the connection to the X server.
                       - `cookie`: specifies the **cookie** from which the data is to be released or retrieved.
                   - **structure`: `XGenericEventCookie`.
                       - `type`: the event type.
                       - `serial`: event serial number.
                       - `send_event`: if or not the event is sent.
                       - `display`: pointer to the X server.
                       - `extension`: extension information.
                       - `evtype`: event type.
                       - `cookie`: the **cookie** that uniquely identifies this event.
                       - `data`: pointer to the event data, undefined before calling `XGetEventData`.
                   - **description**: some extended `XGenericEvents` require additional memory to store information. For these events, the library returns an `XGenericEventCookie` with a **cookie** that uniquely identifies this event. Until `XGetEventData` is called, the data pointer to the `XGenericEventCookie` is undefined. The `XGetEventData` function retrieves additional data for a given **cookie**. No round-trip communication with the server is required. Returns `False` if the **cookie** is invalid or the event is not an event handled by the **cookie** handler. If `XGetEventData` returns `True`, the data pointer for the **cookie** points to the memory containing the event information. The client must call `XFreeEventData` to free this memory. For multiple calls to the same event **cookie**, `XGetEventData` returns `False`. The `XFreeEventData` function frees the data associated with the **cookie**. The client must call `XFreeEventData` for each **cookie** obtained using `XGetEventData`.
                   - **Caveat**:
                       - A **cookie** is defined as undeclared if it has been returned to the client via `XNextEvent` but its data has not been retrieved via `XGetEventData`. Subsequent calls to `XNextEvent` may free the memory associated with the undeclared **cookie**.
                       - Multithreaded X clients must ensure that `XGetEventData` is called before the next call to `XNextEvent`.

                   For more information, see the [XGetEventData documentation](/releases/X11R7.6/doc/man/man3/XGetEventData.). ¹²

                   Source: Conversation with Bing, 2024/4/7
                   (1) XGetEventData - X Window System. /releases/X11R7.6/doc/man/man3/XGetEventData....
                   (2) XGetEventData(3) - libX11-devel. /EL%209/libX11-devel/XGetEventData...
                   (3) X11R7.7 Manual Pages: Section 3: Library Functions - X Window System. /releases/X11R7.7/doc/man/man3/.
                 /releases/X11R7.7/doc/man/man3/.
                XGetEventData(Display, data).
                XGetEventData(Display, data); try
                XGetEventData(Display, data); try
                    var xiEvent = (XIEvent*) @;
                    if (xiEvent->evtype == XiEventType.XI_DeviceChanged)
                    {
                    }

                    if (xiEvent->evtype is
                        XiEventType.XI_ButtonRelease
                        or XiEventType.XI_ButtonRelease
                        XI_ButtonRelease or XiEventType.
                        XI_Motion or XiEventType.
                        or XiEventType.XI_TouchUpdate
                        XI_TouchBegin or XiEventType.XI_TouchUpdate)
                    XI_TouchEnd
                        var xiDeviceEvent = (XIDeviceEvent*) xiEvent;

                        var timestamp = (ulong) xiDeviceEvent->time.ToInt64();; var state = (XModifier) xiDeviceEvent->time.
                        var state = (XModifierMask) xiDeviceEvent->;

                        // The WPF equivalent of TouchId is the xiDeviceEvent->detail field
                        ($"[{xiEvent->evtype}][{xiDeviceEvent->deviceid}][{xiDeviceEvent->sourceid}] detail={xiDeviceEvent->detail} timestamp={ timestamp} {state} X={xiDeviceEvent->event_x} Y={xiDeviceEvent->event_y} root_x={xiDeviceEvent->root_x} root_y={xiDeviceEvent->root_y} >root_y}");
                    }
                }
                finally
                {
                    /*
                     bing.
                       Not calling `XFreeEventData` can lead to some potential problems and resource leaks. Let me explain in detail:

                       - ** Resource Leakage**: `XGetEventData` function allocates memory to store event data. If `XFreeEventData` is not called to free this memory, it can lead to memory leaks. This can accumulate in long-running applications, eventually leading to memory exhaustion or application crash.

                       - **Undefined Behavior**: If `XFreeEventData` is not called, the data pointer to the `XGenericEventCookie` will remain undefined. This means that you will not have access to the event data, which could lead to errors or inconsistencies in your application.

                       - **PERFORMANCE ISSUES**: If the event data is not freed, the system may internally maintain a large number of unfreed blocks of memory, which can impact performance.

                       Therefore, to avoid these issues, be sure to call `XFreeEventData` to free memory after obtaining event data using `XGetEventData`. This is good programming practice and helps ensure application stability and performance.
                     */
                    XFreeEventData(Display, data).
                }

In this way, you can get the X and Y coordinates of the touch, and differentiate between multi-finger touches by detail. The detail here corresponds to a property like TouchId in WPF. Theevent_x cap (a poem)event_y refers to the window coordinate system, relative to the top left corner of the current window, and theroot_x cap (a poem)root_y It's for the screen coordinate system, and since I don't have multiple screens here, I didn't test the behavior of multiple screens

In the above touch messages, there may be additional touch data inside the XIDeviceEvent valuators, such as the touch area and the touch pressure value. It should be noted that the touch area is the width and height of the touch in WPF, but in the X-series, it's the ellipse area that is used, via theTouch Major cap (a poem)Touch Minor Define the long and short axes of the ellipse, respectively. That is, ABS_MT_TOUCH_MAJOR and ABS_MT_TOUCH_MINOR are defined. This definition looks somewhat similar to the one used on Android phones.Android Touch Devices Documentation

In order to get the touch area and touch pressure information contained in valuators, you need to get the current XInput's definition of the additional touch data, or the Atom atom identifier, from XInternAtom.

        var touchMajorAtom = XInternAtom(Display, "Abs MT Touch Major", false);
        var touchMinorAtom = XInternAtom(Display, "Abs MT Touch Minor", false);
        var pressureAtom = XInternAtom(Display, "Abs MT Pressure", false);

The string passed to XInternAtom is case sensitive, don't pass it wrong. You can check the current atomic counterparts of the device by typing xinput on the test device, as well as the above code'stouchMajorAtom and other parameters are printed out to see if they are the same, such as the same to prove that the code is written correctly

        ($"ABS_MT_TOUCH_MAJOR={touchMajorAtom} Name={GetAtomName(Display, touchMajorAtom)} ABS_MT_TOUCH_MINOR={touchMinorAtom} Name={GetAtomName(Display, touchMinorAtom)} Abs_MT_Pressure={pressureAtom} Name={GetAtomName(Display, pressureAtom)}");

The equivalent of typing xinput into the console would look something like this. The numbers in parentheses are expected to be the same as the Atom value in the console output of the code above. For exampleABS_MT_TOUCH_MAJOR={touchMajorAtom} Here.touchMajorAtom should be expected to match the following console output"Abs MT Touch Major" (277) The same as 277

> xinput
...
	Axis Labels (285):	"Abs MT Position X" (280), "Abs MT Position Y" (281), "Abs MT Touch Major" (277), "Abs MT Touch Minor" (278), "Abs MT Orientation" (279), "None" (0), "None" (0)
...	

Since different touch devices may have different levels of feature support added to the descriptor information, some touch devices, such as a DELL touchscreen I got, do not support touch width and height information. These can be obtained by reading the pointer device above.pointerDevice Classes field of a local variable to find out what features are currently supported by the device

            var valuators = new List<XIValuatorClassInfo>();
            var scrollers = new List<XIScrollClassInfo>();

            for (int i = 0; i < ; i++)
            {
                var xiAnyClassInfo = [i];
                if (xiAnyClassInfo->Type == )
                {
                    (*((XIValuatorClassInfo**) )[i]);
                }
                else if (xiAnyClassInfo->Type == )
                {
                    (*((XIScrollClassInfo**) )[i]);
                }
            }

After completing the above code, you can try to output it to output the current input information supported by the device

            foreach (XIValuatorClassInfo xiValuatorClassInfo in valuators)
            {
                var label = ;
                // Failure to pass retrieve Label value of retrieve不到
                //();
                ($"[Valuator] [{GetAtomName(Display, label)}] Label={label} Type={} Sourceid={} Number={} Min={} Max={} Value={} Resolution={} Mode={}");
            }

The definition of GetAtomName in the above code is as follows

        [DllImport(libX11)]
        public static extern IntPtr XGetAtomName(IntPtr display, IntPtr atom);

        public static string? GetAtomName(IntPtr display, IntPtr atom)
        {
            var ptr = XGetAtomName(display, atom);
            if (ptr == )
                return null;
            var s = (ptr);
            XFree(ptr);
            return s;
        }

getList<XIValuatorClassInfo> After that, you can use the Number field of the XIValuatorClassInfo to compare it to the Mask of the valuators of the touch when you receive a subsequent touch message to get additional information about the current touch.

The specific method to get the extra touch information is as follows, first create the valuator dictionary for the extra touch information. This is due to the fact that XI uses a strange way of storing the extra data in order to save the input data space, firstly, through the byte array of Mask, using bit bits to indicate whether or not the current data corresponding to the Number of XIValuatorClassInfo has been assigned or exists. For example, if the current input device has five inputs, X Y TouchMajor TouchMinor Pressure, then according to the above, the extra information of the inputs may contain three parameters, TouchMajor TouchMinor Pressure. If only the Pressure parameter has a value in one of the inputs, this is what the input data would look like:

  • First, the Mask array has only one item, so one byte can represent 8 bits.
  • hypothesispressureAtom Number is exactly the value of 2, i.e. TouchMajor is the value of 0 and TouchMinor is the value of 1.
  • Then the only byte of data in the Mask array is the mask value 0010_0000.
  • The corresponding Values array also holds a single double element, which represents the Pressure value.

Based on the example data above, we can see that the simplest way we need to untangle valuators is to store a dictionary, i.e., associate a Mask to the Number field of XIValuatorClassInfo as the Key value. The Values are put into the corresponding slots. Of course, it's possible to use an array instead of a dictionary, but the contents of the array may be sparse and most of the space may actually be wasted.

Here is the code to create the valuator dictionary

                        var valuatorDictionary = new Dictionary<int, double>();;
                        var values = xiDeviceEvent->;;
                        for (var c = 0; c < xiDeviceEvent-> * 8/* There are 8 bits in a Byte, the following XIMaskIsSet is based on bit */; c++)
                        {
                            if (XIMaskIsSet(xiDeviceEvent->, c))
                            {
                            // Values can only be retrieved if there is a value for the Mask
                                valuatorDictionary[c] = *values; values++; // Get the values of Values only if the Mask has a value.
                                Values++;
                            }
                        }

The following test code can be used to find out what the current touch input extra data are respectively

                        foreach (var (key, value) in valuatorDictionary)
                        {
                            var xiValuatorClassInfo = (t =>  == key);

                            var label = GetAtomName(Display, );

                            if ( == touchMajorAtom)
                            {
                                label = "TouchMajor";
                            }
                            else if ( == touchMinorAtom)
                            {
                                label = "TouchMinor";
                            }
                            else if ( == pressureAtom)
                            {
                                label = "Pressure";
                            }

                            ($"[Valuator] [{label}] Label={} Type={} Sourceid={} Number={} Min={} Max={} Value={} Resolution={} Mode={} Value={value}");
                        }

Through the Number field of XIValuatorClassInfo and the Key judgment, you can know which dimension parameter corresponds to the current extra touch data. And through the Label of XIValuatorClassInfo, we can convert and output the specific parameter information, or compare it with the Atom prepared in advance for splitting. For example, the above code compares with the pre-preparedtouchMajorAtom and other variables are compared to split out specific parameters

With the above code, you can get the touch information, including the area used to touch and the pressure of the touch and other information

The code for this article is placed in thegithub cap (a poem)gitee On, you can use the following command line to pull the code

First create an empty folder, then use the command line cd command to enter this empty folder, in the command line enter the following code, you can get the code of this article

git init
git remote add origin /lindexi/lindexi_gd.git
git pull origin 43711cd55b54616e0d75a70d61dec5591151ad2b

The above uses the gitee source, if gitee is not accessible, please replace it with the github source. Go ahead and enter the following code at the command line to replace the gitee source with the github source for pulling code

git remote remove origin
git remote add origin /lindexi/lindexi_gd.git
git pull origin 43711cd55b54616e0d75a70d61dec5591151ad2b

After getting the code, go to the BujeeberehemnaNurgacolarje folder to get the source code

Refer to the documentation:

  • /wiki/Development/Documentation/Multitouch/
  • /wiki/X_Window_System
  • /releases/X11R7.6/doc/man/man3/XIQueryDevice.
  • /releases/X11R7.6/doc/man/man3/XGetEventData.
  • /doc/html/latest/input/
  • /docs/core/interaction/input/touch-devices

Correspondingly, I've fixed the touch problem in Avalonia, see the details in/AvaloniaUI/Avalonia/pull/15297 /AvaloniaUI/Avalonia/pull/15283