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.qml412
1 files changed, 265 insertions, 147 deletions
diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
index 007ce100..3e0b8fd9 100644
--- a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
+++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
@@ -114,232 +114,350 @@ Item {
114 114
115 implicitWidth: tooltipContent.width 115 implicitWidth: tooltipContent.width
116 implicitHeight: tooltipContent.height 116 implicitHeight: tooltipContent.height
117 color: "black" 117 color: "transparent"
118 118
119 WrapperMouseArea { 119 Rectangle {
120 id: tooltipMouseArea 120 width: tooltip.width
121 height: tooltipLayout.childrenRect.height + 16
122 color: "black"
123 }
121 124
122 hoverEnabled: true 125 WrapperItem {
123 enabled: true 126 id: tooltipContent
124 127
125 anchors.fill: parent 128 bottomMargin: Math.max(0, 200 - tooltipLayout.implicitHeight)
129
130 WrapperMouseArea {
131 id: tooltipMouseArea
126 132
127 WrapperItem { 133 hoverEnabled: true
128 id: tooltipContent 134 enabled: true
129 135
130 margin: 8 136 WrapperItem {
131 bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight) 137 margin: 8
138 bottomMargin: 8
132 139
133 GridLayout { 140 GridLayout {
134 id: tooltipLayout 141 id: tooltipLayout
135 142
136 columns: 4 143 columns: 4
137 144
138 Repeater { 145 Repeater {
139 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") 146 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
140 147
141 Item { 148 Item {
142 id: descItem 149 id: descItem
143 150
144 required property var modelData 151 required property var modelData
145 required property int index 152 required property int index
146 153
147 Layout.column: 0 154 Layout.column: 0
148 Layout.row: index 155 Layout.row: index
149 156
150 implicitWidth: descText.contentWidth 157 implicitWidth: descText.contentWidth
151 implicitHeight: descText.contentHeight 158 implicitHeight: descText.contentHeight
152 159
153 Text { 160 Text {
154 id: descText 161 id: descText
155 162
156 color: "white" 163 color: "white"
157 font.pointSize: 10 164 font.pointSize: 10
158 font.family: "Fira Sans" 165 font.family: "Fira Sans"
159 166
160 text: descItem.modelData.description 167 text: descItem.modelData.description
168 }
161 } 169 }
162 } 170 }
163 }
164 171
165 Repeater { 172 Repeater {
166 id: defaultSinkRepeater 173 id: defaultSinkRepeater
167 174
168 model: { 175 model: {
169 Array.from(Pipewire.devices.values) 176 Array.from(Pipewire.devices.values)
170 .filter(dev => dev.type == "Audio/Device") 177 .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 )); 178 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id ));
172 } 179 }
173 180
174 Item { 181 Item {
175 id: defaultSinkItem 182 id: defaultSinkItem
176 183
177 required property var modelData 184 required property var modelData
178 required property int index 185 required property int index
179 186
180 PwObjectTracker { 187 visible: Boolean(modelData)
181 objects: [defaultSinkItem.modelData]
182 }
183 188
184 Layout.column: 1 189 PwObjectTracker {
185 Layout.row: index 190 objects: [defaultSinkItem.modelData]
191 }
186 192
187 Layout.fillHeight: true 193 Layout.column: 1
194 Layout.row: index
188 195
189 implicitWidth: 16 + 8 196 Layout.fillHeight: true
190 197
191 WrapperMouseArea { 198 implicitWidth: 16 + 8
192 id: defaultSinkMouseArea
193 199
194 anchors.fill: parent 200 WrapperMouseArea {
195 hoverEnabled: true 201 id: defaultSinkMouseArea
196 cursorShape: Qt.PointingHandCursor
197 202
198 onClicked: { 203 anchors.fill: parent
199 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData 204 hoverEnabled: true
200 } 205 cursorShape: Qt.PointingHandCursor
201 206
202 Rectangle { 207 onClicked: {
203 id: defaultSinkWidget 208 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData
209 }
204 210
205 anchors.fill: parent 211 onWheel: event => scrollVolume(event);
206 color: { 212 property real sensitivity: (1 / 40) / 120
207 if (defaultSinkMouseArea.containsMouse) 213 function scrollVolume(event) {
208 return "#33808080"; 214 defaultSinkItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
209 return "transparent";
210 } 215 }
211 216
212 MaterialDesignIcon { 217 Rectangle {
213 width: 16 218 id: defaultSinkWidget
214 height: 16 219
220 anchors.fill: parent
221 color: {
222 if (defaultSinkMouseArea.containsMouse)
223 return "#33808080";
224 return "transparent";
225 }
226
227 MaterialDesignIcon {
228 width: 16
229 height: 16
230 anchors.centerIn: parent
231
232 icon: {
233 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id)
234 return "speaker";
235 return "speaker-off";
236 }
237 color: icon == "speaker" ? "white" : "#555"
238 }
239 }
240 }
241
242 PopupWindow {
243 id: volumeTooltip
244
245 property bool nextVisible: defaultSinkMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
246
247 anchor {
248 item: defaultSinkMouseArea
249 edges: Edges.Bottom | Edges.Left
250 }
251 visible: false
252
253 onNextVisibleChanged: volumeHangTimer.restart()
254
255 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
256
257 Timer {
258 id: volumeHangTimer
259 interval: 100
260 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
261 }
262
263 implicitWidth: volumeTooltipText.contentWidth + 16
264 implicitHeight: volumeTooltipText.contentHeight + 16
265 color: "black"
266
267 WrapperMouseArea {
268 id: volumeTooltipMouseArea
269
270 hoverEnabled: true
271 enabled: true
272
273 onWheel: event => defaultSinkMouseArea.scrollVolume(event);
274
215 anchors.centerIn: parent 275 anchors.centerIn: parent
216 276
217 icon: { 277 Text {
218 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id) 278 id: volumeTooltipText
219 return "speaker"; 279
220 return "speaker-off"; 280 font.pointSize: 10
281 font.family: "Fira Sans"
282 color: "white"
283
284 text: `${Math.round(defaultSinkItem.modelData?.audio?.volume * 100)}%`
221 } 285 }
222 color: icon == "speaker" ? "white" : "#555"
223 } 286 }
224 } 287 }
225 } 288 }
226 } 289 }
227 }
228 290
229 Repeater { 291 Repeater {
230 id: defaultSourceRepeater 292 id: defaultSourceRepeater
231 293
232 model: { 294 model: {
233 Array.from(Pipewire.devices.values) 295 Array.from(Pipewire.devices.values)
234 .filter(dev => dev.type == "Audio/Device") 296 .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 )); 297 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id ));
236 } 298 }
237 299
238 Item { 300 Item {
239 id: defaultSourceItem 301 id: defaultSourceItem
240 302
241 required property var modelData 303 required property var modelData
242 required property int index 304 required property int index
243 305
244 PwObjectTracker { 306 visible: Boolean(modelData)
245 objects: [defaultSourceItem.modelData] 307
246 } 308 PwObjectTracker {
309 objects: [defaultSourceItem.modelData]
310 }
311
312 Layout.column: 2
313 Layout.row: index
247 314
248 Layout.column: 2 315 Layout.fillHeight: true
249 Layout.row: index 316
317 implicitWidth: 16 + 8
318
319 WrapperMouseArea {
320 id: defaultSourceMouseArea
321
322 anchors.fill: parent
323 hoverEnabled: true
324 cursorShape: Qt.PointingHandCursor
250 325
251 Layout.fillHeight: true 326 onClicked: {
327 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData
328 }
252 329
253 implicitWidth: 16 + 8 330 onWheel: event => scrollVolume(event);
331 property real sensitivity: (1 / 40) / 120
332 function scrollVolume(event) {
333 defaultSourceItem.modelData.audio.volume += event.angleDelta.y * sensitivity;
334 }
254 335
255 WrapperMouseArea { 336 Rectangle {
256 id: defaultSourceMouseArea 337 id: defaultSourceWidget
257 338
258 anchors.fill: parent 339 anchors.fill: parent
259 hoverEnabled: true 340 color: {
260 cursorShape: Qt.PointingHandCursor 341 if (defaultSourceMouseArea.containsMouse)
342 return "#33808080";
343 return "transparent";
344 }
261 345
262 onClicked: { 346 MaterialDesignIcon {
263 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData 347 width: 16
348 height: 16
349 anchors.centerIn: parent
350
351 icon: {
352 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id)
353 return "microphone";
354 return "microphone-off";
355 }
356 color: icon == "microphone" ? "white" : "#555"
357 }
358 }
264 } 359 }
265 360
266 Rectangle { 361 PopupWindow {
267 id: defaultSourceWidget 362 id: volumeTooltip
268 363
269 anchors.fill: parent 364 property bool nextVisible: defaultSourceMouseArea.containsMouse || volumeTooltipMouseArea.containsMouse
270 color: { 365
271 if (defaultSourceMouseArea.containsMouse) 366 anchor {
272 return "#33808080"; 367 item: defaultSourceMouseArea
273 return "transparent"; 368 edges: Edges.Bottom | Edges.Left
274 } 369 }
370 visible: false
371
372 onNextVisibleChanged: volumeHangTimer.restart()
373
374 onVisibleChanged: tooltip.openPopup = volumeTooltip.visible
375
376 Timer {
377 id: volumeHangTimer
378 interval: 100
379 onTriggered: volumeTooltip.visible = volumeTooltip.nextVisible
380 }
381
382 implicitWidth: volumeTooltipText.contentWidth + 16
383 implicitHeight: volumeTooltipText.contentHeight + 16
384 color: "black"
385
386 WrapperMouseArea {
387 id: volumeTooltipMouseArea
388
389 hoverEnabled: true
390 enabled: true
391
392 onWheel: event => defaultSourceMouseArea.scrollVolume(event);
275 393
276 MaterialDesignIcon {
277 width: 16
278 height: 16
279 anchors.centerIn: parent 394 anchors.centerIn: parent
280 395
281 icon: { 396 Text {
282 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id) 397 id: volumeTooltipText
283 return "microphone"; 398
284 return "microphone-off"; 399 font.pointSize: 10
400 font.family: "Fira Sans"
401 color: "white"
402
403 text: `${Math.round(defaultSourceItem.modelData?.audio?.volume * 100)}%`
285 } 404 }
286 color: icon == "microphone" ? "white" : "#555"
287 } 405 }
288 } 406 }
289 } 407 }
290 } 408 }
291 }
292 409
293 Repeater { 410 Repeater {
294 id: profileRepeater 411 id: profileRepeater
295 412
296 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") 413 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
297 414
298 Item { 415 Item {
299 id: profileItem 416 id: profileItem
300 417
301 required property var modelData 418 required property var modelData
302 required property int index 419 required property int index
303 420
304 PwObjectTracker { 421 PwObjectTracker {
305 objects: [profileItem.modelData] 422 objects: [profileItem.modelData]
306 } 423 }
307 424
308 Layout.column: 3 425 Layout.column: 3
309 Layout.row: index 426 Layout.row: index
310 427
311 Layout.fillWidth: true 428 Layout.fillWidth: true
312 429
313 implicitWidth: Math.max(profileBox.implicitWidth, 300) 430 implicitWidth: Math.max(profileBox.implicitWidth, 300)
314 implicitHeight: profileBox.height 431 implicitHeight: profileBox.height
315 432
316 ComboBox { 433 ComboBox {
317 id: profileBox 434 id: profileBox
318 435
319 model: profileItem.modelData.profiles 436 model: profileItem.modelData.profiles
320 437
321 textRole: "description" 438 textRole: "description"
322 valueRole: "index" 439 valueRole: "index"
323 onActivated: profileItem.modelData.setProfile(currentValue) 440 onActivated: profileItem.modelData.setProfile(currentValue)
324 441
325 anchors.fill: parent 442 anchors.fill: parent
326 443
327 implicitContentWidthPolicy: ComboBox.WidestText 444 implicitContentWidthPolicy: ComboBox.WidestText
328 445
329 Connections { 446 Connections {
330 target: profileItem.modelData 447 target: profileItem.modelData
331 function onCurrentProfileChanged() { 448 function onCurrentProfileChanged() {
449 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
450 }
451 }
452 Component.onCompleted: {
332 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); 453 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
333 } 454 }
334 }
335 Component.onCompleted: {
336 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
337 }
338 455
339 Connections { 456 Connections {
340 target: profileBox.popup 457 target: profileBox.popup
341 function onVisibleChanged() { 458 function onVisibleChanged() {
342 tooltip.openPopup = profileBox.popup.visible 459 tooltip.openPopup = profileBox.popup.visible
460 }
343 } 461 }
344 } 462 }
345 } 463 }