pragma ComponentBehavior: Bound import qs.config import qs.custom import qs.services import qs.util import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets import Quickshell.Services.Pipewire Item { id: root required property PersistentProperties uiState required property int index readonly property list nodes: Pipewire.nodes.values.filter(node => node.isSink && node.isStream) width: Config.dashboard.mixerWidth height: Config.dashboard.mixerHeight PwObjectTracker { objects: root.nodes } ColumnLayout { id: layout anchors.fill: parent spacing: 7 CustomText { Layout.topMargin: 6 text: qsTr("Master Volume") color: Config.colors.secondary } CustomMouseArea { Layout.fillWidth: true implicitHeight: Config.osd.sliderWidth function onWheel(event: WheelEvent) { if (event.angleDelta.y > 0) Audio.increaseVolume(); else if (event.angleDelta.y < 0) Audio.decreaseVolume(); } acceptedButtons: Qt.RightButton onClicked: Audio.sink.audio.muted = !Audio.muted CustomFilledSlider { anchors.fill: parent orientation: Qt.Horizontal color: Audio.muted ? Config.colors.error : Config.colors.volume icon: Icons.getVolumeIcon(value, Audio.muted) value: Audio.volume onMoved: Audio.setVolume(value) Behavior on color { CAnim { duration: Config.anim.durations.small } } } } CustomRect { Layout.fillWidth: true Layout.topMargin: 5 Layout.bottomMargin: 5 height: 1 color: Config.colors.inactive } Item { Layout.fillWidth: true Layout.fillHeight: true CustomListView { id: list anchors.fill: parent spacing: 12 model: ScriptModel { values: [...root.nodes] objectProp: "id" } CustomScrollBar.vertical: CustomScrollBar { flickable: list } delegate: RowLayout { id: entry required property PwNode modelData spacing: 6 width: root.width IconImage { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter visible: source != "" implicitSize: slider.height * 1.4 source: { const icon = entry.modelData.properties["application.icon-name"]; if (icon) return Icons.getAppIcon(icon, "image-missing"); Icons.getAppIcon(entry.modelData.name, "image-missing") } } ColumnLayout { Layout.fillWidth: true spacing: 6 CustomText { Layout.fillWidth: true elide: Text.ElideRight text: { // application.name -> description -> name const app = entry.modelData.properties["application.name"] ?? (entry.modelData.description != "" ? entry.modelData.description : entry.modelData.name); const media = entry.modelData.properties["media.name"]; return media != undefined ? `${app} 🞄 ${media}` : app; } color: Config.colors.secondary } CustomMouseArea { id: slider Layout.fillWidth: true implicitHeight: Config.osd.sliderWidth acceptedButtons: Qt.RightButton onClicked: entry.modelData.audio.muted = !entry.modelData.audio.muted CustomFilledSlider { anchors.fill: parent orientation: Qt.Horizontal color: entry.modelData.audio.muted ? Config.colors.error : Config.colors.volume icon: Icons.getVolumeIcon(value, entry.modelData.audio.muted) value: entry.modelData.audio.volume onMoved: { if (entry.modelData.ready) entry.modelData.audio.volume = Math.max(0, Math.min(1, value)); } Behavior on color { CAnim { duration: Config.anim.durations.small } } } } } } } CustomText { anchors.centerIn: parent opacity: list.count === 0 ? 1 : 0 visible: opacity > 0 text: qsTr("No audio sources") color: Config.colors.tertiary font.pointSize: Config.font.size.normal Behavior on opacity { Anim {} } } } /* RowLayout { */ /* id: deviceSelectorRowLayout */ /* Layout.fillWidth: true */ /* uniformCellSizes: true */ /* AudioDeviceSelectorButton { */ /* Layout.fillWidth: true */ /* input: false */ /* onClicked: root.showDeviceSelectorDialog(input) */ /* } */ /* AudioDeviceSelectorButton { */ /* Layout.fillWidth: true */ /* input: true */ /* onClicked: root.showDeviceSelectorDialog(input) */ /* } */ /* } */ } }