Wednesday, September 21, 2016

Using the Apple Magic Mouse with Ubuntu (16.0.4)

 ... Detailed description on how to use the Apple Magic Mouse on Ubuntu, including how to make the settings permanent ...

I love my Apple Magic Mouse.

When I tried using it on a Ubuntu system, it took me a while to get it working as I could not find an authoritative guide on how to properly install it. I just found a bunch of posts that gave partial information. So I decided to write this blog to hopefully help anyone out there that tries to do the same thing.

I have a Magic Mouse V1, so hopefully the same applies to the more recent version as well. This blog really covers two things:
  1. How to manually play with the settings so that you may find the right ones for you.
  2. Describe the changes you have to make to automate the loading of the settings and make them permanent.

Before starting, it is important to understand that the settings of the mouse are distributed into two areas:
  1. The Magic Mouse Kernel Module, and
  2. The mouse driver
I will cover both.

Manual Set Up

  1. Ubuntu 16 has all the drivers it needs, so there is no need to do anything complicated.  Connect the Magic Mouse as you would any other Bluetooth device. If you need help, see this link.
  2. Once the Magic Mouse is connected, you can see the current Kernel Module settings by executing the following command:
    $ systool -avm hid_magicmouse
    Module = "hid_magicmouse"
    [. . .]
    Parameters:
      emulate_3button = "Y"
      emulate_scroll_wheel= "Y"
      report_undeciphered = "N"
      scroll_acceleration = "N"
      scroll_speed = "35"
    [. . .]
    Note that if you are missing a package to run systool, Ubuntu will give you instructions on the package you need to load.
  3. We can now update the settings in the hid_magicmouse Kernel Module.  Here is a brief description of each:
    • emulate_3button = enables the third button when your finger presses the middle.  I find this finicky, so I disable it.  In MacOS only the left/right buttons are used. (boolean)
    • emulate_scroll_wheel = the module interprets horizontal and vertical swipes as wheel ticks.  The vertical function works fine by emulating both press and release for each tick, but the horizontal one only emulates presses and not releases which causes problems in the back and forth swipes in Chrome, so I disable it in the driver (see more in the driver section). (boolean)
    • report_undeciphered = no need to turn it on. (boolean)
    • scroll_acceleration = the faster you move your finger, the more ticks per distance it generates. (boolean)
    • scroll_speed = this tells it how many ticks to generate when you swipe your finger a given distance; I like it to be sensitive, so I set it to 55. (0 - 63)
    To update the settings, the easiest way is to unload the module and reload it with the new settings.  You can do this at the terminal prompt as follows:
    $ sudo rmmod hid_magicmouse
    $ sudo modprobe hid_magicmouse emulate_3button=0 scroll_acceleration=1 scroll_speed=55
    You can check that the parameters have taken hold by running the above systool command again.
  4. Before we can play with the mouse driver settings, we have to figure out the Product name.  We will also get the assigned ID which will be used further down.  Once the mouse is connected, this information can be found by executing 'xinput list'.
    $ xinput list
    ⎡ Virtual core pointer id=2 [master pointer (3)]
    ⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
    ⎜ ↳ USB Optical Mouse id=10 [slave pointer (2)]
    ⎜ ↳ MCE IR Keyboard/Mouse (nuvoton-cir) id=13 [slave pointer (2)]
    ⎜ ↳ Steve’s Mouse id=15 [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)]
    ↳ Power Button id=8 [slave keyboard (3)]
    ↳ Sleep Button id=9 [slave keyboard (3)]
    ↳ AT Raw Set 2 keyboard id=11 [slave keyboard (3)]
    ↳ Nuvoton w836x7hg Infrared Remote Transceiver id=12 [slave keyboard (3)]
    ↳ Steve’s Keyboard id=14 [slave keyboard (3)]
    In my case above, the Product name is 'Steve's Mouse', and the id is '15'.
  5. Excellent, now we can look at the driver settings and change them as desired.  First, lets take a look at the current settings:
    $ xinput list-props "Steve’s Mouse"
    Device 'Steve’s Mouse':
      Device Enabled (133): 1
      Coordinate Transformation Matrix (135): 1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000
      Device Accel Profile (257): 0
      Device Accel Constant Deceleration (258): 5.000000
      Device Accel Adaptive Deceleration (259): 1.000000
      Device Accel Velocity Scaling (260): 8.000000
      Device Product ID (251): 1452, 781
      Device Node (252): "/dev/input/event14"
      Evdev Axis Inversion (261): 0, 0
      Evdev Axes Swap (263): 0
      Axis Labels (264): "Rel X" (143), "Rel Y" (144), "Rel Horiz Wheel" (255), "Rel Vert Wheel" (256)
      Button Labels (265): "Button Left" (136), "Button Unknown" (254), "Button Right" (138), "Button Wheel Up" (139), "Button Wheel Down" (140), "Button Horiz Wheel Left" (141), "Button Horiz Wheel Right" (142)
    [. . .]
    The more useful settings are as follows:
    • Device Accel Profile = This selects the algorithm used for accelerating the cursor in relation to the speed of the mouse. It can take a number of settings, check this link for the Xorg man pages.
    • Device Accel Constant Deceleration = This decelerates the mouse by a given factor, the higher the number, the slower the cursor will move.
    • Device Accel Velocity Scaling = This one is profile dependent, and it scales the acceleration.
    • Device Accel Adaptive Deceleration = Allows the acceleration profile to actually decelerate the pointer by the given factor. Adaptive deceleration is a good tool allowing precise pointing, while maintaining pointer speed in general.
    • set-button-map = This allows you to remap the buttons. The first number is the assigned ID that we recovered earlier (15 in this case).  For the rest, the positional order is as outlined in the 'Button Labels' string above, starting with the number 1 and ending with 7. A value of zero disables the relative button (note that we have disabled the middle button at the kernel module, so no need to do it here). For Natural scrolling, I like to reverse the left/right scroll, as well as the up/down scroll; however, due to the left/right issue already mentioned (only emulates presses and not releases), I like to disable the left/right scroll.  If it was not disabled, below instead of '0 0' at the end it would be '7 6'
    You can read more about all the available settings at the above link. I have settled on these changes from the default to give me what I like (remember to change the items in red to match your setup):
    $ xinput set-prop "Steve’s Mouse" "Device Accel Constant Deceleration" 5
    $ xinput set-prop "Steve’s Mouse" "Device Accel Velocity Scaling" 8
    $ xinput set-button-map 15 1 2 3 5 4 0 0
    You can execute the xinput list-props command again to check that the commands have taken.
  6. That's it . . . now you can experiment with different values and find ones that you like.  Feel free to share in the comments different settings that you think work better . . . describe why they are better so that other people can try them to.

