clinuxxlibxorg

Counting mouse buttons X11 Linux


This is my first question, but I am really lost and I need your help. I am writing a program in C, that prints out amount of buttons a mouse. I decided to use XI.h for this matter. The program behaves strangely.

The code presented is a code of a larger program, that's why so many libs are included. Just don't look at it.

Sources used: XListInputDevices XInternAtom

Code of the program (mouse.c)

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XI.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h> /* socket() connect() bind() listen() accept() socketpair() */
#include <sys/types.h>
#include <unistd.h> 
#include <string.h> /* strcpy() */
#include <stdio.h> /* perror() */
#include <errno.h> /* error numbers */
#include <stdlib.h>

int main ( void )
{
    XDeviceInfo mouse_info;
    XDeviceInfoPtr ptr_mouse_info;
    XDevice *mouse;
    XID mouse_id;
    XButtonInfoPtr ptr_mouse_buttons;
    XKeyInfoPtr ptr_mouse_keys;
    XValuatorInfoPtr ptr_mouse_axes;
    char * mouse_name;
    int num_devices, num_props, i;
    Display *display = XOpenDisplay(NULL);
    if (display == NULL)
    {
        perror("XOpenDisplay error");
        exit(-1);
    }
    
    ptr_mouse_info = XListInputDevices(display, &num_devices);
    for (i = 0; i < num_devices; i++)
    {
        if (XInternAtom(display, XI_MOUSE, 1) == ptr_mouse_info[i].type)
        {
            mouse_id=ptr_mouse_info[i].id;
            mouse_name=ptr_mouse_info[i].name;
            mouse = XOpenDevice(display, mouse_id);
            
            ptr_mouse_buttons = (XButtonInfoPtr)&ptr_mouse_info[i].inputclassinfo[ButtonClass];
            ptr_mouse_keys = (XKeyInfoPtr) &ptr_mouse_info[i].inputclassinfo[KeyClass];
            printf("Mouse name: %s\n", mouse_name);
            printf("Number of buttons: %d\n", ptr_mouse_buttons->num_buttons);
            printf("Number of keys: %d\n", ptr_mouse_keys->num_keys);
        }
    }
    printf("Mouse name: %s\n", mouse_name);
    printf("Number of buttons: %d\n", ptr_mouse_buttons->num_buttons);
    printf("Number of keys: %d\n", ptr_mouse_keys->num_keys);
    XFreeDeviceList(ptr_mouse_info);
    exit(0);
}

Compile

cc mouse_test.c -lX11 -lXfixes -lXi -o mouse_test

I expect to get the amount of buttons on my mouse(which is 7), but I always get:

Mouse name: Logitech Mechanical keyboard Logitech Mechanical keyboard Keyboard
Number of buttons: 4
Number of keys: 2
Mouse name: ROCCAT ROCCAT Kone Pro Keyboard
Number of buttons: 4
Number of keys: 2
Mouse name: ROCCAT ROCCAT Kone Pro
Number of buttons: 4
Number of keys: 2
Mouse name: ROCCAT ROCCAT Kone Pro
Number of buttons: 4
Number of keys: 2

I think, this maybe related to linux drivers, but I am not sure


