filter_evdev project

Page creation: 2009-11-25. Last update: 2009-11-28.

Project author: André Gillibert


filter_evdev is a Linux C utility providing fine-grained custom sensitivity and acceleration for mice, touchpads, joysticks, or any input device. It can also remap buttons and keys such as inverting mouse buttons.

Licensed under WTFPL



System requirements




The need of this program came from my experience with the RX-250 Logitech optical mouse which has firmware-level acceleration that cannot be disabled. Disliking mouse acceleration, I tried to counter this acceleration with negative acceleration. That worked well enough on 1.3, but 1.6 seeming to support positive acceleration only I decided to act at a low level.

First, I tried to approximate the firmware algorithm with help of gnuplot. Second, I wrote a very simple user-space driver, translating accelerated evdev mouse offset events into normalized offsets.

I had learned that GNU/Linux input devices configuration is weak, so that I had to write a program to do something as simple as decelerating the mouse. That's why I transformed my simple program into a powerful input devices configuration utility and now share it with the Linux community.

Required concepts

Input devices

Mice, keyboards, graphics tablets, joysticks, gamepads are all input devices, also known as HID devices.

Input devices can be represented as a set of buttons, keys, switches, sliders and axes. All these can be abstracted to three types of objects: Keys, relative axes and absolute axes. When the state of an object change it's perceived by the system as an event.

Linux system interface

Devices on UNIX

UNIX introduced the idea that hardware devices be represented as files on the file system. Reading and writing to a device file, respectively, receives and transmits information to this device. Device files actually take very little disk space as they are only nodes identifying the kernel device with two numbers and a bit. Device files typically live in the /dev directory.

A device node is defined by a major number identifying the device class, so that the kernel can use the right driver, and a minor number identifying the device number if more than one device of that class is available. Moreover, a device node can either identify a block or a character device. Node numbers of block and character devices live in two different name spaces.

A block device node identifies a random-access storage device. The kernel provides transparent buffering for these devices. Floppy disk drives, hard drives, CD-ROM drives are all block devices. They're called block devices because data is accessed through small fixed-size chunks of data. The two most common block sizes are 512 bytes and one 1 KB.

A character device node identifies any other type of device. The kernel is agnostic about read/write operations meaning. The driver and user-space programs agree on a protocol. Such protocols are defined for sound devices (OSS and now ALSA on Linux), mice (evdev on Linux), display devices (frame buffer), terminals (tty).

Some devices match well the file metaphor. OSS sound devices are simply configured with ioctl(2) and then, raw waveform sound data written to the device file is output through speakers, and data read from the device file represents samples digitalized from the microphone. However seeking in the device file does nothing. Terminals are even simplier, reading from them, reads characters from the keyboard, and writing to them outputs data on screen.

Devices on Linux

UNIX major/minor/(block|char) device nodes are present on UNIX clones, including Linux. However, the limited number of major/minor numbers (1 byte values), the unreliable numbering (changing the physical plug positions of disks may change their minor numbers), the requirement of an administrator to create device nodes of new devices, or a default /dev bloated with nodes for all devices that may ever be connected to the computer, all worsen by plug and play, prompted for improvement.

Modern 2.6 kernels uses the udev system to solve these issues.

Kernel drivers, at boot, or when a device is plugged, assign a name, a major/minor pair and block/char bit, to an internal device structure and send this information to the generic kernel driver subsystem, which, in turn, records these values and notifies the user space with uevents through netlink sockets or invoking /sbin/hotplug with information passed in parameters and environment variables.

The udevd daemon listens for uevents on a netlink socket. These uevents give it name and node information about a plugged or unplugged device. Additionnal information about the device is read from the sysfs pseudo file system mounted on /sys. From this information, plus /etc/udev/ text files full of rules, udevd creates the device node in /dev or one of its subdirectories (e.g. /dev/input/event5), assigns appropriate owner, group and permissions, creates symbolic links with more reliable names, e.g. /dev/input/by-id/usb-Logitech_USB-PS.2_Optical_Mouse-event-mouse and /dev/input/by-path/pci-0000:00:03.0-usb-0:1:1.0-event-mouse.

For many drivers, there's still one major number assigned to the driver. See /usr/src/linux/Documentation/devices.txt for an exhaustive list. Evdev devices are character devices with major number 13 and minor number >= 64. Major number 13 with minor numbers < 32 is reserved for the old joystick interface, and major number 13 with minor 32 <= numbers < 63 is reserved for the old mouse interface. These minor numbers depend on the order devices are plugged. They're allocated in order. Fortunately, consistent names are provided by udev in /dev/input/by-id or custom udev rules in /etc/udev.

Example on a typical PC: On boot, the kernel finds a PS/2 keyboard, assigns it to major.minor 13.64, give it a name "event0", then, it finds an USB mouse, assigns it to 13.65, give it a name "event1". Once the kernel is booted, init(8) is invoked, which, directly or indirectly launches udevd, which, at first, enumerates all existing devices and create /dev/input/event0 as node 13.64c, /dev/input/event1 as node 13.65c, and symbolic links in /dev/input/by-id/ and /dev/input/by-path/. Later, a second USB mouse is plugged in the computer. The kernel USB bus enumerator detects that and informs relevant kernel drivers so that, the USB HID driver gets this signal and notifies the input subsystem to create room for a new input device. The new input device has the node 13.66c and is named "event2". An uevent message is raised and received by udevd through a netlink socket so that it creates a /dev/input/event2 node and symbolic links in /dev/input/by-id/ and /dev/input/by-path/.

