Files
Cachy-Predator-Keyboard/predator_control/cli.py
T
MrBlakeandClaude Opus 5.5 2ef62e85cd Add charging indicator and pacman package, bump to 1.1.0
- 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>
2026-10-07 17:37:03 +02:00

152 lines
6.0 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"])
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())