Solaar's old rotating sw_id (cycling 0x2..0xF on every request) eats
HID++ replies addressed to other userspace clients sharing the same
device, because reply matching is feature + function + sw_id only and
Solaar eventually claims every value in the range. Cooperative use
with OpenRGB, LGSTrayEx, etc. is impossible by construction.
Pick one value and hold it. Other tools can pick a different one and
filter Solaar's traffic out of their reply stream cleanly.
0x07 OpenRGB
0x0A LGSTrayEx
0x0D Logitech G HUB (host-side)
0x0F Logitech firmware (sub-device self-enumeration on wired)
0x0B is unallocated among the above and keeps the high bit set so
replies stay trivially distinguishable from notifications (sw_id=0).
Audit of why nothing breaks:
- Reply matcher in request() still works — Solaar's request loop is
synchronous per device, so (feat, func, fixed_sw_id) is enough to
identify the in-flight request's reply. The rotation never bought
uniqueness across processes; it only avoided self-collision across
successive synchronous requests, which doesn't exist as a problem.
- Ping reply identification uses a separate random mark byte
(getrandbits(8) appended to the request data, checked at byte 4 of
the reply). That randomization is unchanged.
- Stale-reply protection comes from _read_input_buffer draining the
device handle before every new write. A delayed reply from a prior
timed-out request gets routed to the notification hook, not
mistaken for the current request's reply — independent of whether
sw_id rotates.
- The "separate results and notifications" claim in the old docstring
was misleading: true notifications carry sw_id=0 per the HID++ spec.
What actually keeps replies distinguishable is the high bit being
set, which 0x0B preserves.
- Centurion bridge in device.py uses the same sw_id-as-correlation-
token pattern with the same synchronous-per-device flow; fixed sw_id
works identically.
* Treat empty hidraw read as device removal (EOF)
When select() reports a hidraw fd as readable but os.read() returns
empty bytes, that's EOF per POSIX — the device has been removed.
Previously this was silently treated as no data, causing the listener
to loop indefinitely on a gone device instead of cleaning up.
* Fix test_ping_errors missing mocks for _read_input_buffer and write
The test was not mocking _read_input_buffer or write, so ping() would
call into real hidapi.read() with a fake handle (fd 1). The empty-read
EOF detection added in the previous commit made this consistently fail
by raising OSError → NoReceiver before reaching the mocked _read path.
Add the same mocks used by the adjacent test_request_errors.
---------
Co-authored-by: Peter F. Patel-Schneider <pfpschneider@gmail.com>
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError (Fix pre-commit checks)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for request function before replacing ERROR NamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (add exclusion for macOS)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (fix for python < 3.10)
* refactor(solaar/cli./probe.py): create unit tests for run function before replacing ERROR NamedInts by IntEnum (focusing on the call order when receiving errors)
* refactor(solaar/cli./probe.py): refactor register processing to handle short and long registers in a single loop structure for improved readability and reduced code duplication.
* refactor(logitech_receiver/hidpp10_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp10_constants.py): distinguish hidpp10 and hidpp20 errors in the code for readibility.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum. (fix problem with | operator when typing with python 3.8)
* feature(hide on startup option): Visual test (not binded yet) DRAFT
* refactor(solaar/cli./probe.py): create unit tests for run function before replacing ERROR NamedInts by IntEnum (focusing on the call order when receiving errors)
* refactor(solaar/cli./probe.py): refactor register processing to handle short and long registers in a single loop structure for improved readability and reduced code duplication.
* refactor(logitech_receiver/hidpp10_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum.
* refactor(logitech_receiver/hidpp20_constants.py): replace ERROR NamedInt by IntEnum. (fix problem with | operator when typing with python 3.8)
* feature(hide on startup option): Visual test (not binded yet) DRAFT
* Merge: Refactor: hidpp20 to use enum
* Merge: Refactor: hidpp20 to use enum (fix test)
---------
Co-authored-by: some_developer <some.developper.44@gmail.com>
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError
* refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError (Fix pre-commit checks)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for request function before replacing ERROR NamedInts by IntEnum
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (add exclusion for macOS)
* refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (fix for python < 3.10)