Table of Contents

XInput: Scroll-Wheel Emulation on a 4-button Marble Mouse

Logitech 4-button Marble Mouse, note the absence of a scroll-wheel My 4-button Logitech Marble Mouse does not have a scroll-wheel, and by default in Ubuntu 8.10 does not use any of its buttons to emulate one.

Time to dive into xorg.conf, right?

Wrong! To quote the Ubuntu Documentation:

The newest version of Xorg used in Intrepid brings changes as to how input devices are detected and set up for use. Hal now plays a large part in the process, and as such xorg.conf is largely ignored, so any settings you had in xorg.conf will no longer be in use when X starts. You will now need to add files to /etc/hal/fdi/policy with an .fdi extension.

Now the example on that page is pretty good, and I tweaked it like so (both files had to be created):

Remapping Buttons

~/.Xmodmap:

! Logitech USB Marble Mouse Physical buttons available: 
!  Mouse Big:   1 3
!  Mouse Small: 8 9
! Xmodmap positional meanings:
!  Pointer: Left-click, Middle-click, Right-click, Scroll-up, Scroll-down, Scroll-left, Scroll-right, Scroll-click, 9
pointer = 1 9 3 4 5 6 7 8 2

Ordinarily, the first logical button on a mouse (or “pointer” in this case) will be interpreted as a left-click, the second as middle-click, third as right and so on according to the list above.

Note that scrolling manifests as multiple buttons, one for each direction. Even though most mice have a wheel for this, the snapping-action as you move it acts as a button-push as far as the computer is concerned. Four scrolling directions are available plus a click-action (pushing the scroll wheel down).

This marble mouse, however, reports to have 9 buttons, but in fact only has 4. The big buttons are numbered 1 (left) and 3 (right), and the small buttons as 8 (left) and 9 (right).

I prefer to make the small-right button my middle click, and so I swap the numbers appearing in positions “middle-click” and “9”1).

How did you find all this out?

If you open a terminal and run xev a small window should pop up and a lot of text, which at first glance looks like lots of errors. They are not. xev prints out all the X events the window it creates recieves.

First, make sure your pointer doesn't have any strange mappings:

# Note, this affects all attached mice!
xmodmap -e "pointer = 1 2 3 4 5 6 7 8 9 10 11 12 13"

So if you place your mouse cursor over the window and press-and-hold the button you want to be left click, you should see a message like this:

ButtonRelease event, serial 35, synthetic NO, window 0x1a00002,
    root 0x329, subw 0x1a00003, time 8162295, (31,37), root:(1049,1281),
    state 0x110, button 1, same_screen YES

Note the “button 1” text, telling us the button we just pressed is number 1.

I then repeat this for all buttons. This trick will also be useful for testing the scroll wheel emulation later.

Enabling Emulation (Ubuntu 10.04)

cf. https://wiki.ubuntu.com/X/Config/Input

To enable for the current session (i.e. temporarily):

