aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/sound/SoundPage.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-14 12:24:13 +0200
committerDanilo M. <danix@danix.xyz>2026-09-14 12:24:13 +0200
commitd203d938f005d7542bf13e217ac51917866a8ace (patch)
treec4a6d8728461099638a3fc3378ba7fb34a2b5874 /desktop/modules/sound/SoundPage.qml
parent55619e7dc830697d2929b0bd5cf1c6dc7435720c (diff)
downloadquickshell-d203d938f005d7542bf13e217ac51917866a8ace.tar.gz
quickshell-d203d938f005d7542bf13e217ac51917866a8ace.zip
feat(desktop): move volume-osd in as the sound module
VolumeOsd.qml did three jobs in one file: PipeWire tracking, the OSD surface and the player transport. They become Service, Osd and the page. The OSD keeps its namespace so the existing Hyprland blur rule still matches, and the service stays always-active because the OSD has to answer a keypress with no drawer open.
Diffstat (limited to 'desktop/modules/sound/SoundPage.qml')
-rw-r--r--desktop/modules/sound/SoundPage.qml140
1 files changed, 140 insertions, 0 deletions
diff --git a/desktop/modules/sound/SoundPage.qml b/desktop/modules/sound/SoundPage.qml
new file mode 100644
index 0000000..d530e9d
--- /dev/null
+++ b/desktop/modules/sound/SoundPage.qml
@@ -0,0 +1,140 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Quickshell
+import Quickshell.Services.Pipewire
+import QtQuick
+import "../.."
+
+Column {
+ id: page
+
+ required property var service
+ signal back
+
+ spacing: 16
+
+ // Output and input, each with its own slider.
+ Repeater {
+ model: [
+ { label: "Output", node: page.service.sink },
+ { label: "Input", node: page.service.source },
+ ]
+
+ Column {
+ required property var modelData
+ readonly property var audio: modelData.node?.audio ?? null
+
+ width: page.width
+ spacing: 6
+
+ Item {
+ width: parent.width
+ implicitHeight: name.implicitHeight
+
+ Text {
+ id: name
+ anchors.left: parent.left
+ text: modelData.label
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true }
+ color: Theme.text
+ }
+
+ Text {
+ anchors.right: parent.right
+ text: !audio ? "—" : audio.muted ? "muted" : Math.round(audio.volume * 100) + "%"
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 }
+ color: audio?.muted ? Theme.red : Theme.subtext
+ }
+ }
+
+ Text {
+ width: parent.width
+ elide: Text.ElideRight
+ text: modelData.node?.description ?? modelData.node?.name ?? ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.overlay
+ }
+
+ // Click or drag anywhere on the bar to set the level.
+ Rectangle {
+ width: parent.width
+ height: 8
+ radius: 4
+ color: Theme.surface
+
+ Rectangle {
+ height: parent.height
+ radius: parent.radius
+ width: parent.width * Math.min(audio?.volume ?? 0, 1)
+ color: audio?.muted ? Theme.red : Theme.accent
+ opacity: audio?.muted ? 0.5 : 1
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: audio !== null
+ onPositionChanged: mouse => set(mouse.x)
+ onPressed: mouse => set(mouse.x)
+ function set(x) {
+ if (audio) audio.volume = Math.max(0, Math.min(1, x / width));
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.12) }
+
+ // What is playing, with transport. Same Player singleton the OSD uses,
+ // so playerctld's duplicate is already filtered out by dbusName.
+ Column {
+ width: parent.width
+ spacing: 8
+ visible: Player.active
+
+ Text {
+ width: parent.width
+ elide: Text.ElideRight
+ text: Player.title || "Nothing playing"
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true }
+ color: Theme.text
+ }
+
+ Text {
+ width: parent.width
+ elide: Text.ElideRight
+ visible: Player.artist !== ""
+ text: Player.artist
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ color: Theme.subtext
+ }
+
+ Row {
+ spacing: 4
+ TransportButton {
+ glyph: ""
+ enabled: Player.current?.canGoPrevious ?? false
+ onClicked: Player.current?.previous()
+ }
+ TransportButton {
+ glyph: Player.playing ? "" : ""
+ enabled: Player.current?.canTogglePlaying ?? false
+ onClicked: Player.current?.togglePlaying()
+ }
+ TransportButton {
+ glyph: ""
+ enabled: Player.current?.canGoNext ?? false
+ onClicked: Player.current?.next()
+ }
+ }
+ }
+}