| @@ -0,0 +1,110 @@ |
| 1 | + | import QtQuick |
| 2 | + | import Quickshell |
| 3 | + | import Quickshell.Wayland |
| 4 | + | import Quickshell.Services.Pipewire |
| 5 | + | |
| 6 | + | // Volume / mic / brightness popup: bottom-centre, click-through, auto-hides. |
| 7 | + | // Volume + mute changes are picked up natively from Pipewire (any source: keys, wpctl, panel). |
| 8 | + | // Brightness is pushed in via show() from the `bright` IPC call. |
| 9 | + | PanelWindow { |
| 10 | + | id: win |
| 11 | + | property bool suppressed: false // hidden while the control panel is open |
| 12 | + | property bool shown: false |
| 13 | + | property string icon: "" |
| 14 | + | property real value: 0 |
| 15 | + | property bool muted: false |
| 16 | + | property string valueText: "" |
| 17 | + | |
| 18 | + | function show(icon, value, muted, valueText) { |
| 19 | + | win.icon = icon |
| 20 | + | win.value = value |
| 21 | + | win.muted = muted |
| 22 | + | win.valueText = valueText !== undefined ? valueText : Math.round(value * 100) + "%" |
| 23 | + | shown = true |
| 24 | + | hide.restart() |
| 25 | + | } |
| 26 | + | |
| 27 | + | visible: shown && !suppressed |
| 28 | + | anchors.bottom: true |
| 29 | + | margins.bottom: 80 |
| 30 | + | implicitWidth: 280 |
| 31 | + | implicitHeight: 40 |
| 32 | + | exclusiveZone: 0 |
| 33 | + | color: "transparent" |
| 34 | + | mask: Region {} |
| 35 | + | WlrLayershell.layer: WlrLayer.Overlay |
| 36 | + | WlrLayershell.namespace: "aurora-osd" |
| 37 | + | WlrLayershell.keyboardFocus: WlrKeyboardFocus.None |
| 38 | + | |
| 39 | + | Timer { id: hide; interval: 1500; onTriggered: win.shown = false } |
| 40 | + | |
| 41 | + | // ---- pipewire watchers ---- |
| 42 | + | // ignore the burst of change signals on startup and when the default device switches |
| 43 | + | property bool armed: false |
| 44 | + | Timer { id: arm; interval: 1000; running: true; onTriggered: win.armed = true } |
| 45 | + | function rearm() { armed = false; arm.restart() } |
| 46 | + | |
| 47 | + | readonly property var sink: Pipewire.defaultAudioSink |
| 48 | + | readonly property var source: Pipewire.defaultAudioSource |
| 49 | + | onSinkChanged: rearm() |
| 50 | + | onSourceChanged: rearm() |
| 51 | + | |
| 52 | + | function showVol() { |
| 53 | + | var a = sink ? sink.audio : null |
| 54 | + | if (!armed || !a) return |
| 55 | + | show(Theme.volIcon(a.volume, a.muted), a.volume, a.muted) |
| 56 | + | } |
| 57 | + | Connections { |
| 58 | + | target: win.sink ? win.sink.audio : null |
| 59 | + | function onVolumeChanged() { win.showVol() } |
| 60 | + | function onMutedChanged() { win.showVol() } |
| 61 | + | } |
| 62 | + | Connections { |
| 63 | + | target: win.source ? win.source.audio : null |
| 64 | + | function onMutedChanged() { |
| 65 | + | var a = win.source.audio |
| 66 | + | if (!win.armed) return |
| 67 | + | win.show(a.muted ? Theme.iMicOff : Theme.iMic, a.volume, a.muted, a.muted ? "muted" : "on") |
| 68 | + | } |
| 69 | + | } |
| 70 | + | |
| 71 | + | // ---- ui ---- |
| 72 | + | Rectangle { |
| 73 | + | anchors.fill: parent |
| 74 | + | color: Theme.bgA |
| 75 | + | border.width: 1 |
| 76 | + | border.color: Theme.accent |
| 77 | + | |
| 78 | + | Row { |
| 79 | + | anchors.fill: parent |
| 80 | + | anchors.leftMargin: 12 |
| 81 | + | anchors.rightMargin: 12 |
| 82 | + | spacing: 10 |
| 83 | + | |
| 84 | + | Text { |
| 85 | + | anchors.verticalCenter: parent.verticalCenter |
| 86 | + | width: 20 |
| 87 | + | text: win.icon |
| 88 | + | color: win.muted ? Theme.dim : Theme.fg |
| 89 | + | font.family: Theme.font |
| 90 | + | font.pixelSize: 16 |
| 91 | + | } |
| 92 | + | BarSlider { |
| 93 | + | anchors.verticalCenter: parent.verticalCenter |
| 94 | + | width: parent.width - 20 - 44 - parent.spacing * 2 |
| 95 | + | value: win.value |
| 96 | + | muted: win.muted |
| 97 | + | } |
| 98 | + | Text { |
| 99 | + | anchors.verticalCenter: parent.verticalCenter |
| 100 | + | width: 44 |
| 101 | + | horizontalAlignment: Text.AlignRight |
| 102 | + | text: win.valueText |
| 103 | + | color: win.muted ? Theme.dim : Theme.fg |
| 104 | + | font.family: Theme.font |
| 105 | + | font.pixelSize: Theme.fontSize |
| 106 | + | font.bold: true |
| 107 | + | } |
| 108 | + | } |
| 109 | + | } |
| 110 | + | } |