meermanr@ryoga:~
$ xinput list
⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad              	id=10	[slave  pointer  (2)]
⎜   ↳ Serial Wacom Tablet                     	id=12	[slave  pointer  (2)]
⎜   ↳ Serial Wacom Tablet eraser              	id=11	[slave  pointer  (2)]
⎜   ↳ Macintosh mouse button emulation        	id=13	[slave  pointer  (2)]
⎜   ↳ Logitech USB Trackball                  	id=14	[slave  pointer  (2)]
⎜   ↳ Microsoft Natural® Ergonomic Keyboard 4000	id=16	[slave  pointer  (2)]
⎣ Virtual core keyboard                   	id=3	[master keyboard (2)]
    ↳ Virtual core XTEST keyboard             	id=5	[slave  keyboard (3)]
    ↳ Power Button                            	id=6	[slave  keyboard (3)]
    ↳ Video Bus                               	id=7	[slave  keyboard (3)]
    ↳ Sleep Button                            	id=8	[slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard            	id=9	[slave  keyboard (3)]
    ↳ Microsoft Natural® Ergonomic Keyboard 4000	id=15	[slave  keyboard (3)]

meermanr@ryoga:~
$ xinput query-state "Logitech USB Trackball"
2 classes :
ButtonClass
	button[1]=up
	button[2]=up
	button[3]=up
	button[4]=up
	button[5]=up
	button[6]=up
	button[7]=up
	button[8]=up
	button[9]=up
ValuatorClass Mode=Relative Proximity=In
	valuator[0]=151
	valuator[1]=388

meermanr@ryoga:~
$ xinput list-props "Logitech USB Trackball"
Device 'Logitech USB Trackball':
	Device Enabled (117):	1
	Device Accel Profile (236):	0
	Device Accel Constant Deceleration (237):	1.000000
	Device Accel Adaptive Deceleration (239):	1.000000
	Device Accel Velocity Scaling (240):	10.000000
	Evdev Reopen Attempts (235):	10
	Evdev Axis Inversion (305):	0, 0
	Evdev Axes Swap (307):	0
	Axis Labels (308):	"Rel X" (125), "Rel Y" (126)
	Button Labels (309):	"Button Left" (118), "Button Middle" (119), "Button Right" (120), "Button Wheel Up" (121), "Button Wheel Down" (122), "Button Horiz Wheel Left" (123), "Button Horiz Wheel Right" (124), "Button Side" (581), "Button Extra" (582)
	Evdev Middle Button Emulation (310):	2
	Evdev Middle Button Timeout (311):	50
	Evdev Wheel Emulation (312):	1
	Evdev Wheel Emulation Axes (313):	0, 0, 4, 5
	Evdev Wheel Emulation Inertia (314):	10
	Evdev Wheel Emulation Timeout (315):	200
	Evdev Wheel Emulation Button (316):	4
	Evdev Drag Lock Buttons (317):	0

meermanr@ryoga:~
$ xinput set-int-prop "Logitech USB Trackball" "Evdev Wheel Emulation Button" 8 8

meermanr@ryoga:~
$ xinput set-int-prop "Logitech USB Trackball" "Evdev Wheel Emulation" 8 1


meermanr@ryoga:~
$ xinput list-props "Logitech USB Trackball"Device 'Logitech USB Trackball':
	Device Enabled (117):	1
	Device Accel Profile (236):	0
	Device Accel Constant Deceleration (237):	1.000000
	Device Accel Adaptive Deceleration (239):	1.000000
	Device Accel Velocity Scaling (240):	10.000000
	Evdev Reopen Attempts (235):	10
	Evdev Axis Inversion (305):	0, 0
	Evdev Axes Swap (307):	0
	Axis Labels (308):	"Rel X" (125), "Rel Y" (126)
	Button Labels (309):	"Button Left" (118), "Button Middle" (119), "Button Right" (120), "Button Wheel Up" (121), "Button Wheel Down" (122), "Button Horiz Wheel Left" (123), "Button Horiz Wheel Right" (124), "Button Side" (581), "Button Extra" (582)
	Evdev Middle Button Emulation (310):	2
	Evdev Middle Button Timeout (311):	50
	Evdev Wheel Emulation (312):	1
	Evdev Wheel Emulation Axes (313):	0, 0, 4, 5
	Evdev Wheel Emulation Inertia (314):	10
	Evdev Wheel Emulation Timeout (315):	200
	Evdev Wheel Emulation Button (316):	8
	Evdev Drag Lock Buttons (317):	0

Enabling Emulation (Ubuntu 9.10)

/etc/hal/fdi/policy/10-marblemouse.fdi

<deviceinfo version="0.2">
  <device>
    <match key="info.product" string="Logitech USB Trackball">
      <merge key="input.x11_options.EmulateWheel" type="string">true</merge>
      <merge key="input.x11_options.EmulateWheelTimeout" type="string">200</merge>
      <merge key="input.x11_options.EmulateWheelButton" type="string">8</merge>
      <merge key="input.x11_options.YAxisMapping" type="string">4 5</merge>
      <merge key="input.x11_options.XAxisMapping" type="string">6 7</merge>
      <merge key="input.x11_options.Emulate3Buttons" type="string">true</merge>
    </match>
    <match key="info.product" string="Marble Mouse (4-button)">
      <merge key="input.x11_options.EmulateWheel" type="string">true</merge>
      <merge key="input.x11_options.EmulateWheelTimeout" type="string">200</merge>
      <merge key="input.x11_options.EmulateWheelButton" type="string">8</merge>
      <merge key="input.x11_options.YAxisMapping" type="string">4 5</merge>
      <merge key="input.x11_options.XAxisMapping" type="string">6 7</merge>
      <merge key="input.x11_options.Emulate3Buttons" type="string">true</merge>
    </match>
  </device>
</deviceinfo>

Note in particular:

<match key="info.product" string="Logitech USB Trackball">
<match key="info.product" string="Marble Mouse (4-button)">

These strings must exactly match that used by HAL itself. You can list all HAL devices with the command hal-device. My marble mouse looks like one of the following2):

