111 lines
4.2 KiB
Go
111 lines
4.2 KiB
Go
package hhkb
|
|
|
|
import "strings"
|
|
|
|
// Variant is the physical key layout of a model.
|
|
type Variant int
|
|
|
|
const (
|
|
US Variant = iota
|
|
JIS
|
|
)
|
|
|
|
func (v Variant) String() string {
|
|
if v == JIS {
|
|
return "JIS"
|
|
}
|
|
return "US"
|
|
}
|
|
|
|
// Model is a keyboard this tool understands. Keys is its physical layout, in
|
|
// reading order; Hybrid marks the Hybrid/Type-S boards, which reserve a few Fn
|
|
// keys in firmware.
|
|
type Model struct {
|
|
Name string
|
|
Product uint16
|
|
Variant Variant
|
|
Hybrid bool
|
|
Keys []Key
|
|
}
|
|
|
|
// Models lists every supported model. The US/JIS split mirrors the official
|
|
// tool; the product id groups the Classic, Hybrid, and Hybrid Type-S families.
|
|
var Models = []Model{
|
|
{"HHKB Professional Classic (US)", 0x0020, US, false, ansiKeys},
|
|
{"HHKB Professional Classic (JIS)", 0x0020, JIS, false, jisKeys},
|
|
{"HHKB Professional Hybrid (US)", 0x0021, US, true, ansiKeys},
|
|
{"HHKB Professional Hybrid (JIS)", 0x0021, JIS, true, jisKeys},
|
|
{"HHKB Professional Hybrid Type-S (US)", 0x0022, US, true, ansiKeys},
|
|
{"HHKB Professional Hybrid Type-S (JIS)", 0x0022, JIS, true, jisKeys},
|
|
}
|
|
|
|
// MatchModel picks the model for a USB product id and reported type number.
|
|
// JIS boards carry "20" in their type number (PD-KB420, PD-KB820); the rest
|
|
// are US. Falls back to any model sharing the product id.
|
|
func MatchModel(product uint16, typeNumber string) (Model, bool) {
|
|
want := US
|
|
if strings.Contains(typeNumber, "20") {
|
|
want = JIS
|
|
}
|
|
for _, m := range Models {
|
|
if m.Product == product && m.Variant == want {
|
|
return m, true
|
|
}
|
|
}
|
|
for _, m := range Models {
|
|
if m.Product == product {
|
|
return m, true
|
|
}
|
|
}
|
|
return Model{}, false
|
|
}
|
|
|
|
// DetectModel identifies the connected keyboard from its product id and info.
|
|
func (d *Device) DetectModel() (Model, bool, error) {
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return Model{}, false, err
|
|
}
|
|
m, ok := MatchModel(d.product, info.TypeNumber)
|
|
return m, ok, nil
|
|
}
|
|
|
|
// ansiKeys is the US (60-key) layout. Geometry matches QMK's LAYOUT_60_hhkb:
|
|
// {Num, Def, Row, X, W}, in key units.
|
|
var ansiKeys = []Key{
|
|
{60, 0x29, 0, 0, 1}, {59, 0x1e, 0, 1, 1}, {58, 0x1f, 0, 2, 1}, {57, 0x20, 0, 3, 1}, {56, 0x21, 0, 4, 1},
|
|
{55, 0x22, 0, 5, 1}, {54, 0x23, 0, 6, 1}, {53, 0x24, 0, 7, 1}, {52, 0x25, 0, 8, 1}, {51, 0x26, 0, 9, 1},
|
|
{50, 0x27, 0, 10, 1}, {49, 0x2d, 0, 11, 1}, {48, 0x2e, 0, 12, 1}, {47, 0x31, 0, 13, 1}, {46, 0x35, 0, 14, 1},
|
|
{45, 0x2b, 1, 0, 1.5}, {44, 0x14, 1, 1.5, 1}, {43, 0x1a, 1, 2.5, 1}, {42, 0x08, 1, 3.5, 1}, {41, 0x15, 1, 4.5, 1},
|
|
{40, 0x17, 1, 5.5, 1}, {39, 0x1c, 1, 6.5, 1}, {38, 0x18, 1, 7.5, 1}, {37, 0x0c, 1, 8.5, 1}, {36, 0x12, 1, 9.5, 1},
|
|
{35, 0x13, 1, 10.5, 1}, {34, 0x2f, 1, 11.5, 1}, {33, 0x30, 1, 12.5, 1}, {32, 0x2a, 1, 13.5, 1.5},
|
|
{31, 0xe0, 2, 0, 1.75}, {30, 0x04, 2, 1.75, 1}, {29, 0x16, 2, 2.75, 1}, {28, 0x07, 2, 3.75, 1}, {27, 0x09, 2, 4.75, 1},
|
|
{26, 0x0a, 2, 5.75, 1}, {25, 0x0b, 2, 6.75, 1}, {24, 0x0d, 2, 7.75, 1}, {23, 0x0e, 2, 8.75, 1}, {22, 0x0f, 2, 9.75, 1},
|
|
{21, 0x33, 2, 10.75, 1}, {20, 0x34, 2, 11.75, 1}, {19, 0x28, 2, 12.75, 2.25},
|
|
{18, 0xe1, 3, 0, 2.25}, {17, 0x1d, 3, 2.25, 1}, {16, 0x1b, 3, 3.25, 1}, {15, 0x06, 3, 4.25, 1}, {14, 0x19, 3, 5.25, 1},
|
|
{13, 0x05, 3, 6.25, 1}, {12, 0x11, 3, 7.25, 1}, {11, 0x10, 3, 8.25, 1}, {10, 0x36, 3, 9.25, 1}, {9, 0x37, 3, 10.25, 1},
|
|
{8, 0x38, 3, 11.25, 1}, {7, 0xe5, 3, 12.25, 1.75}, {6, 0x01, 3, 14, 1},
|
|
// row 4 — key5/key1 are the narrow outer keys, key4/key2 the wide ones next to
|
|
// Space. Which sends Alt vs GUI(◇) depends on the keyboard's mode (HHK vs Mac),
|
|
// so the labels come from the live read; the Def values here are the HHK defaults.
|
|
{5, 0xe3, 4, 1.5, 1}, {4, 0xe2, 4, 2.5, 1.5}, {3, 0x2c, 4, 4, 7}, {2, 0xe6, 4, 11, 1.5}, {1, 0xe7, 4, 12.5, 1},
|
|
}
|
|
|
|
// jisKeys is the Japanese (69-key) layout. Key numbers and rows are fixed; the
|
|
// geometry is a uniform grid for now (exact JIS widths are a later refinement),
|
|
// and default scancodes are read from the device when connected.
|
|
var jisKeys = buildJIS()
|
|
|
|
func buildJIS() []Key {
|
|
rows := [...][2]int{{69, 55}, {54, 41}, {40, 28}, {27, 14}, {13, 1}}
|
|
var keys []Key
|
|
for row, span := range rows {
|
|
x := 0.0
|
|
for n := span[0]; n >= span[1]; n-- {
|
|
keys = append(keys, Key{Num: n, Row: row, X: x, W: 1})
|
|
x++
|
|
}
|
|
}
|
|
return keys
|
|
}
|