Files
dotfiles/roles/quickshell/files/bar/VolumeMixerContent.qml
2026-04-14 18:13:39 +02:00

144 lines
4.2 KiB
QML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import "../components"
// Volume mixer a plain Item so it can live inside an expanding section border.
Item {
id: root
implicitWidth: 300
implicitHeight: mixerCol.implicitHeight + 24
PwObjectTracker {
objects: Pipewire.nodes.values
}
function safeVolume(node) {
if (!node || !node.ready || !node.audio) return 0
const v = node.audio.volume
return (v !== undefined && !isNaN(v)) ? v : 0
}
function setVolume(node, v) {
if (!node || !node.ready || !node.audio) return
node.audio.volume = Math.max(0, Math.min(1, v))
}
function volIcon(vol, muted) {
if (muted) return "\uf6a9"
if (vol > 0.6) return "\uf028"
if (vol > 0.2) return "\uf027"
return "\uf026"
}
ColumnLayout {
id: mixerCol
anchors { fill: parent; margins: 12 }
spacing: 4
Text {
text: "Output Devices"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
Layout.bottomMargin: 2
}
Repeater {
model: Pipewire.nodes
delegate: NodeRow {
required property var modelData
Layout.fillWidth: true
node: modelData
visible: modelData.isSink && !modelData.isStream && modelData.ready
}
}
Text {
text: "Applications"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
Layout.topMargin: 6
Layout.bottomMargin: 2
}
Repeater {
model: Pipewire.nodes
delegate: NodeRow {
required property var modelData
Layout.fillWidth: true
node: modelData
visible: modelData.isStream && !modelData.isSink && modelData.ready
&& !(modelData.description ?? "").toLowerCase().includes("monitor")
}
}
}
component NodeRow: RowLayout {
id: row
required property var node
spacing: 8
implicitHeight: 28
Text {
text: root.volIcon(root.safeVolume(row.node), row.node.audio?.muted ?? false)
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 13
color: (row.node.audio?.muted ?? false) ? Theme.textDim : Theme.text
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (row.node.ready && row.node.audio)
row.node.audio.muted = !row.node.audio.muted
}
}
}
Text {
text: row.node.description || row.node.nickname || row.node.name || "?"
font.pixelSize: 11
color: Theme.textDim
Layout.preferredWidth: 90
elide: Text.ElideRight
}
Item {
Layout.fillWidth: true
implicitHeight: 16
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 3
radius: 2
color: Theme.progressTrack
Rectangle {
width: parent.width * Math.min(1, root.safeVolume(row.node))
height: parent.height
radius: parent.radius
color: Theme.accent
}
}
MouseArea {
anchors { fill: parent; topMargin: -4; bottomMargin: -4 }
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
function seek(mx) { root.setVolume(row.node, mx / width) }
onClicked: seek(mouseX)
onPositionChanged: if (pressed) seek(mouseX)
}
}
Text {
text: Math.round(root.safeVolume(row.node) * 100) + "%"
font.pixelSize: 10
color: Theme.textDim
Layout.preferredWidth: 30
horizontalAlignment: Text.AlignRight
}
}
}