add popup for audio management

This commit is contained in:
lucy 2026-08-25 20:11:19 +02:00
commit 5005ba24db
11 changed files with 298 additions and 120 deletions

View file

@ -0,0 +1,34 @@
pragma Singleton
import Quickshell
import QtQuick
import Quickshell.Services.Pipewire
Singleton {
id: root
property bool ready: Pipewire.ready
property bool muted: defaultAudio.audio.muted
property PwNode defaultAudio: Pipewire.defaultAudioSink
property PwNode preferredAudio: Pipewire.preferredDefaultAudioSink
property real percentage: Math.round(preferredAudio?.audio.volume * 100)
readonly property list<PwNode> sinks: Pipewire.nodes.values.filter(n => n.audio && n.isSink && !n.isStream)
readonly property list<PwNode> sources: Pipewire.nodes.values.filter(n => n.audio && !n.isSink && !n.isStream)
PwObjectTracker {
objects: [...root.sinks, ...root.sources]
}
function getVolumeIcon(sink) {
let mute = sink.audio.muted
let volume = sink.audio.volume
if (mute) {
return "audio-volume-muted-symbolic";
}
if (volume <= 0.33) {
return "audio-volume-low-symbolic";
}
if (volume <= 0.66) {
return "audio-volume-medium-symbolic";
}
if (volume >= 0.66) {
return "audio-volume-high-symbolic";
}
}
}

View file

@ -0,0 +1,10 @@
pragma Singleton
import Quickshell
import QtQuick
import Quickshell.Pipewire
Singleton {
property
}

View file

@ -6,44 +6,48 @@ import qs.settings
import qs.widgets
import qs
Variants {
model: Quickshell.screens
delegate: PanelWindow {
required property ShellScreen modelData
id: root
screen: modelData
anchors {
top: true
left: true
right: true
}
color: 'transparent'
implicitHeight: Config.barHeight
Rectangle {
anchors.fill: parent
color: Colors.barBg
RowLayout {
anchors.leftMargin: 10
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
Ws { barScreen: root.modelData }
ActiveWindow {}
}
RowLayout {
anchors.centerIn: parent
Date {}
}
RowLayout {
anchors.rightMargin: 10
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Mpris {}
Bat {}
Pw {}
Tray {}
WallPicker {screenHeight: root.screen.height}
}
}
id: root
required property ShellScreen modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
color: 'transparent'
implicitHeight: Config.barHeight
Rectangle {
anchors.fill: parent
color: Colors.barBg
RowLayout {
anchors.leftMargin: 10
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
Ws {
barScreen: root.modelData
}
ActiveWindow {}
}
RowLayout {
anchors.centerIn: parent
Date {}
}
RowLayout {
anchors.rightMargin: 10
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Mpris {}
Bat {}
Tray {}
WallPicker {
screenHeight: root.screen.height
}
Pw {}
}
}
}
}

View file

