Files
Cachy-Predator-Keyboard/predator_control/cli.py
T
MrBlakeandClaude Opus 5.5 ea7ce49452 Initial release of Predator Control 1.0.0
PredatorSense replacement for the Acer Predator Helios 16 (PH16-71) on Linux:

- predatord: root service for profiles, fans (incl. temperature curves),
  keyboard RGB, lightbar and battery health; restores settings at boot
  and after resume, switches profiles on AC/battery changes
- Qt desktop app with dashboard, fan curve editor and per-key RGB studio
- Plasma 6 panel widget with quick controls
- predatorctl command line tool
- predator_wmi kernel module (DKMS) as a minimal WMI method bridge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:38:14 +02:00

146 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""predatorctl – command line interface for Predator Control.
Examples:
predatorctl status
predatorctl profile performance
predatorctl fans max | auto | custom 60 70 | curve
predatorctl kbd static '#ff0000' --brightness 255
predatorctl kbd off
predatorctl lightbar wave --color '#00ffcc' --speed 7
predatorctl battery-limit on
predatorctl open rgb (opens the main window on a given page)
"""
import argparse
import json
import os
import subprocess
import sys
from .client import Client, DaemonError, read_state
def _bool(v):
return v.lower() in ("1", "on", "true", "yes")
def main(argv=None):
p = argparse.ArgumentParser(prog="predatorctl", description="Predator Control CLI")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("status", help="show status")
sp = sub.add_parser("state", help="raw state as JSON")
sp.add_argument("--cached", action="store_true", help="read state.json instead of asking the daemon")
sub.add_parser("restore", help="re-apply saved settings")
sp = sub.add_parser("profile", help="set power profile")
sp.add_argument("profile")
sp = sub.add_parser("profile-cycle", help="switch to the next profile")
sp = sub.add_parser("fans", help="fan mode")
sp.add_argument("mode", choices=["auto", "max", "custom", "curve"])
sp.add_argument("cpu", nargs="?", type=int)
sp.add_argument("gpu", nargs="?", type=int)
sp = sub.add_parser("kbd", help="keyboard lighting (effect name, 'on' or 'off')")
sp.add_argument("effect")
sp.add_argument("color", nargs="?")
sp.add_argument("--brightness", "-b", type=int)
sp = sub.add_parser("kbd-brightness", help="keyboard brightness 0-255 or +/-step")
sp.add_argument("value")
sp = sub.add_parser("preset", help="load a keyboard preset")
sp.add_argument("name")
sp = sub.add_parser("lightbar", help="Lightbar")
sp.add_argument("mode", nargs="?")
sp.add_argument("--color", "-c")
sp.add_argument("--brightness", "-b", type=int)
sp.add_argument("--speed", "-s", type=int)
sp.add_argument("--sync", choices=["on", "off"])
sp = sub.add_parser("battery-limit", help="80 %% charge limit")
sp.add_argument("value", choices=["on", "off"])
sp = sub.add_parser("open", help="open the main window")
sp.add_argument("page", nargs="?", default="home")
a = p.parse_args(argv)
if a.cmd == "open":
subprocess.Popen(["predator-control", "--page", a.page], start_new_session=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return 0
if a.cmd == "state" and a.cached:
print(json.dumps(read_state()))
return 0
c = Client()
try:
if a.cmd == "status":
st = c.call("state")
s, b = st["sensors"], st["battery"]
lbl = st["profile_labels"].get(st["profile"], st["profile"])
print("Profile: %s" % lbl)
print("CPU: %s °C %s %% fan %s RPM" % (s["cpu_temp"], st["cpu_load"], s["cpu_fan"]))
print("GPU: %s fan %s RPM" % ("%s °C" % s["gpu_temp"] if s["gpu_temp"] else "asleep", s["gpu_fan"]))
print("Fan mode: %s" % st["config"]["fans"]["mode"])
print("Battery: %s %% (%s), AC: %s" % (b.get("capacity"), b.get("status"), "yes" if b["ac_online"] else "no"))
k = st["config"]["keyboard"]
print("Keyboard: %s %s brightness %s" % (k["effect"], k["color"], k["brightness"]))
for k, v in st["errors"].items():
print("Error [%s]: %s" % (k, v))
elif a.cmd == "state":
print(json.dumps(c.call("state")))
elif a.cmd == "restore":
c.call("restore")
elif a.cmd == "profile":
c.call("set_profile", profile=a.profile)
elif a.cmd == "profile-cycle":
st = c.call("state")
ch = st["profile_choices"]
nxt = ch[(ch.index(st["profile"]) + 1) % len(ch)] if st["profile"] in ch else ch[0]
c.call("set_profile", profile=nxt)
print(st["profile_labels"].get(nxt, nxt))
elif a.cmd == "fans":
args = {"mode": a.mode}
if a.cpu is not None:
args["cpu"] = a.cpu
if a.gpu is not None:
args["gpu"] = a.gpu
c.call("set_fans", **args)
elif a.cmd == "kbd":
args = {"effect": a.effect}
if a.color:
args["color"] = a.color if a.color.startswith("#") else "#" + a.color
if a.brightness is not None:
args["brightness"] = a.brightness
c.call("set_keyboard", **args)
elif a.cmd == "kbd-brightness":
cur = c.call("state")["config"]["keyboard"]["brightness"]
v = a.value
val = cur + int(v) if v[0] in "+-" else int(v)
c.call("set_keyboard", brightness=max(0, min(255, val)))
elif a.cmd == "preset":
c.call("load_preset", name=a.name)
elif a.cmd == "lightbar":
args = {"mode": a.mode} if a.mode else {}
if a.color:
args["color"] = a.color if a.color.startswith("#") else "#" + a.color
if a.brightness is not None:
args["brightness"] = a.brightness
if a.speed is not None:
args["speed"] = a.speed
if a.sync:
args["sync_keyboard"] = a.sync == "on"
c.call("set_lightbar", **args)
elif a.cmd == "battery-limit":
c.call("set_battery_limit", enabled=_bool(a.value))
except DaemonError as e:
print("Error: %s" % e, file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())