Evdev interface

On 2.6 Linux kernels, input devices are exposed to user-space programs through the evdev character devices. These are device files usually stored in /dev/input/, with descriptions available in /proc/bus/input/devices.

This interface is independent of the low-level protocol such as PS/2, USB or gameport.

Evdev device files can be opened with open(2) and fixed-size event structures (16 bytes on i386) can be read from them. If several programs open and read the same input device, they all receive the complete set of events. That way, input events are broadcasted to the interested programs.

Every device has a long user-friendly name such as "AT Translated Set 2 keyboard", "Logitech USB/PS/2 Optical Mouse" or "Lid Switch".

An evdev event structure contains four values, stored in this order.

  1. A microsecond precise timestamp of the event.
  2. The type of event as an integer symbolic constant:

    When a continuous movement is recorded in an axis, reports are usually sent at a pre-defined rate. For example, 125 Hertz for a RX-250 Logitech mouse.

  3. The key/axis code symbolic constant, such as KEY_ENTER, REL_X or ABS_Y. See /usr/include/linux/input.h for the complete set.
  4. A relative integer value whose meaning depends on the event type.

Special EV_SYN events are sent after a set of events in order to indicate that they're simultaneous. Without that, a diagonal mouse displacement would appear as square stairs on screen.

uinput interface

This interface allows user-space programs to create evdev input devices that are exposed back to other user space programs as if it was a hardware device.

The user-space program opens the /dev/misc/uinput char device file (major 10, see /proc/misc for minor), configure it with ioctl, and then, write event structures to it.

filter_evdev system interface

Basically, it's a both an evdev client and an uinput client. It reads input from a specified evdev input device, creates a new input device whose name is derived from the source input device (it appends "+sensitivity" to the name) and send translated reports in the new input device.

Configuration consist of commands sent to a UNIX socket. All commands are executed for every set of simultaneous events received. Commands sent to the socket are recorded, until a "clear" command resets all configuration.

filter_evdev synopsis

filter_evdev device socket

Invocation example

filter_evdev /dev/input/event8 /tmp/mouse_control


Events the physical input device are translated by filter_evdev and sent to the virtual input device. The name of the virtual input device is derived from the name of the physical input device, by appending "+sensitivity". e.g. "Logitech USB/PS/2 Optical Mouse+sensitivity".


Linefeed-terminated commands are sent to the UNIX socket to control filter_evdev translation behavior. The telfilter_evdev program does this job.

Currently, three command types are supported: Translation commands, commit command and clear command.

When sent to the socket, translation and commit commands are appended to the internal list of commands of filter_evdev, and executed everytime a set of simultaneous events is received.

Before any command is received, filter_evdev acts as a simple forwarding system. The virtual input device is a mirror of the physical input device.


REL_X REL_Y [0,5] * 2
REL_X remap REL_Y
REL_Y remap REL_X

This multiplies by two any left, right up or down displacement less than 5 units, and swaps horizontal and vertical axes.

The commit commands is needed to make changes of the first line effective. Without it, the third and four lines commands would map the unchanged values.

Translation commands

command: <axis or key>* <range>? <operation>* <mapping command>*
axis or key: <symbolic constant>|<type>/<code>
symbolic constant: REL_* ABS_* KEY_* as defined in /usr/include/linux/input.h
range: (+|-)?[ <float>?, <float>? ]
operation: (+|-|*|>|<) <float>
mapping command: (map|remap) <axis or key>|unmap
type: integer
code: integer

Every command has conditions: Axes or keys it applies to and values ranges the event values must match. Shortcut: You can use a single X or Y character instead of REL_X or REL_Y. Moreover, no whitespace is required between X and Y if both are specified.

If no sign prefix is specified for the range, it includes both the positive and negative ranges. If a range bound is not specified, it's taken as -infinity for the left bound, and +infinity for the right bound. If no range is specified, it's equivalent to [-infinity, +infinity]. If an event match a condition, its value gets the operations applied and then the mapping commands are executed on this value.

A mapping command assign a type and code to the computed value and generates the event. e.g. map REL_X map REL_Y generates two events: One EV_REL/REL_X with the computed value, and another EV_REL/REL_Y with the same value. By default, even without any mapping command, the axis or key is implicitly mapped to itself. The unmap command prevents this behavior.

remap <axis or key> is equivalent to unmap map <axis or key>.

Commit command

The "commit" word appearing alone on a line.

Conditions and values used in computations are not updated until a "commit" command is met, so that, virtually, all commands in a sequence are executed at once.

Clear command

The "clear" word appearing alone on a line.

This immediately resets all recorded commands, so that, the virtual input device become a simple mirror of the physical input device.


SIGINT, SIGTERM and SIGQUIT exit the program immediately. Both the physical and virtual input devices are closed.



Environment variables