diff --git a/README.md b/README.md index 30ed883..bf21997 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Current Stable Release: v0.3.5 ## Features - Static RGB color control +- Software rainbow cycling with adjustable speed (works on all supported devices, no OEM firmware support required) - Fine brightness scaling (0–100%) - Custom profiles - Firmware autonomous mode toggle @@ -224,6 +225,20 @@ OEM Rainbow Mode (requires sudo) sudo vrgb rainbow on sudo vrgb rainbow off +Software Rainbow Cycle (no sudo, works on all supported devices) + + vrgb cycle [percent] [period_seconds] [fps] + +*Example (full brightness, one full color spectrum every 4 seconds):* + + vrgb cycle 100 4 + +Runs in the foreground until stopped with Ctrl+C. Unlike `rainbow`, this +does not depend on OEM firmware support and gives full control over speed +and brightness. To run it continuously in the background (e.g. across +logins), manage it with a process supervisor such as a systemd `--user` +service. + Debug Mode vrgb --debug status diff --git a/vrgb.py b/vrgb.py index c1fcc1d..0da0bb4 100755 --- a/vrgb.py +++ b/vrgb.py @@ -5,6 +5,8 @@ import json import fcntl import pwd +import time +import colorsys from pathlib import Path # ===== Debug ===== @@ -675,6 +677,55 @@ def cmd_rainbow(cfg, devinfo, state): save_config(cfg) +def cmd_cycle(cfg, devinfo, percent=None, period=None, fps=None): + if percent is None: + percent = cfg["percent"] + if period is None: + period = 6.0 + if fps is None: + fps = 20.0 + + percent = clamp(int(percent), 0, 100) + intensity = percent_to_intensity(percent) + + try: + period = float(period) + fps = float(fps) + except (TypeError, ValueError): + die("period and fps must be numbers") + + if period <= 0: + die("period must be greater than 0") + if fps <= 0: + die("fps must be greater than 0") + + debug(f"cmd_cycle percent={percent} period={period} fps={fps}") + + print(f"Cycling through the color spectrum (period={period}s, {fps} fps). Press Ctrl+C to stop.") + + frame_delay = 1.0 / fps + start = time.monotonic() + + try: + set_firmware_mode(devinfo, False) + while True: + try: + hue = ((time.monotonic() - start) / period) % 1.0 + r, g, b = (round(c * 255) for c in colorsys.hsv_to_rgb(hue, 1.0, 1.0)) + set_color(devinfo, r, g, b, intensity) + except OSError as e: + # The hidraw node can briefly disappear or re-enumerate + # around suspend/resume; reacquire it and keep cycling. + debug(f"cmd_cycle lost device ({e}); reacquiring") + time.sleep(1.0) + devinfo = find_device() + set_firmware_mode(devinfo, False) + continue + time.sleep(frame_delay) + except KeyboardInterrupt: + print("\nStopped cycling.") + + def cmd_off(cfg, devinfo): r, g, b = hex_to_rgb(cfg["color"]) debug("cmd_off") @@ -728,6 +779,7 @@ def main(): vrgb brightness 0-100 vrgb auto on|off vrgb rainbow on|off + vrgb cycle [percent] [period_seconds] [fps] vrgb off vrgb restore vrgb profile save NAME @@ -752,6 +804,7 @@ def main(): "brightness", "auto", "rainbow", + "cycle", "off", "restore", "profile", @@ -790,6 +843,13 @@ def main(): devinfo = find_device() cmd_rainbow(cfg, devinfo, args[1]) + elif cmd == "cycle": + devinfo = find_device() + percent = args[1] if len(args) > 1 else None + period = args[2] if len(args) > 2 else None + fps = args[3] if len(args) > 3 else None + cmd_cycle(cfg, devinfo, percent, period, fps) + elif cmd == "off": devinfo = find_device() cmd_off(cfg, devinfo)