Automatic Set Up

Now that you found the settings you want, it is time to make them permanent.  This is the part that I had the toughest time to get right.  Basically you have to set up configuration files for both the kernel module as well as the driver that gets loaded every time the mouse gets attached.
  1. For setting up the Kernel module, you have to add the following one liner configuration file, call it 'magicmouse.conf' and put it in the '/etc/modprobe.d/' directory:
    options hid_magicmouse emulate_3button=0 scroll_acceleration=1 scroll_speed=55
  2. For the driver settings, you have to add the following configuration file, call it '50-magicmouse.conf' and put it in the '/usr/share/X11/xorg.conf.d/' directory (note that the leading spaces are single tabs, but I don't think that it matters . . . also, don't forget to update the red item with the name of your mouse, per instructions in the previous section):
    Section "InputClass"
      Identifier "Apple Magic Mouse" # This can be anything
      MatchIsPointer "on"
      MatchDevicePath "/dev/input/event*"
      MatchProduct "Steve’s Mouse" # Product name from 'xinput list'
      Driver "evdev" # From '/var/log/Xorg.0.log' after mouse connected
      Option "AccelerationProfile" "0"
      Option "ConstantDeceleration" "5"
      Option "AdaptiveDeceleration" "1"
      Option "VelocityScale" "8"
      Option "ButtonMapping" "1 2 3 5 4 0 0"
    EndSection
    See below on how I derived the driver.
  3. Now it is time to reboot and see if it all works as expected.  Before you do that, make sure that there are no spelling mistakes in all the keywords as otherwise your system will fail to boot (purple screen). If this happens, start the computer in safe mode by holding 'left-shift' during boot-up or repeatedly pressing the 'esc' key during boot up; once you are at the prompt, look at the most recent Xorg log, such as '/var/log/Xorg.0.log' and check for the detected error and fix it ... or remove the '50-magicmouse.conf' file.

Some Additional Notes

Hopefully I have done a good job at explaining this. In case you run into some issues, here are some additional notes that may help you debug them:
  • Once the mouse is connected, you can find the kernel module by executing 'lsmod | grep mouse'.  This will yield the module name of 'hid_magicmouse'.
  • The driver is loaded every time that the mouse attaches via Bluetooth. You can gleam a lot of information from the logs of when it attaches and how the settings are assigned:
    $ more /var/log/Xorg.0.log
    [. . .]
    [ 239.508] (II) config/udev: Adding input device Steve’s Mouse (/dev/input/mouse2)
    [ 239.508] (II) No input driver specified, ignoring this device.
    [ 239.508] (II) This device may have been added with another device file.
    [ 239.540] (II) config/udev: Adding input device Steve’s Mouse (/dev/input/event14)
    [ 239.540] (**) Steve’s Mouse: Applying InputClass "evdev pointer catchall"
    [ 239.540] (**) Steve’s Mouse: Applying InputClass "Apple Magic Mouse"
    [ 239.540] (II) Using input driver 'evdev' for 'Steve’s Mouse'
    [ 239.540] (**) Steve’s Mouse: always reports core events
    [ 239.540] (**) evdev: Steve’s Mouse: Device: "/dev/input/event14"
    [. . .]
    As you can see, it uses the 'evdev' driver.
  • To look at how the mouse is generating events, run the following command and move the mouse to the displayed square and perform the various activities. This is how I noticed that the left/right scroll only registers presses but no releases, I am not sure if this is intentional but it did cause strange behavior in Chrome. It should generate back and forward browsing commands, but instead it cause the window to scroll and display the other page, but it does not activate it until something times out.
    $ xev | grep button
      state 0x0, button 1, same_screen YES
      state 0x100, button 1, same_screen YES
      state 0x0, button 3, same_screen YES
      state 0x400, button 3, same_screen YES
      state 0x0, button 5, same_screen YES
      state 0x1000, button 5, same_screen YES
      state 0x0, button 5, same_screen YES
      state 0x1000, button 5, same_screen YES
That's it for now . . . good luck.  Feel free to leave a comment if you have any additional suggestion.