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>
146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""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())
|