diff --git a/lib/logitech_receiver/rgb_power.py b/lib/logitech_receiver/rgb_power.py index 96ae6e2f..464d0ca3 100644 --- a/lib/logitech_receiver/rgb_power.py +++ b/lib/logitech_receiver/rgb_power.py @@ -802,6 +802,17 @@ class RGBPowerManager: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: failed to wake RGB LEDs: %s", self._device, e) + # --- Suspend / Restore (used e.g. to toggle LEDs on screen lock) --- + + def suspend_leds(self): + """Force enter sleep state (e.g. on screen lock).""" + if self._state != self.SLEEPING: + self._start_sleep() + + def restore_leds(self): + """Force wake from sleep state (e.g. on screen unlock).""" + self._wake() + def _cancel_dim_timer(self): if self._dim_timer_id is not None: GLib.source_remove(self._dim_timer_id) diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index e0680820..84a7b978 100755 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -198,6 +198,8 @@ def main(): else: dbus.watch_suspend_resume(lambda: listener.ping_all(True)) + dbus.watch_screen_lock_state(listener.suspend_leds, listener.restore_leds) + configuration.defer_saves = True # allow configuration saves to be deferred # main UI event loop diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index f09a9f18..983b8314 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -30,11 +30,8 @@ from typing import Callable import gi import logitech_receiver -from logitech_receiver import base -from logitech_receiver import exceptions -from logitech_receiver import hidpp10_constants -from logitech_receiver import listener -from logitech_receiver import notifications +from logitech_receiver import base, exceptions, hidpp10_constants, listener, notifications, rgb_power, settings_templates as st +from logitech_receiver.hidpp20 import LEDEffectSetting from . import configuration from . import dbus @@ -452,6 +449,49 @@ def ping_all(resuming=False): break +def suspend_leds(): + """Turn off LEDs for all connected devices.""" + logger.info("screen locked, suspending LED lighting on all devices") + + for listener_thread in _all_listeners.values(): + devices = [listener_thread.receiver] if listener_thread.receiver.isDevice else list(listener_thread.receiver) + for dev in devices: + if not dev.online: + continue + + if mgr := rgb_power.get_manager(dev): + mgr.suspend_leds() + return + + for s in dev.settings: + if s.name in (st.Backlight.name, st.Backlight2.name, st.Backlight2Level.name): + s.write(0, save=False, volatile=True) + elif s.name.startswith(st.LEDZoneSetting.name) or s.name.startswith(st.RGBEffectSetting.name): + s.write(LEDEffectSetting(ID=0), save=False, volatile=True) + elif s.name == st.PerKeyLighting.name and s.read(): + s.write({k: 0 for k in s._value.keys()}, save=False, volatile=True) + + +def restore_leds(): + """Restore LED lighting for all connected devices to their saved states.""" + logger.info("screen unlocked, restoring LED lighting on all devices") + + for listener_thread in _all_listeners.values(): + devices = [listener_thread.receiver] if listener_thread.receiver.isDevice else list(listener_thread.receiver) + for dev in devices: + if not dev.online: + continue + + if mgr := rgb_power.get_manager(dev): + mgr.restore_leds() + return + + for s in dev.settings: + if s.name in (st.Backlight.name, st.Backlight2.name, st.Backlight2Level.name, st.PerKeyLighting.name) \ + or s.name.startswith(st.LEDZoneSetting.name) or s.name.startswith(st.RGBEffectSetting.name): + s.apply() + + _status_callback = None # GUI callback to change UI in response to changes to receiver or device status _setting_callback = None # GUI callback to change UI in response to changes to status _error_callback = None # GUI callback to report errors