Reverse-engineering Kensington's secret PS/2 protocol to give a billiard ball trackball new life over USB
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.
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.
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.
| PS/2 Pin | Signal | Pro Micro Pin |
|---|---|---|
| 1 | Data | Pin 9 (PB5) |
| 5 | Clock | Pin 7 (PE6) |
| 4 | VCC | VCC (5V) |
| 3 | GND | GND |
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 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.
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.
In native mode, the ThinkingMouse repurposes three bits of the standard PS/2 byte 0:
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.
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.
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.
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.
Nothing about this project worked on the first try.
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.
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.
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.
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.
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.
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.
github.com/LXXero/ThinkHarder-USB
Includes the QMK keyboard definition, ThinkingMouse init sequence, driver patch, and build instructions.
FreeBSD sys/dev/atkbdc/psm.c — enable_kmouse() function
X.org xf86-input-mouse src/mouse.c — PROT_THINKPS2 parser
Linux drivers/input/mouse/psmouse-base.c — thinking_detect()