From a6709d0ee41cd904e08a06b887a5b06051023fc2 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Sun, 12 Apr 2026 13:57:21 -0700 Subject: [PATCH] Enable GUI unpair for Lightspeed receivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/logitech_receiver/base_usb.py | 2 +- lib/solaar/ui/action.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index 513173da..acc2d631 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -123,7 +123,7 @@ def _lightspeed_receiver(product_id: int) -> dict: "usb_interface": 2, "receiver_kind": "lightspeed", "name": _("Lightspeed Receiver"), - "may_unpair": False, + "may_unpair": True, "re_pairs": True, } diff --git a/lib/solaar/ui/action.py b/lib/solaar/ui/action.py index 58f6dc1a..6751f92b 100644 --- a/lib/solaar/ui/action.py +++ b/lib/solaar/ui/action.py @@ -98,6 +98,10 @@ def unpair(window, device): device_number = device.number try: - del receiver[device_number] + # force=True ensures the unpair register write is issued even on + # re_pairs receivers (Lightspeed, Nano); otherwise _unpair_device + # short-circuits to cache-invalidation only and the slot stays + # bound on the hardware. + receiver._unpair_device(device_number, True) except Exception: common.error_dialog(common.ErrorReason.UNPAIR, device)