From bcba5f33fc997660ee4305d76b3a8948954f4b5c Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Tue, 17 Mar 2026 01:36:36 +0300 Subject: [PATCH] OpenRGB dedup fix, device card URL badge overflow fix - Replace threshold-based dedup with exact equality check in OpenRGB client; threshold dedup caused animation stutter at low software brightness - Add brightness_control capability to OpenRGB provider (software simulated) - Fix device card URL/COM badge overlapping close button: badge stays inside card-title flex container, both name and badge truncate with ellipsis Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/wled_controller/core/devices/openrgb_client.py | 8 ++++---- server/src/wled_controller/static/css/cards.css | 9 +++++++++ server/src/wled_controller/static/js/features/devices.js | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/server/src/wled_controller/core/devices/openrgb_client.py b/server/src/wled_controller/core/devices/openrgb_client.py index 7c3324b..cd8f2c1 100644 --- a/server/src/wled_controller/core/devices/openrgb_client.py +++ b/server/src/wled_controller/core/devices/openrgb_client.py @@ -96,7 +96,7 @@ class OpenRGBLEDClient(LEDClient): self._send_pending: Optional[Tuple[np.ndarray, int]] = None # (pixels, brightness) self._send_thread: Optional[threading.Thread] = None self._send_stop = threading.Event() - self._last_sent_pixels: Optional[np.ndarray] = None # for change-threshold dedup + self._last_sent_pixels: Optional[np.ndarray] = None async def connect(self) -> bool: """Connect to OpenRGB server and access the target device.""" @@ -295,10 +295,10 @@ class OpenRGBLEDClient(LEDClient): # Change-threshold dedup — compare RAW pixels before brightness scaling # so low brightness doesn't crush differences below the threshold. - # GPU I2C/SMBus writes cause system-wide stalls; minimizing writes is critical. + # Exact-match dedup — skip only if pixels are identical to last sent frame. + # Threshold-based dedup caused stutter at low brightness. if self._last_sent_pixels is not None and self._last_sent_pixels.shape == pixel_array.shape: - diff = np.mean(np.abs(pixel_array.astype(np.int16) - self._last_sent_pixels.astype(np.int16))) - if diff < 2.0: + if np.array_equal(pixel_array, self._last_sent_pixels): return self._last_sent_pixels = pixel_array.copy() diff --git a/server/src/wled_controller/static/css/cards.css b/server/src/wled_controller/static/css/cards.css index 17a8330..022065e 100644 --- a/server/src/wled_controller/static/css/cards.css +++ b/server/src/wled_controller/static/css/cards.css @@ -451,6 +451,15 @@ body.cs-drag-active .card-drag-handle { font-family: monospace; text-decoration: none; transition: background 0.2s; + white-space: nowrap; + flex-shrink: 1; + overflow: hidden; + min-width: 0; +} + +.device-url-text { + overflow: hidden; + text-overflow: ellipsis; } .device-url-badge:hover { diff --git a/server/src/wled_controller/static/js/features/devices.js b/server/src/wled_controller/static/js/features/devices.js index d9e3245..3a95dae 100644 --- a/server/src/wled_controller/static/js/features/devices.js +++ b/server/src/wled_controller/static/js/features/devices.js @@ -156,7 +156,7 @@ export function createDeviceCard(device) {
- ${device.name || device.id} + ${device.name || device.id} ${device.url && device.url.startsWith('http') ? `${escapeHtml(device.url.replace(/^https?:\/\//, ''))}${ICON_WEB}` : (device.url && !device.url.startsWith('mock://') && !device.url.startsWith('ws://') && !device.url.startsWith('openrgb://') && !device.url.startsWith('http') ? `${escapeHtml(device.url)}` : '')} ${healthLabel}