2

I'm trying to better understand (conceptually) how a system like X works. My understanding is that for X to receive keyboard events, it has an internal event loop which performs a blocking read on /dev/input/event0, for example, which the kernel exposes as a way to read user input. It then takes that event, does some processing (in userspace) and hands it off to the active window using some form of an event queue. Please correct me if I'm mistaken with how this essentially works.

But here's where I'm confused. If /dev/input/event0 or eventXX is being read directly by X and hence those events are being consumed, how is it possible to have other processes performing reads on eventXX? We can have a Python process read from the device, we can read from the command line, etc.

My understanding (and this could be where I'm wrong) is that a character device's output gets consumed by a single process, so if two processes were reading from /dev/... then only one read call would return with the given data. So if X is grabbing all the data from the character device, how are other processes able to read the same keyboard data?

1
  • What do you mean with "we can read from the command line"? Commented Jan 23, 2022 at 21:45

2 Answers 2

2

Xorg and the other programs negotiate sharing I/O devices. They operate in two modes. When active, they are polling the input devices, kevent(…EV_ENABLE…) or similar having been used against their file descriptors, and writing to the display devices. When inactive they are not polling the input devices, kevent(…EV_DISABLE…) or similar having been used against their file descriptors, and not writing to the display devices.

Only one program is ever accessing the I/O devices at any given time. This is because, as you observe, if multiple programs do so, displays get corrupted and it is indeterminate where individual input events end up. (The built-in terminal emulator in Linux counts as a program, here, although it is not a process. It doesn't use polling, but instead has internal hooks into the input device drivers and gates the flow of input on and off, and has a flag telling it when it should be realizing updated character cells to a display device, achieving the same effect.)

They use a kernel virtual terminal as the means of negotiating this. This is why Xorg servers allocate a KVT. Using a protocol that is specific to KVT devices, which involves ioctl()s that only that type of device understands and sending signals to processes, each one can negotiate when it is in charge of the display and the Human Input Devices (HIDs) and when the kernel built-in terminal emulator program, or some other program (including another X server), is.

There's a reinvention of this idea in systemd's logind program, that was aimed at allowing the same negotiation between cöoperating programs for I/O devices that aren't used by the KVT subsystem, such as completely independent secondary displays and so forth, or when there isn't a KVT subsystem in Linux in the first place. The same principle applies. Programs don't use the file descriptors when inactive.

Further reading

1
  • Thank you! This is helpful—I'm still trying to grasp then, if only one program is accessing I/O devices at a given time, how it possible that I can open /dev/input/mice like this in my process and receive those mouse events in my process, yet also X server is receiving these very same events, presumably by also reading from the same /dev/input/mice?
    – rb612
    Commented Jun 5, 2020 at 4:43
1

Multiple readers all get copies of input subsystem events, at least on my system (Linux 5.10), for keyboard and mouse events. At least all of the devices matching the glob:

/dev/input/by-path/*-event-{kbd,mouse}

work this way for me, even though Xorg, systemd-login and acpid all have them open (as shown by lsof).

You can do test reads using evtest(1) as long as --grab is not given (apparently X does not "grab" it), e.g. a "shift" keypress/release on my keyboard"

 $ sudo evtest /dev/input/by-path/platform-i8042-serio-0-event-kbd |& grep ^Event:
Event: time 1642971418.691004, type 4 (EV_MSC), code 4 (MSC_SCAN), value 36
Event: time 1642971418.691004, type 1 (EV_KEY), code 54 (KEY_RIGHTSHIFT), value 1
Event: time 1642971418.691004, -------------- SYN_REPORT ------------
Event: time 1642971418.729131, type 4 (EV_MSC), code 4 (MSC_SCAN), value 36
Event: time 1642971418.729131, type 1 (EV_KEY), code 54 (KEY_RIGHTSHIFT), value 0
Event: time 1642971418.729131, -------------- SYN_REPORT ------------

(type 4 is the raw scancode, which comes alongside the logical keyboard event type 1). Single reads return multiples of struct input_event (24 bytes on x86_64), where type 0, code 0 (EV_SYN, SYN_REPORT) packets delimit groups of related input packets. This is more relevant with touchpads and mice, which have multi-dimensional inputs.

(According to package metadata, evemu-tools should be used these days for testing rather than evtest, but I didn't try it.)

Note that I'm successfully using the event device to monitor keyboard idle time, and the script correctly reads events entered either in X or from the console, while other readers continue to get copies of all those keypresses.

see the following files for more details:

  • /usr/include/linux/input.h
  • /usr/include/linux/input-event-codes.h
  • /usr/src/linux/Documentation/input/input.rst

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .