From 01c892df2ee8e18c0ea94aa641e6dd53b39c72d5 Mon Sep 17 00:00:00 2001 From: kyle Date: Mon, 19 Jan 2026 00:56:03 +1100 Subject: [PATCH] feat(audio): implement volume feedback sound --- Assets/Sounds/volume-change.wav | Bin 0 -> 11342 bytes Assets/Translations/en.json | 4 ++- Assets/Translations/zh-CN.json | 4 ++- CREDITS.md | 1 + Commons/Settings.qml | 1 + .../Settings/Tabs/Audio/VolumesSubTab.qml | 15 +++++++++ Services/Media/AudioService.qml | 30 ++++++++++++++++++ 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Assets/Sounds/volume-change.wav diff --git a/Assets/Sounds/volume-change.wav b/Assets/Sounds/volume-change.wav new file mode 100644 index 0000000000000000000000000000000000000000..24bcc44602936e7efa543b659936b11691dd931d GIT binary patch literal 11342 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 mprisBlacklist: [] property string preferredPlayer: "" + property bool volumeFeedback: false } // brightness diff --git a/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml b/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml index bc86441af..f4fa88824 100644 --- a/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Audio/VolumesSubTab.qml @@ -61,6 +61,21 @@ ColumnLayout { } } + // Volume Feedback sound Toggle + ColumnLayout { + spacing: Style.marginS + Layout.fillWidth: true + + NToggle { + label: I18n.tr("panels.audio.volumes-volume-feedback-label") + description: I18n.tr("panels.audio.volumes-volume-feedback-description") + checked: Settings.data.audio.volumeFeedback + defaultValue: Settings.getDefaultValue("audio.volumeFeedback") + onToggled: checked => Settings.data.audio.volumeFeedback = checked + } + } + + // Mute Toggle ColumnLayout { spacing: Style.marginS diff --git a/Services/Media/AudioService.qml b/Services/Media/AudioService.qml index aa3949eed..2befa950d 100644 --- a/Services/Media/AudioService.qml +++ b/Services/Media/AudioService.qml @@ -4,10 +4,15 @@ import QtQuick import Quickshell import Quickshell.Services.Pipewire import qs.Commons +import qs.Services.System Singleton { id: root + // Rate limiting for volume feedback (minimum 100ms between sounds) + property var lastVolumeFeedbackTime: 0 + readonly property int minVolumeFeedbackInterval: 100 + // Devices readonly property PwNode sink: Pipewire.ready ? Pipewire.defaultAudioSink : null readonly property PwNode source: validatedSource @@ -230,6 +235,8 @@ Singleton { sink.audio.muted = false; sink.audio.volume = clampedVolume; + playVolumeFeedback(clampedVolume); + // Clear flag after a short delay to allow external changes to be detected Qt.callLater(() => { isSettingOutputVolume = false; @@ -305,6 +312,29 @@ Singleton { }); } + function playVolumeFeedback(currentVolume: real) { + if (!SoundService.multimediaAvailable) { + return; + } + + if (!Settings.data.audio.volumeFeedback) { + return; + } + + const now = Date.now(); + if (now - lastVolumeFeedbackTime < minVolumeFeedbackInterval) { + return; + } + lastVolumeFeedbackTime = now; + + const feedbackVolume = currentVolume; + SoundService.playSound("volume-change.wav", { + volume: feedbackVolume, + fallback: false, + repeat: false + }); + } + function setInputMuted(muted: bool) { if (!Pipewire.ready || !source?.audio) { Logger.w("AudioService", "No source available or Pipewire not ready");