Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Assets/Translations/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,8 @@
"types-lockkey-label": "Lock keys",
"types-media-description": "Show OSD when media playback state changes (play, pause, skip).",
"types-media-label": "Media playback",
"types-media-volume-description": "Show OSD when media volume changes.",
"types-media-volume-label": "Media volume",
"types-title": "OSD trigger events",
"types-volume-description": "Show OSD when audio output volume changes.",
"types-volume-label": "Output volume"
Expand Down
2 changes: 2 additions & 0 deletions Assets/Translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,8 @@
"types-lockkey-label": "Lock keys",
"types-media-description": "Show OSD when media playback state changes (play, pause, skip).",
"types-media-label": "Media playback",
"types-media-volume-description": "Show OSD when media volume changes.",
"types-media-volume-label": "Media volume",
"types-title": "OSD trigger events",
"types-volume-description": "Show OSD when audio output volume changes.",
"types-volume-label": "Output volume"
Expand Down
3 changes: 2 additions & 1 deletion Assets/settings-default.json
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@
"enabledTypes": [
0,
1,
2
2,
4
],
"monitors": []
},
Expand Down
2 changes: 1 addition & 1 deletion Commons/Settings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ Singleton {
property int autoHideMs: 2000
property bool overlayLayer: true
property real backgroundOpacity: 1.0
property list<var> enabledTypes: [OSD.Type.Volume, OSD.Type.InputVolume, OSD.Type.Brightness]
property list<var> enabledTypes: [OSD.Type.Volume, OSD.Type.InputVolume, OSD.Type.Brightness, OSD.Type.MediaVolume]
property list<string> monitors: [] // holds osd visibility per monitor
}

