first commit

This commit is contained in:
2026-05-27 18:50:35 +09:00
commit d80de116c6
17 changed files with 1381 additions and 0 deletions

BIN
doc/base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
doc/fn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

91
doc/protocol.md Normal file
View File

@@ -0,0 +1,91 @@
# 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, 224231) 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).