package hhkb import ( "bytes" "fmt" "strings" ) // Command ids — byte 3 of every request. const ( cmdNotifyApp = 1 cmdGetInfo = 2 cmdFactoryReset = 3 cmdConfirmKeymap = 4 cmdGetMode = 6 cmdResetDIP = 7 cmdWriteKeymap = 134 cmdGetKeymap = 135 ) // Info describes the connected keyboard. type Info struct { TypeNumber string Revision string Serial string AppFirmware string BootFirmware string } // Info reads the keyboard's identification. func (d *Device) Info() (Info, error) { r, err := d.do(cmdGetInfo) if err != nil { return Info{}, err } p := r[6:] return Info{ TypeNumber: cstr(p[0:20]), Revision: cstr(p[20:24]), Serial: cstr(p[24:40]), AppFirmware: fmt.Sprintf("%X", p[40:48]), BootFirmware: fmt.Sprintf("%X", p[48:56]), }, nil } // Mode reads the active keyboard mode (set by DIP switches). func (d *Device) Mode() (Mode, error) { r, err := d.do(cmdGetMode) if err != nil { return 0, err } return Mode(r[6]), nil } // ReadLayer reads one full key layer: the base layer, or the Fn layer when // fn is true. The reply arrives as three reports that tile the 128-byte layer. func (d *Device) ReadLayer(fn bool) (Layer, error) { mode, err := d.Mode() if err != nil { return Layer{}, err } return d.readLayer(mode, fn) } func (d *Device) readLayer(mode Mode, fn bool) (Layer, error) { var lay Layer if err := d.send(request(cmdGetKeymap, 0, 2, byte(mode), boolByte(fn))); err != nil { return lay, err } for _, seg := range [...]struct{ off, n int }{{0, 58}, {58, 58}, {116, 12}} { r, err := d.recv() if err != nil { return lay, err } copy(lay[seg.off:seg.off+seg.n], r[6:6+seg.n]) } return lay, nil } // writeLayer uploads a full layer in the three passes the firmware expects. // The leading byte pairs (65,59 / 130,59 / 195,12) are offset and length // markers the controller verifies; the data window is mode+fn followed by the // 128 key bytes. func (d *Device) writeLayer(mode Mode, fn bool, lay Layer) error { passes := []struct { mark [2]byte head []byte data []byte }{ {[2]byte{65, 59}, []byte{byte(mode), boolByte(fn)}, lay[0:57]}, {[2]byte{130, 59}, nil, lay[57:116]}, {[2]byte{195, 12}, nil, lay[116:128]}, } for _, p := range passes { args := append([]byte{p.mark[0], p.mark[1]}, p.head...) args = append(args, p.data...) if _, err := d.do(cmdWriteKeymap, args...); err != nil { return err } } return nil } // Remap assigns scancode to a key (by its key number) on the base or Fn layer. // It mirrors the official tool's sequence: announce the tool, edit the layer, // write it back, commit, and reset DIP state. The change is reversible with // Reset or by remapping again. func (d *Device) Remap(keyNum int, scancode byte, fn bool) error { if keyNum < 1 || keyNum >= LayerLen { return fmt.Errorf("key number %d out of range", keyNum) } if err := d.notifyApp(true); err != nil { return err } defer d.notifyApp(false) mode, err := d.Mode() if err != nil { return err } lay, err := d.readLayer(mode, fn) if err != nil { return err } lay[keyNum] = scancode if err := d.writeLayer(mode, fn, lay); err != nil { return err } if _, err := d.do(cmdConfirmKeymap); err != nil { return err } _, err = d.do(cmdResetDIP, 0, 1) return err } // Reset restores the factory default keymap. func (d *Device) Reset() error { r, err := d.do(cmdFactoryReset) if err != nil { return err } if !bytes.HasPrefix(r, []byte{0x55, 0x55, 0x03, 0x00}) { return fmt.Errorf("unexpected factory-reset reply: % X", r[:6]) } return nil } // notifyApp tells the keyboard whether the configuration tool is active. func (d *Device) notifyApp(open bool) error { _, err := d.do(cmdNotifyApp, 0, 1, boolByte(!open)) return err } func boolByte(b bool) byte { if b { return 1 } return 0 } // cstr converts a fixed-width, NUL-padded field into a Go string. func cstr(b []byte) string { if i := strings.IndexByte(string(b), 0); i >= 0 { b = b[:i] } return strings.TrimSpace(string(b)) }