# HHKB Professional vendor HID protocol Reverse-engineered notes for the configuration channel used by PFU's Keymap Tool, as implemented in `internal/hhkb`. Verified against the HHKB Professional Classic (`04fe:0020`); the Hybrid (`0021`/`0022`) is the same. ## Transport The keyboard exposes three USB-HID interfaces. Interface 2 is vendor-defined and carries the config protocol. Its report descriptor begins: ``` 06 00 ff Usage Page (Vendor-Defined 0xFF00) 09 01 Usage 1 a1 01 Collection (Application) 09 02 ... 75 08 95 40 81 02 Input report, 64 bytes 09 03 ... 75 08 95 40 91 02 Output report, 64 bytes c0 ``` We find it by scanning `/sys/class/hidraw/*`: the node whose `device/uevent` contains HID id `…04FE:00000020` and whose `device/report_descriptor` starts with `06 00 ff`. Reports are 64 bytes with no report id. A `write(2)` to the hidraw node takes a leading `0x00` report-number byte (65 bytes total); the kernel strips it. ## Framing Request (64 bytes): ``` byte 0 0xAA byte 1 0xAA byte 2 command byte 3+ arguments ``` Reply (64 bytes): ``` byte 0 0x55 acknowledgement byte 1 0x55 byte 2+ status / payload (data fields start at byte 6) ``` ## Commands | id | name | request args | reply payload (from byte 6) | |-----|-----------------|-----------------------------|------------------------------------| | 1 | notify app | `00 01 ` | — | | 2 | get info | — | type[20] rev[4] serial[16] fw… | | 3 | factory reset | — | header `55 55 03 00` on success | | 4 | confirm keymap | — | — | | 5 | get DIP | — | 6 bytes, one per switch | | 6 | get mode | — | 1 byte: 0 HHK, 1 Mac, 2 Lite, 3 Secret | | 7 | reset DIP | `00 01` | — | | 134 | write keymap | see below | — | | 135 | get keymap | `00 02 ` | streamed, see below | Firmware commands (208, 224–231) exist but are deliberately unimplemented here. ## Keymap layout A layer is 128 bytes: `layer[keyNumber] = scancode`. Key numbers and the US (ANSI) geometry live in `keymap.go` (`ANSI`). `` selects base (0) or Fn (1). ### Read (command 135) One request, then three input reports tiling the layer (data at byte 6 of each): ``` report 1 -> layer[0:58] report 2 -> layer[58:116] report 3 -> layer[116:128] ``` ### Write (command 134) Three requests. The pair after the command id marks offset/length; the first pass also carries mode and fn before the key bytes: ``` AA AA 86 41 3B layer[0:57] AA AA 86 82 3B layer[57:116] AA AA 86 C3 0C layer[116:128] ``` A full remap is: notify-app(open) → get mode → read layer → set the byte → write (3 passes) → confirm (4) → reset DIP (7) → notify-app(closed). Reversible via factory reset (3).