1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

brightness: fix ddc erasing devices, fix OSD behaviors

This commit is contained in:
bbedward
2025-12-01 16:32:10 -05:00
parent 1d91d8fd94
commit bd99be15c2
10 changed files with 127 additions and 111 deletions

View File

@@ -37,25 +37,18 @@ func NewDDCBackend() (*DDCBackend, error) {
} }
func (b *DDCBackend) scanI2CDevices() error { func (b *DDCBackend) scanI2CDevices() error {
b.scanMutex.Lock() return b.scanI2CDevicesInternal(false)
lastScan := b.lastScan }
b.scanMutex.Unlock()
if time.Since(lastScan) < b.scanInterval {
return nil
}
func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
b.scanMutex.Lock() b.scanMutex.Lock()
defer b.scanMutex.Unlock() defer b.scanMutex.Unlock()
if time.Since(b.lastScan) < b.scanInterval { if !force && time.Since(b.lastScan) < b.scanInterval {
return nil return nil
} }
b.devices.Range(func(key string, value *ddcDevice) bool { activeBuses := make(map[int]bool)
b.devices.Delete(key)
return true
})
for i := 0; i < 32; i++ { for i := 0; i < 32; i++ {
busPath := fmt.Sprintf("/dev/i2c-%d", i) busPath := fmt.Sprintf("/dev/i2c-%d", i)
@@ -68,17 +61,31 @@ func (b *DDCBackend) scanI2CDevices() error {
continue continue
} }
activeBuses[i] = true
id := fmt.Sprintf("ddc:i2c-%d", i)
if _, exists := b.devices.Load(id); exists {
continue
}
dev, err := b.probeDDCDevice(i) dev, err := b.probeDDCDevice(i)
if err != nil || dev == nil { if err != nil || dev == nil {
continue continue
} }
id := fmt.Sprintf("ddc:i2c-%d", i)
dev.id = id dev.id = id
b.devices.Store(id, dev) b.devices.Store(id, dev)
log.Debugf("found DDC device on i2c-%d", i) 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() b.lastScan = time.Now()
return nil 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 { func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error {
_, ok := b.devices.Load(id) _, 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 { if !ok {
return fmt.Errorf("device not found: %s", id) 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 { func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error {
dev, ok := b.devices.Load(id) 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 { if !ok {
return fmt.Errorf("device not found: %s", id) return fmt.Errorf("device not found: %s", id)
} }

View File

@@ -34,7 +34,10 @@ func (m *UdevMonitor) run(manager *Manager) {
matcher := &netlink.RuleDefinitions{ matcher := &netlink.RuleDefinitions{
Rules: []netlink.RuleDefinition{ Rules: []netlink.RuleDefinition{
{Env: map[string]string{"SUBSYSTEM": "backlight"}}, {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 { if err := matcher.Compile(); err != nil {

View File

@@ -30,6 +30,11 @@ Singleton {
onTriggered: root.suppressOSD = false onTriggered: root.suppressOSD = false
} }
function suppressOSDTemporarily() {
suppressOSD = true;
osdSuppressTimer.restart();
}
Connections { Connections {
target: SessionService target: SessionService
function onSessionResumed() { function onSessionResumed() {

View File

@@ -402,9 +402,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (modelData) { if (modelData) {
AudioService.suppressOSD = true; SessionData.suppressOSDTemporarily();
modelData.audio.muted = !modelData.audio.muted; modelData.audio.muted = !modelData.audio.muted;
AudioService.suppressOSD = false;
} }
} }
} }
@@ -446,18 +445,9 @@ Rectangle {
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: appVolumeRow.sliderTrackColor.a > 0 ? appVolumeRow.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) 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) { onSliderValueChanged: function (newValue) {
if (modelData) { if (modelData) {
SessionData.suppressOSDTemporarily();
modelData.audio.volume = newValue / 100.0; modelData.audio.volume = newValue / 100.0;
if (newValue > 0 && modelData.audio.muted) { if (newValue > 0 && modelData.audio.muted) {
modelData.audio.muted = false; modelData.audio.muted = false;

View File

@@ -1,7 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -30,9 +27,8 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSink) { if (defaultSink) {
AudioService.suppressOSD = true SessionData.suppressOSDTemporarily();
defaultSink.audio.muted = !defaultSink.audio.muted defaultSink.audio.muted = !defaultSink.audio.muted;
AudioService.suppressOSD = false
} }
} }
} }
@@ -40,15 +36,19 @@ Row {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
if (!defaultSink) return "volume_off" if (!defaultSink)
return "volume_off";
let volume = defaultSink.audio.volume let volume = defaultSink.audio.volume;
let muted = defaultSink.audio.muted let muted = defaultSink.audio.muted;
if (muted || volume === 0.0) return "volume_off" if (muted || volume === 0.0)
if (volume <= 0.33) return "volume_down" return "volume_off";
if (volume <= 0.66) return "volume_up" if (volume <= 0.33)
return "volume_up" return "volume_down";
if (volume <= 0.66)
return "volume_up";
return "volume_up";
} }
size: Theme.iconSize size: Theme.iconSize
color: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText color: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
@@ -70,21 +70,14 @@ Row {
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onIsDraggingChanged: { onSliderValueChanged: function (newValue) {
if (isDragging) {
AudioService.suppressOSD = true
} else {
Qt.callLater(() => { AudioService.suppressOSD = false })
}
}
onSliderValueChanged: function(newValue) {
if (defaultSink) { if (defaultSink) {
defaultSink.audio.volume = newValue / 100.0 SessionData.suppressOSDTemporarily();
defaultSink.audio.volume = newValue / 100.0;
if (newValue > 0 && defaultSink.audio.muted) { if (newValue > 0 && defaultSink.audio.muted) {
defaultSink.audio.muted = false defaultSink.audio.muted = false;
} }
AudioService.playVolumeChangeSoundIfEnabled() AudioService.playVolumeChangeSoundIfEnabled();
} }
} }
} }

View File

@@ -1,7 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -30,9 +27,8 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSource) { if (defaultSource) {
AudioService.suppressOSD = true SessionData.suppressOSDTemporarily();
defaultSource.audio.muted = !defaultSource.audio.muted defaultSource.audio.muted = !defaultSource.audio.muted;
AudioService.suppressOSD = false
} }
} }
} }
@@ -40,13 +36,15 @@ Row {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
if (!defaultSource) return "mic_off" if (!defaultSource)
return "mic_off";
let volume = defaultSource.audio.volume let volume = defaultSource.audio.volume;
let muted = defaultSource.audio.muted let muted = defaultSource.audio.muted;
if (muted || volume === 0.0) return "mic_off" if (muted || volume === 0.0)
return "mic" return "mic_off";
return "mic";
} }
size: Theme.iconSize size: Theme.iconSize
color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
@@ -67,14 +65,12 @@ Row {
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onIsDraggingChanged: { onSliderValueChanged: function (newValue) {
AudioService.suppressOSD = isDragging
}
onSliderValueChanged: function(newValue) {
if (defaultSource) { if (defaultSource) {
defaultSource.audio.volume = newValue / 100.0 SessionData.suppressOSDTemporarily();
defaultSource.audio.volume = newValue / 100.0;
if (newValue > 0 && defaultSource.audio.muted) { if (newValue > 0 && defaultSource.audio.muted) {
defaultSource.audio.muted = false defaultSource.audio.muted = false;
} }
} }
} }

View File

@@ -205,6 +205,7 @@ Item {
const current = Math.round(currentVolume * 100); const current = Math.round(currentVolume * 100);
const newVolume = Math.min(100, Math.max(0, current + step)); const newVolume = Math.min(100, Math.max(0, current + step));
SessionData.suppressOSDTemporarily();
if (usePlayerVolume) { if (usePlayerVolume) {
activePlayer.volume = newVolume / 100; activePlayer.volume = newVolume / 100;
} else if (AudioService.sink?.audio) { } else if (AudioService.sink?.audio) {
@@ -790,6 +791,7 @@ Item {
volumeButtonExited(); volumeButtonExited();
} }
onClicked: { onClicked: {
SessionData.suppressOSDTemporarily();
if (currentVolume > 0) { if (currentVolume > 0) {
volumeButton.previousVolume = currentVolume; volumeButton.previousVolume = currentVolume;
if (usePlayerVolume) { if (usePlayerVolume) {
@@ -807,6 +809,7 @@ Item {
} }
} }
onWheel: wheelEvent => { onWheel: wheelEvent => {
SessionData.suppressOSDTemporarily();
const delta = wheelEvent.angleDelta.y; const delta = wheelEvent.angleDelta.y;
const current = (currentVolume * 100) || 0; const current = (currentVolume * 100) || 0;
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5); const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);

View File

@@ -17,14 +17,14 @@ DankOSD {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!AudioService.suppressOSD && SettingsData.osdVolumeEnabled) { if (SettingsData.osdVolumeEnabled) {
root.show() root.show();
} }
} }
function onMutedChanged() { function onMutedChanged() {
if (!AudioService.suppressOSD && SettingsData.osdVolumeEnabled) { if (SettingsData.osdVolumeEnabled) {
root.show() root.show();
} }
} }
} }
@@ -34,7 +34,7 @@ DankOSD {
function onSinkChanged() { function onSinkChanged() {
if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) { if (root.shouldBeVisible && SettingsData.osdVolumeEnabled) {
root.show() root.show();
} }
} }
} }
@@ -76,10 +76,10 @@ DankOSD {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
AudioService.toggleMute() AudioService.toggleMute();
} }
onContainsMouseChanged: { onContainsMouseChanged: {
setChildHovered(containsMouse || volumeSlider.containsMouse) setChildHovered(containsMouse || volumeSlider.containsMouse);
} }
} }
} }
@@ -105,21 +105,20 @@ DankOSD {
Component.onCompleted: { Component.onCompleted: {
if (AudioService.sink && AudioService.sink.audio) { 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 => { onSliderValueChanged: newValue => {
if (AudioService.sink && AudioService.sink.audio) { if (AudioService.sink && AudioService.sink.audio) {
AudioService.suppressOSD = true SessionData.suppressOSDTemporarily();
AudioService.sink.audio.volume = newValue / 100 AudioService.sink.audio.volume = newValue / 100;
AudioService.suppressOSD = false resetHideTimer();
resetHideTimer() }
} }
}
onContainsMouseChanged: { onContainsMouseChanged: {
setChildHovered(containsMouse || muteButton.containsMouse) setChildHovered(containsMouse || muteButton.containsMouse);
} }
Connections { Connections {
@@ -127,7 +126,7 @@ DankOSD {
function onVolumeChanged() { function onVolumeChanged() {
if (volumeSlider && !volumeSlider.pressed) { 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 hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
AudioService.toggleMute() AudioService.toggleMute();
} }
onContainsMouseChanged: { onContainsMouseChanged: {
setChildHovered(containsMouse || vertSliderArea.containsMouse) setChildHovered(containsMouse || vertSliderArea.containsMouse);
} }
} }
} }
@@ -207,9 +206,9 @@ DankOSD {
height: 8 height: 8
radius: Theme.cornerRadius radius: Theme.cornerRadius
y: { y: {
const ratio = vertSlider.value / 100 const ratio = vertSlider.value / 100;
const travel = parent.height - height const travel = parent.height - height;
return Math.max(0, Math.min(travel, travel * (1 - ratio))) return Math.max(0, Math.min(travel, travel * (1 - ratio)));
} }
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: Theme.primary color: Theme.primary
@@ -226,36 +225,35 @@ DankOSD {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: { onContainsMouseChanged: {
setChildHovered(containsMouse || muteButtonVert.containsMouse) setChildHovered(containsMouse || muteButtonVert.containsMouse);
} }
onPressed: mouse => { onPressed: mouse => {
vertSlider.dragging = true vertSlider.dragging = true;
updateVolume(mouse) updateVolume(mouse);
} }
onReleased: { onReleased: {
vertSlider.dragging = false vertSlider.dragging = false;
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (pressed) { if (pressed) {
updateVolume(mouse) updateVolume(mouse);
} }
} }
onClicked: mouse => { onClicked: mouse => {
updateVolume(mouse) updateVolume(mouse);
} }
function updateVolume(mouse) { function updateVolume(mouse) {
if (AudioService.sink && AudioService.sink.audio) { if (AudioService.sink && AudioService.sink.audio) {
const ratio = 1.0 - (mouse.y / height) const ratio = 1.0 - (mouse.y / height);
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100))) const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)));
AudioService.suppressOSD = true SessionData.suppressOSDTemporarily();
AudioService.sink.audio.volume = volume / 100 AudioService.sink.audio.volume = volume / 100;
AudioService.suppressOSD = false resetHideTimer();
resetHideTimer()
} }
} }
} }
@@ -265,7 +263,7 @@ DankOSD {
function onVolumeChanged() { function onVolumeChanged() {
if (!vertSlider.dragging) { 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: { onOsdShown: {
if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) { if (AudioService.sink && AudioService.sink.audio && contentLoader.item && contentLoader.item.item) {
if (!useVertical) { 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) { 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));
} }
} }
} }

