summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml')
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml353
1 files changed, 353 insertions, 0 deletions
diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
new file mode 100644
index 00000000..007ce100
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
@@ -0,0 +1,353 @@
1import QtQuick
2import QtQuick.Layouts
3import QtQuick.Controls.Fusion
4import Quickshell
5import Quickshell.Services.Pipewire
6import Quickshell.Widgets
7
8Item {
9 height: parent.height
10 width: volumeIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 PwObjectTracker {
14 objects: [Pipewire.defaultAudioSink]
15 }
16
17 WrapperMouseArea {
18 id: widgetMouseArea
19
20 anchors.fill: parent
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: {
25 if (!Pipewire.defaultAudioSink)
26 return;
27 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
28 }
29
30 property real sensitivity: (1 / 40) / 120
31 onWheel: event => {
32 if (!Pipewire.defaultAudioSink)
33 return;
34 Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity;
35 }
36
37 Rectangle {
38 id: volumeWidget
39
40 anchors.fill: parent
41 color: {
42 if (widgetMouseArea.containsMouse)
43 return "#33808080";
44 return "transparent";
45 }
46
47 Item {
48 anchors.fill: parent
49
50 MaterialDesignIcon {
51 id: volumeIcon
52
53 implicitSize: 14
54 anchors.centerIn: parent
55
56 icon: {
57 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
58 return "volume-off";
59 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
60 return "volume-low";
61 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
62 return "volume-medium";
63 return "volume-high";
64 }
65 color: "#555"
66 }
67 }
68 }
69 }
70
71 Loader {
72 id: tooltipLoader
73
74 active: false
75
76 Connections {
77 target: widgetMouseArea
78 function onContainsMouseChanged() {
79 if (widgetMouseArea.containsMouse)
80 tooltipLoader.active = true;
81 }
82 }
83
84 PwObjectTracker {
85 objects: Pipewire.devices
86 }
87 PwObjectTracker {
88 objects: Pipewire.nodes
89 }
90
91 sourceComponent: PopupWindow {
92 id: tooltip
93
94 property bool openPopup: false
95 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup
96
97 anchor {
98 item: widgetMouseArea
99 edges: Edges.Bottom | Edges.Left
100 }
101 visible: false
102
103 onNextVisibleChanged: hangTimer.restart()
104
105 Timer {
106 id: hangTimer
107 interval: 100
108 onTriggered: {
109 tooltip.visible = tooltip.nextVisible;
110 if (!tooltip.visible)
111 tooltipLoader.active = false;
112 }
113 }
114
115 implicitWidth: tooltipContent.width
116 implicitHeight: tooltipContent.height
117 color: "black"
118
119 WrapperMouseArea {
120 id: tooltipMouseArea
121
122 hoverEnabled: true
123 enabled: true
124
125 anchors.fill: parent
126
127 WrapperItem {
128 id: tooltipContent
129
130 margin: 8
131 bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight)
132
133 GridLayout {
134 id: tooltipLayout
135
136 columns: 4
137
138 Repeater {
139 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
140
141 Item {
142 id: descItem
143
144 required property var modelData
145 required property int index
146
147 Layout.column: 0
148 Layout.row: index
149
150 implicitWidth: descText.contentWidth
151 implicitHeight: descText.contentHeight
152
153 Text {
154 id: descText
155
156 color: "white"
157 font.pointSize: 10
158 font.family: "Fira Sans"
159
160 text: descItem.modelData.description
161 }
162 }
163 }
164
165 Repeater {
166 id: defaultSinkRepeater
167
168 model: {
169 Array.from(Pipewire.devices.values)
170 .filter(dev => dev.type == "Audio/Device")
171 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id ));
172 }
173
174 Item {
175 id: defaultSinkItem
176
177 required property var modelData
178 required property int index
179
180 PwObjectTracker {
181 objects: [defaultSinkItem.modelData]
182 }
183
184 Layout.column: 1
185 Layout.row: index
186
187 Layout.fillHeight: true
188
189 implicitWidth: 16 + 8
190
191 WrapperMouseArea {
192 id: defaultSinkMouseArea
193
194 anchors.fill: parent
195 hoverEnabled: true
196 cursorShape: Qt.PointingHandCursor
197
198 onClicked: {
199 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData
200 }
201
202 Rectangle {
203 id: defaultSinkWidget
204
205 anchors.fill: parent
206 color: {
207 if (defaultSinkMouseArea.containsMouse)
208 return "#33808080";
209 return "transparent";
210 }
211
212 MaterialDesignIcon {
213 width: 16
214 height: 16
215 anchors.centerIn: parent
216
217 icon: {
218 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id)
219 return "speaker";
220 return "speaker-off";
221 }
222 color: icon == "speaker" ? "white" : "#555"
223 }
224 }
225 }
226 }
227 }
228
229 Repeater {
230 id: defaultSourceRepeater
231
232 model: {
233 Array.from(Pipewire.devices.values)
234 .filter(dev => dev.type == "Audio/Device")
235 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id ));
236 }
237
238 Item {
239 id: defaultSourceItem
240
241 required property var modelData
242 required property int index
243
244 PwObjectTracker {
245 objects: [defaultSourceItem.modelData]
246 }
247
248 Layout.column: 2
249 Layout.row: index
250
251 Layout.fillHeight: true
252
253 implicitWidth: 16 + 8
254
255 WrapperMouseArea {
256 id: defaultSourceMouseArea
257
258 anchors.fill: parent
259 hoverEnabled: true
260 cursorShape: Qt.PointingHandCursor
261
262 onClicked: {
263 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData
264 }
265
266 Rectangle {
267 id: defaultSourceWidget
268
269 anchors.fill: parent
270 color: {
271 if (defaultSourceMouseArea.containsMouse)
272 return "#33808080";
273 return "transparent";
274 }
275
276 MaterialDesignIcon {
277 width: 16
278 height: 16
279 anchors.centerIn: parent
280
281 icon: {
282 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id)
283 return "microphone";
284 return "microphone-off";
285 }
286 color: icon == "microphone" ? "white" : "#555"
287 }
288 }
289 }
290 }
291 }
292
293 Repeater {
294 id: profileRepeater
295
296 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
297
298 Item {
299 id: profileItem
300
301 required property var modelData
302 required property int index
303
304 PwObjectTracker {
305 objects: [profileItem.modelData]
306 }
307
308 Layout.column: 3
309 Layout.row: index
310
311 Layout.fillWidth: true
312
313 implicitWidth: Math.max(profileBox.implicitWidth, 300)
314 implicitHeight: profileBox.height
315
316 ComboBox {
317 id: profileBox
318
319 model: profileItem.modelData.profiles
320
321 textRole: "description"
322 valueRole: "index"
323 onActivated: profileItem.modelData.setProfile(currentValue)
324
325 anchors.fill: parent
326
327 implicitContentWidthPolicy: ComboBox.WidestText
328
329 Connections {
330 target: profileItem.modelData
331 function onCurrentProfileChanged() {
332 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
333 }
334 }
335 Component.onCompleted: {
336 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
337 }
338
339 Connections {
340 target: profileBox.popup
341 function onVisibleChanged() {
342 tooltip.openPopup = profileBox.popup.visible
343 }
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353}