4

ThinkHarder-USB

Reverse-engineering Kensington's secret PS/2 protocol to give a billiard ball trackball new life over USB

Kensington Expert Mouse trackball with red billiard ball
The Kensington Expert Mouse — not actually a billiard ball, but close enough that people steal cue balls for them

The Kensington Expert Mouse is a PS/2 trackball with a 55mm phenolic resin ball that looks and feels like something you'd find on a pool table. Four symmetrical buttons surround the ball. It's heavy, precise, and built like it predates the concept of planned obsolescence.

It also has a secret: Kensington implemented a proprietary PS/2 protocol extension called ThinkingMouse to support buttons 3 and 4. Without the correct initialization sequence, those buttons silently mirror buttons 1 and 2. The protocol was never publicly documented. Kensington took it to the grave.

The Problem

Modern computers don't have PS/2 ports. Active USB-to-PS/2 adapters don't speak the ThinkingMouse protocol—they only handle standard 3-button PS/2 mice. There is no off-the-shelf solution to use this trackball with all 4 buttons on a modern machine.

The Build

A Pro Micro (ATmega32U4) running QMK Firmware acts as a PS/2-to-USB bridge. The Pro Micro speaks PS/2 on one side (interrupt-driven, hardware-timed) and USB HID on the other. QMK handles the USB mouse report generation.

Full setup showing trackball, Pro Micro, and crossed-out USB-PS/2 adapter
The active USB-PS/2 adapter (crossed out) doesn't speak ThinkingMouse
Pro Micro on perfboard, front side with screw terminal and reset button
Pro Micro on perfboard with screw terminal and reset button
Angled view of the converter board showing wiring
4.7k pull-ups, screw terminal for the PS/2 cable, and a little red jellybean reset button
Back side of the perfboard showing solder traces
The back side — hand-routed solder traces

Wiring

PS/2 PinSignalPro Micro Pin
1DataPin 9 (PB5)
5ClockPin 7 (PE6)
4VCCVCC (5V)
3GNDGND

4.7k pull-up resistors from Clock to VCC and Data to VCC. PS/2 is an open-collector bus—the host must provide pull-ups. The ATmega32U4's internal pull-ups (~20-50k) are too weak and cause framing errors.

The ThinkingMouse Protocol

The ThinkingMouse protocol was reverse-engineered from the FreeBSD psm.c kernel driver and the X.org xf86-input-mouse driver source—the only two places on earth this protocol is documented in code.

Detection & Init

The magic sequence that wakes up the 4th button:

1. Set sample rate to 10      → triggers native mode
2. Get device ID               → returns 0x02 (normally 0x00)
3. Set resolution LOW
4. Send magic rate sequence:    {20, 60, 40, 20, 20, 60, 40, 20, 20}
5. Re-enable data reporting

Without step 4, buttons 3 and 4 silently mirror buttons 1 and 2. The mouse lies to you and you'd never know.

Byte 0 Reinterpretation

In native mode, the ThinkingMouse repurposes three bits of the standard PS/2 byte 0:

bit 7
bit 6
bit 5
bit 4
bit 3
bit 2
bit 1
bit 0
sync
was Y ovf
X MSB
was X ovf
Y sign
X sign
btn 4
was sync
btn 3
btn 2
btn 1

Bit 7 becomes a sync marker (always 1), used to detect dropped bytes. Bit 6 carries the MSB of X movement data—the mouse only sends 7 bits of X in byte 1, hiding the 8th bit here. Bit 3 gains button 4 where the standard PS/2 sync bit used to live.

The X MSB Kludge

This is the weirdest part of the protocol. The mouse splits X movement across two locations: 7 bits in byte 1 and 1 bit in byte 0. Without reconstructing the MSB, moving left produces garbage values. FreeBSD calls this the "Kensington kludge" in the source comments.

Button Map

4
Top-Left
Button 4
3
Top-Right
Middle / Scroll
1
Bottom-Left
Left Click
2
Bottom-Right
Right Click

Top-left and top-right are swapped in firmware so the top-right button (more natural reach) acts as middle-click and scroll. Hold it and roll the ball to scroll.

QMK Driver Patches

The stock QMK PS/2 mouse driver only supports 3-button standard PS/2 mice. ThinkHarder-USB patches the driver with:

PS2_MOUSE_THINKING — Full ThinkingMouse protocol handling: sync checking against bit 7, X MSB reconstruction from bit 6, overflow bit reinterpretation so QMK doesn't clip all Y movement to ±127.

PS2_MOUSE_SKIP_RESET — Skips the PS/2 reset command on init. The mouse already did BAT on power-up, and stale BAT bytes in the ring buffer cause protocol desync and phantom right-clicks.

PS2_MOUSE_THINKING_BTN_SWAP — Swaps two button bits for physical remapping without touching the keymap.

Sync recovery flush — When a desync is detected (byte 0 missing bit 7), the driver now flushes remaining bytes from the broken packet instead of discarding one byte at a time. This was the fix for phantom button releases during click-and-drag: a parity error dropping byte 0 would let byte 2 (Y movement with bit 7 set for negative values) pass the sync check as a fake byte 0, producing a phantom button release.

The Debugging Journey

Nothing about this project worked on the first try.

Device resets on connect

The Pro Micro would reset every time the PS/2 cable was plugged in. Suspected power draw, interrupt storms, missing pull-ups. Turned out to be bad wiring. Rewired with proper headers and it stopped.

Phantom right-click on startup

The mouse's power-on self-test (BAT) sends 0xAA. This byte sat in the ring buffer and got interpreted as a mouse packet: 0xAA & 0x07 = 0x02 = right button. Fixed by flushing stale bytes before enabling reporting.

Buttons 3+4 mirror buttons 1+2

Without the ThinkingMouse init sequence, the mouse only reports 2-bit button state. Implemented the magic sample rate sequence from FreeBSD's enable_kmouse(). All four buttons now report distinct values.

All Y movement clips to max speed

ThinkingMouse bit 7 is always 1 (sync), but QMK interprets it as "Y overflow" and clips Y to ±127. Fixed by stripping bits 6 and 7 before QMK processes them.

Moving left jerks to the edge of the screen

The mouse only sends 7 bits of X in byte 1. Negative X values lose their MSB, turning -5 into +123, which QMK then clips to -127. The "Kensington kludge": reconstruct the MSB from byte 0 bit 6.

Double-click during click-and-drag

The real beast. A parity error on the PS/2 bus would silently drop byte 0. The old sync check discarded one byte at a time, but bytes 1 and 2 from the broken packet stayed in the buffer. Byte 2 (Y data) with a negative value has bit 7 set—passing the sync check as a fake byte 0 with garbage button state. One frame of "buttons = 0" mid-drag = the OS sees a double-click. Fixed by flushing the entire broken packet on sync failure instead of discarding one byte.

Source

github.com/LXXero/ThinkHarder-USB

Includes the QMK keyboard definition, ThinkingMouse init sequence, driver patch, and build instructions.

Protocol Sources

FreeBSD sys/dev/atkbdc/psm.cenable_kmouse() function
X.org xf86-input-mouse src/mouse.cPROT_THINKPS2 parser
Linux drivers/input/mouse/psmouse-base.cthinking_detect()