phplinuxunpack

Processing Linux input events (/dev/input/event*) in PHP


I'm trying to get keyboard events on GNU/Linux using php, with this code:

<?php
$fd = fopen("/dev/input/event0", "rb");
while (true) {
    $ev = fread($fd, 24);
    $event = unpack("Lsec/Lusec/Stype/Scode/Ivalue", $ev);
    var_dump($event);
}
fclose($fd);
?>

but the result is different from event I get in the following C program:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main()
{
    struct input_event ev;
    int fd = open("/dev/input/event0", O_RDONLY);
    while(1)
    {
        read(fd, &ev, sizeof(ev));
        printf ("Event sec: %lu\n", ev.time.tv_sec);
        printf ("Event usec: %lu\n", ev.time.tv_usec);
        printf("Event type: %d\n", ev.type);
        printf("Event code: %d\n", ev.code);
        printf("Event value: %d\n", ev.value);
        printf("Event value: %s\n", "***************");
    }
    close(fd);
}

Comparing a part of results:

C:
Event sec: 1700377522
Event usec: 896483
Event type: 4
Event code: 4
Event value: 31
Event value: ***************
Event sec: 1700377522
Event usec: 896483
Event type: 1
Event code: 31
Event value: 1
Event value: ***************
Event sec: 1700377522
Event usec: 896483
Event type: 0
Event code: 0
Event value: 0
Event value: ***************

PHP:
array(5) {
  ["sec"]=>
  int(1700377522)
  ["usec"]=>
  int(0)
  ["type"]=>
  int(44515)
  ["code"]=>
  int(13)
  ["value"]=>
  int(0)
}
array(5) {
  ["sec"]=>
  int(1700377522)
  ["usec"]=>
  int(0)
  ["type"]=>
  int(44515)
  ["code"]=>
  int(13)
  ["value"]=>
  int(0)
}
array(5) {
  ["sec"]=>
  int(1700377522)
  ["usec"]=>
  int(0)
  ["type"]=>
  int(44515)
  ["code"]=>
  int(13)
  ["value"]=>
  int(0)
}

The sec parameter seems to be decoded alright, other parameters on the other hand, I'm guessing the problem is caused by unpack formats: unpack("Lsec/Lusec/Stype/Scode/Ivalue", $ev);, but I set everything according to /usr/include/linux/input.h:

struct input_event {
        struct timeval time; = {long seconds, long microseconds}
        unsigned short type;
        unsigned short code;
        unsigned int value;
};

What's wrong with that?

Note: I see some padding bytes in "input.h":

#if defined(__sparc__) && defined(__arch64__)
    unsigned int __usec;
    unsigned int __pad;
#else

Could it be the problem? How can I fix it?


Solution

  • On a 64-bit Linux, the C integer types have the following widths:

    The input_event struct size is 8 + 8 + 2 + 2 + 4 = 24 bytes.

    The documentation for the PHP pack() function says:

    L unsigned long (always 32 bit, machine byte order)

    There is a mismatch here: you're reading 32 bits instead of 64 for the long fields. You should use this instead:

    q signed long long (always 64 bit, machine byte order)

    So the correct unpacking is:

    $event = unpack("qsec/qusec/Stype/Scode/Lvalue", $ev);