169 lines
4.0 KiB
Go
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))
|
|
}
|