package openrgb import ( "fmt" "log" "os/exec" "time" ) const ( maxRetries = 4 retryDelay = 3 * time.Second ) // Client controls RGB devices via the openrgb CLI. type Client struct { bin string } func Connect() (*Client, error) { bin, err := exec.LookPath("openrgb") if err != nil { return nil, fmt.Errorf("openrgb not found in PATH: %w", err) } // Verify openrgb can list devices (confirms it's running/accessible) cmd := exec.Command(bin, "--list-devices") if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("openrgb list-devices failed: %w\n%s", err, out) } return &Client{bin: bin}, nil } func (c *Client) Close() error { return nil } // run executes an openrgb command with retries to handle transient failures // during early boot when device enumeration may not be complete. func (c *Client) run(args ...string) error { var lastErr error for attempt := range maxRetries { cmd := exec.Command(c.bin, args...) out, err := cmd.CombinedOutput() if err == nil { return nil } lastErr = fmt.Errorf("openrgb %v: %w\n%s", args, err, out) if attempt < maxRetries-1 { log.Printf("openrgb command failed (attempt %d/%d), retrying in %v: %v", attempt+1, maxRetries, retryDelay, lastErr) time.Sleep(retryDelay) } } return lastErr } // SetAllOff sets all devices to black (off) using direct mode. func (c *Client) SetAllOff() error { return c.run("--mode", "direct", "--color", "000000") } // SetAllOn sets all devices to the given color. func (c *Client) SetAllOn(r, g, b byte) error { color := fmt.Sprintf("%02X%02X%02X", r, g, b) return c.run("--mode", "direct", "--color", color) }