Add power settings for the lighting, bump to 1.4.0
New Power page: - toggle for the firmware's 30 s keyboard backlight timeout - idle timeout for keyboard and lightbar, separately for AC and battery; predatord watches all input devices and restores the lighting on the next input - battery rules: dimmed keyboard brightness, keyboard off, lightbar off The rules only change what is sent to the hardware; the saved lighting settings stay untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ from . import CONFIG_DIR, CONFIG_PATH, SOCKET_PATH, STATE_PATH, __version__
|
||||
from . import hardware as hw
|
||||
from . import updater
|
||||
from .hotkey import HotkeyListener, launch_in_session
|
||||
from .idle import IdleWatcher
|
||||
from .keyboard import EFFECTS, NUM_CELLS, Keyboard
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
@@ -51,6 +52,13 @@ DEFAULT_CONFIG = {
|
||||
"keyboard_presets": {},
|
||||
"updates": {"auto_check": True, "interval_hours": 6},
|
||||
"predator_key": "open", # open | cycle_profile | none
|
||||
"power": {
|
||||
"idle_off_ac": 0, # seconds without input until lighting turns off, 0 = never
|
||||
"idle_off_battery": 120,
|
||||
"battery_brightness": 100, # keyboard brightness on battery, % of the normal value
|
||||
"battery_lights_off": False,
|
||||
"battery_lightbar_off": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +108,9 @@ class Daemon:
|
||||
self.update_info = None
|
||||
self.update_error = None
|
||||
self._update_checking = False
|
||||
self.idle = IdleWatcher(self._activity)
|
||||
self.lights_idle = False
|
||||
self.on_ac = True
|
||||
|
||||
# ---------------------------------------------------------- Configuration
|
||||
def _load_config(self):
|
||||
@@ -132,8 +143,35 @@ class Daemon:
|
||||
self.errors[key] = str(e)
|
||||
return None
|
||||
|
||||
# ---------------------------------------------------------- Power rules
|
||||
def keyboard_effective(self):
|
||||
k = dict(self.config["keyboard"])
|
||||
p = self.config["power"]
|
||||
if self.lights_idle or (not self.on_ac and p.get("battery_lights_off")):
|
||||
k["effect"] = "off"
|
||||
elif not self.on_ac and p.get("battery_brightness", 100) < 100:
|
||||
k["brightness"] = int(k["brightness"] * p["battery_brightness"] / 100)
|
||||
return k
|
||||
|
||||
def idle_timeout(self):
|
||||
p = self.config["power"]
|
||||
return int(p.get("idle_off_ac" if self.on_ac else "idle_off_battery") or 0)
|
||||
|
||||
def update_idle(self):
|
||||
t = self.idle_timeout()
|
||||
idle = t > 0 and self.idle.idle_seconds() >= t
|
||||
if idle != self.lights_idle:
|
||||
self.lights_idle = idle
|
||||
log("Lighting %s" % ("off (idle)" if idle else "restored"))
|
||||
self.apply_keyboard()
|
||||
|
||||
def _activity(self):
|
||||
if self.lights_idle:
|
||||
with self.lock:
|
||||
self.update_idle()
|
||||
|
||||
def apply_keyboard(self, cfg=None):
|
||||
k = cfg or self.config["keyboard"]
|
||||
k = cfg or self.keyboard_effective()
|
||||
frame = bytes.fromhex(k.get("frame") or "") if k["effect"] == "perkey" else None
|
||||
self._try("keyboard", self.kbd.apply, k["effect"], hex_to_rgb(k["color"]),
|
||||
k["brightness"], frame, int(k.get("effect_id") or 0))
|
||||
@@ -142,8 +180,11 @@ class Daemon:
|
||||
|
||||
def lightbar_effective(self):
|
||||
lb = dict(self.config["lightbar"])
|
||||
if self.lights_idle or (not self.on_ac and self.config["power"].get("battery_lightbar_off")):
|
||||
lb["mode"] = "off"
|
||||
return lb
|
||||
if lb.get("sync_keyboard"):
|
||||
k = self.config["keyboard"]
|
||||
k = self.keyboard_effective()
|
||||
lb["brightness"] = k["brightness"]
|
||||
if k["effect"] == "off":
|
||||
lb["mode"] = "off"
|
||||
@@ -179,6 +220,9 @@ class Daemon:
|
||||
def restore(self):
|
||||
log("Restoring settings")
|
||||
bat = hw.battery()
|
||||
self.on_ac = bat.get("ac_online", True)
|
||||
self.idle.last_input = time.monotonic() # e.g. after resume: lights on
|
||||
self.lights_idle = False
|
||||
if self.config["profile"].get("auto_switch"):
|
||||
self.apply_profile_for_power(bat.get("ac_online", True))
|
||||
self.apply_fans()
|
||||
@@ -237,6 +281,7 @@ class Daemon:
|
||||
"lightbar_effective": self.lightbar_effective(),
|
||||
"fan_curve_applied": self.curve_applied,
|
||||
"battery_limit": self.battery_limit,
|
||||
"lights_idle": self.lights_idle,
|
||||
"update": self.update_info,
|
||||
"update_error": self.update_error,
|
||||
"update_status": updater.status(),
|
||||
@@ -308,7 +353,11 @@ class Daemon:
|
||||
log("Power source changed:", "AC" if ac else "battery")
|
||||
if self.config["profile"].get("auto_switch"):
|
||||
self.apply_profile_for_power(ac)
|
||||
if ac != self.on_ac:
|
||||
self.on_ac = ac
|
||||
self.apply_keyboard() # battery lighting rules
|
||||
self.last_ac = ac
|
||||
self.update_idle()
|
||||
self.fan_curve_tick()
|
||||
self._ticks += 1
|
||||
if self._ticks % 20 == 1:
|
||||
@@ -342,6 +391,17 @@ class Daemon:
|
||||
merge(self.config["updates"], {k: a[k] for k in ("auto_check", "interval_hours") if k in a})
|
||||
self.save_config()
|
||||
return {}
|
||||
if cmd == "set_power":
|
||||
p = self.config["power"]
|
||||
for key in ("idle_off_ac", "idle_off_battery", "battery_brightness",
|
||||
"battery_lights_off", "battery_lightbar_off"):
|
||||
if key in a:
|
||||
p[key] = a[key]
|
||||
p["battery_brightness"] = max(0, min(100, int(p["battery_brightness"])))
|
||||
self.save_config()
|
||||
self.update_idle()
|
||||
self.apply_keyboard()
|
||||
return {}
|
||||
if cmd == "set_predator_key":
|
||||
if a["action"] not in ("open", "cycle_profile", "none"):
|
||||
raise ValueError("unknown action")
|
||||
@@ -532,6 +592,7 @@ def main():
|
||||
signal.signal(signal.SIGUSR1, lambda *_: threading.Thread(target=lambda: (d.lock.acquire(), d.restore(), d.lock.release())).start())
|
||||
threading.Thread(target=d.loop, daemon=True).start()
|
||||
HotkeyListener(d.predator_key, log).start()
|
||||
d.idle.start()
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user