Krolyxon
Commit af47ed6

Branches

Refs →

aurora.configquickshellauroraAudioPage.qml

130 lines · 5.2 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell.Services.Pipewire
4
5// Control panel > sound (also the settings app's sound section): output / input levels, devices, per-app volume.
6Flickable {
7 id: flick
8 clip: true
9 contentWidth: width
10 contentHeight: col.implicitHeight
11 implicitHeight: col.implicitHeight
12 boundsBehavior: Flickable.StopAtBounds
13
14 function mediaClass(n) {
15 return (n.properties && n.properties["media.class"]) ? String(n.properties["media.class"]) : ""
16 }
17 function label(n) { return n.description || n.nickname || n.name }
18
19 // kind: "sink" | "source" | "stream"
20 function nodes(kind) {
21 var l = Pipewire.nodes.values, out = []
22 for (var i = 0; i < l.length; i++) {
23 var n = l[i]
24 if (!n.audio) continue
25 if (kind === "sink" && !n.isStream && n.isSink) out.push(n)
26 else if (kind === "source" && !n.isStream && !n.isSink) out.push(n)
27 else if (kind === "stream" && n.isStream && mediaClass(n).indexOf("Output") >= 0) out.push(n)
28 }
29 return out
30 }
31 function sinkIcon(n) {
32 var s = (n.name + " " + n.description).toLowerCase()
33 return (s.indexOf("bluez") >= 0 || s.indexOf("head") >= 0) ? Theme.iHeadphones : Theme.iSpeaker
34 }
35
36 readonly property var sinks: nodes("sink")
37 readonly property var sources: nodes("source")
38 readonly property var streams: nodes("stream")
39 readonly property var out: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
40 readonly property var inp: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
41
42 ColumnLayout {
43 id: col
44 width: flick.width
45 spacing: 6
46
47 SectionLabel { text: "levels" }
48 SliderRow {
49 Layout.fillWidth: true
50 Layout.topMargin: 4
51 icon: Theme.volIcon(flick.out ? flick.out.volume : 0, flick.out ? flick.out.muted : true)
52 value: flick.out ? flick.out.volume : 0
53 muted: flick.out ? flick.out.muted : false
54 onMoved: v => { if (flick.out) flick.out.volume = v }
55 onIconClicked: if (flick.out) flick.out.muted = !flick.out.muted
56 }
57 SliderRow {
58 Layout.fillWidth: true
59 Layout.topMargin: 6
60 icon: flick.inp && !flick.inp.muted ? Theme.iMic : Theme.iMicOff
61 value: flick.inp ? flick.inp.volume : 0
62 muted: flick.inp ? flick.inp.muted : false
63 onMoved: v => { if (flick.inp) flick.inp.volume = v }
64 onIconClicked: if (flick.inp) flick.inp.muted = !flick.inp.muted
65 }
66
67 SectionLabel { text: "output"; count: String(flick.sinks.length); Layout.topMargin: 14 }
68 Repeater {
69 model: flick.sinks
70 delegate: ListRow {
71 required property var modelData
72 icon: flick.sinkIcon(modelData)
73 title: flick.label(modelData)
74 subtitle: current ? "default" : ""
75 current: Pipewire.defaultAudioSink === modelData
76 trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : ""
77 onClicked: Pipewire.preferredDefaultAudioSink = modelData
78 }
79 }
80
81 SectionLabel { text: "input"; count: String(flick.sources.length); Layout.topMargin: 14 }
82 Repeater {
83 model: flick.sources
84 delegate: ListRow {
85 required property var modelData
86 icon: Theme.iMic
87 title: flick.label(modelData)
88 subtitle: current ? "default" : ""
89 current: Pipewire.defaultAudioSource === modelData
90 trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : ""
91 onClicked: Pipewire.preferredDefaultAudioSource = modelData
92 }
93 }
94
95 SectionLabel { text: "apps"; count: String(flick.streams.length); Layout.topMargin: 14 }
96 Lb {
97 visible: flick.streams.length === 0
98 Layout.leftMargin: 6
99 text: "no app is playing"
100 color: Theme.mute
101 }
102 Repeater {
103 model: flick.streams
104 delegate: ColumnLayout {
105 required property var modelData
106 Layout.fillWidth: true
107 Layout.topMargin: 4
108 spacing: 6
109 Lb {
110 Layout.leftMargin: 6
111 Layout.fillWidth: true
112 elide: Text.ElideRight
113 px: 10
114 text: (modelData.properties && modelData.properties["application.name"])
115 ? modelData.properties["application.name"] : flick.label(modelData)
116 }
117 SliderRow {
118 Layout.fillWidth: true
119 implicitHeight: 44
120 icon: Theme.volIcon(modelData.audio.volume, modelData.audio.muted)
121 value: modelData.audio.volume
122 muted: modelData.audio.muted
123 onMoved: v => modelData.audio.volume = v
124 onIconClicked: modelData.audio.muted = !modelData.audio.muted
125 }
126 }
127 }
128 Item { Layout.preferredHeight: 4 }
129 }
130}