|
|
@@ -23,6 +23,17 @@ For the effect catalog specifically, read it from no document at all. Run
|
|
|
effects than Backlight, and only the tool knows what the current firmware
|
|
|
supports.
|
|
|
|
|
|
+## Keep README.md Short, Because a Person Reads It
|
|
|
+
|
|
|
+A fact belongs in README.md only while it changes what someone types. The
|
|
|
+measured detail behind a fact — how many collections a board reports, which usage
|
|
|
+page a firmware picks, what a filter rejects and why — belongs here, and the
|
|
|
+README may carry it as one sentence or a console transcript, not as the argument.
|
|
|
+Two tests before a paragraph goes in: would a user who only wants to run the
|
|
|
+command have to read it, and is it a fact about the software rather than about
|
|
|
+how the software was arrived at? A README that has to be read twice before the
|
|
|
+command is written has stopped being a reference.
|
|
|
+
|
|
|
## Fetch the Remote Before the First Edit
|
|
|
|
|
|
`origin/main` moves while you work, and this repository is worked on from more
|
|
|
@@ -58,142 +69,94 @@ Cross-platform Go CLI library for programmatic/agent-friendly control of QMK key
|
|
|
|
|
|
## Device Selection
|
|
|
|
|
|
-Discovery matches every connected keyboard exposing the QMK Raw HID signature
|
|
|
-(Usage Page `0xFF60`, Usage `0x61`) — the same check `qmk/qmk_udev` performs.
|
|
|
-Nothing has to be registered for a board to work. A keyboard states its own name
|
|
|
-over USB, and the definition file that describes it names its channels; a board
|
|
|
-with neither is still driven, because the channel numbers follow from the QMK
|
|
|
-subsystem they belong to. `keyboards.json` used to supply both and is gone: two
|
|
|
-places to update is how the channel names and the catalog drifted apart.
|
|
|
-
|
|
|
-**An empty discovery has to explain itself, and that is the whole of what
|
|
|
-`DiscoverEvery` is for.** "No keyboard" is what a board that is not connected
|
|
|
-looks like, and it is also what a board that is connected and not reachable looks
|
|
|
-like — a firmware that puts Raw HID on another usage page, a wireless receiver, a
|
|
|
-firmware without Raw HID at all. Nothing in the tool can tell those apart from
|
|
|
-outside, so `keyboard info` prints the HID devices it passed over, one line per
|
|
|
-device with the usage pages it exposes, and the JSON shape carries them in
|
|
|
-`otherHidDevices` on every run.
|
|
|
-
|
|
|
-The two lines above that list say what was looked for and why a board has it, and
|
|
|
-what they may **not** say is that VIA was checked. They did not: this command
|
|
|
-never opens a device, and the filter asks about a usage page, not about VIA. A
|
|
|
-QMK firmware has the collection when Raw HID is enabled and VIA's build cannot be
|
|
|
-compiled without it (`quantum/via.c` errors out), which is a property of the
|
|
|
-firmware and belongs in the sentence as the reason — not as a claim about what
|
|
|
-this command observed. The same goes for "No QMK keyboard found", which is
|
|
|
-overstated anyway: most QMK builds do not enable Raw HID at all, so a plain QMK
|
|
|
-keyboard is a QMK keyboard this tool does not see.
|
|
|
-
|
|
|
-Two decisions there are load-bearing and were measured, not chosen. It is one line
|
|
|
-per **device**, not per collection: a keyboard over USB reports five collections
|
|
|
-(measured on the Impact 80: `0x0001/0x02`, `0x0001/0x01`, `0x0001/0x80`,
|
|
|
-`0x000C/0x01`, `0x0001/0x06`), so per collection buries the one line that says
|
|
|
-something. And in text the list appears **only** when nothing was found: a Mac has
|
|
|
-43 HID collections across 10 devices, none of which is ever a keyboard, and
|
|
|
-printing them beside a successful listing buries the line the user came for. The
|
|
|
-JSON always has them, because a consumer asking "why not this one" needs them
|
|
|
-whether or not the tool found something.
|
|
|
-
|
|
|
-The signature is an exact pair, and it is checked per collection rather than per
|
|
|
-device because macOS reports one HID device per usage pair and Linux one interface
|
|
|
-at a time; the `seen` map is keyed on the path *after* the filter, so a keyboard's
|
|
|
-own keyboard collection cannot consume the raw HID entry. A vendor page near
|
|
|
-`0xFF60` is not a raw HID interface and no guessing is done about intent — the
|
|
|
-firmware picked the page, and saying which page it picked is the answer.
|
|
|
-
|
|
|
-Where a board's own files live is `definitions/` and `profiles/`, both under the
|
|
|
-platform's per-user configuration directory, which `os.UserConfigDir` answers — a
|
|
|
-hardcoded `~/.config` would be wrong on macOS and Windows. `os.UserConfigDir` is
|
|
|
-what names it, and `userDataDir` in `cmd/qmk-rgb-tool/datadir.go` is the only
|
|
|
-place that asks.
|
|
|
-
|
|
|
-Each of the two is one directory, and it is both what is written and what is
|
|
|
-read: `definitionsPath()` and `profilesPath()`, and nothing else. Both were
|
|
|
-searches until they were not — next to the executable, then the working
|
|
|
-directory, then the user's — and the search was a defect in both cases, for the
|
|
|
-same reason in reverse. A definition is written by `keyboard fetch` and read by
|
|
|
-every command, so a directory beside the binary hides the file the user just
|
|
|
-fetched, for as long as the binary is run from a checkout, which is where it is
|
|
|
-run from. A profile is written to the user's directory and read from a
|
|
|
-checkout's, so `load <name>` finds a different file with the same name depending
|
|
|
-on where the command is run, and `save` followed by `load` does not return what
|
|
|
-was saved. A test that a definitions or profiles directory beside the binary is
|
|
|
-ignored, in `TestDataDirectoriesAreNotSearched`, is what stops the search coming
|
|
|
-back.
|
|
|
-
|
|
|
-One argument reaches a file outside those directories, and it is deliberately not
|
|
|
-a search: a profile argument ending in `.json` is a path, handed to the file
|
|
|
-system as written, so `load profiles/lava.json` reads that file. `resolveProfileTarget`
|
|
|
-in `cmd/qmk-rgb-tool/profile.go` is the whole rule — a `.json` suffix is a path,
|
|
|
-anything else is a name in `profilesPath()` — and it has no fallback between the
|
|
|
-two, because a fallback is the search this file rules out. `delete` and `list` stay
|
|
|
-name-only, so there is no way to remove or list a file by path and the per-user
|
|
|
-directory remains the one place a *name* resolves to.
|
|
|
-
|
|
|
-A board's definition has one further source: the file built into the binary, which
|
|
|
-`candidateDefinitions` in `cmd/qmk-rgb-tool/catalog.go` consults after the
|
|
|
-per-user directory.
|
|
|
-
|
|
|
-`keyboard info` does not open the board, so it must not report the board's
|
|
|
-channels: it does not know them, and printing the ones a definition names would
|
|
|
-claim more than it knows. `info` and the effect list open the board and report
|
|
|
-them.
|
|
|
-
|
|
|
-No command validates the zone or `--device` before the command that needs it
|
|
|
-does. A root-level pre-run would make `keyboard info`, `list`, `delete` and
|
|
|
-`completion` demand a keyboard, and the one you run to choose a keyboard cannot
|
|
|
-require that you have chosen one. Where two errors apply, the board wins: a
|
|
|
-keyboard that cannot be selected is reported before a zone name, because the
|
|
|
-vocabulary of a board cannot be known without the board.
|
|
|
-
|
|
|
-`keyboard info` numbers the connected keyboards from 1 in a stable order
|
|
|
-(vendor ID, product ID, path). `--device` accepts that number, never a HID path.
|
|
|
-Without `--device`, commands proceed only when exactly one keyboard is connected;
|
|
|
-otherwise they fail and list the selectable numbers, so a command never targets
|
|
|
-an unintended keyboard. Device numbers are stable for the current session only —
|
|
|
-HID paths are reassigned on reboot and most keyboards report no serial number.
|
|
|
-
|
|
|
-The zone is a **positional argument**, not a flag, and that is not a style choice.
|
|
|
-It was a flag, on the root, and a flag on the root is a flag every help lists:
|
|
|
-`keyboard info` and `list` were told about a channel neither can address, and
|
|
|
-ignored it. An argument is spelled only where it is read, so there is nothing to
|
|
|
-ignore. `zoneArgs` in `cmd/qmk-rgb-tool/main.go` is the one place a *zone*
|
|
|
-argument count is declared — `withZoneArgs` is every command's route into it, and
|
|
|
-`load` calls it directly because its zone is the second argument. Commands that
|
|
|
-take no zone use cobra's own `NoArgs` or `MaximumNArgs`, which is correct: a
|
|
|
-stray token there is a mistyped command, not a missing channel. `zoneArgs` prints
|
|
|
-the usage line on a wrong count, because cobra's own message does not say what
|
|
|
-the missing argument should have been.
|
|
|
-
|
|
|
-The zone takes a VIA lighting channel, named by its QMK subsystem, and a board's
|
|
|
-definition file may name a channel differently. README.md carries the vocabulary.
|
|
|
-Several channels are written comma separated and `all` means every channel the
|
|
|
-keyboard reports; `all` next to another name is `all`, since the union is the
|
|
|
-whole keyboard. Both live in `resolveZoneName` in `cmd/qmk-rgb-tool/zones.go`, and
|
|
|
-so does the fact that a list comes back in channel order however it was written —
|
|
|
-one order for every spelling of one selection, which is what a JSON consumer
|
|
|
-parses. The channels themselves are discovered by asking the keyboard, not
|
|
|
-assumed, and a name that resolves to a channel the board does not have is refused
|
|
|
-by name, every missing one of a list, so nothing is written on the way to a
|
|
|
-failure.
|
|
|
-
|
|
|
-A command that writes lighting cannot be called without a zone, and the argument
|
|
|
-count is what keeps it that way: `brightness` given one argument fails before the
|
|
|
-keyboard is opened. A command that only reads — `info`, `effect` without a name,
|
|
|
-`save` — takes no zone and reports every channel, which is what they are for.
|
|
|
-`load` takes a profile name and at most a zone, and applies the profile to the
|
|
|
-channels the zone names. Write the arity into a new command that writes, or the
|
|
|
-new command will be the one command that writes every channel by default.
|
|
|
-
|
|
|
-The zone is a parameter of `prepareTarget` and `openTarget` rather than a
|
|
|
-package-level variable, which is what makes the rule above enforceable: there is
|
|
|
-no global a test or a command could set by accident, and a command that forgets to
|
|
|
-pass one passes `""`, which means every channel. Only `load` skips a channel that
|
|
|
-does not support an effect, warning on stderr, because there a whole profile is
|
|
|
-being applied. `effect` refuses instead, `all` included: `effect all` asks for
|
|
|
-every channel, so a name only one of them has is a request the tool cannot carry
|
|
|
-out rather than a reason to write the others and report success.
|
|
|
+Rules, with the reason they exist after the dash. The measurements behind them
|
|
|
+are in [Measurements](#measurements); `README.md` carries the user-facing half.
|
|
|
+
|
|
|
+**Discovery**
|
|
|
+
|
|
|
+- Match the QMK Raw HID signature: Usage Page `0xFF60`, Usage `0x61`, the same
|
|
|
+ check `qmk/qmk_udev` performs. Nothing has to be registered for a board; a board
|
|
|
+ states its own name over USB, and a board with no name and no definition file is
|
|
|
+ still driven, because channel numbers follow from the QMK subsystem.
|
|
|
+- The pair is exact and is checked **per collection**, not per device: macOS
|
|
|
+ reports one HID device per usage pair, Linux one interface at a time. Key the
|
|
|
+ `seen` map on the path *after* the filter, or a keyboard's own keyboard
|
|
|
+ collection consumes the raw HID entry. A vendor page near `0xFF60` is not a raw
|
|
|
+ HID interface and no intent is guessed — the firmware picked the page, and
|
|
|
+ saying which page it picked is the answer.
|
|
|
+- An empty result must explain itself. `DiscoverEvery` is the whole of that:
|
|
|
+ `keyboard info` prints the HID devices it passed over, one line per device with
|
|
|
+ the usage pages each exposes, and `otherHidDevices` carries them in the JSON
|
|
|
+ shape on every run. "No keyboard" is what an unplugged board looks like and also
|
|
|
+ what an unreachable one looks like, and nothing outside the tool can tell them
|
|
|
+ apart.
|
|
|
+- Those lines may say what was looked for and why a QMK firmware has that
|
|
|
+ collection; they may **not** say VIA was checked — this command never opens a
|
|
|
+ device, and the filter asks about a usage page. "No QMK keyboard found" is
|
|
|
+ overstated anyway: most QMK builds do not enable Raw HID at all.
|
|
|
+
|
|
|
+**Where a board's files live**
|
|
|
+
|
|
|
+- `definitionsPath()` and `profilesPath()` in `cmd/qmk-rgb-tool/datadir.go` are the
|
|
|
+ only things that say where, and both are the per-user directory `os.UserConfigDir`
|
|
|
+ answers — a hardcoded `~/.config` is wrong on macOS and Windows.
|
|
|
+ `userDataDir` is the only place that asks.
|
|
|
+- One directory each, and it is both what is written and what is read. No search,
|
|
|
+ ever: a directory beside the executable hides the file `keyboard fetch` just
|
|
|
+ wrote, and a checkout's `profiles/` makes `load <name>` read a different file
|
|
|
+ than `save <name>` wrote. `TestDataDirectoriesAreNotSearched` is what keeps the
|
|
|
+ search from coming back.
|
|
|
+- A profile argument ending in `.json` is a path, handed to the file system as
|
|
|
+ written (`resolveProfileTarget`, `cmd/qmk-rgb-tool/profile.go`). No fallback
|
|
|
+ between path and name, because a fallback is the search this file rules out.
|
|
|
+ `delete` and `list` stay name-only.
|
|
|
+- A board's definition has one further source: the file built into the binary with
|
|
|
+ `go:embed` (`definitions/embed.go`), consulted last by `candidateDefinitions` in
|
|
|
+ `cmd/qmk-rgb-tool/catalog.go`, after `--definition` and after the per-user
|
|
|
+ directory. Embedding a vendor's file is not adding a list: one copy of the
|
|
|
+ vendor's own bytes, and `go install` creates no data directory, so without it an
|
|
|
+ installed tool has no definition for the Impact 80.
|
|
|
+
|
|
|
+**Devices and zones**
|
|
|
+
|
|
|
+- `keyboard info` numbers the keyboards from 1 in a stable order (vendor ID,
|
|
|
+ product ID, path). `--device` takes that number, never a HID path. Without it a
|
|
|
+ command runs only when exactly one keyboard is connected, and otherwise fails and
|
|
|
+ lists the numbers — so a command never targets an unintended keyboard. Numbers
|
|
|
+ hold for the current session only: HID paths are reassigned on reboot.
|
|
|
+- No command validates the zone or `--device` before the command that needs it
|
|
|
+ does. A root-level pre-run would make `keyboard info`, `list`, `delete` and
|
|
|
+ `completion` demand a keyboard, and the command you run to choose a keyboard
|
|
|
+ cannot require that you have chosen one. Where two errors apply the board wins:
|
|
|
+ a keyboard's channel vocabulary is not knowable without the keyboard.
|
|
|
+- `keyboard info` must not report the board's channels: it does not open the board
|
|
|
+ and does not know them. `info` and the effect list open it and do know.
|
|
|
+- The zone is a **positional argument**, not a flag — a flag on the root is a flag
|
|
|
+ every help lists, and `keyboard info` and `list` were told about a channel they
|
|
|
+ cannot address and ignored it. `zoneArgs` in `cmd/qmk-rgb-tool/main.go` is the
|
|
|
+ one place a zone count is declared; `withZoneArgs` is every command's route into
|
|
|
+ it and `load` calls it directly because its zone is the second argument.
|
|
|
+ Commands that take no zone use cobra's `NoArgs`/`MaximumNArgs` — a stray token
|
|
|
+ there is a mistyped command, not a missing channel. `zoneArgs` prints the usage
|
|
|
+ line, because cobra's own message does not say what the argument should have been.
|
|
|
+- The zone takes a VIA lighting channel named by its QMK subsystem; a board's
|
|
|
+ definition may name a channel differently. Comma separated lists, `all` for every
|
|
|
+ channel, and `all` next to another name is `all` — all in `resolveZoneName` in
|
|
|
+ `cmd/qmk-rgb-tool/zones.go`, which also returns a list in channel order however it
|
|
|
+ was written, one order for every spelling of one selection. Channels are asked of
|
|
|
+ the keyboard, never assumed, and a name the board does not have is refused by
|
|
|
+ name, every missing one of a list, so nothing is written on the way to a failure.
|
|
|
+- A command that writes lighting cannot be called without a zone, and the argument
|
|
|
+ count is what keeps it that way. A command that only reads — `info`, `effect`
|
|
|
+ without a name, `save` — takes no zone and reports every channel. `load` takes a
|
|
|
+ profile name and at most a zone. Write the arity into a new command that writes,
|
|
|
+ or it becomes the one command that writes every channel by default.
|
|
|
+- The zone is a parameter of `prepareTarget` and `openTarget`, never a package-level
|
|
|
+ variable, so a command that forgets to pass one passes `""` — which means every
|
|
|
+ channel. Only `load` skips a channel that does not support an effect, warning on
|
|
|
+ stderr, because there a whole profile is applied. `effect` refuses instead, `all`
|
|
|
+ included: `effect all` asks for every channel, so a name only one of them has is
|
|
|
+ a request the tool cannot carry out.
|
|
|
|
|
|
## Stack
|
|
|
|
|
|
@@ -226,65 +189,50 @@ Documented behavior must also match the code:
|
|
|
|
|
|
## Firmware Transforms Values
|
|
|
|
|
|
-The Impact 80 firmware rescales or clamps brightness and speed per channel, so
|
|
|
-an accepted 0–255 request is not the value the keyboard holds. `logo` and `side`
|
|
|
-cap brightness at 160 and have only three reachable speeds — 0 freezes the
|
|
|
-animation, 1 is the slowest movement, anything above 1 becomes 4 — while
|
|
|
-`backlight` scales brightness up to 255 and applies speed as given. README.md
|
|
|
-tabulates the per-channel behaviour; the rule that follows from it is the part
|
|
|
-that matters when you write code here:
|
|
|
-
|
|
|
-`brightness`, `speed`, `color` and `effect` read every selected zone
|
|
|
-back and
|
|
|
-print what was actually applied; where all zones match they print the plain
|
|
|
-success line, and where any zone differs they print one summary line naming each
|
|
|
-zone's real value and the request. Never let a command report success for a
|
|
|
-value the keyboard did not accept. All of them read back through the shared
|
|
|
-`readBackValues`, so a future parameter needs no new read-back path — but the
|
|
|
-helper above it is not as shared as it looks. `setValueVerified` writes one
|
|
|
-value to every channel, which fits `brightness` and `speed` and fits nothing
|
|
|
-else: `effect` carries a different ID per channel, because one effect name is a
|
|
|
-different index on each subsystem, and so has its own `setEffectVerified`.
|
|
|
-Assume the next parameter needs its own wrapper, and share only the read.
|
|
|
-
|
|
|
-A board can refuse an ID that looks valid. The Impact 80's `logo` and `side`
|
|
|
-channels read effect ID 0 as "lighting off" and leave the mode register alone,
|
|
|
-so `effect logo none` there does nothing to the effect and is reported as the
|
|
|
-effect still running. `disable` is the command that turns a channel off, because
|
|
|
-it also writes brightness 0. Above the top the behaviour is the opposite: the
|
|
|
-backlight channel does not refuse ID 47 or ID 99 but clamps it to the 46 it
|
|
|
-holds, so an effect index there is reported as the index the keyboard ended
|
|
|
- up with. Read
|
|
|
-the register back; never report the number that was asked for.
|
|
|
-
|
|
|
-There is no hand-written effect catalog, and that is deliberate. A board's names
|
|
|
-come from its VIA definition file, read at runtime, and the Impact 80's file is
|
|
|
-vendored in `definitions/`. A hand-written list beside the vendor's file is two
|
|
|
-places to update one name, which is exactly how the two drifted apart: the
|
|
|
-compiled-in list spelled `breathing` and `fixed_wave` where the file writes
|
|
|
-`breathe` and `fixed wave`, and the file spells the same effect two ways on two
|
|
|
-channels of one board. Do not add such a list back, and do not "correct" a
|
|
|
-definition file either.
|
|
|
-
|
|
|
-**Embedding a vendor's file is not adding a list.** `definitions/embed.go` builds
|
|
|
-the JSON files in that directory into the binary with `go:embed`, and
|
|
|
-`candidateDefinitions` in `cmd/qmk-rgb-tool/catalog.go` consults them last, after
|
|
|
-`--definition` and after a file in a definitions directory. The distinction is
|
|
|
-the one that matters: the bytes are the vendor's own file, there is exactly one
|
|
|
-copy of it in the repository, and nothing transcribes it. A file the user places
|
|
|
-still wins, so the built-in copy is a fallback and not a second source a name
|
|
|
-could drift from. `go install` copies a binary to `$GOPATH/bin` and creates no
|
|
|
-data directory, so without the embed an installed tool has no definition for the
|
|
|
-Impact 80 at all — and that board is not in VIA's collection, so there is nothing
|
|
|
-to fetch either.
|
|
|
-
|
|
|
-The file names only the effects a board's firmware implements, so it may stop
|
|
|
-short of the board's highest ID: this one's backlight channel ends at 45 while
|
|
|
-the board takes 46, which is left `unknown` on purpose. A name no source publishes
|
|
|
-is a name the tool would be inventing. The live register proves which IDs a board
|
|
|
-takes, never which name belongs to one.
|
|
|
-
|
|
|
-When you see a name or a behavior in one file, grep for it across the whole repo before deciding if a change is consistent.
|
|
|
+The Impact 80 firmware rescales or clamps brightness and speed per channel, so an
|
|
|
+accepted 0–255 request is not the value the keyboard holds. README.md tabulates
|
|
|
+the per-channel behaviour; the rule that follows from it is the part that matters
|
|
|
+when you write code here:
|
|
|
+
|
|
|
+- `brightness`, `speed`, `color` and `effect` read every selected zone back and
|
|
|
+ print what was actually applied; where all zones match they print the plain
|
|
|
+ success line, where any differs they print one summary line naming each zone's
|
|
|
+ real value and the request. Never let a command report success for a value the
|
|
|
+ keyboard did not accept.
|
|
|
+- All of them read back through the shared `readBackValues`, so a future parameter
|
|
|
+ needs no new read-back path — but the helper above it is not as shared as it
|
|
|
+ looks. `setValueVerified` writes one value to every channel, which fits
|
|
|
+ `brightness` and `speed` and fits nothing else: `effect` carries a different ID
|
|
|
+ per channel, because one effect name is a different index on each subsystem, and
|
|
|
+ so has its own `setEffectVerified`. Assume the next parameter needs its own
|
|
|
+ wrapper, and share only the read.
|
|
|
+- A board can refuse an ID that looks valid. The Impact 80's `logo` and `side`
|
|
|
+ channels read effect ID 0 as "lighting off" and leave the mode register alone,
|
|
|
+ so `effect logo none` there does nothing to the effect and is reported as the
|
|
|
+ effect still running. `disable` is the command that turns a channel off, because
|
|
|
+ it also writes brightness 0. Above the top the behaviour is the opposite: the
|
|
|
+ backlight channel does not refuse ID 47 or ID 99 but clamps it to the 46 it
|
|
|
+ holds, so an effect index there is reported as the index the keyboard ended up
|
|
|
+ with. Read the register back; never report the number that was asked for.
|
|
|
+- There is no hand-written effect catalog, and that is deliberate. A board's names
|
|
|
+ come from its VIA definition file, read at runtime. A hand-written list beside the
|
|
|
+ vendor's file is two places to update one name, which is exactly how the two
|
|
|
+ drifted apart: the compiled-in list spelled `breathing` and `fixed_wave` where
|
|
|
+ the file writes `breathe` and `fixed wave`. Do not add such a list back, and do
|
|
|
+ not "correct" a definition file either.
|
|
|
+- **Embedding a vendor's file is not adding a list.** `definitions/embed.go` builds
|
|
|
+ the JSON files in that directory into the binary with `go:embed`, and
|
|
|
+ `candidateDefinitions` consults them last, after `--definition` and after a file
|
|
|
+ in a definitions directory. The distinction is the one that matters: the bytes
|
|
|
+ are the vendor's own file, there is exactly one copy of it in the repository,
|
|
|
+ and nothing transcribes it. A file the user places still wins, so the built-in
|
|
|
+ copy is a fallback and not a second source a name could drift from.
|
|
|
+- A file names only the effects a board's firmware implements, so it may stop
|
|
|
+ short of the board's highest ID: this one's backlight channel ends at 45 while
|
|
|
+ the board takes 46, which is left `unknown` on purpose. A name no source
|
|
|
+ publishes is a name the tool would be inventing.
|
|
|
+- When you see a name or a behavior in one file, grep for it across the whole repo
|
|
|
+ before deciding if a change is consistent.
|
|
|
|
|
|
## Read the Firmware When Something Is Unclear
|
|
|
|
|
|
@@ -329,12 +277,11 @@ step: `CatalogFor` is gone, and a command that reached for a catalog itself woul
|
|
|
silently ignore a file the user had placed, so do not add one.
|
|
|
|
|
|
Two things about reading those files are easy to get wrong, and both were
|
|
|
-measured on VIA's own collection of 2029 definitions rather than assumed:
|
|
|
+measured on VIA's own collection rather than assumed:
|
|
|
|
|
|
-- An option's number is not its position. 194 options carry a number that differs
|
|
|
- from where they stand, so a board can name its first effect as ID 1 and have
|
|
|
- no ID 0 at all. `internal/rgb/definition.go` reads the number and falls back to
|
|
|
- the position only when there is none. This is why `Catalog` holds
|
|
|
+- An option's number is not its position, so a board can name its first effect as
|
|
|
+ ID 1 and have no ID 0 at all. `internal/rgb/definition.go` reads the number and
|
|
|
+ falls back to the position only when there is none. This is why `Catalog` holds
|
|
|
`[]Effect{ID, Name}` and not a `[]string` indexed by ID.
|
|
|
- A definition names its channels too, in the sub-menu it puts each one under,
|
|
|
and those names are what VIA shows, so they are the display name. The QMK
|
|
|
@@ -351,11 +298,11 @@ The files are VIA's format, but their addressing is QMK's, and that is not a
|
|
|
choice this tool made. The HID interface, the channel numbers, the lighting
|
|
|
value IDs and the whole channel probe are QMK's; only the names come from the
|
|
|
file, because the VIA protocol cannot carry names and VIA reads them from a
|
|
|
-file for the same reason. Measured over VIA's collection, all 190 effect
|
|
|
-dropdowns there use a QMK value key, so keying the parser on `id_qmk_*_effect`
|
|
|
-loses nothing today. `effectValueKeys` in `internal/rgb/definition.go` is the
|
|
|
-only place to register a vendor's own key, and a board that invents one gets no
|
|
|
-names until it is added there.
|
|
|
+file for the same reason. All effect dropdowns in VIA's collection use a QMK
|
|
|
+value key, so keying the parser on `id_qmk_*_effect` loses nothing today.
|
|
|
+`effectValueKeys` in `internal/rgb/definition.go` is the only place to register a
|
|
|
+vendor's own key, and a board that invents one gets no names until it is added
|
|
|
+there.
|
|
|
|
|
|
A definition writes the manufacturer's spelling, which need not be the tool's,
|
|
|
so the alias table is a package default every catalog gets rather than part of
|
|
|
@@ -364,10 +311,7 @@ aliases, and to an alias of it, and `byName` treats a space and an underscore as
|
|
|
the same character, because that is the whole difference between a
|
|
|
manufacturer's display spelling and the tool's identifier — `fixed wave` and
|
|
|
`fixed_wave` are one effect, and a documented name may not stop working because a
|
|
|
-definition file is present. Without the last of those, the Impact 80's
|
|
|
-profile stops loading the moment a definition file is placed: the file writes
|
|
|
-`breathe`, the compiled-in catalog writes `breathing`, and only one of them is
|
|
|
-present at a time.
|
|
|
+definition file is present.
|
|
|
|
|
|
A definition names the effects a board's firmware implements, so it may cover
|
|
|
fewer IDs than the board takes. A file replaces the built-in copy for its
|
|
|
@@ -376,19 +320,15 @@ file stops at ID 45, so no name for ID 46 is reachable while that file is
|
|
|
present, built in or placed. README.md carries the user-facing half of this.
|
|
|
|
|
|
There is no shared database of effect names to be had, and do not plan around
|
|
|
-one. The closest thing is VIA's collection, `the-via/keyboards`: 2029
|
|
|
-definitions, one per unique VID/PID, of which 179 name VIA's built-in
|
|
|
-`qmk_rgb_matrix` menu and 163 ship their own `id_qmk_rgb_matrix_effect`
|
|
|
-dropdown, which is where a board's effect names actually live — not under a
|
|
|
+one. The closest thing is VIA's collection, `the-via/keyboards`, and a board's
|
|
|
+names live in an `id_qmk_rgb_matrix_effect` dropdown under `menus` — not under a
|
|
|
`lighting` or `effects` key, so a grep for those finds nothing and looks like
|
|
|
-proof that no bulk source exists. The 153 that deviate are not merely shorter
|
|
|
-lists: they reorder IDs and spell names as QMK enum identifiers or as numbered
|
|
|
-display labels, and only 10 are an exact prefix of the built-in 45. So a
|
|
|
-subsystem default is wrong for most boards, and a board's names come from the
|
|
|
+proof that no bulk source exists. Most boards deviate from the built-in list, so
|
|
|
+a subsystem default is wrong for most of them, and a board's names come from the
|
|
|
definition its own vendor publishes. OpenRGB drives the same raw HID interface
|
|
|
but keeps its device data in C++ controllers, so it settles VID/PID, not effects.
|
|
|
-README.md carries the details; the conclusion for code here is that a catalog is
|
|
|
-transcribed per board and cannot be generated from a common source.
|
|
|
+The conclusion for code here is that a catalog is transcribed per board and
|
|
|
+cannot be generated from a common source.
|
|
|
|
|
|
**The names for VIA's built-in menu are in no definition file at all.** A
|
|
|
definition that writes `"menus": ["qmk_rgb_matrix"]` — a string, not a menu object —
|
|
|
@@ -411,42 +351,39 @@ projects drive the same raw HID interface from a terminal, and each of them take
|
|
|
an effect **ID**:
|
|
|
|
|
|
- <https://github.com/FrameworkComputer/qmk_hid> — Rust CLI, plus a Python GUI in
|
|
|
- `python/`; BSD-3-Clause. `qmk_hid via --rgb-effect 38`, plus `--rgb-brightness`,
|
|
|
- `--rgb-hue`, `--rgb-saturation`, `--rgb-color`, `--rgb-effect-speed`,
|
|
|
+ `python/`; BSD-3-Clause. `qmk_hid via --rgb-effect 38`, `--rgb-brightness`,
|
|
|
+ `--rgb-hue`, `--rgb-saturation`, `--rgb-effect-speed`, `--rgb-color`,
|
|
|
`--backlight`, `--backlight-breathing`, `--save`, `--device-indication`,
|
|
|
- `--eeprom-reset`, `--bootloader`, and `-l`/`--vid`/`--pid`. Its README states
|
|
|
- the reason out loud: "the effect numbers can be different per keyboard", and it
|
|
|
- says the tool "will soon be superceded by QMK XAP".
|
|
|
+ `--eeprom-reset`, `--bootloader`, `-l`/`--vid`/`--pid`. Its README states the
|
|
|
+ reason out loud: "the effect numbers can be different per keyboard", and it says
|
|
|
+ the tool "will soon be superceded by QMK XAP".
|
|
|
- <https://github.com/njkevlani/qmk-light> — C++ against hidapi, one `qmk-light.cpp`;
|
|
|
- **no licence file**, so nothing may be taken from it. `--list`, `--get-brightness`,
|
|
|
- `--set-brightness` (absolute or `+5`/`-10`), `--list-effects`, `--get-effect`,
|
|
|
- `--set-effect`, `--get-effect-speed`, `--set-effect-speed`, `--get-color`,
|
|
|
- `--set-color h,s`, `--device <index|path>`, `--first-device`, `--quiet`.
|
|
|
+ **no licence file**, so nothing may be taken from it. `--list`,
|
|
|
+ `--get-brightness`, `--set-brightness` (absolute or `+5`/`-10`),
|
|
|
+ `--list-effects`, `--get-effect`, `--set-effect`, `--get-effect-speed`,
|
|
|
+ `--set-effect-speed`, `--get-color`, `--set-color h,s`,
|
|
|
+ `--device <index|path>`, `--first-device`, `--quiet`.
|
|
|
- <https://github.com/Drugantibus/qmk-hid-rgb> — Python, GPL-3.0, untouched since
|
|
|
2021; a proof of concept that needs a keymap of its own with `RAW_ENABLE = yes`
|
|
|
and the board's VID/PID written into the source.
|
|
|
|
|
|
None of them reads a definition file, so none can name an effect. That is the gap
|
|
|
-this tool's catalog and `keyboard fetch` sit in.
|
|
|
-
|
|
|
-Read them for that gap, and for anything this tool does not do yet: they are the
|
|
|
-three live implementations of the same wire protocol, and their flag surfaces are
|
|
|
-where a gap in ours shows up first — persistence (`--save`, `--eeprom-reset`),
|
|
|
-device indication, effect speed as its own parameter, colour by name, jumping to
|
|
|
-the bootloader, and explicit device selection where more than one board is
|
|
|
-attached (`--first-device`). Any of those is a candidate feature here, not a
|
|
|
-duplicate to reimplement badly. Check a project's licence before taking code from
|
|
|
-it, and check whether it has moved on before treating its behaviour as current.
|
|
|
+this tool's catalog and `keyboard fetch` sit in. Read them for that gap, and for
|
|
|
+anything this tool does not do yet: their flag surfaces are where a gap in ours
|
|
|
+shows up first — persistence (`--save`, `--eeprom-reset`), device indication,
|
|
|
+effect speed as its own parameter, colour by name, jumping to the bootloader, and
|
|
|
+explicit device selection where more than one board is attached (`--first-device`).
|
|
|
+Any of those is a candidate feature here, not a duplicate to reimplement badly.
|
|
|
+Check a project's licence before taking code from it, and check whether it has
|
|
|
+moved on before treating its behaviour as current.
|
|
|
|
|
|
**The `qmk` CLI has never had a lighting command.** Do not explain a missing
|
|
|
-feature by saying QMK removed one. Measured on `qmk_firmware`: `lib/python/qmk/cli/`
|
|
|
-carries no `led`, `rgblight` or `hid` module at tags 0.6, 0.9, 0.10, 0.15 through
|
|
|
-0.21, 0.24 or on master, the commits API returns zero commits for those paths, and
|
|
|
+feature by saying QMK removed one. `lib/python/qmk/cli/` carries no `led`,
|
|
|
+`rgblight` or `hid` module at tags 0.6, 0.9, 0.10, 0.15 through 0.21, 0.24 or on
|
|
|
+master, the commits API returns zero commits for those paths, and
|
|
|
`docs/cli_commands.md` documents none at any of those tags. The path filter does
|
|
|
-report deletions — `lib/python/qmk/cli/cformat.py` and `multibuild.py` both end at
|
|
|
-`4723f308a`, *"Remove CLI commands: `multibuild`, `cformat`, `fileformat`,
|
|
|
-`pyformat`"*, 2023-01-18 — so an empty result means the file was never there, not
|
|
|
-that it was removed.
|
|
|
+report deletions, so an empty result means the file was never there, not that it
|
|
|
+was removed.
|
|
|
|
|
|
## Output Shape
|
|
|
|
|
|
@@ -481,6 +418,28 @@ All documentation (README.md, comments, AGENTS.md) must stay in sync with the co
|
|
|
When code and documentation conflict, **ask the user** before deciding which one to change.
|
|
|
Do not silently pick a winner — explicitly state the conflict and get direction.
|
|
|
|
|
|
+## Measurements
|
|
|
+
|
|
|
+Facts about the world that a rule above rests on. They are recorded here because
|
|
|
+the rules are what someone needs while editing, and because a rule without the
|
|
|
+number behind it tends to be adjusted on feeling. Re-measure before quoting one
|
|
|
+twice; several are counts of a collection that moves.
|
|
|
+
|
|
|
+| What | Measured | Where |
|
|
|
+|---|---|---|
|
|
|
+| VIA's collection | 2029 definitions, one per unique VID/PID | `the-via/keyboards`, tree API |
|
|
|
+| …name VIA's built-in `qmk_rgb_matrix` menu | 179 boards | as above |
|
|
|
+| …ship their own `id_qmk_rgb_matrix_effect` dropdown | 163 boards, of which 10 are an exact prefix of the built-in 45 | as above |
|
|
|
+| …of those, write names as QMK enum identifiers | 11; the other 152 use display labels | as above |
|
|
|
+| Effect options whose number is not their position | 194 | as above |
|
|
|
+| Effect dropdowns in the collection that use a QMK value key | 190, which is all of them, so keying on `id_qmk_*_effect` loses nothing today | as above |
|
|
|
+| HID collections per device | 5 on the Impact 80 (`0x0001/0x02`, `0x0001/0x01`, `0x0001/0x80`, `0x000C/0x01`, `0x0001/0x06`) | macOS, this Mac, `qmk-rgb-tool`-adjacent enumeration |
|
|
|
+| HID devices a Mac reports that are never a keyboard | 43 collections across 10 devices, Impact 80 attached | macOS, same run |
|
|
|
+| `qmk` CLI lighting commands | none, at any tag — see the rule above | `qmk/qmk_firmware`, contents + commits API per path; the path filter does report deletions, e.g. `4723f308a` removes `cformat` and `multibuild` |
|
|
|
+| Impact 80 brightness and speed per channel | `logo`/`side` cap at 160 with three reachable speeds (0 freezes, 1 slowest, above 1 becomes 4); `backlight` scales to 255 | README tabulates it; measured on the board |
|
|
|
+| Impact 80 effect IDs | `backlight` takes 0–46 and clamps above; `logo`/`side` take 0–6 and read ID 0 as off | measured on the board |
|
|
|
+| VIA's built-in menu names | 45, `All Off` … `Solid Multi Splash`, at IDs 0–44 | `the-via/reader/src/common-menus/qmk_rgb_matrix.ts` |
|
|
|
+
|
|
|
## Go Development
|
|
|
|
|
|
### Tooling
|