fix: use openrgb static mode + SDK client to stop crashes on login

direct-mode color apply aborts (vector OOB → SIGABRT) on ASUS Aura
devices that expose addressable zones with zero LEDs; switch to static
mode, which applies the same color across all devices/zones without
hitting that path.

Also connect to the running OpenRGB SDK server via --client host:port
instead of re-detecting all hardware on every toggle. This wires up the
previously-unused openrgb.host/port config fields and removes a full
USB/HID re-scan per command — that re-scan racing the server is what made
it flakiest at login/boot. The existing retry logic now serves its
intended purpose (waiting for the server to come up if the daemon starts
first) rather than re-running a guaranteed-crashing command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 20:35:11 +03:00
parent 182aa34008
commit cc890824bd
3 changed files with 29 additions and 14 deletions

View File

@@ -19,7 +19,7 @@ func New(cfg *config.Config) (*Controller, error) {
c := &Controller{cfg: cfg} c := &Controller{cfg: cfg}
if cfg.OpenRGB.Enabled { if cfg.OpenRGB.Enabled {
client, err := openrgb.Connect() client, err := openrgb.Connect(cfg.OpenRGB.Host, cfg.OpenRGB.Port)
if err != nil { if err != nil {
log.Printf("warning: openrgb unavailable: %v", err) log.Printf("warning: openrgb unavailable: %v", err)
} else { } else {
@@ -59,7 +59,7 @@ func (c *Controller) SetLEDs(on bool) error {
func (c *Controller) setOpenRGB(on bool) error { func (c *Controller) setOpenRGB(on bool) error {
if c.orgb == nil { if c.orgb == nil {
client, err := openrgb.Connect() client, err := openrgb.Connect(c.cfg.OpenRGB.Host, c.cfg.OpenRGB.Port)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -3,7 +3,9 @@ package openrgb
import ( import (
"fmt" "fmt"
"log" "log"
"net"
"os/exec" "os/exec"
"strconv"
"time" "time"
) )
@@ -12,24 +14,30 @@ const (
retryDelay = 3 * time.Second 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 { 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") bin, err := exec.LookPath("openrgb")
if err != nil { if err != nil {
return nil, fmt.Errorf("openrgb not found in PATH: %w", err) return nil, fmt.Errorf("openrgb not found in PATH: %w", err)
} }
// Verify openrgb can list devices (confirms it's running/accessible) server := net.JoinHostPort(host, strconv.Itoa(port))
cmd := exec.Command(bin, "--list-devices")
// 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 { 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 { 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 // run executes an openrgb command with retries to handle transient failures
// during early boot when device enumeration may not be complete. // during early boot when device enumeration may not be complete.
func (c *Client) run(args ...string) error { func (c *Client) run(args ...string) error {
full := append([]string{"--client", c.server}, args...)
var lastErr error var lastErr error
for attempt := range maxRetries { for attempt := range maxRetries {
cmd := exec.Command(c.bin, args...) cmd := exec.Command(c.bin, full...)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err == nil { if err == nil {
return nil return nil
@@ -56,13 +65,19 @@ func (c *Client) run(args ...string) error {
return lastErr 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 { 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 { func (c *Client) SetAllOn(r, g, b byte) error {
color := fmt.Sprintf("%02X%02X%02X", r, g, b) color := fmt.Sprintf("%02X%02X%02X", r, g, b)
return c.run("--mode", "direct", "--color", color) return c.run("--mode", "static", "--color", color)
} }

Binary file not shown.