import QtQuick import Quickshell import Quickshell.Services.Pipewire import "." as M import "../services" as S import "../applets" as C M.BarModule { id: root active: S.Modules.volume.enable spacing: S.Theme.moduleSpacing tooltip: "Volume: " + Math.round(volume * 100) + "%" + (muted ? " (muted)" : "") panelNamespace: "nova-volume" panelContentWidth: 220 panelComponent: Component { C.VolumeApplet { width: parent.width sink: root.sink sinkList: root._sinkList streamList: root._streamList accentColor: root.accentColor } } 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 ? S.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 _volumeInit: false property bool _mutedInit: false onVolumeChanged: { if (!_volumeInit) { _volumeInit = true; return; } flashPanel(); } onMutedChanged: { if (!_mutedInit) { _mutedInit = true; return; } flashPanel(); } M.BarIcon { icon: root._volumeIcon minIcon: "\uF028" color: root._volumeColor anchors.verticalCenter: parent.verticalCenter } M.BarLabel { label: Math.round(root.volume * 100) + "%" minText: "100%" color: root._volumeColor anchors.verticalCenter: parent.verticalCenter } 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)); } } }