92 lines
3.1 KiB
Markdown
92 lines
3.1 KiB
Markdown
# 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 <closed>` | — |
|
||
| 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 <mode> <fn>` | 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`). `<fn>` 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 <mode> <fn> 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).
|