Solaar/lib/logitech_receiver
Ken Sanislo ba0b45df22
device: Support per-slot unpair on Lightspeed receivers (CLI + GUI) (#3183)
* Add CLI --slot unpair for Lightspeed receivers

Adds Receiver.force_unpair_slot(), a low-level method that writes the
RECEIVER_PAIRING unpair register (action 0x03) for a given slot regardless
of cache state, bypassing may_unpair and re_pairs gates. Intended for
clearing stale pairings on Lightspeed receivers where Solaar cannot read
the slot's pairing info or the device is no longer reachable on RF.

Extends the `solaar unpair` CLI with three new flags:
  --receiver <name>   select which receiver to target
  --slot <N>          target a specific slot number directly
  --dry-run           print what would happen without issuing the write

The --slot path is gated to Lightspeed receivers only (by receiver_kind)
so Unifying/Bolt/Nano behavior is unchanged. It populates the cache first
and prints the current slot contents so the user can confirm what is
about to be cleared, but does not refuse based on active/offline state —
the explicit --slot N is treated as sufficient intent.

Verified end-to-end on a C547 dual-slot Lightspeed receiver: stale slot
cleared, RECEIVER_INFO sub-registers 0x21/0x31 went to None, connection
count register dropped from 2 to 1, running solaar daemon picked up the
change in real time via the existing DJ pairing notification hook.

Covered by 5 unit tests against a mocked Receiver: empty slot, stale
sentinel, active device invalidation, register write failure, closed
handle.

* Enable GUI unpair for Lightspeed receivers

Flip _lightspeed_receiver() to may_unpair: True so the GUI unpair button
becomes sensitive for Lightspeed-paired devices, and route the GUI unpair
action through _unpair_device(n, force=True) so the unpair register write
actually fires instead of short-circuiting to cache invalidation.

The previous GUI path called `del receiver[n]`, which dispatches to
Receiver.__delitem__ → _unpair_device(n, force=False). On receivers with
re_pairs=True (Lightspeed, Nano) that hits the cache-invalidation branch
at receiver.py:391 and never writes the unpair register — a "fake unpair"
that would have left the slot bound on the hardware even after the button
was enabled.

With force=True, the GUI now issues RECEIVER_PAIRING action 0x03 for the
selected slot, matching the CLI unpair path (cli/unpair.py:39) which has
always used force=True. Lightspeed and Unifying unpair behavior are now
symmetric: the button is enabled, the confirmation dialog is shown, and
the register write is performed.

The pair/add flow is untouched: it still uses set_lock(device=0) which
lets the receiver firmware pick an empty slot, re_pairs remains True so
the listener's silent-replace branch continues to handle re-pair into an
occupied devnumber. Verified on dual-slot C547 hardware that pairing into
an empty slot preserves the occupant of the other slot.

Stale pairings where Solaar can't enumerate the slot (no cached device
row to right-click) still require the --slot CLI from the preceding
commit — that path is orthogonal to this GUI enablement.

* Apply suggestion from @pfps

Lightspeed receivers don't appear to re-pair.

---------

Co-authored-by: Peter F. Patel-Schneider <pfpschneider@gmail.com>
2026-04-17 09:34:58 -04:00
..
__init__.py Refactor: Distinguish module from package 2024-09-15 09:18:51 -04:00
base.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
base_usb.py device: Support per-slot unpair on Lightspeed receivers (CLI + GUI) (#3183) 2026-04-17 09:34:58 -04:00
centurion.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
centurion_constants.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
common.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
descriptors.py device: fix interface for K845 2026-04-14 11:45:17 -04:00
desktop_notifications.py testing: upgrade desktop notifications tests to take notifications availability into account 2025-01-02 10:47:53 -05:00
device.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
diversion.py rules: remove use of XTest and use uinput in all cases 2026-03-08 20:58:43 -04:00
exceptions.py Use double quotes for module level docstrings 2024-06-02 09:54:21 -04:00
hidpp10.py cli: Fix crash when showing notification flags. (#3070) 2025-12-12 04:54:10 -05:00
hidpp10_constants.py device: Handle composite IntFlag.name on Python < 3.11 (#3187) 2026-04-16 11:38:25 -04:00
hidpp20.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
hidpp20_constants.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
i18n.py Unify imports in logitech package 2024-05-23 16:42:18 -04:00
listener.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
notifications.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
onboard_eq.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
receiver.py device: Support per-slot unpair on Lightspeed receivers (CLI + GUI) (#3183) 2026-04-17 09:34:58 -04:00
settings.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
settings_new.py settings: add setting for HAPTIC feature 2025-11-12 14:50:46 -05:00
settings_templates.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
settings_validator.py centurion: support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) 2026-04-14 11:43:23 -04:00
special_keys.py device: add special keys from Logitech 2025-10-16 20:57:15 -04:00