Expand Down
24 changes: 24 additions & 0 deletions Modules/Bar/Widgets/MediaMini.qml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ Item {
readonly property bool shouldHideEmpty: !hasPlayer && hideMode === "hidden"
readonly property bool isHidden: shouldHideIdle || shouldHideEmpty

// Volume scroll
property int wheelAccumulator: 0

// Title
readonly property string title: {
if (!hasPlayer)
Expand Down Expand Up @@ -392,6 +395,27 @@ Item {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton | Qt.ForwardButton | Qt.BackButton

onWheel: function (ev) {
// Hide tooltip as soon as the user starts scrolling to adjust volume
TooltipService.hide();

ev.accepted = true;

var delta = ev.pixelDelta.y;

if (ev.inverted)
delta *= -1;

wheelAccumulator += delta;
if (wheelAccumulator >= 120) {
wheelAccumulator = 0;
MediaService.increaseVolume();
} else if (wheelAccumulator <= -120) {
wheelAccumulator = 0;
MediaService.decreaseVolume();
}
}

onClicked: mouse => {
TooltipService.hide();
if (mouse.button === Qt.LeftButton) {
Expand Down
71 changes: 71 additions & 0 deletions Modules/Cards/MediaCard.qml
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,77 @@ NBox {
}
}

// Volume slider
Item {
id: volumeWrapper
visible: MediaService.volumeSupported
Layout.fillWidth: true
height: Style.baseWidgetSize * 0.5

property real localVolume: -1
property real lastSentVolume: -1
property real volumeEpsilon: 0.01
property real volume: {
if (!MediaService.volumeSupported)
return 0;
return MediaService.volume;
}

Timer {
id: volumeDebounce
interval: 75
repeat: false
onTriggered: {
if (volumeWrapper.localVolume >= 0) {
const next = Math.max(0, Math.min(1, volumeWrapper.localVolume));
if (volumeWrapper.lastSentVolume < 0 || Math.abs(next - volumeWrapper.lastSentVolume) >= volumeWrapper.volumeEpsilon) {
MediaService.setVolume(next);
volumeWrapper.lastSentVolume = next;
}
}
}
}

RowLayout {
anchors.fill: parent
spacing: 2

NIcon {
icon: "volume"
color: Color.mOnSurfaceVariant
}

NSlider {
id: volumeSlider
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0
snapAlways: false
heightRatio: 0.6

value: MediaService.volume

onMoved: {
volumeWrapper.localVolume = value;
volumeDebounce.restart();
}
onPressedChanged: {
if (pressed) {
volumeWrapper.localVolume = value;
MediaService.setVolume(value);
volumeWrapper.lastSentVolume = value;
} else {
volumeDebounce.stop();
MediaService.setVolume(value);
volumeWrapper.localVolume = -1;
volumeWrapper.lastSentVolume = -1;
}
}
}
}
}

// Spacer to push media controls down
Item {
Layout.preferredHeight: Style.marginL
Expand Down
22 changes: 19 additions & 3 deletions Modules/OSD/OSD.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Variants {
Volume,
InputVolume,
Brightness,
LockKey
LockKey,
MediaVolume
}

model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled)
Expand All @@ -45,6 +46,7 @@ Variants {
readonly property bool isMuted: AudioService.muted
readonly property real currentInputVolume: AudioService.inputVolume
readonly property bool isInputMuted: AudioService.inputMuted
readonly property real currentMediaVolume: MediaService.volume
readonly property real epsilon: 0.005

// LockKey OSD enabled state (reactive to settings)
Expand Down Expand Up @@ -72,6 +74,9 @@ Variants {
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
case OSD.Type.InputVolume:
return isInputMuted ? "microphone-off" : "microphone";
case OSD.Type.MediaVolume:
// Show music-off icon when volume is effectively 0% (within rounding threshold)
return currentMediaVolume < root.epsilon ? "music-off" : "music";
case OSD.Type.Brightness:
// Show sun-off icon when brightness is effectively 0% (within rounding threshold)
if (currentBrightness < root.epsilon)
Expand All @@ -90,6 +95,8 @@ Variants {
return isMuted ? 0 : currentVolume;
case OSD.Type.InputVolume:
return isInputMuted ? 0 : currentInputVolume;
case OSD.Type.MediaVolume:
return currentMediaVolume;
case OSD.Type.Brightness:
return currentBrightness;
case OSD.Type.LockKey:
Expand Down Expand Up @@ -124,7 +131,7 @@ Variants {
}

function getProgressColor() {
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted) || (currentOSDType === OSD.Type.MediaVolume && currentMediaVolume < root.epsilon);
if (isMutedState) {
return Color.mError;
}
Expand All @@ -150,7 +157,7 @@ Variants {
}

function getIconColor() {
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted) || (currentOSDType === OSD.Type.MediaVolume && currentMediaVolume < root.epsilon);
if (isMutedState)
return Color.mError;

Expand Down Expand Up @@ -313,6 +320,15 @@ Variants {
}
}

// MediaService monitoring
Connections {
target: MediaService

function onVolumeChanged() {
showOSD(OSD.Type.MediaVolume);
}
}

// Brightness monitoring
Connections {
target: BrightnessService
Expand Down
103 changes: 103 additions & 0 deletions Modules/Panels/Media/MediaPlayerPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,109 @@ SmartPanel {
}
}

Item {
id: volumeWrapper
visible: MediaService.volumeSupported
Layout.fillWidth: true
Layout.preferredHeight: volumeColumn.implicitHeight

property real localVolume: -1
property real lastSentVolume: -1
property real volumeEpsilon: 0.01
property real volume: {
if (!MediaService.volumeSupported)
return 0;
return MediaService.volume;
}

Timer {
id: volumeDebounce
interval: 75
repeat: false
onTriggered: {
if (volumeWrapper.localVolume >= 0) {
const next = Math.max(0, Math.min(1, volumeWrapper.localVolume));
if (volumeWrapper.lastSentVolume < 0 || Math.abs(next - volumeWrapper.lastSentVolume) >= volumeWrapper.volumeEpsilon) {
MediaService.setVolume(next);
volumeWrapper.lastSentVolume = next;
}
}
}
}



ColumnLayout {
id: volumeColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 2

RowLayout {
anchors.fill: parent
spacing: 2

NIcon {
icon: "volume"
color: Color.mOnSurfaceVariant
}

Item {
Layout.fillWidth: true
Layout.preferredHeight: root.compactMode ? (Style.baseWidgetSize * 0.4) : (Style.baseWidgetSize * 0.5)

NSlider {
id: volumeSlider
anchors.fill: parent
from: 0
to: 1
stepSize: 0
snapAlways: false
heightRatio: 0.4

value: MediaService.volume

onMoved: {
volumeWrapper.localVolume = value;
volumeDebounce.restart();
}
onPressedChanged: {
if (pressed) {
volumeWrapper.localVolume = value;
MediaService.setVolume(value);
volumeWrapper.lastSentVolume = value;
} else {
volumeDebounce.stop();
MediaService.setVolume(value);
volumeWrapper.localVolume = -1;
volumeWrapper.lastSentVolume = -1;
}
}
}
}
}

RowLayout {
Layout.fillWidth: true
spacing: 0

Item {
Layout.fillWidth: true
Layout.minimumWidth: 0
}

NText {
text: MediaService.volumeString
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignRight
visible: volumeWrapper.visible
}
}
}
}

Item {
Layout.preferredHeight: root.isSideBySide ? Style.marginM : Style.marginS
}
Expand Down
4 changes: 4 additions & 0 deletions Modules/Panels/Settings/Tabs/Osd/EventsSubTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ ColumnLayout {
{
type: OSD.Type.LockKey,
key: "types-lockkey"
},
{
type: OSD.Type.MediaVolume,
key: "types-media-volume"
}
]
delegate: NCheckbox {
Expand Down
8 changes: 8 additions & 0 deletions Services/Control/IPCService.qml
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,14 @@ Singleton {
}
MediaService.seekByRatio(positionVal);
}

function increase() {
MediaService.increaseVolume();
}

function decrease() {
MediaService.decreaseVolume();
}
}

IpcHandler {
Expand Down
Loading