|
@@ -5,24 +5,102 @@ import (
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"os"
|
|
"os"
|
|
|
"path/filepath"
|
|
"path/filepath"
|
|
|
|
|
+ "sort"
|
|
|
|
|
|
|
|
"netdome.biz/paul/impact-80/internal/hid"
|
|
"netdome.biz/paul/impact-80/internal/hid"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// Keyboard represents a keyboard definition from keyboards.json.
|
|
|
|
|
|
|
+// Keyboard is a metadata record from keyboards.json.
|
|
|
|
|
+// It names a known keyboard, but it is not required for discovery: unknown
|
|
|
|
|
+// QMK keyboards are usable too. Fields the tool does not read are ignored,
|
|
|
|
|
+// so existing entries may keep carrying them.
|
|
|
type Keyboard struct {
|
|
type Keyboard struct {
|
|
|
- Name string `json:"name"`
|
|
|
|
|
- VendorID uint16 `json:"vendorId"`
|
|
|
|
|
- ProductID uint16 `json:"productId"`
|
|
|
|
|
- Protocol string `json:"protocol"`
|
|
|
|
|
- VIAVersion int `json:"viaVersion"`
|
|
|
|
|
- LEDLayout string `json:"ledLayout"`
|
|
|
|
|
|
|
+ Name string `json:"name"`
|
|
|
|
|
+ VendorID uint16 `json:"vendorId"`
|
|
|
|
|
+ ProductID uint16 `json:"productId"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Device represents a connected keyboard device.
|
|
|
|
|
|
|
+// Device is a connected QMK Raw HID keyboard.
|
|
|
|
|
+// Index is 1-based and is what --device accepts.
|
|
|
type Device struct {
|
|
type Device struct {
|
|
|
- Keyboard
|
|
|
|
|
- Path string
|
|
|
|
|
|
|
+ Index int `json:"index"`
|
|
|
|
|
+ Path string `json:"path"`
|
|
|
|
|
+ VendorID uint16 `json:"vendorId"`
|
|
|
|
|
+ ProductID uint16 `json:"productId"`
|
|
|
|
|
+ Name string `json:"name,omitempty"`
|
|
|
|
|
+ Known bool `json:"known"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// DiscoverAll returns every connected QMK Raw HID keyboard, ordered
|
|
|
|
|
+// deterministically and numbered from 1. Keyboards absent from
|
|
|
|
|
+// keyboards.json are included with Known false and no Name.
|
|
|
|
|
+func DiscoverAll() ([]Device, error) {
|
|
|
|
|
+ infos, err := hid.DiscoverAll()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("discover qmk devices: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ known, err := knownKeyboards()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ // Missing or malformed keyboards.json only costs us names, not
|
|
|
|
|
+ // the ability to drive the keyboard.
|
|
|
|
|
+ known = nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ devices := make([]Device, 0, len(infos))
|
|
|
|
|
+ for _, info := range infos {
|
|
|
|
|
+ kb, ok := known[deviceKey(info.VendorID, info.ProductID)]
|
|
|
|
|
+ devices = append(devices, Device{
|
|
|
|
|
+ Path: info.Path,
|
|
|
|
|
+ VendorID: info.VendorID,
|
|
|
|
|
+ ProductID: info.ProductID,
|
|
|
|
|
+ Name: kb.Name,
|
|
|
|
|
+ Known: ok,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return indexDevices(devices), nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// indexDevices sorts devices deterministically and assigns 1-based indexes.
|
|
|
|
|
+// The input slice is left untouched.
|
|
|
|
|
+func indexDevices(devices []Device) []Device {
|
|
|
|
|
+ sorted := make([]Device, len(devices))
|
|
|
|
|
+ copy(sorted, devices)
|
|
|
|
|
+
|
|
|
|
|
+ sort.SliceStable(sorted, func(i, j int) bool {
|
|
|
|
|
+ a, b := sorted[i], sorted[j]
|
|
|
|
|
+ switch {
|
|
|
|
|
+ case a.VendorID != b.VendorID:
|
|
|
|
|
+ return a.VendorID < b.VendorID
|
|
|
|
|
+ case a.ProductID != b.ProductID:
|
|
|
|
|
+ return a.ProductID < b.ProductID
|
|
|
|
|
+ default:
|
|
|
|
|
+ return a.Path < b.Path
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ for i := range sorted {
|
|
|
|
|
+ sorted[i].Index = i + 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return sorted
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func deviceKey(vendorID, productID uint16) string {
|
|
|
|
|
+ return fmt.Sprintf("%04x:%04x", vendorID, productID)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func knownKeyboards() (map[string]Keyboard, error) {
|
|
|
|
|
+ keyboards, err := LoadKeyboards()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ known := make(map[string]Keyboard, len(keyboards))
|
|
|
|
|
+ for _, kb := range keyboards {
|
|
|
|
|
+ known[deviceKey(kb.VendorID, kb.ProductID)] = kb
|
|
|
|
|
+ }
|
|
|
|
|
+ return known, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// LoadKeyboards reads keyboard definitions from keyboards.json.
|
|
// LoadKeyboards reads keyboard definitions from keyboards.json.
|
|
@@ -70,61 +148,3 @@ var findKeyboardsJSON = func() (string, error) {
|
|
|
|
|
|
|
|
return "", fmt.Errorf("keyboards.json not found")
|
|
return "", fmt.Errorf("keyboards.json not found")
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// UnknownDevice represents a QMK Raw HID keyboard not yet in keyboards.json.
|
|
|
|
|
-type UnknownDevice struct {
|
|
|
|
|
- VendorID uint16 `json:"vendorId"`
|
|
|
|
|
- ProductID uint16 `json:"productId"`
|
|
|
|
|
- Path string `json:"path"`
|
|
|
|
|
- Supported bool `json:"supported"`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// DiscoverAllQMK scans for all connected QMK keyboards (both known and unknown).
|
|
|
|
|
-func DiscoverAllQMK(keyboards []Keyboard) ([]UnknownDevice, error) {
|
|
|
|
|
- qmkDevices, err := hid.DiscoverAll()
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("discover qmk devices: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- known := make(map[string]bool)
|
|
|
|
|
- for _, kb := range keyboards {
|
|
|
|
|
- known[fmt.Sprintf("%04x:%04x", kb.VendorID, kb.ProductID)] = true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var results []UnknownDevice
|
|
|
|
|
- for _, q := range qmkDevices {
|
|
|
|
|
- key := fmt.Sprintf("%04x:%04x", q.VendorID, q.ProductID)
|
|
|
|
|
- results = append(results, UnknownDevice{
|
|
|
|
|
- VendorID: q.VendorID,
|
|
|
|
|
- ProductID: q.ProductID,
|
|
|
|
|
- Path: q.Path,
|
|
|
|
|
- Supported: known[key],
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- return results, nil
|
|
|
|
|
-}
|
|
|
|
|
-func Discover(keyboards []Keyboard) ([]Device, error) {
|
|
|
|
|
- var found []Device
|
|
|
|
|
- for _, kb := range keyboards {
|
|
|
|
|
- err := hid.Enumerate(kb.VendorID, kb.ProductID, func(info *hid.DeviceInfo) error {
|
|
|
|
|
- found = append(found, Device{
|
|
|
|
|
- Keyboard: kb,
|
|
|
|
|
- Path: info.Path,
|
|
|
|
|
- })
|
|
|
|
|
- return nil
|
|
|
|
|
- })
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("enumerate VID=0x%04x PID=0x%04x: %w", kb.VendorID, kb.ProductID, err)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if len(found) > 0 {
|
|
|
|
|
- fmt.Fprintf(os.Stderr, "Discovered %d keyboard(s):\n", len(found))
|
|
|
|
|
- for _, d := range found {
|
|
|
|
|
- fmt.Fprintf(os.Stderr, " - %s (VID=0x%04x PID=0x%04x) %s\n", d.Name, d.VendorID, d.ProductID, d.Path)
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- fmt.Fprintln(os.Stderr, "No keyboards found.")
|
|
|
|
|
- }
|
|
|
|
|
- return found, nil
|
|
|
|
|
-}
|
|
|