@ -9,6 +9,7 @@ import QtQuick.Effects
import qs
import qs.widgets
import qs.settings
import qs.modules.Services
BarComponent {
id: root
@ -17,50 +18,23 @@ BarComponent {
property bool mute: Pipewire.ready ? defaultAudio?.audio.muted : null
implicitHeight: Config.moduleHeight
implicitWidth: pwLayout.implicitWidth + 16
function getVolumeIcon() {
let volume = Pipewire.ready ? root.defaultAudio.audio.volume : null;
if (root.mute) {
return "audio-volume-muted";
}
if (volume <= 0.33) {
return "audio-volume-low";
}
if (volume <= 0.66) {
return "audio-volume-medium";
}
if (volume >= 0.66) {
return "audio-volume-high";
}
}
PwObjectTracker {
objects: [Pipewire.ready ? Pipewire.defaultAudioSink : null]
}
Process {
id: pavuLauncher
command: ["pavucontrol"]
running: false
}
RowLayout {
id: pwLayout
anchors.centerIn: root
spacing: 4
IconImage {
source: Quickshell.iconPath(root.getVolumeIcon() + "-symbolic", root.getVolumeIcon())
implicitSize: 14
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: Colors.text
brightness: 0.5
}
}
IconImage {
source: Quickshell.iconPath(Audio.getVolumeIcon(Audio.defaultAudio))
implicitSize: 14
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: Colors.text
brightness: 0.5
}
}
StyledText {
text: Math.round(root.defaultAudio?.audio.volume * 100) + "%"
opacity: root.defaultAudio?.audio.muted ? 0.6 : 1.0
text: Audio.percentage + "%"
opacity: Audio.muted ? 0.6 : 1.0
}
}
MouseArea {
@ -71,35 +45,8 @@ BarComponent {
anchors.fill: root
onClicked: volPopup.visible = !volPopup.visible
}
PopupWindow {
VolPopup {
id: volPopup
anchor.item: root
implicitWidth: 250
implicitHeight: 600
anchor.edges: Edges.Top
anchor.gravity: Edges.Bottom
anchor.margins.top: 40
color: 'transparent'
Rectangle {
id: test
anchors.fill: parent
color: Colors.activeModuleBg
radius: Config.rounding
ListView {
id: sourceLayout
model: Pipewire.nodes
delegate: Rectangle {
implicitHeight: 10
required property var modelData
StyledText {
text: parent.modelData.nickname
}
}
}
}
}
}

102
modules/bar/VolPopup.qml Normal file
View file

@ -0,0 +1,102 @@
import qs
import qs.settings
import Quickshell
import QtQuick
import qs.widgets
import qs.modules.Services
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell.Widgets
import QtQuick.Effects
PopupWindow {
implicitWidth: 400
implicitHeight: audioLayout.implicitHeight + 24
anchor.edges: Edges.Top | Edges.Right
anchor.gravity: Edges.Bottom | Edges.Left
anchor.margins.top: 40
color: 'transparent'
Rectangle {
id: test
anchors.fill: parent
color: Colors.moduleBg
radius: Config.rounding
ColumnLayout {
id: audioLayout
anchors.fill: parent
anchors.margins: 12
spacing: 8
Repeater {
model: Audio.sinks
delegate: Rectangle {
id: audioDelContainer
color: Colors.activeModuleBg
radius: Config.rounding
required property var modelData
implicitWidth: 380
implicitHeight: audioRow.implicitHeight + 8
RowLayout {
id: audioRow
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.fill: parent
StyledText {
text: audioDelContainer.modelData.nickname
Layout.leftMargin: 10
Layout.fillWidth: true
}
IconImage {
source: Quickshell.iconPath(Audio.getVolumeIcon(audioDelContainer.modelData))
implicitSize: 16
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: Colors.text
brightness: 0.5
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
audioDelContainer.modelData.audio.muted = !audioDelContainer.modelData.audio.muted
}
}
}
IconImage {
source: Quickshell.iconPath(audioDelContainer.modelData === Audio.preferredAudio ? "radio-checked-symbolic" : "radio-symbolic")
implicitSize: 16
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: Colors.text
brightness: 0.5
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onClicked: {
Audio.preferredAudio = audioDelContainer.modelData
}
}
}
Slider {
Layout.rightMargin: 10
value: audioDelContainer.modelData.audio.volume
from: 0
to: 1
onMoved: {
audioDelContainer.modelData.audio.volume = value;
}
}
}
}
}
}
}
}

39
modules/bar/VolPopup.qml~ Normal file
View file

@ -0,0 +1,39 @@
import qs
import qs.settings
import Quickshell
import QtQuick
import qs.widgets
PopupWindow {
id: volPopup
anchor.item: root
implicitWidth: 500
implicitHeight: 600
anchor.edges: Edges.Top | Edges.Right
anchor.gravity: Edges.Bottom | Edges.Left
anchor.margins.top: 40
anchor.margins.right: 0
color: 'transparent'
Rectangle {
id: test
anchors.fill: parent
color: Colors.activeModuleBg
radius: Config.rounding
ListView {
id: sourceLayout
model: Pipewire.nodes
delegate: Rectangle {
implicitHeight: 10
required property var modelData
StyledText {
text: parent.modelData.nickname
}
}
}
}
}

40
modules/bar/volPopup.qml~ Normal file
View file

@ -0,0 +1,40 @@
import qs
import qs.settings
import Quickshell
import Quickshell.Widgets
import QtQuick
import qs.widgets
PopupWindow {
id: volPopup
anchor.item: root
implicitWidth: 500
implicitHeight: 600
anchor.edges: Edges.Top | Edges.Right
anchor.gravity: Edges.Bottom | Edges.Left
anchor.margins.top: 40
anchor.margins.right: 0
color: 'transparent'
Rectangle {
id: test
anchors.fill: parent
color: Colors.activeModuleBg
radius: Config.rounding
ListView {
id: sourceLayout
model: Pipewire.nodes
delegate: Rectangle {
implicitHeight: 10
required property var modelData
StyledText {
text: parent.modelData.nickname
}
}
}
}
}