Files
Cachy-Predator-Keyboard/predator_control/cli.py
T
MrBlakeandClaude Opus 5.5 cc4fdb9cd0 Add built-in updater, bump to 1.2.0
- predatord checks the Gitea releases every 6 hours, downloads the
  newest pacman package, verifies its SHA-256 from the release notes
  and installs it with `pacman -U` in a separate systemd unit
- Update notice and install button on the System page, in the sidebar
  and in the panel widget; `predatorctl update [--check] [--yes]`
- Optional API token for private repositories
- packaging/release.py builds and publishes releases in the format
  the updater expects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 17:42:02 +02:00

171 lines
7.1 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("update", help="check for and install updates")
sp.add_argument("--check", action="store_true", help="only check, do not install")
sp.add_argument("--yes", "-y", action="store_true", help="install without asking")
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 == "update":
u = c.call("check_update")
if not u["available"]:
print("Predator Control %s is up to date." % u["current"])
return 0
print("Update available: %s -> %s\n%s" % (u["current"], u["latest"], u["url"]))
if a.check:
return 0
if not u["can_install"]:
print("Automatic install needs the pacman package. Update with: git pull && sudo ./install.sh")
return 1
if not a.yes and sys.stdin.isatty() and input("Install now? [Y/n] ").strip().lower() not in ("", "y", "yes"):
return 0
c.call("install_update")
print("Installing %s in the background (journalctl -fu predator-control-update)." % u["asset"])
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())