From 9ccc7231f048895a929c3c2926741335378c4542 Mon Sep 17 00:00:00 2001 From: jkhsjdhjs Date: Thu, 2 Jul 2026 20:13:59 +0200 Subject: [PATCH] feature: toggle LED lighting of connected devices with screen locking When the screen is locked, LED lighting is turned off. When the screen is unlocked, it is turned on again to its previous state. To accomplish this, the devices RGB settings are changed in a volatile manner when the screen is locked, by writing only to the device and not to the cache or configuration, causing the Solaars state and the state of the devices to become intentionally out-of-sync. When the screen is unlocked again, the local configuration values can just be applied again. --- lib/logitech_receiver/rgb_power.py | 11 +++++++ lib/solaar/gtk.py | 2 ++ lib/solaar/listener.py | 50 +++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 5 deletions(-) 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