diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index e72a69c4..15f9a9cc 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -189,11 +189,12 @@ class Receiver: def close(self): if logger.isEnabledFor(logging.INFO): logger.info("%s: closing - handle %s %s", self, type(self.handle), self.handle) - handle, self.handle = self.handle, None + # Close paired devices first: their cleanup writes go over this receiver's handle. for _n, d in self._devices.items(): if d: d.close() self._devices.clear() + handle, self.handle = self.handle, None return handle and self.low_level.close(handle) def __del__(self): diff --git a/lib/logitech_receiver/rgb_power.py b/lib/logitech_receiver/rgb_power.py index 96ae6e2f..ce01075c 100644 --- a/lib/logitech_receiver/rgb_power.py +++ b/lib/logitech_receiver/rgb_power.py @@ -338,8 +338,8 @@ def cleanup(device): device.feature_request(SupportedFeature.PROFILE_MANAGEMENT, 0x60, b"\x03") elif device.features and SupportedFeature.ONBOARD_PROFILES in device.features: device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x10, b"\x01") - except Exception: - pass # Device may already be offline + except Exception as e: + logger.debug("%s: cleanup release writes failed (device offline?): %s", device, e) if getattr(device, "_rgb_has_shutdown_cap", False): try: # SetRgbPowerMode(set=1, mode=0) — firmware off transition. diff --git a/tests/logitech_receiver/test_receiver.py b/tests/logitech_receiver/test_receiver.py index 2e8775ec..7af920eb 100644 --- a/tests/logitech_receiver/test_receiver.py +++ b/tests/logitech_receiver/test_receiver.py @@ -369,3 +369,21 @@ def test_force_unpair_slot_no_handle(): assert r.force_unpair_slot(2) is False r._unpair_device_per_receiver.assert_not_called() + + +def test_close_keeps_handle_until_paired_devices_closed(): + """Paired-device cleanup writes go over the receiver's handle, so + Receiver.close must close its devices before dropping it.""" + device_info = DeviceInfo("11") + mock_low_level = LowLevelInterfaceFake(responses_unifying) + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + handle_at_device_close = [] + dev = mock.Mock() + dev.close.side_effect = lambda: handle_at_device_close.append(r.handle) + r._devices[1] = dev + + r.close() + + assert handle_at_device_close == [fake_hidpp.open_path("11")] + assert r.handle is None + assert r._devices == {}