| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package device
- import (
- "fmt"
- "sort"
- "netdome.biz/paul/qmk-rgb/internal/hid"
- )
- // Device is a connected QMK Raw HID keyboard.
- // Index is 1-based and is what --device accepts.
- type Device struct {
- Index int `json:"index"`
- Path string `json:"path"`
- VendorID uint16 `json:"vendorId"`
- ProductID uint16 `json:"productId"`
- Name string `json:"name,omitempty"`
- }
- // DiscoverAll returns every connected QMK Raw HID keyboard, ordered
- // deterministically and numbered from 1. A keyboard that does not report a USB
- // product string comes back with no Name; that costs the name and nothing else.
- func DiscoverAll() ([]Device, error) {
- infos, err := hid.DiscoverAll()
- if err != nil {
- return nil, fmt.Errorf("discover qmk devices: %w", err)
- }
- devices := make([]Device, 0, len(infos))
- for _, info := range infos {
- devices = append(devices, Device{
- Path: info.Path,
- VendorID: info.VendorID,
- ProductID: info.ProductID,
- Name: info.ProductString,
- })
- }
- return indexDevices(devices), nil
- }
- // UsagePair is one HID collection of a device: its usage page and its usage.
- // Both are needed to describe a collection, because a keyboard reports several
- // on one device and the page alone does not tell them apart.
- type UsagePair struct {
- UsagePage uint16 `json:"usagePage"`
- Usage uint16 `json:"usage"`
- }
- // HIDDevice is a connected device whose HID collections are not the QMK Raw HID
- // one, which is what a discovery failure has to name: a board that is connected
- // but not reachable is a different fact from a board that is not connected, and
- // the usage pages are the only thing that tells the two apart.
- //
- // It is per device rather than per collection, because a device that is not
- // reachable usually has several collections and one line per collection buries
- // the one line that says something. A keyboard over USB typically has five: a
- // keyboard page, two consumer-control pages, mouse and a vendor page. What is
- // missing is the vendor page at 0xFF60.
- //
- // There is deliberately no Index and no Path. A number would look like something
- // --device accepts, and nothing accepts these.
- type HIDDevice struct {
- VendorID uint16 `json:"vendorId"`
- ProductID uint16 `json:"productId"`
- Name string `json:"name,omitempty"`
- UsagePairs []UsagePair `json:"usagePages"`
- }
- // DiscoverOther returns every connected HID device that is not a QMK Raw HID
- // keyboard, each with the usage pages it does expose. Devices this tool drives
- // are left out: they are what DiscoverAll numbers, and a list repeating them
- // would suggest a second way to select a keyboard.
- //
- // Order is the order the HID layer reported the devices in, and the pairs within
- // a device are in the order they were reported. Neither is sorted, because
- // neither carries a number a user could act on and a keyboard's collections mean
- // nothing in an order.
- func DiscoverOther() ([]HIDDevice, error) {
- infos, err := hid.DiscoverEvery()
- if err != nil {
- return nil, fmt.Errorf("discover hid collections: %w", err)
- }
- return groupOther(infos), nil
- }
- // groupOther turns HID collections into the per-device list the commands report.
- // It is separate from DiscoverOther because the grouping is the whole of the
- // decision and nothing about it needs a keyboard plugged in to be checked.
- func groupOther(infos []hid.DeviceInfo) []HIDDevice {
- var devices []HIDDevice
- index := make(map[string]int)
- for _, info := range infos {
- if info.RawHID {
- continue
- }
- key := fmt.Sprintf("%04X/%04X/%s", info.VendorID, info.ProductID, info.ProductString)
- at, seen := index[key]
- if !seen {
- devices = append(devices, HIDDevice{
- VendorID: info.VendorID,
- ProductID: info.ProductID,
- Name: info.ProductString,
- })
- at = len(devices) - 1
- index[key] = at
- }
- pair := UsagePair{UsagePage: info.UsagePage, Usage: info.Usage}
- if !containsUsagePair(devices[at].UsagePairs, pair) {
- devices[at].UsagePairs = append(devices[at].UsagePairs, pair)
- }
- }
- return devices
- }
- // containsUsagePair reports whether a device already lists a collection. The HID
- // layer reports some devices once per interface, so the same pair arrives twice
- // for one device and a line that says it twice is a line about nothing.
- func containsUsagePair(pairs []UsagePair, want UsagePair) bool {
- for _, p := range pairs {
- if p == want {
- return true
- }
- }
- return false
- }
- // 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
- }
|