1. Overview

A modern computer is surrounded by a multitude of peripheral devices. We can plug in things like keyboards and mice, or use a built-in touchpad. Less obvious devices are camera lens covers and drone controllers. Each device feeds the system with its specific input. So, the kernel needs to make applications understand diverse incoming events.

In this tutorial, we’ll learn how Linux’s kernel provides user applications with events in a well-defined, universal format.

2. The evdev Interface

The evdev name stands for event device. We can regard it as an interface that separates input devices from the user’s applications. So, working as Linux developers, we don’t need to bother with the details of each device. evdev translates the devices’ input obtained by the kernel into a series of standardized events. Then, we can read these events from character device files.

Next, the libevdev library provides a type-safe interface for handling the evdev events. With libevdev, we can read events in both a blocking and non-blocking manner, and we can write events, too. Usually, the end-user application (including Xorg or Wayland) sits atop libevdev:

Wayland client
---------------------|
Compositor           |
------------------|  |
libinput          |  |
---------------|  |  |
libevdev       |  |  |
------------|  |  |  |
kernel      |  |  |  |
---------|  |  |  |  |
device   |  |  |  |  |

2.1. Devices File System

Let’s examine the device filesystems located in the /dev/input/ folder:

$ sudo ls -ld  /dev/input/*
drwxr-xr-x 2 root root 100 Feb 2 17:20 /dev/input/by-id
drwxr-xr-x 2 root root 100 Feb 2 17:20 /dev/input/by-path
crw-rw---- 1 root input 13, 64 Feb 2 17:20 /dev/input/event0
crw-rw---- 1 root input 13, 65 Feb 2 17:20 /dev/input/event1
crw-rw---- 1 root input 13, 74 Feb 2 17:20 /dev/input/event10
crw-rw---- 1 root input 13, 66 Feb 2 17:20 /dev/input/event2
crw-rw---- 1 root input 13, 67 Feb 2 17:20 /dev/input/event3
crw-rw---- 1 root input 13, 68 Feb 2 17:20 /dev/input/event4
crw-rw---- 1 root input 13, 69 Feb 2 17:20 /dev/input/event5
crw-rw---- 1 root input 13, 70 Feb 2 17:20 /dev/input/event6
crw-rw---- 1 root input 13, 71 Feb 2 17:20 /dev/input/event7
crw-rw---- 1 root input 13, 72 Feb 2 17:20 /dev/input/event8
crw-rw---- 1 root input 13, 73 Feb 2 17:20 /dev/input/event9
crw-rw---- 1 root input 13, 63 Feb 2 17:20 /dev/input/mice
crw-rw---- 1 root input 13, 32 Feb 2 17:20 /dev/input/mouse0

First, let’s take a look at the file type. The c in the ls output stands for ‘character special file’ and indicates a character device file. Such a device transfers data. Now, let’s find the mouse device file. To do so, let’s examine the links in the /dev/input/by-id folder:

$ ls -l /dev/input/by-id/*
lrwxrwxrwx 1 root root 9 Feb  7 19:33 /dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-kbd -> ../event3
lrwxrwxrwx 1 root root 9 Feb  7 19:33 /dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse -> ../event4
lrwxrwxrwx 1 root root 9 Feb  7 19:33 /dev/input/by-id/usb-Logitech_USB_Receiver-if01-mouse -> ../mouse0

We’ve found USB devices responsible for the keyboard and mouse events, mapped to event3 and event4 files, respectively. Then, let’s query the /proc/bus/input/devices file for event4:

$ cat /proc/bus/input/devices | grep -A3 -B5 event4
I: Bus=0003 Vendor=046d Product=4091 Version=0111
N: Name="Logitech Wireless Mouse"
P: Phys=usb-0000:00:14.0-2/input1:2
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2:1.1/0003:046D:C534.0002/0003:046D:4091.0004/input/input19
U: Uniq=4091-00-00-00-00
H: Handlers=mouse0 event4
B: PROP=0
B: EV=17
B: KEY=ffff0000 0 0 0 0

So, the mouse has two handlers: mouse0 and event4. We should view mouse0 as a default device file. It’s created by the system regardless of whether any mouse is present. Moreover, it collects events from all plugged-in mice. On the other hand, event4 is dedicated to our wireless mouse and is created when we plug the wireless connector in. Finally, let’s keep in mind that the number 4 is assigned on an ad-hoc basis and may change.

3. Reading the Mouse Input Device

Let’s read the mouse event by simply printing the /dev/input/event4 file:

$ sudo cat /dev/input/event4

The terminal remains empty until we move the mouse pointer somewhere on the screen. Then, we’re going to obtain rather meaningless output:

event 4 raw output

As the data stream is binary, let’s use od to make it more readable. We want to show 24 columns of two-byte hexadecimal numbers:

$ sudo cat /dev/input/event4 | od -t x1 -w24
0000000 2e 16 e9 63 00 00 00 00 f3 75 0a 00 00 00 00 00 02 00 00 00 ff ff ff ff
0000030 2e 16 e9 63 00 00 00 00 f3 75 0a 00 00 00 00 00 02 00 01 00 ff ff ff ff
# ...

So, these 24 bytes relate to the structure, which is used by the kernel code to store the event. Thus, let’s split them into parts:

2e 16 e9 63 00 00 00 00 17 95 0a 00 00 00 00 00 02 00 00 00 fd ff ff ff
|          16 bytes long system time           |type |code |   value   |

The first 16 bytes are for the timestamp, then come the event type and code, each two bytes wide. Finally, we have an event value four bytes wide.

4. Using the evemu-tools Package

To work with input device files, we can install the evemu-tools package. With its commands, we can obtain information about devices and read events in a more comfortable way. The tool uses libevdev directly, so it doesn’t depend on higher layers of the event processing.

First, let’s list the devices with evemu-describe. Next, let’s input 4 to obtain the mouse events details:

$ sudo evemu-describe
Available devices:
/dev/input/event0:	Power Button
/dev/input/event1:	Power Button
/dev/input/event2:	AT Translated Set 2 keyboard
/dev/input/event3:	Logitech Wireless Keyboard PID:4023
/dev/input/event4:	Logitech Wireless Mouse
/dev/input/event5:	Video Bus
/dev/input/event6:	HDA Intel PCH Rear Mic
/dev/input/event7:	HDA Intel PCH Front Mic
/dev/input/event8:	HDA Intel PCH Line Out
/dev/input/event9:	HDA Intel PCH Front Headphone
/dev/input/event10:	HDA Intel PCH HDMI/DP,pcm=3
/dev/input/event11:	HDA Intel PCH HDMI/DP,pcm=7
Select the device event number [0-11]: 4
# EVEMU 1.3
# ...
# Input device name: "Logitech Wireless Mouse"
# Input device ID: bus 0x03 vendor 0x46d product 0x4091 version 0x111
# Supported events:
#   Event type 0 (EV_SYN)
#     Event code 0 (SYN_REPORT)
#     Event code 1 (SYN_CONFIG)
#     Event code 2 (SYN_MT_REPORT)
#     Event code 3 (SYN_DROPPED)
#     Event code 4 ((null))
# ...
#     Event code 15 (SYN_MAX)
#   Event type 1 (EV_KEY)
#     Event code 272 (BTN_LEFT)
#     Event code 273 (BTN_RIGHT)
#     Event code 274 (BTN_MIDDLE)
#     Event code 275 (BTN_SIDE)
#     Event code 276 (BTN_EXTRA)
#     Event code 277 (BTN_FORWARD)
#     Event code 278 (BTN_BACK)
#     Event code 279 (BTN_TASK)
#     Event code 280 ((null))
# ...
#   Event type 2 (EV_REL)
#     Event code 0 (REL_X)
#     Event code 1 (REL_Y)
#     Event code 6 (REL_HWHEEL)
#     Event code 8 (REL_WHEEL)
#     Event code 11 (REL_WHEEL_HI_RES)
#     Event code 12 (REL_HWHEEL_HI_RES)
#   Event type 4 (EV_MSC)
#     Event code 4 (MSC_SCAN)
# ...

Finally, we can print this information immediately for the event4 file:

$ sudo evemu-describe /dev/input/event4

4.1. Reading Mouse Events

Let’s print the mouse events with evemu-record. Then, when we start using a mouse, the events appear:

$ sudo evemu-record /dev/input/event4
# ...
################################
#      Waiting for events      #
################################
# ...
E: 0.171981 0002 0001 0001	# EV_REL / REL_Y                1
E: 0.171981 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +30ms
E: 0.177971 0002 0000 -001	# EV_REL / REL_X                -1
E: 0.177971 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +6ms
E: 0.376022 0002 0000 0001	# EV_REL / REL_X                1
E: 0.376022 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +199ms
E: 0.958058 0004 0004 589825	# EV_MSC / MSC_SCAN             589825
E: 0.958058 0001 0110 0001	# EV_KEY / BTN_LEFT             1
E: 0.958058 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +582ms
# ...

Right after the “E:” labels, we see the elapsed time since the start of the recording. Next come event type, code, and value as decimal numbers. Finally, the event is printed in a human-readable format.

5. Mouse Event Types and Codes

Notice that we have event types and, inside them, the codes. So, we should always regard them as a pair, because the code itself doesn’t need to be unique. First, let’s take a look at EV_SYN. Along with code SYN_REPORT, it’s a synchronization event indicating that preceding events have taken place at the same moment.

Another special event type is EV_MSC for miscellaneous events, which don’t fit other categories.

5.1. Mouse Movement Events

Events of type EV_REL describe the relative movements. By the event code, we need to find out if it’s the mouse or its wheel that is moving:

Event type 2 (EV_REL)
  Event code 0 (REL_X)
  Event code 1 (REL_Y)
  Event code 6 (REL_HWHEEL)
  Event code 8 (REL_WHEEL)
  Event code 11 (REL_WHEEL_HI_RES)
  Event code 12 (REL_HWHEEL_HI_RES)

So, let’s observe the mouse move events in the evemu-record output:

E: 3.761863 0002 0000 -003	# EV_REL / REL_X                -3
E: 3.761863 0002 0001 0002	# EV_REL / REL_Y                2
E: 3.761863 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +8ms

In this bunch of events, REL_X tells us about a move by -3 units along the x-axis. At the same time, the mouse moves by 2 units along the y-axis, as REL_Y indicates.

Next, let’s check the wheel activity:

E: 8.057816 0002 0008 0001	# EV_REL / REL_WHEEL            1
E: 8.057816 0002 000b 0120	# EV_REL / REL_WHEEL_HI_RES     120
E: 8.057816 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +8ms

Now, we have one event REL_WHEEL, and another, REL_WHEEL_HI_RES. The value of REL_WHEEL is the number of detents (notches) we scroll by. Next, the unit of REL_WHEEL_HI_RES is 1/120th part of one detent. However, we can benefit from it only if the device supports high-resolution scrolling. Unfortunately, this isn’t our case, and REL_WHEEL_HI_RES only takes multiples of 120.

Finally, types REL_HWHEEL and REL_HWHEEL_HI_RES refer to a horizontal wheel, which our mouse is lacking.

5.2. Mouse Key Events

Next, let’s examine the key events of the EV_KEY type. We have a lot of them, corresponding to various mouse buttons:

Event type 1 (EV_KEY)
  Event code 272 (BTN_LEFT)
  Event code 273 (BTN_RIGHT)
  Event code 274 (BTN_MIDDLE)
  Event code 275 (BTN_SIDE)
  Event code 276 (BTN_EXTRA)
  Event code 277 (BTN_FORWARD)
  Event code 278 (BTN_BACK)
  Event code 279 (BTN_TASK)

The event value equal to 1 indicates pressing a button, while the value 0 is for release. So, let’s look at an example provided by evemu-record, ignoring the MSC_SCAN event:

E: 1.799945 0004 0004 589825	# EV_MSC / MSC_SCAN             589825
E: 1.799945 0001 0110 0001	# EV_KEY / BTN_LEFT             1
E: 1.799945 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +108ms
E: 1.905965 0004 0004 589825	# EV_MSC / MSC_SCAN             589825
E: 1.905965 0001 0110 0000	# EV_KEY / BTN_LEFT             0
E: 1.905965 0000 0000 0000	# ------------ SYN_REPORT (0) ---------- +106ms

We can find out two consecutive bunches of events. The initial one is for pressing the left button and the final one is for its release. We can interpret this sequence as a mouse click because the events occur in a very short time interval (around 0.1s). However, this kind of deduction is up to the userspace application which consumes events.

6. Grabbing Events

Next, let’s demonstrate a blocking reading from the device file. As evemu-tools commands don’t have such a feature, let’s use evtest with the grab option. So, let’s open a terminal and run:

$ sudo evtest --grab /dev/input/event4

We can observe the stream of events only in the terminal output. No matter how we move the mouse, we won’t see the cursor on the screen. Then, the evemu-record will inform us:

$ sudo evemu-record /dev/input/event4
error: this device is grabbed and I cannot record events
# ...

7. Generating Events

With evemu-event, we can generate a new event, which is written to the selected device file. To do that, we need to provide the device file and event details. We can use the type, code, and values in the same format as shown by evemu-describe. So, let’s simulate a right button press:

$ sudo evemu-event /dev/input/event4 --type EV_KEY --code BTN_RIGHT --value 1 --sync

With the sync option, evemu-event adds the EV_SYN/SYN_REPORT synchronization event.

7.1. Simple Mouse Simulator

We can write a simple Bash script mouse_sim to simulate mouse activity. Its goal is to draw a rectangular shape while keeping the left button pressed. So, we’re going to press the left button, then launch a series of EV_REL events, and finally release the button. Let’s move the mouse by three units in each of the 100 steps:

#!/bin/bash

device="/dev/input/event4"

function move_mouse
{
  for step in {1..100}
  do
    evemu-event ${device} --type EV_REL --code $1 --value $2 --sync
    sleep 0.01
  done
}

for stage in Steady, Ready, Go!
{
  echo ${stage}
  sleep 1
}

evemu-event ${device} --type EV_KEY --code BTN_LEFT --value 1 --sync

move_mouse REL_Y 3
move_mouse REL_X 3
move_mouse REL_Y -3
move_mouse REL_X -3

evemu-event ${device} --type EV_KEY --code BTN_LEFT --value 0 --sync

Let’s make the script executable and start it as root. Then, the motion starts from the current mouse position. Therefore, we definitely should not place the cursor over an application, in which such drag-and-drop activity would be harmful.

Although this example seems a bit inflated, we can simulate any input device in this way — keyboard, touchpad, power button, and more. Let’s be creative!

8. Overview

In this article, we learned about Linux event handling, with a special focus on mouse events. We introduced the evdev interface that separates physical devices from the userspace applications. Then, we examined the device input files. As they provided kernel-generated events to the applications, we read their binary output.

Next, we used the evemu-tools package to peek into the events’ details. First, we listed all kinds of mouse events, regarding their types, codes, and values. Subsequently, we read events and translated them into human-readable form.

Afterward, we saw the blocking read from the device file. Finally, we devised a simple script to simulate mouse activity.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.