PLOOPY TRACKBALL NANO ON-THE-FLY DPI ADJUSTMENTS

Ploopy Trackball Nano is a tiny pointing device comprising of zero
buttons and one big red ball. It is powered by QMK firmware, which
means there is a rather friendly API available for customizing the
device's behavior.

Out the gates, the trackball's DPI settings are rather ripped. I
wanted to be able to arbitrarily control the trackball's DPI without
having to reflash firmware or adjust tracking speed via X. As the
story goes, the shoulders beneath me had already made this
possible. Aidan Gauland et. al made a keymap that makes the device
listen for 2-bit commands that are sent by quickly pulsing either the
caps or num lock keys of some other connected device. It's rather
clever, yes?


Step-by-step
----------------------------------------------------------------------
Flashing firmware
......................................................................
First, I had to flash the firmware to my Ploopy. That was easy. Before
moving on too quickly, let's take a gander over some implementation
details of the 2-bit command feature.

The device's `keymap.c' file overrides the QMK library's
`led_update_user' function, which is a callback invoked whenever a
'lock' key LED is updated (i.e.: caps lock, num lock). (This function
is defined in `quantum/led.c' on line 76 and invoked on line 80.) Lock
key LED states are the same across devices. So these states can be
used to communicate between devices.

In order to differentiate between important and arbitrary lock key LED
states, the program manages a command window, which is an interval of
25ms during which a sequence of two toggled LED states will be
interpreted as a command. The interval opens when a LED states changes
(and no other interval is open). The interval closes by way of
`defer_exec', a QMK library function that executes a given command
after an amount of time. Here, that means waiting 25ms and then
closing the interval and processing any received commands.


Adding udev rule
......................................................................
Next I wanted to get console events setup for the device so I could
troubleshoot possible issues. That was a bit more difficult. I needed
to add a `udev' rule. The rule is located at
`/etc/udev/rules.d/69-usb-ploopy.rules', containing:

,----
| SUBSYSTEMS=="usb", ATTRS{idVendor}=="5043", ATTRS{idProduct}=="54a3", MODE="0660"
`----

`udevadm control --reload-rules' will make this rule take immediate
effect.

The values contained in the rule were found using `lsusb' and/or
`udevadm info'. In my notes, I wrote that somehow I used `udevadm
monitor' to get the device node, which I then use with `udevadm info
--attribute-walk --name=' . I can't reproduce these
directions, but I found the same information with `udevadm info
--attribute-walk --name=/dev/input/mouse4'. Presumably, `mouse4' would
vary on another system. Anyways, through either method I extracted the
vendor ID (5043) and the product ID (54a3). The other values in the
`udev' rule are defaults (`0660' is `udev' default user/group
read/write permissions).


Adding macros to send commands
......................................................................
Sending commands to the trackball is done via macros run on another
device. I use my Reviung41.

First, custom keycodes are added:

,----
|   enum custom_keycodes {
|       PLPY_1,
|       PLPY_2
|   };
`----

These keycodes are used somewhere in the keymap:

,----
|   const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|     ...
|     [4] = LAYOUT(... PLPY_1, PLPY_2 ...),
|     ...
|   }
`----

And the macros are defined and sent using `SEND_STRING':

,----
|       bool process_record_user(uint16_t keycode, keyrecord_t *record) {
|         switch (keycode) {
|           case PLPY_1:
|             SEND_STRING(SS_TAP(X_CAPS_LOCK) SS_DELAY(25) SS_TAP(X_CAPS_LOCK));
|             return false;
|             break;
|           case PLPY_2:
|             SEND_STRING(SS_TAP(X_NUM_LOCK) SS_DELAY(25) SS_TAP(X_NUM_LOCK));
|             return false;
|            break;
|           }
|         return false;
|       }
`----


End notes
......................................................................
That's all, folks.