package device import ( "encoding/json" "fmt" "os" "path/filepath" "github.com/wobkey/rgb/internal/hid" ) // Keyboard represents a VIA-compatible keyboard 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 VendorID uint16 ProductID uint16 } // 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") } // Discover scans all HID devices and returns connected VIA-compatible keyboards. func Discover(keyboards []Keyboard) ([]Device, error) { devices, err := hid.DiscoverAll() if err != nil { return nil, fmt.Errorf("enumerate HID devices: %w", err) } var found []Device for _, kb := range keyboards { for _, d := range devices { if d.VendorID == kb.VendorID && d.ProductID == kb.ProductID { found = append(found, Device{ Keyboard: kb, Path: d.Path, VendorID: d.VendorID, ProductID: d.ProductID, }) } } } return found, nil }