Files
hkkb-layout-web/internal/hhkb/proto.go
2026-05-27 18:30:14 +09:00

169 lines
4.0 KiB
Go

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))
}