package device import ( "encoding/json" "fmt" "os" "path/filepath" "netdome.biz/paul/impact-80/internal/hid" ) // Keyboard represents a keyboard definition from keyboards.json. 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"` } // Device represents a connected keyboard device. type Device struct { Keyboard Path string } // LoadKeyboards reads keyboard definitions from keyboards.json. func LoadKeyboards() ([]Keyboard, error) { path, err := findKeyboardsJSON() if err != nil { return nil, fmt.Errorf("find keyboards.json: %w", err) } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read keyboards.json: %w", err) } var keyboards []Keyboard if err := json.Unmarshal(data, &keyboards); err != nil { return nil, fmt.Errorf("parse keyboards.json: %w", err) } return keyboards, nil } // findKeyboardsJSON searches for keyboards.json in common locations. var findKeyboardsJSON = func() (string, error) { exe, err := os.Executable() if err == nil { path := filepath.Join(filepath.Dir(exe), "keyboards.json") if _, err := os.Stat(path); err == nil { return path, nil } } path := "keyboards.json" if _, err := os.Stat(path); err == nil { return path, nil } cwd, err := os.Getwd() if err == nil { path := filepath.Join(cwd, "keyboards.json") if _, err := os.Stat(path); err == nil { return path, nil } } 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 }