first commit
This commit is contained in:
168
internal/hhkb/proto.go
Normal file
168
internal/hhkb/proto.go
Normal file
@@ -0,0 +1,168 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user