Files
dotfiles/dotfiles/.config/quickshell/modules/osd/VolumeOsd.qml
2026-10-11 17:28:55 +01:00

123 lines
4.0 KiB
QML

import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import "../../config"
PanelWindow {
id: osd
visible: false
color: "transparent"
exclusiveZone: 0
// Bottom-center: anchoring only the bottom edge lets the layer shell
// center it horizontally.
anchors { bottom: true }
margins.bottom: Appearance.spacing * 4
implicitWidth: 300
implicitHeight: 60
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell:osd"
readonly property var sink: Pipewire.defaultAudioSink
PwObjectTracker { objects: osd.sink ? [osd.sink] : [] }
Timer {
id: hideTimer
interval: 1400
onTriggered: osd.visible = false
}
Connections {
target: osd.sink ? osd.sink.audio : null
function onVolumeChanged() { osd.visible = true; hideTimer.restart() }
function onMutedChanged() { osd.visible = true; hideTimer.restart() }
}
Rectangle {
anchors.fill: parent
radius: Appearance.radius
color: Colors.surface
border.color: Colors.muted
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 13
Text {
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 21
color: Colors.foreground
text: {
const audio = osd.sink ? osd.sink.audio : null
if (!audio || audio.muted || audio.volume <= 0) return "volume_off"
return audio.volume < 0.5 ? "volume_down" : "volume_up"
}
}
Item {
id: slider
Layout.fillWidth: true
implicitHeight: 18
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
readonly property bool enabled: !!osd.sink
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 5
radius: 0
color: Colors.muted
opacity: 0.35
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: track.width * Math.max(0, Math.min(1, slider.value))
height: 5
radius: 0
color: Colors.primary
opacity: slider.enabled ? 1 : 0.4
}
Rectangle {
width: 14
height: 14
radius: 7
color: Colors.primary
opacity: slider.enabled ? 1 : 0.4
anchors.verticalCenter: parent.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * slider.value - width / 2))
}
MouseArea {
anchors.fill: parent
enabled: slider.enabled
cursorShape: Qt.PointingHandCursor
function updateFromX(mx) {
if (!osd.sink || !osd.sink.audio) return
osd.sink.audio.volume = Math.max(0, Math.min(1, mx / track.width))
}
onPressed: (mouse) => updateFromX(mouse.x)
onPositionChanged: (mouse) => { if (pressed) updateFromX(mouse.x) }
}
}
Text {
Layout.preferredWidth: 38
horizontalAlignment: Text.AlignRight
font.family: Appearance.fontFamily
font.pixelSize: 13
color: Colors.muted
text: Math.round(slider.value * 100) + "%"
}
}
}
}