Conversation
nickbock
force-pushed
the
fix/origin-hash-node-collision
branch
from
June 14, 2026 08:12
dde9a7f to
4afe426
Compare
nickbock
force-pushed
the
fix/origin-hash-node-collision
branch
from
June 27, 2026 06:45
4afe426 to
070c36f
Compare
When a hardware device exposes multiple /dev/input/event nodes, the injector picked which node(s) to grab and start read loops on by keying devices on get_device_hash(). Two problems caused it to read from the wrong node and silently miss events: 1. Hash collisions. get_device_hash is md5(capabilities + name), which is not unique per devnode. Some devices (e.g. SteelSeries Aerox 9 Wireless) expose several event nodes with byte-identical capabilities and name, so they share an origin_hash. Keying devices_by_hash / grabbed_devices by hash collapsed these collisions to a single entry, so the injector grabbed and read from the wrong twin and never saw the mapped button. 2. Over-strict capability gate. _find_input_device also required the mapped code to be in the node's advertised capabilities, which discarded a valid origin_hash for nodes that emit codes outside their static capabilities, causing _update_preset to fall back to a different node. Replace _find_input_device with _find_input_devices, which returns every devnode whose hash matches the origin_hash (no capability gate). Key _grab_devices by path instead of hash so colliding nodes are all grabbed, and start one read loop per devnode. Forwarding devices stay keyed by hash (one per unique hash, since colliding nodes are interchangeable there). Adds regression tests for both the hash-collision and capability-underreport cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nickbock
force-pushed
the
fix/origin-hash-node-collision
branch
from
June 27, 2026 06:46
070c36f to
2d9a6b6
Compare
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.
Fixes #1309
Problem
On a device that exposes multiple
/dev/input/event*nodes, the injector could grab and start its read loop on the wrong node, so the mapped button never produced any output. Observed with a SteelSeries Aerox 9 Wireless: the mapped button emits on one event node, but the injector read from a different one and nothing was injected. A single-interface device (e.g. a plain keyboard) was unaffected.Root cause
Device selection in
Injectorkeyed everything onget_device_hash(), which ismd5(str(capabilities) + name). Two issues:Hash collisions. That hash is not unique per devnode. Some hardware (the Aerox 9 among them) exposes several event nodes with byte-identical capabilities and name, so they produce the same
origin_hash._find_input_devicebuiltdevices_by_hash = {hash: device}and_grab_devicesreturned{hash: device}— both collapse colliding nodes to a single entry, so only one twin was grabbed/read. If the event is emitted on the other twin, it is silently missed.Over-strict capability gate.
_find_input_deviceadditionally required the mappedcodeto be present in the node's advertised capabilities. Nodes that emit codes outside their static capabilities had their validorigin_hashdiscarded, and_update_presetthen overwrote it via the device-type fallback — pointing the injector at a different node.Fix
_find_input_devicewith_find_input_devices, returning every devnode whose hash matches theorigin_hash(and dropping the capability gate — a matching hash already guarantees identical capabilities to recording time, so the recorded node is authoritative)._grab_devicesby path instead of hash, so colliding nodes are all grabbed.run(). Forwarding devices stay keyed by hash (one per unique hash; colliding nodes are interchangeable for forwarding).When
origin_hashis absent or matches nothing (old presets / configs moved between machines), behavior is unchanged: the existing_find_input_device_fallbackstill runs.Tests
Adds two regression tests in
tests/unit/test_injector.py:test_grabs_all_nodes_sharing_an_origin_hash— two hash-identical nodes are both grabbed (the Aerox 9 case).test_reads_from_origin_hash_node_on_multi_interface_device— a node that under-reports the mapped code in its capabilities is still honored, not overwritten by the fallback.Both fail on
mainand pass with the fix.test_injector.py,test_event_pipeline,test_groups,test_event_readerandtest_contextpass (the two pre-existing subprocess-spawning failures intest_injectorfail identically onmainand are unrelated to this change).Verified live on the affected SteelSeries Aerox 9 Wireless: the mapped button now works.
🤖 Generated with Claude Code