c++linuxkeypressposix-select

Linux select not detecting key presses


I am trying to debug an issue with random key presses being registered even though I have not typed them. I had started the question here.

Initially I ran evtest and specified each device. I could clearly see the random characters appear but evtest was not registering the keypress. I spent a lot of time rerunning evtest each time only monitoring for 1 input device. In an act of impatience, I modified evtest.c by replacing the scan_devices function with the hope of whenever a keypress event happens it will monitor all devices and notify me which device triggered the keypress. Below code is based on this:

static char* scan_devices(void)
{
    struct dirent **namelist;
    int i, ndev, devnum;
    char *filename;
    int max_device = 0;

    ndev = scandir(DEV_INPUT_EVENT, &namelist, is_event_device, versionsort);
    if (ndev <= 0)
        return NULL;

    fprintf(stderr, "Available devices:\n");

    for (i = 0; i < ndev; i++){
        char fname[300];
        int fd = -1;
        char name[256] = "???";

        snprintf(fname, sizeof(fname),
             "%s/%s", DEV_INPUT_EVENT, namelist[i]->d_name);
        fd = open(fname, O_RDONLY);
        if (fd < 0)
            continue;
        ioctl(fd, EVIOCGNAME(sizeof(name)), name);

        fprintf(stderr, "%s:    %s\n", fname, name);
        close(fd);

        sscanf(namelist[i]->d_name, "event%d", &devnum);
        if (devnum > max_device)
            max_device = devnum;

        free(namelist[i]);
    }//end for

    /* open and monitor nuevents files*/

    struct input_event ev[64];

    int rd;
    fd_set rdfs;
    int nuevents=28;
    int fd[28]; //should be equal to nuevents
    int ret=0;

    printf("Size of rdfs %d B \nStart Monitor \n", (int) sizeof(rdfs));

    /* Next we clear the file descriptors*/
    FD_ZERO(&rdfs);

    /* We need to open each device and then add them to the file descriptors */
    for (i = 0; i < nuevents; i++){
        char fname[300];

        snprintf(fname, sizeof(fname),
             "%s/%s", DEV_INPUT_EVENT, namelist[i]->d_name);
        fd[i] = open(fname, O_RDONLY);
        if (fd[i]<0){
            printf("error file %d \n",(int)i);
            return 0;

        }// end if

        FD_SET(fd[i], &rdfs);

    }// end for

    while (!stop) {
    /*
    https://www.man7.org/linux/man-pages/man2/select.2.html
    */
        ret = select(nuevents, &rdfs, NULL, NULL, NULL);
        if (stop)
            break;
        if (ret < 0){
            printf("ret failure");
            exit (EXIT_FAILURE);
        }// end if

        for (i=0;i<nuevents;i++){
            if (FD_ISSET(i,&rdfs)){
                printf("received data on device %d\n",(int)i);
                rd = read(fd[i], ev, sizeof(ev));

                if (rd < (int) sizeof(struct input_event)) {
                    printf("expected %d bytes, got %d\n", (int) sizeof(struct input_event), rd);
                    perror("\nevtest: error reading");
                    return "error";
                }   // end of if

            }// end of if
        }// end of for[

    }// end of while

    for (i = 0; i < nuevents; i++){
        ioctl(fd[i], EVIOCGRAB, (void*)0); /* Use IO CTRL to release device*/
    }//end for

    return "done";
}

The output looks generally correct but if I manually type an event the character shows up but an event never triggers so I must be doing something wrong. I did run it as root. Example output is below.

No device specified, trying to scan all of /dev/input/event*
Available devices:  
/dev/input/event0:  Power Button  
/dev/input/event1:  Sleep Button  
/dev/input/event2:  Lid Switch  
/dev/input/event3:  Power Button  
/dev/input/event4:  AT Translated Set 2 keyboard  
/dev/input/event5:  System76 ACPI Hotkeys  
/dev/input/event6:  FTCS1000:00 2808:0102 Mouse  
/dev/input/event7:  FTCS1000:00 2808:0102 Touchpad  
/dev/input/event8:  MOSART Semi. 2.4G INPUT DEVICE  
/dev/input/event9:  MOSART Semi. 2.4G INPUT DEVICE Mouse  
/dev/input/event10: MOSART Semi. 2.4G INPUT DEVICE Consumer Control   
/dev/input/event11: MOSART Semi. 2.4G INPUT DEVICE System Control  
/dev/input/event12: MOSART Semi. 2.4G INPUT DEVICE  
/dev/input/event13: Lite-On Technology Corp. USB Multimedia Keyboard  
/dev/input/event14: Lite-On Technology Corp. USB Multimedia Keyboard System Control  
/dev/input/event15: Lite-On Technology Corp. USB Multimedia Keyboard Consumer Control  
/dev/input/event16: Lite-On Technology Corp. USB Multimedia Keyboard  
/dev/input/event17: Video Bus  
/dev/input/event18: Intel HID events  
/dev/input/event19: HDA Intel PCH Mic  
/dev/input/event20: HDA Intel PCH Headphone  
/dev/input/event21: HDA Intel PCH HDMI/DP,pcm=3  
/dev/input/event22: HDA Intel PCH HDMI/DP,pcm=7  
/dev/input/event23: HDA Intel PCH HDMI/DP,pcm=8  
/dev/input/event24: HDA Intel PCH HDMI/DP,pcm=9  
/dev/input/event25: HDA NVidia HDMI/DP,pcm=3  
/dev/input/event26: HDA NVidia HDMI/DP,pcm=7  
/dev/input/event27: HDA NVidia HDMI/DP,pcm=8  
/dev/input/event28: HDA NVidia HDMI/DP,pcm=9  
Size of rdfs 128 B  
Start Monitor 
received data on device 7  
a    
a  

At the end I pressed the "A" button on the keyboard but did not get a message of which device was pressed that I expected. Any help of ideas to try to fix this?

After adding a large amount of printf I found that the program seems to be hanging during the statement:

rd = read(fd[i], ev, sizeof(ev));  

Solution

  • This line in the original code looks suspicious:

    if (FD_ISSET(i,&rdfs)){
    

    According to man select FD_ISSET() does the following:

    select() modifies the contents of the sets according to the rules described below. After calling select(), the FD_ISSET() macro can be used to test if a file descriptor is still present in a set. FD_ISSET() returns nonzero if the file descriptor fd is present in set, and zero if it is not.

    So, in the original code

    for (i=0;i<nuevents;i++){
          if (FD_ISSET(i,&rdfs)){
    

    FD_ISSET checks whether a value held in the variable i is present in the set rdfs, but the set consists of file descriptors held in the array int fd[28]:

    FD_SET(fd[i], &rdfs);
    

    Therefore, the check should also be done against this array:

    if (FD_ISSET(fd[i],&rdfs)){
    

    After adding a large amount of printf I found that the program seems to be hanging during the statement:

    rd = read(fd[i], ev, sizeof(ev));

    Your discovery makes sense. During one of the iterations of the for loop the erroneous code if (FD_ISSET(i,&rdfs)){ returns true for some integer in range 0 - 27 (whatever the current value of i is). The code proceeds to reading data by calling read, and passes it a file descriptor fd[i], which is not guaranteed to be the same as i and thus not guaranteed to have any data available for reading. If there is no data to be read from the file descriptor fd[i], the read call blocks the execution of the calling thread.

    That being said, replace if (FD_ISSET(i,&rdfs)){ with if (FD_ISSET(fd[i],&rdfs)){