178 lines
5.2 KiB
Python
178 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Interactive menu for PH18 HID RGB controls."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HEX_COLOR_RE = re.compile(r"^[0-9a-fA-F]{6}$")
|
|
CSV_COLOR_RE = re.compile(r"^\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*$")
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
HID_RGB_SCRIPT = SCRIPT_DIR / "ph18_hid_rgb.py"
|
|
|
|
|
|
def prompt_menu_choice() -> str:
|
|
"""Prompt for the main menu action."""
|
|
print("\nPH18 RGB Menu")
|
|
print("1. List detected HID devices and mappings")
|
|
print("2. Set keyboard single static color")
|
|
print("3. Set keyboard 4-zone static colors")
|
|
print("4. Switch keyboard to dynamic mode")
|
|
print("5. Set rear/logo/bar static color")
|
|
print("6. Turn rear/logo/bar off")
|
|
print("7. Set rear/logo/bar progressbar effect")
|
|
print("0. Exit")
|
|
return input("Choose an option: ").strip()
|
|
|
|
|
|
def prompt_yes_no(question: str, *, default_yes: bool = True) -> bool:
|
|
"""Prompt for a yes/no answer."""
|
|
suffix = " [Y/n]: " if default_yes else " [y/N]: "
|
|
raw = input(question + suffix).strip().lower()
|
|
if not raw:
|
|
return default_yes
|
|
return raw in {"y", "yes"}
|
|
|
|
|
|
def _valid_csv_color(value: str) -> bool:
|
|
if not CSV_COLOR_RE.match(value):
|
|
return False
|
|
try:
|
|
parts = [int(part.strip()) for part in value.split(",")]
|
|
except ValueError:
|
|
return False
|
|
return len(parts) == 3 and all(0 <= part <= 255 for part in parts)
|
|
|
|
|
|
def prompt_color(question: str) -> str:
|
|
"""Prompt for a color in hex (RRGGBB) or CSV (R,G,B)."""
|
|
while True:
|
|
raw = input(f"{question} (RRGGBB or R,G,B): ").strip()
|
|
if HEX_COLOR_RE.match(raw):
|
|
return raw.lower()
|
|
if _valid_csv_color(raw):
|
|
return raw
|
|
print("Invalid color format.")
|
|
|
|
|
|
def prompt_target() -> str:
|
|
"""Prompt for zone target."""
|
|
options = {"1": "rear", "2": "logo", "3": "bar", "4": "all"}
|
|
print("\nTarget:")
|
|
print("1. rear")
|
|
print("2. logo")
|
|
print("3. bar")
|
|
print("4. all")
|
|
while True:
|
|
choice = input("Choose target: ").strip()
|
|
target = options.get(choice)
|
|
if target:
|
|
return target
|
|
print("Invalid choice.")
|
|
|
|
|
|
def prompt_brightness() -> str:
|
|
"""Prompt for brightness byte."""
|
|
while True:
|
|
raw = input("Brightness [0-255] (default 25): ").strip()
|
|
if not raw:
|
|
return "25"
|
|
try:
|
|
value = int(raw, 10)
|
|
except ValueError:
|
|
print("Invalid number.")
|
|
continue
|
|
if 0 <= value <= 255:
|
|
return str(value)
|
|
print("Brightness must be 0..255.")
|
|
|
|
|
|
def run_hid_rgb(args: list[str]) -> int:
|
|
"""Run ph18_hid_rgb.py with provided arguments."""
|
|
cmd = [sys.executable, str(HID_RGB_SCRIPT), *args]
|
|
print(f"\n$ {' '.join(cmd)}")
|
|
result = subprocess.run(cmd, check=False)
|
|
if result.returncode != 0:
|
|
print(f"Command failed with exit code {result.returncode}")
|
|
return result.returncode
|
|
|
|
|
|
def action_keyboard_static() -> None:
|
|
color = prompt_color("Keyboard color")
|
|
args = ["keyboard", "--color", color]
|
|
if not prompt_yes_no("Apply MagKey/WASD overlay color too?", default_yes=True):
|
|
args.append("--no-magkeys")
|
|
run_hid_rgb(args)
|
|
|
|
|
|
def action_keyboard_zones() -> None:
|
|
z1 = prompt_color("Zone 1 color (left)")
|
|
z2 = prompt_color("Zone 2 color")
|
|
z3 = prompt_color("Zone 3 color")
|
|
z4 = prompt_color("Zone 4 color (right)")
|
|
args = ["keyboard-zones", "--z1", z1, "--z2", z2, "--z3", z3, "--z4", z4]
|
|
if not prompt_yes_no("Apply MagKey/WASD overlay from zone1 color?", default_yes=True):
|
|
args.append("--no-magkeys")
|
|
run_hid_rgb(args)
|
|
|
|
|
|
def action_keyboard_dynamic() -> None:
|
|
repeats = input("Dynamic repeats (default 2): ").strip() or "2"
|
|
run_hid_rgb(["keyboard-dynamic", "--repeats", repeats])
|
|
|
|
|
|
def action_zone_static() -> None:
|
|
target = prompt_target()
|
|
color = prompt_color("Zone color")
|
|
brightness = prompt_brightness()
|
|
run_hid_rgb(["zone", target, "static", "--color", color, "--brightness", brightness])
|
|
|
|
|
|
def action_zone_off() -> None:
|
|
target = prompt_target()
|
|
run_hid_rgb(["zone", target, "off"])
|
|
|
|
|
|
def action_zone_progressbar() -> None:
|
|
target = prompt_target()
|
|
run_hid_rgb(["zone", target, "progressbar"])
|
|
|
|
|
|
def main() -> int:
|
|
"""Run the interactive RGB menu."""
|
|
if not HID_RGB_SCRIPT.exists():
|
|
print(f"Missing script: {HID_RGB_SCRIPT}")
|
|
return 1
|
|
|
|
if hasattr(sys, "getuid") and sys.getuid() != 0:
|
|
print("Tip: run with sudo for write operations (e.g. `sudo python3 ph18_rgb_menu.py`).")
|
|
|
|
actions = {
|
|
"1": lambda: run_hid_rgb(["list"]),
|
|
"2": action_keyboard_static,
|
|
"3": action_keyboard_zones,
|
|
"4": action_keyboard_dynamic,
|
|
"5": action_zone_static,
|
|
"6": action_zone_off,
|
|
"7": action_zone_progressbar,
|
|
}
|
|
|
|
while True:
|
|
choice = prompt_menu_choice()
|
|
if choice == "0":
|
|
print("Bye.")
|
|
return 0
|
|
action = actions.get(choice)
|
|
if action is None:
|
|
print("Invalid option.")
|
|
continue
|
|
action()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|