Parcourir la source

let keyboards.json name a board's channels

Paul Klumpp il y a 1 semaine
Parent
commit
30fdafc64c
2 fichiers modifiés avec 83 ajouts et 0 suppressions
  1. 21 0
      internal/device/device.go
  2. 62 0
      internal/device/device_test.go

+ 21 - 0
internal/device/device.go

@@ -18,6 +18,27 @@ type Keyboard struct {
 	Name      string `json:"name"`
 	VendorID  uint16 `json:"vendorId"`
 	ProductID uint16 `json:"productId"`
+	// Channels maps a VIA channel number to the name this board uses for it.
+	// It is optional: a board without it is addressed by its QMK subsystem
+	// name, which follows from the channel number.
+	Channels map[uint16]string `json:"channels"`
+}
+
+// KeyboardFor returns the keyboards.json entry for a device and whether one
+// exists. A missing or malformed file yields no entry and no error, because the
+// file supplies names only: costing names must not cost the ability to drive the
+// keyboard.
+func KeyboardFor(vendorID, productID uint16) (Keyboard, bool, error) {
+	keyboards, err := LoadKeyboards()
+	if err != nil {
+		return Keyboard{}, false, nil
+	}
+	for _, kb := range keyboards {
+		if kb.VendorID == vendorID && kb.ProductID == productID {
+			return kb, true, nil
+		}
+	}
+	return Keyboard{}, false, nil
 }
 
 // Device is a connected QMK Raw HID keyboard.

+ 62 - 0
internal/device/device_test.go

@@ -250,3 +250,65 @@ func TestLoadKeyboardsEmptyFile(t *testing.T) {
 		t.Errorf("LoadKeyboards() returned %d keyboards, want 0", len(keyboards))
 	}
 }
+
+func writeTempKeyboards(t *testing.T, content string) string {
+	t.Helper()
+	path := filepath.Join(t.TempDir(), "keyboards.json")
+	if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
+		t.Fatalf("write keyboards.json: %v", err)
+	}
+	return path
+}
+
+func TestLoadKeyboardsReadsChannelDisplayNames(t *testing.T) {
+	original := findKeyboardsJSON
+	t.Cleanup(func() { findKeyboardsJSON = original })
+	findKeyboardsJSON = func() (string, error) {
+		return writeTempKeyboards(t, `[
+			{"name": "Wobkey Impact 80", "vendorId": 14000, "productId": 12447,
+			 "channels": {"2": "logo", "3": "backlight", "4": "side"}}
+		]`), nil
+	}
+
+	keyboards, err := LoadKeyboards()
+	if err != nil {
+		t.Fatalf("LoadKeyboards() error = %v", err)
+	}
+	if len(keyboards) != 1 {
+		t.Fatalf("keyboards = %d, want 1", len(keyboards))
+	}
+	got := keyboards[0].Channels
+	if got[2] != "logo" || got[3] != "backlight" || got[4] != "side" {
+		t.Errorf("Channels = %v, want 2:logo 3:backlight 4:side", got)
+	}
+}
+
+func TestLoadKeyboardsAcceptsAnEntryWithoutChannels(t *testing.T) {
+	original := findKeyboardsJSON
+	t.Cleanup(func() { findKeyboardsJSON = original })
+	findKeyboardsJSON = func() (string, error) {
+		return writeTempKeyboards(t, `[{"name": "Wobkey Rainy 75", "vendorId": 26214, "productId": 1}]`), nil
+	}
+
+	keyboards, err := LoadKeyboards()
+	if err != nil {
+		t.Fatalf("LoadKeyboards() error = %v", err)
+	}
+	if len(keyboards[0].Channels) != 0 {
+		t.Errorf("Channels = %v, want empty", keyboards[0].Channels)
+	}
+}
+
+// README.md promises that a missing or broken keyboards.json costs names, not
+// the ability to drive the keyboard.
+func TestKeyboardForReportsAbsentRatherThanFailing(t *testing.T) {
+	original := findKeyboardsJSON
+	t.Cleanup(func() { findKeyboardsJSON = original })
+	findKeyboardsJSON = func() (string, error) { return "does-not-exist.json", nil }
+
+	if _, known, err := KeyboardFor(0x6666, 0x0001); err != nil {
+		t.Errorf("KeyboardFor() error = %v, want nil for a missing file", err)
+	} else if known {
+		t.Error("KeyboardFor() = known, want unknown for a missing file")
+	}
+}