diff --git a/controller/controller.go b/controller/controller.go index 66b93e9..ba0f59c 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -19,7 +19,7 @@ func New(cfg *config.Config) (*Controller, error) { c := &Controller{cfg: cfg} if cfg.OpenRGB.Enabled { - client, err := openrgb.Connect() + client, err := openrgb.Connect(cfg.OpenRGB.Host, cfg.OpenRGB.Port) if err != nil { log.Printf("warning: openrgb unavailable: %v", err) } else { @@ -59,7 +59,7 @@ func (c *Controller) SetLEDs(on bool) error { func (c *Controller) setOpenRGB(on bool) error { if c.orgb == nil { - client, err := openrgb.Connect() + client, err := openrgb.Connect(c.cfg.OpenRGB.Host, c.cfg.OpenRGB.Port) if err != nil { return err } diff --git a/controller/openrgb/client.go b/controller/openrgb/client.go index a256176..a078021 100644 --- a/controller/openrgb/client.go +++ b/controller/openrgb/client.go @@ -3,7 +3,9 @@ package openrgb import ( "fmt" "log" + "net" "os/exec" + "strconv" "time" ) @@ -12,24 +14,30 @@ const ( retryDelay = 3 * time.Second ) -// Client controls RGB devices via the openrgb CLI. +// 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 + bin string + server string // host:port of the OpenRGB SDK server } -func Connect() (*Client, error) { +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) } - // Verify openrgb can list devices (confirms it's running/accessible) - cmd := exec.Command(bin, "--list-devices") + 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 list-devices failed: %w\n%s", err, out) + return nil, fmt.Errorf("openrgb client connect to %s failed: %w\n%s", server, err, out) } - return &Client{bin: bin}, nil + return &Client{bin: bin, server: server}, nil } func (c *Client) Close() error { @@ -39,9 +47,10 @@ func (c *Client) Close() error { // 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, args...) + cmd := exec.Command(c.bin, full...) out, err := cmd.CombinedOutput() if err == nil { return nil @@ -56,13 +65,19 @@ func (c *Client) run(args ...string) error { return lastErr } -// SetAllOff sets all devices to black (off) using direct mode. +// 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", "direct", "--color", "000000") + return c.run("--mode", "static", "--color", "000000") } -// SetAllOn sets all devices to the given color. +// 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", "direct", "--color", color) + return c.run("--mode", "static", "--color", color) } diff --git a/led-controller b/led-controller index 6be54c0..4f63e7c 100755 Binary files a/led-controller and b/led-controller differ