Gen 3 keyboards: stop sending the lighting opcode that silences their keys - #16
Merged
Conversation
An Apex Pro Gen 3 (1038:1640, firmware 4.15.3) is the first wired Gen 3 board to be measured rather than inherited from OpenRGB and apex-tux, and it refutes one of the entries it was given. 0x40 is not per-key lighting there. It suspends key reporting for as long as frames keep arriving, which with an animated effect is permanently: the board becomes unusable as a keyboard while Inari runs. Counting real input events off its own event nodes, per second: nothing sent 4.9 OLED 0x61 @ 2 Hz 4.4 direct 0x40 @ 5 Hz 0.2 direct 0x40 @ 30 Hz 0.1 It is the opcode, not the load and not the payload — a bare 0x40 with a zero key count does it just as reliably, and the board recovers within a tick of the frames stopping. Every write is ACKed at a flat 1.2 ms throughout, so the failure never reaches the software: the board answers endpoint 0 from an interrupt while whatever 0x40 starts starves the matrix scan. That is why this shipped at all. Model::direct_dialect now separates "has per-key LEDs" from "Inari knows the opcode for them", the same distinction switch_dialect already draws for the firmware commands. direct_packet() returns None where the dialect is a guess, the draw loop hands those boards to their onboard profile, and has_per_key hides every Inari-rendered effect in the UI rather than offering controls that would paint nothing. Set false for 0x1640 and 0x1642 only. The OLED and the firmware-rendered effects were unaffected on the measured board and are left alone. 0x61 was tried as a replacement: harmless to key reporting, but it paints the wrong thing, so the wired Gen 3 dialect stays unknown rather than trading one guess for another. The docs claimed per-key RGB for these boards; hardware, protocols and keyboard pages corrected.
`npm audit --audit-level=high` went red on advisories published after the last green run, neither of them ours to fix in source: brace-expansion (high, DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation) and postcss (moderate, arbitrary .map reads via an attacker-controlled sourceMappingURL). Lockfile only, both semver-compatible: brace-expansion 5.0.8 -> 5.0.9 and postcss 8.5.19 -> 8.5.25. The root version field came along, having been left at 1.0.8 while package.json moved to 1.0.14. Deliberately a separate commit from the keyboard fix it unblocks, and `npm audit fix` was run with --package-lock-only so it could not rewrite package.json behind the change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An Apex Pro Gen 3 (
1038:1640, firmware 4.15.3) is the first wired Gen 3board to be measured rather than inherited from OpenRGB and
apex-tux, andit refutes one of the entries it was given.
The bug
0x40is not per-key lighting on that board. It suspends key reporting foras long as frames keep arriving — which, with an animated effect at 30 Hz,
is permanently. The keyboard stops typing while Inari runs, and the reporter
described it as "it works briefly, then goes off, then on again, in an
endless loop".
Counting real input events off the board's own
/dev/inputnodes whilereplaying Inari's writes in phases:
0x61@ 2 Hz0x40@ 5 Hz0x40@ 30 HzIt is the opcode, not the write load and not the payload: a bare
0x40carrying an explicit zero key count and 641 zero bytes takes the board to
0.0 keys/s just as reliably, and it recovers within a tick of the frames
stopping.
The reason this shipped is worth recording. Every write is acknowledged at
a flat 1.2 ms throughout — no EPIPE, no timeout, nothing in the log. The
board answers endpoint 0 from an interrupt while whatever
0x40startsstarves its matrix scan, so the failure is invisible to the writer and shows
up only as a dead keyboard. Two plausible theories were checked and
discarded on the way: the kernel log proves the device never re-enumerated,
and the flat write latency rules out overloading it.
The fix
Model::direct_dialectseparates "has per-key LEDs" from "Inari knows theopcode for them" — the same distinction
switch_dialectalready draws forthe firmware commands, and for the same reason.
direct_packet()returnsNonewhere the dialect is a guess, the draw loop hands those boards totheir onboard profile instead, and
has_per_keyhides every Inari-renderedeffect in the UI rather than offering controls that would paint nothing.
Set
falsefor0x1640and0x1642only. The OLED and thefirmware-rendered effects (Reactive, Colour shift) were unaffected on the
measured board and are left working.
0x61was tried as a replacement: itis harmless to key reporting but paints the wrong thing, so the wired Gen 3
per-key dialect stays unknown rather than trading one guess for another.
The likely missing piece is the
start_driver_mode(0x6C) that the vendorspecification wraps around software control on these boards; that was not
tried.
Three documentation pages promised per-key RGB for these boards and have
been corrected.
Verification
475 Rust tests, 168 frontend tests,
clippy -D warningsandtscclean.The regression test asserts at the point where the packet would be born
rather than at the writer, and it was checked by reverting the fix and
watching it go red. The fix itself was verified against the hardware: with
the built binary and the exact configuration that caused the report, the
keyboard reports 1.4 keys/s where it previously reported 0.1, and the
installed binary is hash-identical to the one that was measured.