Initial release
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user