15: udi = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial_if0_logicaldev_input'
  info.subsystem = 'input'  (string)
  info.product = 'Logitech USB Trackball'  (string)
  linux.device_file = '/dev/input/event1'  (string)
  input.x11_driver = 'evdev'  (string)
  linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.7/usb7/7-6/7-6.2/7-6.2:1.0/input/input18/event1'  (string)
  info.udi = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial_if0_logicaldev_input'  (string)
  input.x11_options.EmulateWheel = 'true'  (string)
  info.parent = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial_if0'  (string)
  input.x11_options.EmulateWheelTimeout = '200'  (string)
  input.x11_options.EmulateWheelButton = '8'  (string)
  linux.hotplug_type = 2  (0x2)  (int)
  input.x11_options.YAxisMapping = '4 5'  (string)
  info.category = 'input'  (string)
  linux.subsystem = 'input'  (string)
  input.x11_options.XAxisMapping = '6 7'  (string)
  input.originating_device = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial_if0'  (string)
  info.capabilities = { 'input', 'input.mouse' } (string list)
  input.x11_options.Emulate3Buttons = 'true'  (string)
  input.device = '/dev/input/event1'  (string)
  input.product = 'Logitech USB Trackball'  (string)

17: udi = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial'
  info.vendor = 'Logitech, Inc.'  (string)
  info.subsystem = 'usb_device'  (string)
  linux.hotplug_type = 2  (0x2)  (int)
  info.product = 'Marble Mouse (4-button)'  (string)
  linux.subsystem = 'usb'  (string)
  info.udi = '/org/freedesktop/Hal/devices/usb_device_46d_c408_noserial'  (string)
  info.linux.driver = 'usb'  (string)
  usb_device.linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.7/usb4/4-6/4-6.2'  (string)
  usb_device.configuration_value = 1  (0x1)  (int)
  usb_device.num_configurations = 1  (0x1)  (int)
  usb_device.num_interfaces = 1  (0x1)  (int)
  usb_device.device_class = 0  (0x0)  (int)
  usb_device.device_subclass = 0  (0x0)  (int)
  usb_device.device_protocol = 0  (0x0)  (int)
  usb_device.vendor_id = 1133  (0x46d)  (int)
  usb_device.product_id = 50184  (0xc408)  (int)
  usb_device.vendor = 'Logitech, Inc.'  (string)
  usb_device.product = 'Marble Mouse (4-button)'  (string)
  usb_device.device_revision_bcd = 5120  (0x1400)  (int)
  usb_device.max_power = 50  (0x32)  (int)
  usb_device.num_ports = 0  (0x0)  (int)
  usb_device.linux.device_number = 124  (0x7c)  (int)
  linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.7/usb4/4-6/4-6.2'  (string)
  usb_device.speed = 1.5  (double)
  info.parent = '/org/freedesktop/Hal/devices/usb_device_424_2512_noserial'  (string)
  usb_device.version = 1.1  (double)
  usb_device.is_self_powered = false  (bool)
  usb_device.can_wake_up = true  (bool)
  input.x11_options.EmulateWheel = 'true'  (string)
  input.x11_options.EmulateWheelTimeout = '200'  (string)
  usb_device.bus_number = 4  (0x4)  (int)
  input.x11_options.EmulateWheelButton = '8'  (string)
  linux.device_file = '/dev/bus/usb/004/124'  (string)
  input.x11_options.YAxisMapping = '4 5'  (string)
  input.x11_options.XAxisMapping = '6 7'  (string)
  input.x11_options.Emulate3Buttons = 'true'  (string)

You will notice that the info.product entries in the two config files match exactly. If you are not using a USB mouse my config won't work for you until you update it's info.product entry.

At some point my mouse's info.product entry changed from “Logitech USB Trackball” to “Marble Mouse (4-button)”. I'm not certain what caused it, but am pretty sure it was one of Ubuntu's updates.

Where can I find a definitive list of input.x11_options?

http://manpages.ubuntu.com/manpages/jaunty/man4/evdev.4.html

==

The following does the magic:

<merge key="input.x11_options.EmulateWheel" type="string">true</merge>
<merge key="input.x11_options.EmulateWheelButton" type="string">8</merge>

This causes button 8 (the small-left one) to toggle the interpretation of ball movements. The details of how are:

<merge key="input.x11_options.YAxisMapping" type="string">4 5</merge>
<merge key="input.x11_options.XAxisMapping" type="string">6 7</merge>

Which says movement in the vertical axis should be mapped to buttons 4 and 5, and horizontal movement to 6 and 7.

The following handily allows you to use button 8 as button 8, so long as you don't press it longer than 200ms:

<merge key="input.x11_options.EmulateWheelTimeout" type="string">200</merge>

Testing it all

You don't have to restart X every time you change your settings - you need only restart the HAL daemon and unplug/replug your mouse:

$ sudo invoke-rc.d hal restart
[sudo] password for meermanr:
 * Restarting Hardware abstraction layer hald
   ...done.     

You can then move your mouse over your xev window (see above) and check what the buttons do. You can also hold down the small-left button and move the ball to see button 4/5/6/7 events firing. Let go of the small-left button and no button event fires, but quickly tap it and you do get one - this is thanks to the 200ms timeout.

1)
I've not determined the purpose of this position, if indeed there is a default
2)
Note that this was generated _after_ I finished setting up my mouse, so it's “perfect” already