Elecom Trackball, IOGear GCS1102 KVM Switch, Ubuntu 18

My Logitech Trackman thumb trackball is so old it has a PS/2 plug. Its buttons are finally starting to fail and I needed to replace it. But Logitech doesn’t make wired trackballs anymore. I must have wired because (1) I use an IOGear GCS1102 KVM Switch, and (2) I prefer the instantaneous smoother response of wired devices.

I Googled around and discovered the Elecom M-XT3URBK, worth a try. At first it didn’t work. On a whim, I checked my GCS1102 (open a text editor, hit Ctrl-F12, press F4, and it prints its settings) and found it was in mouse emulation mode. I disabled mouse emulation mode (Ctrl-F12, press m) and the Elecom worked perfectly. Xev detected all buttons except the far right, which I don’t need anyway.

Good stuff:

  • Tracks at least as well (smooth, fast, accurate) as the Logitech Marble
  • Switch-adjustable DPI (low is still pretty high and works best)
  • Buttons
    • Standard L and R
    • Scroll wheel clicks like a button (without occasionally moving, like the Logitech did)
    • Scroll wheel clicks L and R to scroll horizontally
    • Two small buttons to the L of the L button
  • It’s comfortable to use
  • Elecom makes a mirror image left-handed version
  • Price: only $40 – half Logitech’s prices!

Bad stuff:

  • The cable is a bit on the short side, but still long enough I didn’t need an extension.
  • It’s a bit small for my large hands

So far, no problem. It works, scroll wheel and all buttons, without any drivers. Just plug and play. But there’s more…

SENSITIVITY and ADJUSTMENT

The Elecom is so much more sensitive than my old Logitech, I had to turn down the settings. But with the GCS1102’s mouse emulation disabled, every time I switch, each computer sees the mouse being un-plugged and re-plugged. When this happens, Ubuntu doesn’t restore the mouse settings. When I switch away and back, the mouse is back to its hyper-sensitive default setting.

I wrote a shell script /apps/bin/setMouse.sh that uses xinput to set the mouse:

#!/usr/bin/env bash
# The UI for setting mouse track speed in Ubuntu 18 is broken.
# Do it manually here
# NOTE: as of June 2018, the mouse was device 12.
# You can check this with: xinput --list --short
# To show device settings: xinput --list-props 12

echo "`date`, $USER, $DISPLAY, $XAUTHORITY" >> /apps/bin/mouse.log

if [ -z "$1" ]
then
    a="0.1"
else
    a="$1"
fi
if [ -z "$2" ]
then
    m="0.4"
else
    m="$2"
fi
if [ -z "$3" ]
then
    d="`xinput | grep Mouse | grep pointer | cut -f 2 | cut -c4-5`"
else
    d="$3"
fi

aprop=`xinput --list-props $d | grep 'Accel Speed (' | cut -f2 | cut -c23-25`
if ! [ -z "$aprop" ]
then
    xinput set-prop $d $aprop "$a"
fi
mprop=`xinput --list-props $d | grep 'Coordinate Transform' | cut -f 2 | cut -c35-37`
if ! [ -z "$mprop" ]
then
    xinput set-prop $d $mprop "$m", 0.0, 0.0, 0.0, "$m", 0.0, 0.0, 0.0, 1.0
fi

echo "`date` Mouse set up $a $m $d" >> /apps/bin/mouse.log
xinput --list-props "$d" >> /apps/bin/mouse.log

But I don’t want to have to run this script every time I switch the GCS1102. The Linux Udev system should do the job!

UDEV RULES

First, I needed a Udev rule to trigger my script. The rule must detect the USB device being plugged in, so I need its vendor and product IDs. To get these, run lsusb:

Bus 003 Device 122: ID 056e:00fb Elecom Co., Ltd

Now add a Udev rule. Mine was /etc/udev/rules.d/61-elecom-mouse.rules:

SUBSYSTEM=="usb", ATTR{idVendor}=="056e", ATTR{idProduct}=="00fb", ACTION=="add", RUN+="/apps/bin/setMouse.sh"

But it didn’t work. The script was running (I could see the log entry it creates). But the mouse sensitivity wasn’t being set.

DELAYED EXECUTION

My first thought was that the Udev event was firing before the device was registered and ready to be configured. So I added a sleep to the command. But, you can’t sleep while Udev is running your command. So I had Udev run a script that returns immediately, while sleeping a few seconds then running setMouse. A command like this:

(sleep 3 ; /apps/bin/setMouse.sh) &

But that didn’t work. It worked from the command line, but when run from Udev, the background was ignored and Udev waited for the entire time. That’s bad for 2 reasons: (1) hangs Udev for 3 seconds, (2) Udev is still waiting, so the sleep is pointless because the device won’t be ready until Udev is done.

In short, Udev is too smart by half. I needed a way to trick Udev into returning immediately, while my script pauses a few seconds then runs. I found this with the at command. First, install it:

sudo apt install at

Then, make the Udev rule look like this:

SUBSYSTEM=="usb", ATTR{idVendor}=="056e", ATTR{idProduct}=="00fb", ACTION=="add", RUN+="/usr/bin/at -M -f /apps/bin/setMouseElecom.sh now"

This tells at to trigger the script to run immediately, but at returns to Udev immediately. The script doesn’t run under Udev but under at. The setMouseElecom.sh script looks like this:

#!/usr/bin/env sh
# Called by Udev when Elecom trackball is plugged in
sleep 2
/apps/bin/setMouse.sh 0.1 0.4

Note: at can only run sh scripts, not bash.

But, it still didn’t work. The script ran, but its xinput commands always failed.

XINPUT AND XSESSION

I guessed it might be that at, running as root, didn’t have permission for my X session. I had to somehow make it join my X session before the xinput commands would work. I made a script called joinXandRun.sh:

#!/usr/bin/env bash

# Get the number of the active terminal (requires root)
TTY=$((`fgconsole`-7))

# Set env vars needed to simulate the user
export DISPLAY=:${TTY}.0
USER=`who -s | grep "(:$TTY)" | cut -d' ' -f1`
export XAUTHORITY=/home/$USER/.Xauthority

echo "`date`, $USER, $DISPLAY, $XAUTHORITY: $1" >> /apps/bin/joinXandRun.log
sudo -H -u $USER bash -c "$1 $2 $3 $4 $5 $6 $7 $8 $9"

This script finds the active X console, and its user and display. Then it sets the critical DISPLAY  and XAUTHORITY environment vars to “join” that X session. Then it runs another script passed into it, as the given user.

Then I made setMouseElecom.sh call this script:

#!/usr/bin/env sh
# Called by Udev when Elecom trackball is plugged in
sleep 2
/apps/bin/joinXandRun.sh /apps/bin/setMouse.sh 0.1 0.4

Guess what — this worked!

The really cool thing about this is that the Udev rule is device-specific, and setMouse.sh takes parameters. I could set this up to auto-detect and configure several different kinds of mice or trackballs. Each would have a different Udev rule, matching different vendor and device IDs, firing different scripts that pass different mouse setting params to setMouse.sh. This will be useful for my laptop, which uses the Elecom at work but another Logitech Trackman at home.

It turns out all the pieces are essential. Lessons learned:

  • When Udev triggers the add event, the device being added cannot yet be configured. So if your Udev script configures the device, it has to:
    • Return immediately to Udev, so Udev can finish its processing
    • Meanwhile, trigger its config processing to run after a few seconds
  • To configure devices that are part of the X session, you need to “join” the session. Otherwise, the X system won’t let your code configure the device.

PS: none of this is specific to Ubuntu 18. It should work on any recent Linux distro.