package openrgb import ( "fmt" "log" "net" "os/exec" "strconv" "time" ) const ( maxRetries = 4 retryDelay = 3 * time.Second ) // Client controls RGB devices via the openrgb CLI. It talks to a running // OpenRGB SDK server (the --startminimized --server instance) rather than // re-detecting hardware on every command — local re-detection races with the // server for the USB/HID devices during login/boot and is unreliable. type Client struct { bin string server string // host:port of the OpenRGB SDK server } func Connect(host string, port int) (*Client, error) { bin, err := exec.LookPath("openrgb") if err != nil { return nil, fmt.Errorf("openrgb not found in PATH: %w", err) } server := net.JoinHostPort(host, strconv.Itoa(port)) // Verify the SDK server is reachable (confirms it's up and enumerated). cmd := exec.Command(bin, "--client", server, "--list-devices") if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("openrgb client connect to %s failed: %w\n%s", server, err, out) } return &Client{bin: bin, server: server}, 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 { full := append([]string{"--client", c.server}, args...) var lastErr error for attempt := range maxRetries { cmd := exec.Command(c.bin, full...) 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). // // Uses "static" mode, not "direct": OpenRGB's direct-mode color path crashes // (vector out-of-bounds → SIGABRT) on devices that expose addressable zones // with zero LEDs, e.g. unused ASUS Aura ARGB headers. Static mode applies the // same color across all devices and zones without hitting that path. func (c *Client) SetAllOff() error { return c.run("--mode", "static", "--color", "000000") } // SetAllOn sets all devices to the given color. See SetAllOff for why this uses // "static" mode rather than "direct". func (c *Client) SetAllOn(r, g, b byte) error { color := fmt.Sprintf("%02X%02X%02X", r, g, b) return c.run("--mode", "static", "--color", color) }