diff --git a/core/internal/server/brightness/ddc.go b/core/internal/server/brightness/ddc.go index 9a0aef4c..8241be7a 100644 --- a/core/internal/server/brightness/ddc.go +++ b/core/internal/server/brightness/ddc.go @@ -37,25 +37,18 @@ func NewDDCBackend() (*DDCBackend, error) { } func (b *DDCBackend) scanI2CDevices() error { - b.scanMutex.Lock() - lastScan := b.lastScan - b.scanMutex.Unlock() - - if time.Since(lastScan) < b.scanInterval { - return nil - } + return b.scanI2CDevicesInternal(false) +} +func (b *DDCBackend) scanI2CDevicesInternal(force bool) error { b.scanMutex.Lock() defer b.scanMutex.Unlock() - if time.Since(b.lastScan) < b.scanInterval { + if !force && time.Since(b.lastScan) < b.scanInterval { return nil } - b.devices.Range(func(key string, value *ddcDevice) bool { - b.devices.Delete(key) - return true - }) + activeBuses := make(map[int]bool) for i := 0; i < 32; i++ { busPath := fmt.Sprintf("/dev/i2c-%d", i) @@ -68,17 +61,31 @@ func (b *DDCBackend) scanI2CDevices() error { continue } + activeBuses[i] = true + id := fmt.Sprintf("ddc:i2c-%d", i) + + if _, exists := b.devices.Load(id); exists { + continue + } + dev, err := b.probeDDCDevice(i) if err != nil || dev == nil { continue } - id := fmt.Sprintf("ddc:i2c-%d", i) dev.id = id b.devices.Store(id, dev) log.Debugf("found DDC device on i2c-%d", i) } + b.devices.Range(func(id string, dev *ddcDevice) bool { + if !activeBuses[dev.bus] { + b.devices.Delete(id) + log.Debugf("removed DDC device %s (bus no longer exists)", id) + } + return true + }) + b.lastScan = time.Now() return nil @@ -187,6 +194,13 @@ func (b *DDCBackend) SetBrightness(id string, value int, exponential bool, callb func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error { _, ok := b.devices.Load(id) + if !ok { + if err := b.scanI2CDevicesInternal(true); err != nil { + log.Debugf("rescan failed for %s: %v", id, err) + } + _, ok = b.devices.Load(id) + } + if !ok { return fmt.Errorf("device not found: %s", id) } @@ -234,6 +248,13 @@ func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error { dev, ok := b.devices.Load(id) + if !ok { + if err := b.scanI2CDevicesInternal(true); err != nil { + log.Debugf("rescan failed for %s: %v", id, err) + } + dev, ok = b.devices.Load(id) + } + if !ok { return fmt.Errorf("device not found: %s", id) } diff --git a/core/internal/server/brightness/udev.go b/core/internal/server/brightness/udev.go index e455582e..8f5aedad 100644 --- a/core/internal/server/brightness/udev.go +++ b/core/internal/server/brightness/udev.go @@ -34,7 +34,10 @@ func (m *UdevMonitor) run(manager *Manager) { matcher := &netlink.RuleDefinitions{ Rules: []netlink.RuleDefinition{ {Env: map[string]string{"SUBSYSTEM": "backlight"}}, - {Env: map[string]string{"SUBSYSTEM": "leds"}}, + // ! 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"}}, }, } if err := matcher.Compile(); err != nil { diff --git a/quickshell/Common/SessionData.qml b/quickshell/Common/SessionData.qml index 8f276ef7..a8cf6a85 100644 --- a/quickshell/Common/SessionData.qml +++ b/quickshell/Common/SessionData.qml @@ -30,6 +30,11 @@ Singleton { onTriggered: root.suppressOSD = false } + function suppressOSDTemporarily() { + suppressOSD = true; + osdSuppressTimer.restart(); + } + Connections { target: SessionService function onSessionResumed() { diff --git a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml index 0a81e4d3..a3a5ab01 100644 --- a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml +++ b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml @@ -402,9 +402,8 @@ Rectangle { cursorShape: Qt.PointingHandCursor onClicked: { if (modelData) { - AudioService.suppressOSD = true; + SessionData.suppressOSDTemporarily(); modelData.audio.muted = !modelData.audio.muted; - AudioService.suppressOSD = false; } } } @@ -446,18 +445,9 @@ Rectangle { thumbOutlineColor: Theme.surfaceContainer trackColor: appVolumeRow.sliderTrackColor.a > 0 ? appVolumeRow.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - onIsDraggingChanged: { - if (isDragging) { - AudioService.suppressOSD = true; - } else { - Qt.callLater(() => { - AudioService.suppressOSD = false; - }); - } - } - onSliderValueChanged: function (newValue) { if (modelData) { + SessionData.suppressOSDTemporarily(); modelData.audio.volume = newValue / 100.0; if (newValue > 0 && modelData.audio.muted) { modelData.audio.muted = false; diff --git a/quickshell/Modules/ControlCenter/Widgets/AudioSliderRow.qml b/quickshell/Modules/ControlCenter/Widgets/AudioSliderRow.qml index 18358b33..c554d119 100644 --- a/quickshell/Modules/ControlCenter/Widgets/AudioSliderRow.qml +++ b/quickshell/Modules/ControlCenter/Widgets/AudioSliderRow.qml @@ -1,7 +1,4 @@ import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Services.Pipewire import qs.Common import qs.Services import qs.Widgets @@ -30,9 +27,8 @@ Row { cursorShape: Qt.PointingHandCursor onClicked: { if (defaultSink) { - AudioService.suppressOSD = true - defaultSink.audio.muted = !defaultSink.audio.muted - AudioService.suppressOSD = false + SessionData.suppressOSDTemporarily(); + defaultSink.audio.muted = !defaultSink.audio.muted; } } } @@ -40,15 +36,19 @@ Row { DankIcon { anchors.centerIn: parent name: { - if (!defaultSink) return "volume_off" + if (!defaultSink) + return "volume_off"; - let volume = defaultSink.audio.volume - let muted = defaultSink.audio.muted + let volume = defaultSink.audio.volume; + let muted = defaultSink.audio.muted; - if (muted || volume === 0.0) return "volume_off" - if (volume <= 0.33) return "volume_down" - if (volume <= 0.66) return "volume_up" - return "volume_up" + if (muted || volume === 0.0) + return "volume_off"; + if (volume <= 0.33) + return "volume_down"; + if (volume <= 0.66) + return "volume_up"; + return "volume_up"; } size: Theme.iconSize color: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText @@ -70,22 +70,15 @@ Row { thumbOutlineColor: Theme.surfaceContainer trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - onIsDraggingChanged: { - if (isDragging) { - AudioService.suppressOSD = true - } else { - Qt.callLater(() => { AudioService.suppressOSD = false }) - } - } - - onSliderValueChanged: function(newValue) { + onSliderValueChanged: function (newValue) { if (defaultSink) { - defaultSink.audio.volume = newValue / 100.0 + SessionData.suppressOSDTemporarily(); + defaultSink.audio.volume = newValue / 100.0; if (newValue > 0 && defaultSink.audio.muted) { - defaultSink.audio.muted = false + defaultSink.audio.muted = false; } - AudioService.playVolumeChangeSoundIfEnabled() + AudioService.playVolumeChangeSoundIfEnabled(); } } } -} \ No newline at end of file +} diff --git a/quickshell/Modules/ControlCenter/Widgets/InputAudioSliderRow.qml b/quickshell/Modules/ControlCenter/Widgets/InputAudioSliderRow.qml index fd7944c4..d0a2be29 100644 --- a/quickshell/Modules/ControlCenter/Widgets/InputAudioSliderRow.qml +++ b/quickshell/Modules/ControlCenter/Widgets/InputAudioSliderRow.qml @@ -1,7 +1,4 @@ import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Services.Pipewire import qs.Common import qs.Services import qs.Widgets @@ -30,9 +27,8 @@ Row { cursorShape: Qt.PointingHandCursor onClicked: { if (defaultSource) { - AudioService.suppressOSD = true - defaultSource.audio.muted = !defaultSource.audio.muted - AudioService.suppressOSD = false + SessionData.suppressOSDTemporarily(); + defaultSource.audio.muted = !defaultSource.audio.muted; } } } @@ -40,13 +36,15 @@ Row { DankIcon { anchors.centerIn: parent name: { - if (!defaultSource) return "mic_off" + if (!defaultSource) + return "mic_off"; - let volume = defaultSource.audio.volume - let muted = defaultSource.audio.muted + let volume = defaultSource.audio.volume; + let muted = defaultSource.audio.muted; - if (muted || volume === 0.0) return "mic_off" - return "mic" + if (muted || volume === 0.0) + return "mic_off"; + return "mic"; } size: Theme.iconSize color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText @@ -67,16 +65,14 @@ Row { valueOverride: actualVolumePercent thumbOutlineColor: Theme.surfaceContainer trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - onIsDraggingChanged: { - AudioService.suppressOSD = isDragging - } - onSliderValueChanged: function(newValue) { + onSliderValueChanged: function (newValue) { if (defaultSource) { - defaultSource.audio.volume = newValue / 100.0 + SessionData.suppressOSDTemporarily(); + defaultSource.audio.volume = newValue / 100.0; if (newValue > 0 && defaultSource.audio.muted) { - defaultSource.audio.muted = false + defaultSource.audio.muted = false; } } } } -} \ No newline at end of file +} diff --git a/quickshell/Modules/DankDash/MediaPlayerTab.qml b/quickshell/Modules/DankDash/MediaPlayerTab.qml index 0d1cd423..a766d82d 100644 --- a/quickshell/Modules/DankDash/MediaPlayerTab.qml +++ b/quickshell/Modules/DankDash/MediaPlayerTab.qml @@ -205,6 +205,7 @@ Item { const current = Math.round(currentVolume * 100); const newVolume = Math.min(100, Math.max(0, current + step)); + SessionData.suppressOSDTemporarily(); if (usePlayerVolume) { activePlayer.volume = newVolume / 100; } else if (AudioService.sink?.audio) { @@ -790,6 +791,7 @@ Item { volumeButtonExited(); } onClicked: { + SessionData.suppressOSDTemporarily(); if (currentVolume > 0) { volumeButton.previousVolume = currentVolume; if (usePlayerVolume) { @@ -807,6 +809,7 @@ Item { } } onWheel: wheelEvent => { + SessionData.suppressOSDTemporarily(); const delta = wheelEvent.angleDelta.y; const current = (currentVolume * 100) || 0; const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5); diff --git a/quickshell/Modules/OSD/VolumeOSD.qml b/quickshell/Modules/OSD/VolumeOSD.qml index 548aadf5..1f1c6cfa 100644 --- a/quickshell/Modules/OSD/VolumeOSD.qml +++ b/quickshell/Modules/OSD/VolumeOSD.qml @@ -17,14 +17,14 @@ DankOSD { target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null function onVolumeChanged() { - if (!AudioService.suppressOSD && SettingsData.osdVolumeEnabled) { - root.show() + if (SettingsData.osdVolumeEnabled) { + root.show(); } } function onMutedChanged() { - if (!AudioService.suppressOSD && SettingsData.osdVolumeEnabled) { - root.show() + if (SettingsData.osdVolumeEnabled) { + root.show(); } } } @@ -34,7 +34,7 @@ DankOSD { function onSinkChanged() { if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) { - root.show() + root.show(); } } } @@ -76,10 +76,10 @@ DankOSD { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - AudioService.toggleMute() + AudioService.toggleMute(); } onContainsMouseChanged: { - setChildHovered(containsMouse || volumeSlider.containsMouse) + setChildHovered(containsMouse || volumeSlider.containsMouse); } } } @@ -105,21 +105,20 @@ DankOSD { Component.onCompleted: { if (AudioService.sink && AudioService.sink.audio) { - value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) + value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)); } } onSliderValueChanged: newValue => { - if (AudioService.sink && AudioService.sink.audio) { - AudioService.suppressOSD = true - AudioService.sink.audio.volume = newValue / 100 - AudioService.suppressOSD = false - resetHideTimer() - } - } + if (AudioService.sink && AudioService.sink.audio) { + SessionData.suppressOSDTemporarily(); + AudioService.sink.audio.volume = newValue / 100; + resetHideTimer(); + } + } onContainsMouseChanged: { - setChildHovered(containsMouse || muteButton.containsMouse) + setChildHovered(containsMouse || muteButton.containsMouse); } Connections { @@ -127,7 +126,7 @@ DankOSD { function onVolumeChanged() { if (volumeSlider && !volumeSlider.pressed) { - volumeSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) + volumeSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)); } } } @@ -164,10 +163,10 @@ DankOSD { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - AudioService.toggleMute() + AudioService.toggleMute(); } onContainsMouseChanged: { - setChildHovered(containsMouse || vertSliderArea.containsMouse) + setChildHovered(containsMouse || vertSliderArea.containsMouse); } } } @@ -207,9 +206,9 @@ DankOSD { height: 8 radius: Theme.cornerRadius y: { - const ratio = vertSlider.value / 100 - const travel = parent.height - height - return Math.max(0, Math.min(travel, travel * (1 - ratio))) + const ratio = vertSlider.value / 100; + const travel = parent.height - height; + return Math.max(0, Math.min(travel, travel * (1 - ratio))); } anchors.horizontalCenter: parent.horizontalCenter color: Theme.primary @@ -226,36 +225,35 @@ DankOSD { cursorShape: Qt.PointingHandCursor onContainsMouseChanged: { - setChildHovered(containsMouse || muteButtonVert.containsMouse) + setChildHovered(containsMouse || muteButtonVert.containsMouse); } onPressed: mouse => { - vertSlider.dragging = true - updateVolume(mouse) + vertSlider.dragging = true; + updateVolume(mouse); } onReleased: { - vertSlider.dragging = false + vertSlider.dragging = false; } onPositionChanged: mouse => { if (pressed) { - updateVolume(mouse) + updateVolume(mouse); } } onClicked: mouse => { - updateVolume(mouse) + updateVolume(mouse); } function updateVolume(mouse) { if (AudioService.sink && AudioService.sink.audio) { - const ratio = 1.0 - (mouse.y / height) - const volume = Math.max(0, Math.min(100, Math.round(ratio * 100))) - AudioService.suppressOSD = true - AudioService.sink.audio.volume = volume / 100 - AudioService.suppressOSD = false - resetHideTimer() + const ratio = 1.0 - (mouse.y / height); + const volume = Math.max(0, Math.min(100, Math.round(ratio * 100))); + SessionData.suppressOSDTemporarily(); + AudioService.sink.audio.volume = volume / 100; + resetHideTimer(); } } } @@ -265,7 +263,7 @@ DankOSD { function onVolumeChanged() { if (!vertSlider.dragging) { - vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) + vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)); } } } @@ -286,9 +284,9 @@ DankOSD { onOsdShown: { if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) { if (!useVertical) { - const slider = contentLoader.item.item.children[0].children[1] + const slider = contentLoader.item.item.children[0].children[1]; if (slider && slider.value !== undefined) { - slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) + slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)); } } } diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml index cfe93963..898902c1 100644 --- a/quickshell/Services/DisplayService.qml +++ b/quickshell/Services/DisplayService.qml @@ -74,6 +74,10 @@ Singleton { } function updateSingleDevice(device) { + if (device.class === "leds") { + return; + } + const isUserControlled = isDeviceUserControlled(device.id); if (isUserControlled) { return; @@ -267,9 +271,11 @@ Singleton { return; } + const isLedDevice = deviceInfo?.class === "leds"; + if (suppressOsd) { markDeviceUserControlled(actualDevice); - } else { + } else if (!isLedDevice) { markDevicePendingOsd(actualDevice); } @@ -278,6 +284,10 @@ Singleton { deviceBrightness = newBrightness; brightnessVersion++; + if (isLedDevice && !suppressOsd) { + brightnessChanged(true); + } + if (isExponential) { const newUserSet = Object.assign({}, deviceBrightnessUserSet); newUserSet[actualDevice] = clampedValue; diff --git a/quickshell/Services/MprisController.qml b/quickshell/Services/MprisController.qml index ba652aa1..d0962b54 100644 --- a/quickshell/Services/MprisController.qml +++ b/quickshell/Services/MprisController.qml @@ -1,16 +1,13 @@ pragma Singleton - pragma ComponentBehavior: Bound import QtQuick import Quickshell -import Quickshell.Io import Quickshell.Services.Mpris Singleton { id: root readonly property list availablePlayers: Mpris.players.values - property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null }