diff --git a/mixer/MixerEntry.qml b/mixer/MixerEntry.qml new file mode 100644 index 0000000..51e9153 --- /dev/null +++ b/mixer/MixerEntry.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell.Services.Pipewire + +ColumnLayout { + required property PwNode node; + + // bind the node so we can read its properties + PwObjectTracker { objects: [ node ] } + + RowLayout { + Image { + visible: source != "" + source: { + const icon = node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; + return `image://icon/${icon}`; + } + + sourceSize.width: 20 + sourceSize.height: 20 + } + + Label { + text: { + // application.name -> description -> name + const app = node.properties["application.name"] ?? (node.description != "" ? node.description : node.name); + const media = node.properties["media.name"]; + return media != undefined ? `${app} - ${media}` : app; + } + } + + Button { + text: node.audio.muted ? "unmute" : "mute" + onClicked: node.audio.muted = !node.audio.muted + } + } + + RowLayout { + Label { + Layout.preferredWidth: 50 + text: `${Math.floor(node.audio.volume * 100)}%` + } + + Slider { + Layout.fillWidth: true + value: node.audio.volume + onValueChanged: node.audio.volume = value + } + } +} diff --git a/mixer/README.md b/mixer/README.md new file mode 100644 index 0000000..a361eac --- /dev/null +++ b/mixer/README.md @@ -0,0 +1,7 @@ +# Audio mixer + +This is a simple audio mixer built with the pipewire API. + +You can run the mixer with `quickshell -p shell.qml`. + +![](./image.png) diff --git a/mixer/image.png b/mixer/image.png new file mode 100644 index 0000000..3353bd8 Binary files /dev/null and b/mixer/image.png differ diff --git a/mixer/shell.qml b/mixer/shell.qml new file mode 100644 index 0000000..ac8bdf4 --- /dev/null +++ b/mixer/shell.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire + +ShellRoot { + FloatingWindow { + // match the system theme background color + color: contentItem.palette.active.window + + ScrollView { + anchors.fill: parent + contentWidth: availableWidth + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + + // get a list of nodes that output to the default sink + PwNodeLinkTracker { + id: linkTracker + node: Pipewire.defaultAudioSink + } + + MixerEntry { + node: Pipewire.defaultAudioSink + } + + Rectangle { + Layout.fillWidth: true + color: palette.active.text + implicitHeight: 1 + } + + Repeater { + model: linkTracker.linkGroups + + MixerEntry { + required property PwLinkGroup modelData + // Each link group contains a source and a target. + // Since the target is the default sink, we want the source. + node: modelData.source + } + } + } + } + } +}