Solution

  • I know you're likely expecting a practical answer, but before we talk about the empirical aspects we need to discuss some a priori aspects, some philosophical and conceptual matters.

    We have your mouse as part of the physical reality, and this is one thing. Software is not the reality itself, but a model to represent that reality. And as a model, there are always limitations, imperfections, etc.

    So, the appropriate question is: do you want to know the number of buttons of your actual physical device, or the number of buttons recognized by Xorg? If you would like to obtain the number of buttons of the actual physical device, you need to grab it and count it.

    The Xorg libraries will provide you with information about what those libraries see. And unlike in some proprietary operating systems, Xorg as well as most things on Unix(&-like) systems usually relies on more generic drivers. So some Xorg drivers will be adapted to support a large number of devices, using a large number of OS drivers -- since it works on several operating systems as well. (PS: I am no expert on Xorg or Xorg drivers, so if someone could provide more accurate details here, be welcome to do it and remove this note later)

    And yes, our stack has another layer here, the operating system, and Xorg drivers might rely a lot on the OS drivers (in your case, the linux kernel drivers, since I assume you're running Linux). So these drivers might implement all your device's capabilities or might not, and they might or might not offer Xorg accurate information about your actual device.

    So, as you can see, we have a whole complexity in our stack that might help you getting inaccurate information.

    Furthermore, we have the X11 protocol, which is a legacy protocol from the mid 80s, and Xorg's implementation of it, and the libraries and extensions we are running as well as limiting factors to help you obtain accurate information on the device. We need to consider that we're dealing with a very old stack, too complex and developed with very old desktops in mind, that's been adapted, designed and redesigned to make modern hardware of modern desktops fit in old ideas.

    But enough with background, let's go to the solutions.

    Like I said, Xorg is a messy framework. We have XInput API, and it's old, and we have something a little bit more accurate and recent that is XInput2. Your code uses XInput (1), so that might be a limiting factor. In fact, my own mouse with a lot of buttons is listed exactly the same way as yours (and so is my touchpad). I made an adapted version of your original code, because I was trying to run on XWayland (I no longer use X natively), but in the end I configured Xorg for another user to test it natively on X.

    Your code has other issues unrelated to the question that I'll explain in the end, but here what I used:

    #include <stdio.h>
    #include <X11/Xlib.h>
    #include <X11/extensions/XInput.h>
    #include <X11/extensions/XI.h>
    
    int main (int argc, char **argv, char **envp)
    {
        XDeviceInfo mouse_info;
        XDeviceInfoPtr ptr_mouse_info;
        XDevice *mouse = NULL;
        XID mouse_id;
        XButtonInfoPtr ptr_mouse_buttons;
        XKeyInfoPtr ptr_mouse_keys;
        XValuatorInfoPtr ptr_mouse_axes;
        char * mouse_name = NULL;
        int num_devices, num_props, i;
        
        Display *display = XOpenDisplay(NULL);
        if (display == NULL)
        {
            perror("XOpenDisplay error");
            exit(-1);
        }
    
        ptr_mouse_info = XListInputDevices(display, &num_devices);
        for (i = 0; i < num_devices; i++)
        {
            printf("[%d] %s\n", i, ptr_mouse_info[i].name);
            if (XInternAtom(display, XI_MOUSE, 1) == ptr_mouse_info[i].type)
            {
                mouse_id=ptr_mouse_info[i].id;
                mouse_name=ptr_mouse_info[i].name;
                mouse = XOpenDevice(display, mouse_id);
                
                ptr_mouse_buttons = (XButtonInfoPtr)&ptr_mouse_info[i].inputclassinfo[ButtonClass];
                ptr_mouse_keys = (XKeyInfoPtr) &ptr_mouse_info[i].inputclassinfo[KeyClass];
                printf("Mouse name: %s\n", mouse_name);
                printf("Number of buttons: %d\n", ptr_mouse_buttons->num_buttons);
                printf("Number of keys: %d\n", ptr_mouse_keys->num_keys);
            }
        }
        
        XFreeDeviceList(ptr_mouse_info);
        exit(0);
    }
    

    My output, stripping out non-mice entries:

    [7] SYNA7DB5:01 06CB:7DB7 Mouse
    Mouse name: SYNA7DB5:01 06CB:7DB7 Mouse
    Number of buttons: 4
    Number of keys: 2
    [12] INSTANT USB GAMING MOUSE
    Mouse name: INSTANT USB GAMING MOUSE
    Number of buttons: 4
    Number of keys: 2
    

    As you can see, it's either a big coincidence that both our mices all have the same number of buttons and keys, or XInput (1) is not providing accurate information anymore. To test whether it's a problem with the code, I ran xinput list --long where I obtained the following:

    INSTANT USB GAMING MOUSE  Keyboard          id=16   [slave  pointer  (2)]
        Reporting 7 classes:
            Class originated from: 16. Type: XIButtonClass
            Buttons supported: 7
            Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right"
    SYNA7DB5:01 06CB:7DB7 Touchpad              id=19   [slave  pointer  (2)]
        Reporting 7 classes:
            Class originated from: 19. Type: XIButtonClass
            Buttons supported: 7
            Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right"
    

    So, if xinput list could retrieve the information appropriately, I looked into that and figured out it was a XInput 1 problem. So I tried with XInput2, with the code below:

    #include <X11/Xlib.h>
    #include <X11/extensions/XInput2.h>
    #include <errno.h>
    #include <stdio.h>
    
    int main() {
        int num_buttons;
        int num_keys;
        int ret = 0;
    
        Display *display = XOpenDisplay(NULL);
        if (display == NULL) {
            fprintf(stderr, "Error opening display");
            goto main_err;
        }
    
        int opcode, event, error;
        if (!XQueryExtension(display, "XInputExtension", &opcode, &event, &error)) {
            fprintf(stderr, "X Input extension not available.\n");
            goto main_err;
        }
    
        int num_devices;
    
        XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &num_devices);
        if (devices == NULL) {
            fprintf(stderr, "XIQueryDevice failed.\n");
            goto main_err;
        }
        for (int i = 0; i < num_devices; i++) {
            int use = devices[i].use;
            int num_classes = devices[i].num_classes;
            if (use == XIMasterPointer || use == XISlavePointer) {
                printf("Device %d:\n", devices[i].deviceid);
                printf("  Name: %s\n", devices[i].name);
    
                num_buttons = 0;
                num_keys = 0;
                for (int j = 0; j < num_classes; j++) {
                    XIAnyClassInfo *class = devices[i].classes[j];
                    XIButtonClassInfo *btn;
                    XIKeyClassInfo *key;
    
                    if (class->type == XIButtonClass) {
                        btn = (XIButtonClassInfo*) class;
                        num_buttons = btn->num_buttons;
                    } else if (class->type == XIKeyClass) {
                        key = (XIKeyClassInfo*) class;
                        num_keys = key->num_keycodes;
                    }
                }
    
                printf("  Buttons: %d\n", num_buttons);
                printf("     Keys: %d\n", num_keys);
                printf("\n");
            }
        }
    
        XIFreeDeviceInfo(devices);
        XCloseDisplay(display);
    
        goto main_return;
    
    main_err:
        ret = errno;
        perror("Failure: ");
    main_return:
        return ret;
    }
    

    With this code I got a more accurate (I think) result:

    Device 15:
    Name: INSTANT USB GAMING MOUSE
    Buttons: 9
    Keys: 0
    Device 19:
    Name: SYNA7DB5:01 06CB:7DB7 Touchpad
    Buttons: 7
    Keys: 0
    

    So, I think the answer to what I assume to be your question is: Use XInput2, if you really need this information to come from your X11 implementation. Although, I would suggest to use something from your operating system if this is not the case, it depends a lot on what your requirements and modeled user are.

    UNRELATED COMMENTS

    On Wayland, I was getting a Segmentation Fault because you're using lots of uninitialized pointers. Never do that in C, always initialize your pointers, otherwise they'll have memory garbage as value, and memory garbage valued pointers point to undefined locations.