One of the great feature of XInput is “Mouse Wheel Emulation” that I graceful with it for more than 5 years. Lately I notice it relieve my hand If I could trig wheel emulation by a key on keyboard instead of a mouse button.

Unfortunately up to this time Evdev not support that and pushing a mouse button through a synthetic way (like using bash script or liunx API) won’t trick EvDev. The reason behind this is that Evdev Wheel Emulation is a device specific driver. you won’t be able to enable it on all devices so it only executed if a button is pressed only on the specific device that you enable emulation on . Moreover after the triggering, wheel emulation only apply to movement of this specific devices with trigger. The figure below show two scenario that lead to unsuccessful wheel emulation.



In a nutshell if you press mouse button source from a bash script emulation not work because EvDev driver won’t get it at all thus the button press have to come from a event-based device and not a complete virtual software. Additionally the movement and button press should come from same device. So as shown in the image, movement on Mouse #1 won’t do the emulation triggered from Mouse #2. To reacap:

  1. The button press should come from an event-based device
  2. Trigger and movement should be on same device

Solution: Uinput-Mapper

Uinput is a kernel module to create event-based device. Uinput-Mapper is a wrapper around uinput in python that can create duplicate from a physical device and then manipulate it. Unfortunately python and Uinput-Mapper are slow and that can cause unexpected lag on mouse movement so the idea in here is to only use Uinput-Mapper under necessary cicumstances.

Uinput-Mapper can be cloned from this github repositories. If you get a glimpse of uinput-mapper, you notice two files input-read and input-create. input-read is a python script that read all event from a event-based device and spit all events out  in form of pickle to stdout. For those who like me that don’t know what pickle is, pickle is a saving format. It can save and restore any type of variable so with power of pickle you can save a complex class and restor all variables and function in the state that it had been saved before.

Now what is input-create? input-create is twin of input-read that first create a event-base device and then get all event from stdin in form of pickle and then execut the events on a virtual device that has been just created by the script. To summerize input-read capture all events from an actual device and convert it to pickle, and then input create duplicate device and execute captured events coming from input-read.

So you had to pipeline all output of input read to input-create with something like command shown below

$ ./input-read -D /dev/input/event3 | ./input-create

The -D option in above command imply that input-read output is in format of pickle(not in a human readable format).

Keycode Based Shortcut Trigger

Now the time hase come to manipulate uinput-mapper to our requiment. I think posting all codes in here will clutter the post structure so I explain basic of modification applied on uinput-mapper and leave all codes to my github Bijoux repository.

First we need a script to trigger on pressing a key on keyboard. For sake of simplicity and because of my extra experience with bash I use bash over python to do the key detection and then used stdio to pass it to input-read. The big picture here is first using input-mapper we create a duplicate of our physical mouse. How? by running exact command mentioned above. Then we disable the physical mouse in XInput by running

$ xinput disable $MOUSE_ID

Now instead of data passing directly to EvDev, it pipelined through synthetic event-base device and then pass to EvDev. Here the trick is to write a script (we call it shortcut from now on) to detect a shortcut and then when a shortcut is detected we inject a synthetic event into virtual mouse that we have been created previously. Now from EvDev perspective, this won’t be any different from the event Physical Mouse #1 generate so it trigger the emulation wheel and moving in x/y direction actually scroll the screen on the active window. The figure below demostrate the forgoing concept.



shortcut script detect a key press -> send a notification to input-read -> input-read load a previously recorded button press -> send recorded event to uinput-create -> uinput-create execute synthethic event as the same as other event come from mouse #1 -> …

Performance Issue

As remark previously, python scripts are slow intrinsically passing all mouse events through it cannot be accepted from performance point of view. Furthuremore in my brief test script runtime speed create not dramatic but noticable lag on mouse movement. To solve this issue bridge solution described above should only applied if and only if the wheel emulation should excute on that time. To do that following vivid shortcut (script) clears how this can be done.

The script is nothing more than a program than switch between physical mouse and virtual mouse on the fly. This is accomplished by using enable/disable function of XInput


MOUSE_ID=($(xinput list | grep -m 1 MOUSE_MODEL | awk -F "=" '{print $2}' | awk -F " " '{print $1}'))

while read -r line
    #echo $line
	if [ "$line" == "key press   108" ];then
		echo 1 #inform input-read
		xinput disable $MOUSE_ID
		xinput enable $VIRTUAL_ID
		while read -r line
			if [ "$line" == "key release 108" ];then
				echo 0 #inform input-read
				xinput enable $MOUSE_ID
				xinput disable $VIRTUAL_ID
		done < <(xinput test 12)
done < <(xinput test 12)

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Comments