View File

@@ -74,6 +74,10 @@ Singleton {
} }
function updateSingleDevice(device) { function updateSingleDevice(device) {
if (device.class === "leds") {
return;
}
const isUserControlled = isDeviceUserControlled(device.id); const isUserControlled = isDeviceUserControlled(device.id);
if (isUserControlled) { if (isUserControlled) {
return; return;
@@ -267,9 +271,11 @@ Singleton {
return; return;
} }
const isLedDevice = deviceInfo?.class === "leds";
if (suppressOsd) { if (suppressOsd) {
markDeviceUserControlled(actualDevice); markDeviceUserControlled(actualDevice);
} else { } else if (!isLedDevice) {
markDevicePendingOsd(actualDevice); markDevicePendingOsd(actualDevice);
} }
@@ -278,6 +284,10 @@ Singleton {
deviceBrightness = newBrightness; deviceBrightness = newBrightness;
brightnessVersion++; brightnessVersion++;
if (isLedDevice && !suppressOsd) {
brightnessChanged(true);
}
if (isExponential) { if (isExponential) {
const newUserSet = Object.assign({}, deviceBrightnessUserSet); const newUserSet = Object.assign({}, deviceBrightnessUserSet);
newUserSet[actualDevice] = clampedValue; newUserSet[actualDevice] = clampedValue;

View File

@@ -1,16 +1,13 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
Singleton { Singleton {
id: root id: root
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null
} }