Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skills/optional/quickshell/examples/05-osd-volume/shell.qml

Raw
// 05-osd-volume/shell.qml
// OSD volume: appears for 1s when the default sink's volume changes.
// Pattern: Scope (shared state) + Connections (signal) + Timer (debounce) + LazyLoader (memory-aware) + PanelWindow (default layer).
// Run with: qs -p examples/05-osd-volume/shell.qml
// Try: `wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.5` in another terminal to trigger.

import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import Quickshell.Widgets

Scope {
    id: root
    readonly property var sinkAudio: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null

    // Keep the default sink tracked. The Connections below needs it to be live.
    PwObjectTracker { objects: [ Pipewire.defaultAudioSink ] }

    // Show OSD for 1s after every volume change.
    Connections {
        target: root.sinkAudio
        function onVolumeChanged() {
            root.shouldShowOsd = true
            hideTimer.restart()
        }
        function onMutedChanged() {
            root.shouldShowOsd = true
            hideTimer.restart()
        }
    }

    property bool shouldShowOsd: false

    Timer {
        id: hideTimer
        interval: 1000
        onTriggered: root.shouldShowOsd = false
    }

    // LazyLoader: only exists when shouldShowOsd. Frees memory between events.
    // PanelWindow, not FloatingWindow, provides layer-shell placement.
    LazyLoader {
        active: root.shouldShowOsd

        PanelWindow {
            // The compositor picks the current focused monitor when no screen is set.
            anchors.bottom: true
            margins.bottom: screen.height / 5
            exclusiveZone: 0

            implicitWidth: 400
            implicitHeight: 60
            color: "transparent"

            // Empty region = click-through. Clicks pass to whatever's behind.
            mask: Region {}

            Rectangle {
                anchors.fill: parent
                radius: height / 2
                color: "#aa000000"

                RowLayout {
                    anchors {
                        fill: parent
                        leftMargin: 14
                        rightMargin: 18
                    }
                    spacing: 12

                    IconImage {
                        implicitSize: 28
                        source: Quickshell.iconPath(
                            root.sinkAudio && root.sinkAudio.muted
                                ? "audio-volume-muted-symbolic"
                                : "audio-volume-high-symbolic"
                        )
                    }

                    Rectangle {
                        Layout.fillWidth: true
                        implicitHeight: 8
                        radius: 4
                        color: "#33ffffff"

                        Rectangle {
                            anchors {
                                left: parent.left
                                top: parent.top
                                bottom: parent.bottom
                            }
                            implicitWidth: parent.width * Math.min(1, root.sinkAudio ? root.sinkAudio.volume : 0)
                            radius: 4
                            color: "white"
                        }
                    }

                    Text {
                        text: `${Math.floor((root.sinkAudio ? root.sinkAudio.volume : 0) * 100)}%`
                        color: "white"
                        font.pointSize: 13
                        Layout.preferredWidth: 50
                    }
                }
            }
        }
    }
}