- Show charge/discharge power in watts, a battery graphic with charge bolt, a power history graph and the estimated time until full or remaining (respecting the 80 % charge limit) in the app, the panel widget, its tooltip and `predatorctl status` - Add packaging/arch/PKGBUILD that builds a pacman package; the kernel module ships as DKMS source and is rebuilt for every kernel - Bump version to 1.1.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
6.0 KiB
Python
152 lines
6.0 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"])
|
||
line = "Battery: %s %% (%s), AC: %s" % (b.get("capacity"), b.get("status"), "yes" if b["ac_online"] else "no")
|
||
if b.get("power_w"):
|
||
line += ", %s %.1f W" % ("charging at" if b.get("charging") else "drawing", b["power_w"])
|
||
if b.get("minutes_left") is not None:
|
||
h, m = divmod(b["minutes_left"], 60)
|
||
line += ", %d:%02d h %s" % (h, m, "until full" if b.get("charging") else "left")
|
||
print(line)
|
||
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())
|