From 2c484583845d05a95e77212d585df9ad2c8ae697 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 5 Dec 2025 13:18:10 -0500 Subject: [PATCH] brightness: more aggressive ddc rescans on device changes --- core/internal/server/brightness/ddc.go | 12 +++++ core/internal/server/brightness/manager.go | 7 +++ core/internal/server/brightness/udev.go | 62 +++++++++++++++++++--- quickshell/Services/DisplayService.qml | 15 +++++- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/core/internal/server/brightness/ddc.go b/core/internal/server/brightness/ddc.go index 8241be7a..9858f918 100644 --- a/core/internal/server/brightness/ddc.go +++ b/core/internal/server/brightness/ddc.go @@ -40,6 +40,10 @@ func (b *DDCBackend) scanI2CDevices() error { return b.scanI2CDevicesInternal(false) } +func (b *DDCBackend) ForceRescan() error { + return b.scanI2CDevicesInternal(true) +} + func (b *DDCBackend) scanI2CDevicesInternal(force bool) error { b.scanMutex.Lock() defer b.scanMutex.Unlock() @@ -261,8 +265,16 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er busPath := fmt.Sprintf("/dev/i2c-%d", dev.bus) + if _, err := os.Stat(busPath); os.IsNotExist(err) { + b.devices.Delete(id) + log.Debugf("removed stale DDC device %s (bus no longer exists)", id) + return fmt.Errorf("device disconnected: %s", id) + } + fd, err := syscall.Open(busPath, syscall.O_RDWR, 0) if err != nil { + b.devices.Delete(id) + log.Debugf("removed DDC device %s (open failed: %v)", id, err) return fmt.Errorf("open i2c device: %w", err) } defer syscall.Close(fd) diff --git a/core/internal/server/brightness/manager.go b/core/internal/server/brightness/manager.go index 2e0e76e0..c88b6a51 100644 --- a/core/internal/server/brightness/manager.go +++ b/core/internal/server/brightness/manager.go @@ -89,6 +89,13 @@ func (m *Manager) initDDC() { func (m *Manager) Rescan() { log.Debug("Rescanning brightness devices...") + + if m.ddcReady && m.ddcBackend != nil { + if err := m.ddcBackend.ForceRescan(); err != nil { + log.Debugf("DDC force rescan failed: %v", err) + } + } + m.updateState() } diff --git a/core/internal/server/brightness/udev.go b/core/internal/server/brightness/udev.go index 8f5aedad..7e0fcd05 100644 --- a/core/internal/server/brightness/udev.go +++ b/core/internal/server/brightness/udev.go @@ -5,13 +5,18 @@ import ( "path/filepath" "strconv" "strings" + "sync" + "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/pilebones/go-udev/netlink" ) type UdevMonitor struct { - stop chan struct{} + stop chan struct{} + rescanMutex sync.Mutex + rescanTimer *time.Timer + rescanPending bool } func NewUdevMonitor(manager *Manager) *UdevMonitor { @@ -34,10 +39,8 @@ func (m *UdevMonitor) run(manager *Manager) { matcher := &netlink.RuleDefinitions{ Rules: []netlink.RuleDefinition{ {Env: map[string]string{"SUBSYSTEM": "backlight"}}, - // ! TODO: most drivers dont emit this for leds? - // ! inotify brightness_hw_changed works, but thn some devices dont do that... - // ! So for now the GUI just shows OSDs for leds, without reflecting actual HW value - // {Env: map[string]string{"SUBSYSTEM": "leds"}}, + {Env: map[string]string{"SUBSYSTEM": "drm"}}, + {Env: map[string]string{"SUBSYSTEM": "i2c"}}, }, } if err := matcher.Compile(); err != nil { @@ -49,7 +52,7 @@ func (m *UdevMonitor) run(manager *Manager) { errs := make(chan error) conn.Monitor(events, errs, matcher) - log.Info("Udev monitor started for backlight/leds events") + log.Info("Udev monitor started for backlight/drm/i2c events") for { select { @@ -75,11 +78,54 @@ func (m *UdevMonitor) handleEvent(manager *Manager, event netlink.UEvent) { sysname := filepath.Base(devpath) action := string(event.Action) + switch subsystem { + case "drm", "i2c": + m.handleDisplayEvent(manager, action, subsystem, sysname) + case "backlight": + m.handleBacklightEvent(manager, action, sysname) + } +} + +func (m *UdevMonitor) handleDisplayEvent(manager *Manager, action, subsystem, sysname string) { + switch action { + case "add", "remove", "change": + log.Debugf("Udev %s event: %s:%s - queueing DDC rescan", action, subsystem, sysname) + m.debouncedRescan(manager) + } +} + +func (m *UdevMonitor) debouncedRescan(manager *Manager) { + m.rescanMutex.Lock() + defer m.rescanMutex.Unlock() + + m.rescanPending = true + + if m.rescanTimer != nil { + m.rescanTimer.Reset(2 * time.Second) + return + } + + m.rescanTimer = time.AfterFunc(2*time.Second, func() { + m.rescanMutex.Lock() + pending := m.rescanPending + m.rescanPending = false + m.rescanMutex.Unlock() + + if !pending { + return + } + + log.Debug("Executing debounced DDC rescan") + manager.Rescan() + }) +} + +func (m *UdevMonitor) handleBacklightEvent(manager *Manager, action, sysname string) { switch action { case "change": - m.handleChange(manager, subsystem, sysname) + m.handleChange(manager, "backlight", sysname) case "add", "remove": - log.Debugf("Udev %s event: %s:%s - triggering rescan", action, subsystem, sysname) + log.Debugf("Udev %s event: backlight:%s - triggering rescan", action, sysname) manager.Rescan() } } diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml index 507e2bf2..63284d61 100644 --- a/quickshell/Services/DisplayService.qml +++ b/quickshell/Services/DisplayService.qml @@ -752,15 +752,28 @@ Singleton { Timer { id: screenChangeRescanTimer + property int rescanAttempt: 0 interval: 3000 repeat: false - onTriggered: rescanDevices() + onTriggered: { + rescanDevices(); + rescanAttempt++; + if (rescanAttempt < 3) { + interval = rescanAttempt === 1 ? 5000 : 8000; + restart(); + } else { + rescanAttempt = 0; + interval = 3000; + } + } } Connections { target: Quickshell function onScreensChanged() { + screenChangeRescanTimer.rescanAttempt = 0; + screenChangeRescanTimer.interval = 3000; screenChangeRescanTimer.restart(); } }