import QtQuick import Quickshell import Quickshell.Services.Pipewire import "." as M import "content" as C M.BarSection { id: root spacing: M.Theme.moduleSpacing tooltip: "" PwObjectTracker { objects: [Pipewire.defaultAudioSink, ...root._streamList] } readonly property var sink: Pipewire.defaultAudioSink readonly property real volume: sink?.audio?.volume ?? 0 readonly property bool muted: sink?.audio?.muted ?? false readonly property string _volumeIcon: muted ? "\uF026" : (volume > 0.5 ? "\uF028" : (volume > 0 ? "\uF027" : "\uF026")) readonly property color _volumeColor: muted ? M.Theme.base04 : root.accentColor readonly property var _sinkList: { const sinks = []; if (Pipewire.nodes) { for (const node of Pipewire.nodes.values) if (!node.isStream && node.isSink) sinks.push(node); } return sinks; } readonly property var _streamList: { const streams = []; if (Pipewire.nodes) { for (const node of Pipewire.nodes.values) if (node.isStream && node.audio) streams.push(node); } return streams; } property bool _osdActive: false property bool _volumeInit: false property bool _mutedInit: false readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered readonly property bool _showPanel: _anyHover || _osdActive onVolumeChanged: { if (!_volumeInit) { _volumeInit = true; return; } _flashPanel(); } onMutedChanged: { if (!_mutedInit) { _mutedInit = true; return; } _flashPanel(); } function _flashPanel() { _osdActive = true; _osdTimer.restart(); } Timer { id: _osdTimer interval: 1500 onTriggered: root._osdActive = false } M.BarIcon { icon: root._volumeIcon minIcon: "\uF028" color: root._volumeColor anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: if (root.sink?.audio) root.sink.audio.muted = !root.sink.audio.muted } } M.BarLabel { label: Math.round(root.volume * 100) + "%" minText: "100%" color: root._volumeColor anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: if (root.sink?.audio) root.sink.audio.muted = !root.sink.audio.muted } } WheelHandler { onWheel: event => { if (!root.sink?.audio) return; root.sink.audio.volume = Math.max(0, root.sink.audio.volume + (event.angleDelta.y > 0 ? 0.05 : -0.05)); } } M.HoverPanel { id: hoverPanel showPanel: root._showPanel screen: QsWindow.window?.screen ?? null anchorItem: root accentColor: root.accentColor panelNamespace: "nova-volume" panelTitle: "Sound" contentWidth: 220 C.VolumeContent { width: hoverPanel.contentWidth sink: root.sink sinkList: root._sinkList streamList: root._streamList accentColor: root.accentColor } } }