From: Demi Marie Obenour <demiobenour@gmail.com>
To: Spectrum OS Development <devel@spectrum-os.org>
Subject: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
Date: Thu, 17 Jul 2025 22:14:49 -0400 [thread overview]
Message-ID: <25197dbe-d642-4283-80b7-be66dcc4f4eb@gmail.com> (raw)
In-Reply-To: <6ab772e0-62b0-430f-8d3c-0bba79a3282c@gmail.com>
WirePlumber is completely overkill as a session manager here, and
ideally a trivial session manager would be used instead. I did build a
Spectrum OS image and found that PipeWire and WirePlumber both
successfully started. PipeWire is configured to listen on the
PulseAudio socket, so PulseAudio compatibility works. This does inject
a large number of completely unnecessary files into the VM, notably for
libcamera and Bluetooth support.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
Notes:
I tested this with QEMU virtio-sound, but I am not sure if the paramters
are correct. In particular, the sample rate might well be wrong.
img/app/Makefile | 28 +-
img/app/default.nix | 18 +
img/app/etc/pipewire/pipewire.conf | 291 ++++++++
.../etc/s6-rc/app/dependencies.d/directories | 0
.../app/dependencies.d/directories.license | 2 +
.../etc/s6-rc/app/dependencies.d/wireplumber | 0
.../app/dependencies.d/wireplumber.license | 2 +
.../etc/s6-rc/dbus/dependencies.d/directories | 0
.../dbus/dependencies.d/directories.license | 2 +
img/app/etc/s6-rc/directories/type | 1 +
img/app/etc/s6-rc/directories/type.license | 2 +
img/app/etc/s6-rc/directories/up | 11 +
.../s6-rc/pipewire/dependencies.d/directories | 0
.../dependencies.d/directories.license | 2 +
img/app/etc/s6-rc/pipewire/notification-fd | 1 +
.../s6-rc/pipewire/notification-fd.license | 2 +
img/app/etc/s6-rc/pipewire/run | 20 +
img/app/etc/s6-rc/pipewire/type | 1 +
img/app/etc/s6-rc/pipewire/type.license | 2 +
.../dependencies.d/directories | 0
.../dependencies.d/directories.license | 2 +
img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 -
.../etc/s6-rc/wireplumber/dependencies.d/dbus | 0
.../wireplumber/dependencies.d/dbus.license | 2 +
.../s6-rc/wireplumber/dependencies.d/pipewire | 0
.../dependencies.d/pipewire.license | 2 +
img/app/etc/s6-rc/wireplumber/run | 4 +
img/app/etc/s6-rc/wireplumber/type | 1 +
img/app/etc/s6-rc/wireplumber/type.license | 2 +
img/app/etc/wireplumber/wireplumber.conf | 676 ++++++++++++++++++
30 files changed, 1069 insertions(+), 16 deletions(-)
create mode 100644 img/app/etc/pipewire/pipewire.conf
create mode 100644 img/app/etc/s6-rc/app/dependencies.d/directories
create mode 100644 img/app/etc/s6-rc/app/dependencies.d/directories.license
create mode 100644 img/app/etc/s6-rc/app/dependencies.d/wireplumber
create mode 100644 img/app/etc/s6-rc/app/dependencies.d/wireplumber.license
create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories
create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories.license
create mode 100644 img/app/etc/s6-rc/directories/type
create mode 100644 img/app/etc/s6-rc/directories/type.license
create mode 100644 img/app/etc/s6-rc/directories/up
create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/directories
create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/directories.license
create mode 100644 img/app/etc/s6-rc/pipewire/notification-fd
create mode 100644 img/app/etc/s6-rc/pipewire/notification-fd.license
create mode 100644 img/app/etc/s6-rc/pipewire/run
create mode 100644 img/app/etc/s6-rc/pipewire/type
create mode 100644 img/app/etc/s6-rc/pipewire/type.license
create mode 100644 img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories
create mode 100644 img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories.license
create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/dbus
create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/dbus.license
create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire
create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire.license
create mode 100644 img/app/etc/s6-rc/wireplumber/run
create mode 100644 img/app/etc/s6-rc/wireplumber/type
create mode 100644 img/app/etc/s6-rc/wireplumber/type.license
create mode 100644 img/app/etc/wireplumber/wireplumber.conf
diff --git a/img/app/Makefile b/img/app/Makefile
index d81337f99e2e83cf03ac73b9fc96fae6ce118537..691db75c287294104bb2fa332e002792bfa61f6e 100644
--- a/img/app/Makefile
+++ b/img/app/Makefile
@@ -53,7 +53,10 @@ VM_FILES = \
etc/s6-linux-init/scripts/rc.init \
etc/s6-linux-init/scripts/rc.shutdown \
etc/s6-linux-init/scripts/rc.shutdown.final \
- etc/xdg/xdg-desktop-portal/portals.conf
+ etc/xdg/xdg-desktop-portal/portals.conf \
+ etc/pipewire/pipewire.conf \
+ etc/wireplumber/wireplumber.conf
+
VM_DIRS = dev run proc sys tmp \
etc/s6-linux-init/run-image/service
VM_FIFOS = etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/fifo
@@ -81,24 +84,38 @@ build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) bu
) | ../../scripts/make-erofs.sh $@
VM_S6_RC_FILES = \
+ etc/s6-rc/app/dependencies.d/directories \
etc/s6-rc/app/dependencies.d/dbus \
etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \
+ etc/s6-rc/app/dependencies.d/wireplumber \
etc/s6-rc/app/run \
etc/s6-rc/app/type \
+ etc/s6-rc/dbus/dependencies.d/directories \
etc/s6-rc/dbus/notification-fd \
etc/s6-rc/dbus/run \
etc/s6-rc/dbus/type \
+ etc/s6-rc/directories/type \
+ etc/s6-rc/directories/up \
etc/s6-rc/mdevd-coldplug/dependencies \
etc/s6-rc/mdevd-coldplug/type \
etc/s6-rc/mdevd-coldplug/up \
etc/s6-rc/mdevd/notification-fd \
etc/s6-rc/mdevd/run \
etc/s6-rc/mdevd/type \
+ etc/s6-rc/ok-all/contents \
+ etc/s6-rc/ok-all/type \
+ etc/s6-rc/pipewire/dependencies.d/directories \
+ etc/s6-rc/pipewire/notification-fd \
+ etc/s6-rc/pipewire/run \
+ etc/s6-rc/pipewire/type \
+ etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories \
etc/s6-rc/wayland-proxy-virtwl/notification-fd \
etc/s6-rc/wayland-proxy-virtwl/run \
etc/s6-rc/wayland-proxy-virtwl/type \
- etc/s6-rc/ok-all/contents \
- etc/s6-rc/ok-all/type
+ etc/s6-rc/wireplumber/dependencies.d/dbus \
+ etc/s6-rc/wireplumber/dependencies.d/pipewire \
+ etc/s6-rc/wireplumber/run \
+ etc/s6-rc/wireplumber/type
build/etc/s6-rc: $(VM_S6_RC_FILES)
mkdir -p $$(dirname $@)
@@ -135,7 +152,7 @@ start-virtiofsd: scripts/start-virtiofsd.elb
.PHONY: start-virtiofsd
run-qemu: $(imgdir)/appvm/blk/root.img start-vhost-user-net start-virtiofsd
- @../../scripts/run-qemu.sh -m 256 -cpu host -kernel $(KERNEL) -vga none \
+ @../../scripts/run-qemu.sh -m 256 -kernel $(KERNEL) -vga none \
-drive file=$(imgdir)/appvm/blk/root.img,if=virtio,format=raw,readonly=on \
-append "root=PARTLABEL=root nokaslr" \
-gdb unix:build/gdb.sock,server,nowait \
@@ -151,7 +168,8 @@ run-qemu: $(imgdir)/appvm/blk/root.img start-vhost-user-net start-virtiofsd
-parallel none \
-chardev vc,id=virtiocon0 \
-device virtio-serial-pci \
- -device virtconsole,chardev=virtiocon0
+ -device virtconsole,chardev=virtiocon0 \
+ -audio driver=pipewire,model=virtio
.PHONY: run-qemu
run-cloud-hypervisor: $(imgdir)/appvm/blk/root.img start-vhost-user-gpu start-vhost-user-net start-virtiofsd
diff --git a/img/app/default.nix b/img/app/default.nix
index 740643ac41f6473cdb6f6b0fd1f5f47f4493240d..8c469ee5b9a672bf6504600b09ff1f57fb87f2d9 100644
--- a/img/app/default.nix
+++ b/img/app/default.nix
@@ -26,6 +26,20 @@ let
CONFIG_FEATURE_IP_ROUTE y
CONFIG_INIT n
CONFIG_IP y
+ CONFIG_FACTOR n
+ CONFIG_FOLD n
+ CONFIG_LSSCSI n
+ CONFIG_NANDWRITE n
+ CONFIG_NANDDUMP n
+ CONFIG_RAIDAUTORUN n
+ CONFIG_RFKILL n
+ CONFIG_UBIATTACH n
+ CONFIG_UBIDETACH n
+ CONFIG_UBIMKVOL n
+ CONFIG_UBIRMVOL n
+ CONFIG_UBIRSVOL n
+ CONFIG_UBIUPDATEVOL n
+ CONFIG_UBIRENAME n
'';
})
@@ -48,6 +62,10 @@ let
pkgs.xwayland
pkgs.xdg-desktop-portal
pkgs.xdg-desktop-portal-gtk
+ # Depends on pulseaudio libs
+ pkgs.pipewire
+ pkgs.wireplumber
+ pkgs.pulseaudio
];
})).fhsenv;
in
diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf
new file mode 100644
index 0000000000000000000000000000000000000000..30f289e11d6b075c592d36c17ebc681fd7599bea
--- /dev/null
+++ b/img/app/etc/pipewire/pipewire.conf
@@ -0,0 +1,291 @@
+# SPDX-License-Identifier: MIT
+
+# Copyright © 2018 Wim Taymans
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+context.properties = {
+ ## Configure properties in the system.
+ link.max-buffers = 16 # version < 3 clients can't handle more
+
+ core.daemon = true # listening for socket connections
+ core.name = pipewire-0 # core name and socket name
+ default.clock.min-quantum = 1024 # account for running in a VM
+}
+
+context.spa-libs = {
+ #<factory-name regex> = <library-name>
+ #
+ # Used to find spa factory names. It maps an spa factory name
+ # regular expression to a library name that should contain
+ # that factory.
+ #
+ audio.convert.* = audioconvert/libspa-audioconvert
+ api.alsa.* = alsa/libspa-alsa
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ # Uses realtime scheduling to boost the audio thread priorities. This uses
+ # RTKit if the user doesn't have permission to use regular realtime
+ # scheduling. You can also clamp utilisation values to improve scheduling
+ # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices.
+ # use module.rt.args = { ... } to override the arguments.
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ rt.prio = 88
+ }
+ flags = [ ifexists nofail ]
+ condition = [ { module.rt = !false } ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native
+ args = {
+ # List of server Unix sockets, and optionally permissions
+ #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ]
+ }
+ }
+
+ # Allows applications to create metadata objects. It creates
+ # a factory for Metadata objects.
+ { name = libpipewire-module-metadata
+ condition = [ { module.metadata = !false } ]
+ }
+
+ # Creates a factory for making devices that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-device-factory
+ condition = [ { module.spa-device-factory = !false } ]
+ }
+
+ # Creates a factory for making nodes that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-node-factory
+ condition = [ { module.spa-node-factory = !false } ]
+ }
+
+ # Allows creating nodes that run in the context of the
+ # client. Is used by all clients that want to provide
+ # data to PipeWire.
+ { name = libpipewire-module-client-node
+ condition = [ { module.client-node = !false } ]
+ }
+
+ # Allows creating devices that run in the context of the
+ # client. Is used by the session manager.
+ { name = libpipewire-module-client-device
+ condition = [ { module.client-device = !false } ]
+ }
+
+ # The portal module monitors the PID of the portal process
+ # and tags connections with the same PID as portal
+ # connections.
+ { name = libpipewire-module-portal
+ flags = [ ifexists nofail ]
+ condition = [ { module.portal = !false } ]
+ }
+
+ # The access module can perform access checks and block
+ # new clients.
+ { name = libpipewire-module-access
+ args = {
+ # Socket-specific access permissions
+ #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" }
+
+ # Deprecated legacy mode (not socket-based),
+ # for now enabled by default if access.socket is not specified
+ #access.legacy = true
+ }
+ condition = [ { module.access = !false } ]
+ }
+
+ # Makes a factory for wrapping nodes in an adapter with a
+ # converter and resampler.
+ { name = libpipewire-module-adapter
+ condition = [ { module.adapter = !false } ]
+ }
+
+ # Makes a factory for creating links between ports.
+ # use module.link-factory.args = { ... } to override the arguments.
+ { name = libpipewire-module-link-factory
+ args = {
+ #allow.link.passive = false
+ }
+ condition = [ { module.link-factory = !false } ]
+ }
+
+ # Provides factories to make session manager objects.
+ { name = libpipewire-module-session-manager
+ condition = [ { module.session-manager = !false } ]
+ }
+
+ # The PulseAudio communication protocol
+ { name = libpipewire-module-protocol-pulse
+ args = {
+ # the addresses this server listens on
+ server.address = [ "unix:native" ]
+ }
+ }
+]
+
+context.objects = [
+ # A default dummy driver. This handles nodes marked with the "node.always-process"
+ # property when no other driver is currently active. JACK clients need this.
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Dummy-Driver
+ node.group = pipewire.dummy
+ node.sync-group = sync.dummy
+ priority.driver = 200000
+ #clock.id = monotonic # realtime | tai | monotonic-raw | boottime
+ #clock.name = "clock.system.monotonic"
+ }
+ condition = [ { factory.dummy-driver = !false } ]
+ }
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Freewheel-Driver
+ priority.driver = 190000
+ node.group = pipewire.freewheel
+ node.sync-group = sync.dummy
+ node.freewheel = true
+ #freewheel.wait = 10
+ }
+ condition = [ { factory.freewheel-driver = !false } ]
+ }
+
+ { factory = adapter
+ args = {
+ factory.name = "api.alsa.pcm.source"
+ node.name = "alsa_input"
+ media.class = "Audio/Source"
+ alsa.card = 0,
+ alsa.card_name = "VirtIO SoundCard"
+ alsa.device = 0
+ alsa.driver_name = "virtio_snd"
+ alsa.id = "SoundCard"
+ alsa.long_card_name = "VirtIO SoundCard at pci/0000:00:01.0/virtio0"
+ alsa.name = "VirtIO SoundCard"
+ alsa.subdevice = 1
+ alsa.subdevice_name = "subdevice #1"
+ api.alsa.card.longname = "VirtIO SoundCard at pci/0000:00:01.0/virtio0"
+ api.alsa.card.name = "VirtIO SoundCard"
+ api.alsa.headroom = 0
+ api.alsa.path = "hw:0"
+ api.alsa.pcm.card = 0,
+ api.alsa.pcm.stream = "capture"
+ media.class = "Audio/Source"
+ api.alsa.path = "hw:0"
+ node.suspend-on-idle = true
+ audio.format = "S32"
+ audio.rate = 48000
+ audio.allowed-rates = [ ]
+ audio.channels = 2
+ audio.position = "FL,FR"
+ }
+ }
+ { factory = adapter
+ args = {
+ factory.name = "api.alsa.pcm.sink"
+ node.name = "alsa_output"
+ media.class = "Audio/Sink"
+ alsa.card = 0,
+ alsa.card_name = "VirtIO SoundCard"
+ alsa.device = 0
+ alsa.driver_name = "virtio_snd"
+ alsa.id = "SoundCard"
+ alsa.long_card_name = "VirtIO SoundCard at pci/0000:00:01.0/virtio0"
+ alsa.name = "VirtIO SoundCard"
+ alsa.subdevice = 0
+ alsa.subdevice_name = "subdevice #0"
+ api.alsa.card.longname = "VirtIO SoundCard at pci/0000:00:01.0/virtio0"
+ api.alsa.card.name = "VirtIO SoundCard"
+ api.alsa.headroom = 0
+ api.alsa.path = "hw:0"
+ api.alsa.pcm.card = 0,
+ api.alsa.pcm.stream = "playback"
+ node.suspend-on-idle = true
+ audio.format = "S32"
+ audio.rate = 48000
+ audio.allowed-rates = [ ]
+ audio.channels = 2
+ audio.position = "FL,FR"
+ }
+ }
+]
+
+pulse.cmd = [
+ { cmd = "load-module" args = "module-always-sink" flags = [ ]
+ condition = [ { pulse.cmd.always-sink = !false } ] }
+ { cmd = "load-module" args = "module-device-manager" flags = [ ]
+ condition = [ { pulse.cmd.device-manager = !false } ] }
+ { cmd = "load-module" args = "module-device-restore" flags = [ ]
+ condition = [ { pulse.cmd.device-restore = !false } ] }
+ { cmd = "load-module" args = "module-stream-restore" flags = [ ]
+ condition = [ { pulse.cmd.stream-restore = !false } ] }
+]
+
+pulse.properties.rules = [
+ { matches = [ { cpu.vm.name = !null } ]
+ actions = {
+ update-props = {
+ # These overrides are only applied when running in a vm.
+ pulse.min.quantum = 1024/48000 # 22ms
+ }
+ }
+ }
+]
+
+# client/stream specific properties
+pulse.rules = [
+ {
+ # skype does not want to use devices that don't have an S16 sample format.
+ matches = [
+ { application.process.binary = "teams" }
+ { application.process.binary = "teams-insiders" }
+ { application.process.binary = "teams-for-linux" }
+ { application.process.binary = "skypeforlinux" }
+ ]
+ actions = { quirks = [ force-s16-info ] }
+ }
+ {
+ # firefox marks the capture streams as don't move and then they
+ # can't be moved with pavucontrol or other tools.
+ matches = [ { application.process.binary = "firefox" } ]
+ actions = { quirks = [ remove-capture-dont-move ] }
+ }
+ {
+ # speech dispatcher asks for too small latency and then underruns.
+ matches = [ { application.name = "~speech-dispatcher.*" } ]
+ actions = {
+ update-props = {
+ pulse.min.req = 512/48000 # 10.6ms
+ pulse.min.quantum = 512/48000 # 10.6ms
+ pulse.idle.timeout = 5 # pause after 5 seconds of underrun
+ }
+ }
+ }
+]
+
+context.exec = []
diff --git a/img/app/etc/s6-rc/app/dependencies.d/directories b/img/app/etc/s6-rc/app/dependencies.d/directories
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/app/dependencies.d/directories.license b/img/app/etc/s6-rc/app/dependencies.d/directories.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/app/dependencies.d/directories.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/app/dependencies.d/wireplumber b/img/app/etc/s6-rc/app/dependencies.d/wireplumber
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/app/dependencies.d/wireplumber.license b/img/app/etc/s6-rc/app/dependencies.d/wireplumber.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/app/dependencies.d/wireplumber.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/dbus/dependencies.d/directories b/img/app/etc/s6-rc/dbus/dependencies.d/directories
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/dbus/dependencies.d/directories.license b/img/app/etc/s6-rc/dbus/dependencies.d/directories.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/dbus/dependencies.d/directories.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/directories/type b/img/app/etc/s6-rc/directories/type
new file mode 100644
index 0000000000000000000000000000000000000000..bdd22a1850ae6c03a414eeb8084998679a2cdf92
--- /dev/null
+++ b/img/app/etc/s6-rc/directories/type
@@ -0,0 +1 @@
+oneshot
diff --git a/img/app/etc/s6-rc/directories/type.license b/img/app/etc/s6-rc/directories/type.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/directories/type.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up
new file mode 100644
index 0000000000000000000000000000000000000000..2396fad42ac695afe924119b38b36c6654f2a504
--- /dev/null
+++ b/img/app/etc/s6-rc/directories/up
@@ -0,0 +1,11 @@
+#!/bin/execlineb -P
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is>
+#
+# Directory creation (if it's copyrightable):
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2022 Unikie
+
+if { mkdir -m 1755 /tmp/.X11-unix }
+if { mkdir -m 0755 /run/user }
+if { mkdir -m 0700 /run/user/0 }
diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/directories b/img/app/etc/s6-rc/pipewire/dependencies.d/directories
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/directories.license b/img/app/etc/s6-rc/pipewire/dependencies.d/directories.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/dependencies.d/directories.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/pipewire/notification-fd b/img/app/etc/s6-rc/pipewire/notification-fd
new file mode 100644
index 0000000000000000000000000000000000000000..7ed6ff82de6bcc2a78243fc9c54d3ef5ac14da69
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/notification-fd
@@ -0,0 +1 @@
+5
diff --git a/img/app/etc/s6-rc/pipewire/notification-fd.license b/img/app/etc/s6-rc/pipewire/notification-fd.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/notification-fd.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/pipewire/run b/img/app/etc/s6-rc/pipewire/run
new file mode 100644
index 0000000000000000000000000000000000000000..c0b55a152ed2d926d460ff95b6ef242199609e02
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/run
@@ -0,0 +1,20 @@
+#!/bin/execlineb -P
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+s6-ipcserver-socketbinder -B /run/user/0/pipewire-0
+fdmove -c 3 0
+
+s6-ipcserver-socketbinder -B /run/user/0/pipewire-0-manager
+fdmove -c 4 0
+
+redirfd -r 0 /dev/null
+
+# Notify readiness.
+if { fdmove 1 5 echo }
+fdclose 5
+
+export LISTEN_FDS 2
+getpid LISTEN_PID
+pipewire
diff --git a/img/app/etc/s6-rc/pipewire/type b/img/app/etc/s6-rc/pipewire/type
new file mode 100644
index 0000000000000000000000000000000000000000..5883cff0cd1514b2836f4ffa39fdac769a5213cb
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/type
@@ -0,0 +1 @@
+longrun
diff --git a/img/app/etc/s6-rc/pipewire/type.license b/img/app/etc/s6-rc/pipewire/type.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/pipewire/type.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories b/img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories.license b/img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/dependencies.d/directories.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run
index 7b8034368f547cfaf83a81a3b5d73ab864edafff..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755
--- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run
+++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run
@@ -1,17 +1,6 @@
#!/bin/execlineb -P
# SPDX-License-Identifier: EUPL-1.2+
# SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is>
-#
-# Directory creation (if it's copyrightable):
-# SPDX-License-Identifier: MIT
-# SPDX-FileCopyrightText: 2022 Unikie
-
-foreground { mkdir /tmp/.X11-unix }
-foreground { mkdir /run/user }
-foreground {
- umask 077
- mkdir /run/user/0
-}
s6-ipcserver-socketbinder -B /run/user/0/wayland-0
fdmove -c 3 0
diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus b/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus.license b/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire b/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire.license b/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/s6-rc/wireplumber/run b/img/app/etc/s6-rc/wireplumber/run
new file mode 100644
index 0000000000000000000000000000000000000000..e721d8df9b97f05812d63520c1b450092986be96
--- /dev/null
+++ b/img/app/etc/s6-rc/wireplumber/run
@@ -0,0 +1,4 @@
+#!/bin/execlineb -P
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+wireplumber
diff --git a/img/app/etc/s6-rc/wireplumber/type b/img/app/etc/s6-rc/wireplumber/type
new file mode 100644
index 0000000000000000000000000000000000000000..5883cff0cd1514b2836f4ffa39fdac769a5213cb
--- /dev/null
+++ b/img/app/etc/s6-rc/wireplumber/type
@@ -0,0 +1 @@
+longrun
diff --git a/img/app/etc/s6-rc/wireplumber/type.license b/img/app/etc/s6-rc/wireplumber/type.license
new file mode 100644
index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4
--- /dev/null
+++ b/img/app/etc/s6-rc/wireplumber/type.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/img/app/etc/wireplumber/wireplumber.conf b/img/app/etc/wireplumber/wireplumber.conf
new file mode 100644
index 0000000000000000000000000000000000000000..492515df792e309b667804de2c2cadac8f69210a
--- /dev/null
+++ b/img/app/etc/wireplumber/wireplumber.conf
@@ -0,0 +1,676 @@
+# SPDX-License-Identifier: MIT
+
+# Copyright © 2019-2021 Collabora Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+context.spa-libs = {
+ ## SPA factory name to library mappings
+ ## Used to find SPA factory names. It maps a SPA factory name regular
+ ## expression to a library name that should contain that factory.
+ ##
+ ## Syntax:
+ ## <factory-name regex> = <library-name>
+
+ api.alsa.* = alsa/libspa-alsa
+ audio.convert.* = audioconvert/libspa-audioconvert
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ ## PipeWire modules to load.
+ ## These modules are loaded before a connection to pipewire is attempted.
+ ## This section should be kept minimal and load only the modules that are
+ ## necessary for the protocol to work.
+ ##
+ ## If ifexists is given, the module is ignored when it is not found.
+ ## If nofail is given, module initialization failures are ignored.
+ ##
+ ## Syntax:
+ ## {
+ ## name = <module-name>
+ ## [ args = { <key> = <value> ... } ]
+ ## [ flags = [ ifexists | nofail ] ]
+ ## }
+
+ # Uses RTKit to boost the data thread priority. Also allows clamping
+ # of utilisation when using the Completely Fair Scheduler on Linux.
+ {
+ name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ # rt.prio = 88
+ # rt.time.soft = -1
+ # rt.time.hard = -1
+ # uclamp.min = 0
+ # uclamp.max = 1024
+ }
+ flags = [ ifexists, nofail ]
+ }
+
+ ## The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ ## Support for metadata objects
+ { name = libpipewire-module-metadata }
+]
+
+wireplumber.profiles = {
+ ## Syntax:
+ ## <profile> = {
+ ## inherits = [ other, profile, names ] # optional
+ ## # optional is the default
+ ## <feature name> = [ required | optional | disabled ]
+ ## ...
+ ## }
+
+ # The default profile
+ main = {
+ metadata.sm-settings = required
+ metadata.sm-objects = required
+
+ policy.standard = required
+
+ hardware.audio = required
+
+ # PipeWire Media Session is not running.
+ # Do not bother to check.
+ check.no-media-session = disabled
+
+ # No Bluetooth
+ hardware.bluetooth = disabled
+ bluetooth.use-persistent-storage = disabled
+ bluetooth.autoswitch-to-headset-profile = disabled
+
+ # No video capture
+ hardware.video-capture = disabled
+
+ # There is only one instance of WirePlumber, so
+ # pretend that this is a system-wide instance.
+ # See mixin.systemwide-session in the upstream wireplumber.conf.
+ support.reserve-device = disabled
+ monitor.alsa.reserve-device = disabled
+ support.portal-permissionstore = disabled
+ script.client.access-portal = disabled
+ support.logind = disabled
+ monitor.bluez.seat-monitoring = disabled
+
+ # Disable anything that could store state.
+ # See mixin.stateless in the upstream wireplumber.conf.
+ hooks.device.profile.state = disabled
+ hooks.device.routes.state = disabled
+ hooks.default-nodes.state = disabled
+ hooks.stream.state = disabled
+ }
+}
+
+wireplumber.components = [
+ ## WirePlumber components to load.
+ ## These components are loaded after a connection to pipewire is established.
+ ## type is mandatory; rest of the tags are optional
+ ##
+ ## Syntax:
+ ## {
+ ## name = <component-name>
+ ## type = <component-type>
+ ## arguments = { <json object> }
+ ##
+ ## # Feature that this component provides
+ ## provides = <feature>
+ ##
+ ## # List of features that must be provided before this component is loaded
+ ## requires = [ <features> ]
+ ##
+ ## # List of features that would offer additional functionality if provided
+ ## # but are not strictly required
+ ## wants = [ <features> ]
+ ## }
+
+ ## Makes a secondary connection to PipeWire for exporting objects
+ {
+ name = export-core, type = built-in
+ provides = support.export-core
+ }
+
+ ## Enables creating local nodes that are exported to pipewire
+ ## This is needed for LocalNode() / WpImplNode
+ ## This should be used with the export-core to avoid protocol deadlocks,
+ ## unless you know what you are doing
+ {
+ name = libpipewire-module-client-node, type = pw-module
+ provides = pw.client-node
+ wants = [ support.export-core ]
+ }
+
+ ## Enables creating local devices that are exported to pipewire
+ ## This is needed for SpaDevice() / WpSpaDevice
+ ## This should be used with the export-core to avoid protocol deadlocks,
+ ## unless you know what you are doing
+ {
+ name = libpipewire-module-client-device, type = pw-module
+ provides = pw.client-device
+ wants = [ support.export-core ]
+ }
+
+ # Provides a node factory to create SPA nodes
+ # You need this to use LocalNode("spa-node-factory", ...)
+ {
+ name = libpipewire-module-spa-node-factory, type = pw-module
+ provides = pw.node-factory.spa
+ requires = [ pw.client-node ]
+ }
+
+ ## Provides a node factory to create SPA nodes wrapped in an adapter
+ ## You need this to use LocalNode("adapter", ...)
+ {
+ name = libpipewire-module-adapter, type = pw-module
+ provides = pw.node-factory.adapter
+ requires = [ pw.client-node ]
+ }
+
+ ## Provides the "sm-settings" metadata object
+ {
+ name = libwireplumber-module-settings, type = module
+ arguments = { metadata.name = sm-settings }
+ provides = metadata.sm-settings
+ }
+
+ ## Activates a global WpSettings instance, providing settings from
+ ## the sm-settings metadata object. Note that this blocks and waits for the
+ ## sm-settings metadata object to become available, so one instance must
+ ## provide that, while others should only load this to access settings
+ {
+ name = settings-instance, type = built-in
+ arguments = { metadata.name = sm-settings }
+ provides = support.settings
+ after = [ metadata.sm-settings ]
+ }
+
+ ## Log level settings
+ {
+ name = libwireplumber-module-log-settings, type = module
+ provides = support.log-settings
+ }
+
+ ## The lua scripting engine
+ {
+ name = libwireplumber-module-lua-scripting, type = module
+ provides = support.lua-scripting
+ }
+
+ ## Module listening for pipewire objects to push events
+ {
+ name = libwireplumber-module-standard-event-source, type = module
+ provides = support.standard-event-source
+ }
+
+ ## Session item factories
+ {
+ name = libwireplumber-module-si-node, type = module
+ provides = si.node
+ }
+ {
+ name = libwireplumber-module-si-audio-adapter, type = module
+ provides = si.audio-adapter
+ }
+ {
+ name = libwireplumber-module-si-standard-link, type = module
+ provides = si.standard-link
+ }
+
+ ## API to access default nodes from scripts
+ {
+ name = libwireplumber-module-default-nodes-api, type = module
+ provides = api.default-nodes
+ }
+
+ ## API to access mixer controls
+ {
+ name = libwireplumber-module-mixer-api, type = module
+ provides = api.mixer
+ }
+
+ ## API to get notified about file changes
+ {
+ name = libwireplumber-module-file-monitor-api, type = module
+ provides = api.file-monitor
+ }
+
+ ## Provide the "default" pw_metadata
+ {
+ name = metadata.lua, type = script/lua
+ arguments = { metadata.name = default }
+ provides = metadata.default
+ }
+
+ ## Provide the "filters" pw_metadata
+ {
+ name = metadata.lua, type = script/lua
+ arguments = { metadata.name = filters }
+ provides = metadata.filters
+ }
+
+ ## Provide the "sm-objects" pw_metadata, supporting dynamic loadable objects
+ {
+ name = sm-objects.lua, type = script/lua
+ provides = metadata.sm-objects
+ }
+
+ ## Populates the "session.services" property on the WirePlumber client object
+ {
+ name = session-services.lua, type = script/lua
+ provides = support.session-services
+ }
+
+ ## Device monitors' optional features
+ {
+ type = virtual, provides = monitor.alsa-midi.monitoring,
+ requires = [ api.file-monitor ]
+ }
+
+ ## Device monitors
+ {
+ name = monitors/alsa.lua, type = script/lua
+ provides = monitor.alsa
+ requires = [ support.export-core, pw.client-device ]
+ }
+ {
+ name = monitors/alsa-midi.lua, type = script/lua
+ provides = monitor.alsa-midi
+ wants = [ monitor.alsa-midi.monitoring ]
+ }
+
+ ## Client access configuration hooks
+ {
+ name = client/access-default.lua, type = script/lua
+ provides = script.client.access-default
+ }
+ {
+ type = virtual, provides = policy.client.access
+ wants = [ script.client.access-default ]
+ }
+
+ ## Device profile selection hooks
+ {
+ name = device/select-profile.lua, type = script/lua
+ provides = hooks.device.profile.select
+ }
+ {
+ name = device/find-preferred-profile.lua, type = script/lua
+ provides = hooks.device.profile.find-preferred
+ }
+ {
+ name = device/find-best-profile.lua, type = script/lua
+ provides = hooks.device.profile.find-best
+ }
+ {
+ name = device/apply-profile.lua, type = script/lua
+ provides = hooks.device.profile.apply
+ }
+ {
+ type = virtual, provides = policy.device.profile
+ requires = [ hooks.device.profile.select,
+ hooks.device.profile.apply ]
+ wants = [ hooks.device.profile.find-best, hooks.device.profile.find-preferred ]
+ }
+
+ # Device route selection hooks
+ {
+ name = device/select-routes.lua, type = script/lua
+ provides = hooks.device.routes.select
+ }
+ {
+ name = device/find-best-routes.lua, type = script/lua
+ provides = hooks.device.routes.find-best
+ }
+ {
+ name = device/apply-routes.lua, type = script/lua
+ provides = hooks.device.routes.apply
+ }
+ {
+ type = virtual, provides = policy.device.routes
+ requires = [ hooks.device.routes.select,
+ hooks.device.routes.apply ]
+ wants = [ hooks.device.routes.find-best ]
+ }
+
+ ## Default nodes selection hooks
+ {
+ name = default-nodes/rescan.lua, type = script/lua
+ provides = hooks.default-nodes.rescan
+ }
+ {
+ name = default-nodes/find-selected-default-node.lua, type = script/lua
+ provides = hooks.default-nodes.find-selected
+ requires = [ metadata.default ]
+ }
+ {
+ name = default-nodes/find-best-default-node.lua, type = script/lua
+ provides = hooks.default-nodes.find-best
+ }
+ {
+ name = default-nodes/apply-default-node.lua, type = script/lua,
+ provides = hooks.default-nodes.apply
+ requires = [ metadata.default ]
+ }
+ {
+ type = virtual, provides = policy.default-nodes
+ requires = [ hooks.default-nodes.rescan,
+ hooks.default-nodes.apply ]
+ wants = [ hooks.default-nodes.find-selected,
+ hooks.default-nodes.find-best ]
+ }
+
+ ## Node configuration hooks
+ {
+ name = node/create-item.lua, type = script/lua
+ provides = hooks.node.create-session-item
+ requires = [ si.audio-adapter, si.node ]
+ }
+ {
+ name = node/suspend-node.lua, type = script/lua
+ provides = hooks.node.suspend
+ }
+ {
+ name = node/filter-forward-format.lua, type = script/lua
+ provides = hooks.filter.forward-format
+ }
+ {
+ type = virtual, provides = policy.node
+ requires = [ hooks.node.create-session-item ]
+ wants = [ hooks.node.suspend
+ hooks.filter.forward-format ]
+ }
+ {
+ name = node/software-dsp.lua, type = script/lua
+ provides = node.software-dsp
+ }
+ {
+ name = node/audio-group.lua, type = script/lua
+ provides = node.audio-group
+ }
+
+ ## Linking hooks
+ {
+ name = linking/rescan.lua, type = script/lua
+ provides = hooks.linking.rescan
+ }
+ {
+ name = linking/find-media-role-target.lua, type = script/lua
+ provides = hooks.linking.target.find-media-role
+ }
+ {
+ name = linking/find-defined-target.lua, type = script/lua
+ provides = hooks.linking.target.find-defined
+ }
+ {
+ name = linking/find-audio-group-target.lua, type = script/lua
+ provides = hooks.linking.target.find-audio-group
+ requires = [ node.audio-group ]
+ }
+ {
+ name = linking/find-filter-target.lua, type = script/lua
+ provides = hooks.linking.target.find-filter
+ requires = [ metadata.filters ]
+ }
+ {
+ name = linking/find-default-target.lua, type = script/lua
+ provides = hooks.linking.target.find-default
+ requires = [ api.default-nodes ]
+ }
+ {
+ name = linking/find-best-target.lua, type = script/lua
+ provides = hooks.linking.target.find-best
+ requires = [ metadata.filters ]
+ }
+ {
+ name = linking/get-filter-from-target.lua, type = script/lua
+ provides = hooks.linking.target.get-filter-from
+ requires = [ metadata.filters ]
+ }
+ {
+ name = linking/prepare-link.lua, type = script/lua
+ provides = hooks.linking.target.prepare-link
+ requires = [ api.default-nodes ]
+ }
+ {
+ name = linking/link-target.lua, type = script/lua
+ provides = hooks.linking.target.link
+ requires = [ si.standard-link ]
+ }
+ {
+ type = virtual, provides = policy.linking.standard
+ requires = [ hooks.linking.rescan,
+ hooks.linking.target.prepare-link,
+ hooks.linking.target.link ]
+ wants = [ hooks.linking.target.find-media-role,
+ hooks.linking.target.find-defined,
+ hooks.linking.target.find-audio-group,
+ hooks.linking.target.find-filter,
+ hooks.linking.target.find-default,
+ hooks.linking.target.find-best,
+ hooks.linking.target.get-filter-from ]
+ }
+
+ ## Linking: Role-based priority system
+ {
+ name = linking/rescan-media-role-links.lua, type = script/lua
+ provides = hooks.linking.role-based.rescan
+ requires = [ api.mixer ]
+ }
+ {
+ type = virtual, provides = policy.linking.role-based
+ requires = [ policy.linking.standard,
+ hooks.linking.role-based.rescan ]
+ }
+
+ ## Standard policy definition
+ {
+ type = virtual, provides = policy.standard
+ requires = [ policy.client.access
+ policy.device.profile
+ policy.device.routes
+ policy.default-nodes
+ policy.linking.standard
+ policy.linking.role-based
+ policy.node
+ support.standard-event-source ]
+ }
+
+ ## Load targets
+ {
+ type = virtual, provides = hardware.audio
+ wants = [ monitor.alsa, monitor.alsa-midi ]
+ }
+]
+
+wireplumber.components.rules = [
+ ## Rules to apply on top of wireplumber.components
+ {
+ matches = [
+ {
+ type = "script/lua"
+ }
+ ]
+ actions = {
+ merge = {
+ requires = [ support.lua-scripting ]
+ }
+ }
+ }
+ {
+ matches = [
+ {
+ provides = "~hooks.*"
+ name = "!~monitors/.*"
+ }
+ ]
+ actions = {
+ merge = {
+ before = [ support.standard-event-source ]
+ }
+ }
+ }
+ {
+ matches = [
+ { provides = "~monitor.*" }
+ ]
+ actions = {
+ merge = {
+ after = [ support.standard-event-source ]
+ }
+ }
+ }
+ # session-services.lua must execute at the very end
+ {
+ matches = [
+ { name = "!session-services.lua" }
+ ]
+ actions = {
+ merge = {
+ before = [ support.session-services ]
+ }
+ }
+ }
+]
+
+wireplumber.settings.schema = {
+ ## Bluetooth
+ bluetooth.use-persistent-storage = {
+ description = "Whether to use persistent BT storage or not"
+ type = "bool"
+ default = true
+ }
+ bluetooth.autoswitch-to-headset-profile = {
+ description = "Whether to autoswitch to BT headset profile or not"
+ type = "bool"
+ default = true
+ }
+
+ ## Device
+ device.restore-profile = {
+ description = "Whether to restore device profile or not"
+ type = "bool"
+ default = true
+ }
+ device.restore-routes = {
+ description = "Whether to restore device routes or not"
+ type = "bool"
+ default = true
+ }
+ device.routes.default-sink-volume = {
+ description = "The default volume for sink devices"
+ type = "float"
+ default = 0.064
+ min = 0.0
+ max = 1.0
+ }
+ device.routes.default-source-volume = {
+ description = "The default volume for source devices"
+ type = "float"
+ default = 1.0
+ min = 0.0
+ max = 1.0
+ }
+
+ ## Linking
+ linking.role-based.duck-level = {
+ description = "The volume level to apply when ducking (= reducing volume for a higher priority stream to be audible) in the role-based linking policy"
+ type = "float"
+ default = 0.3
+ min = 0.0
+ max = 1.0
+ }
+ linking.allow-moving-streams = {
+ description = "Whether to allow metadata to move streams at runtime or not"
+ type = "bool"
+ default = true
+ }
+ linking.follow-default-target = {
+ description = "Whether to allow streams follow the default device or not"
+ type = "bool"
+ default = true
+ }
+
+ ## Monitor
+ monitor.camera-discovery-timeout = {
+ description = "The camera discovery timeout in milliseconds"
+ type = "int"
+ default = 1000
+ min = 0
+ max = 60000
+ }
+
+ ## Node
+ node.features.audio.no-dsp = {
+ description = "Whether to never convert audio to F32 format or not"
+ type = "bool"
+ default = false
+ }
+ node.features.audio.monitor-ports = {
+ description = "Whether to enable monitor ports on audio nodes or not"
+ type = "bool"
+ default = true
+ }
+ node.features.audio.control-port = {
+ description = "Whether to enable control ports on audio nodes or not"
+ type = "bool"
+ default = false
+ }
+ node.stream.restore-props = {
+ description = "Whether to restore properties on stream nodes or not"
+ type = "bool"
+ default = true
+ }
+ node.stream.restore-target = {
+ description = "Whether to restore target on stream nodes or not"
+ type = "bool"
+ default = true
+ }
+ node.stream.default-playback-volume = {
+ description = "The default volume for playback nodes"
+ type = "float"
+ default = 1.0
+ min = 0.0
+ max = 1.0
+ }
+ node.stream.default-capture-volume = {
+ description = "The default volume for capture nodes"
+ type = "float"
+ default = 1.0
+ min = 0.0
+ max = 1.0
+ }
+ node.stream.default-media-role = {
+ description = "A media.role to assign on streams that have none specified"
+ type = "string"
+ default = null
+ }
+ node.filter.forward-format = {
+ description = "Whether to forward format on filter nodes or not"
+ type = "bool"
+ default = false
+ }
+ node.restore-default-targets = {
+ description = "Whether to restore default targets or not"
+ type = "bool"
+ default = true
+ }
+}
--
Sincerely,
Demi Marie Obenour (she/her/hers)
next prev parent reply other threads:[~2025-07-18 2:15 UTC|newest]
Thread overview: 81+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-11 2:44 [PATCH v3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-14 14:54 ` Alyssa Ross
2025-07-15 20:22 ` Demi Marie Obenour
2025-07-16 10:26 ` Alyssa Ross
2025-07-16 21:16 ` Demi Marie Obenour
2025-07-16 21:27 ` Demi Marie Obenour
2025-07-18 12:16 ` Alyssa Ross
2025-07-17 5:53 ` Demi Marie Obenour
2025-07-18 10:02 ` Alyssa Ross
2025-07-18 10:19 ` Alyssa Ross
2025-07-18 2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-18 2:13 ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
2025-07-18 11:14 ` Alyssa Ross
2025-07-18 2:13 ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
2025-07-18 11:51 ` Alyssa Ross
2025-07-18 11:51 ` Alyssa Ross
2025-07-18 11:53 ` Alyssa Ross
2025-07-18 2:14 ` Demi Marie Obenour [this message]
2025-07-18 11:27 ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Alyssa Ross
2025-07-18 17:59 ` Demi Marie Obenour
2025-07-19 9:22 ` Alyssa Ross
2025-07-19 20:05 ` Demi Marie Obenour
2025-07-19 8:06 ` Alyssa Ross
2025-07-19 20:03 ` Demi Marie Obenour
2025-07-19 20:07 ` Demi Marie Obenour
2025-07-20 7:50 ` Alyssa Ross
2025-07-20 17:58 ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-20 18:02 ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
2025-07-21 9:34 ` Alyssa Ross
2025-07-20 18:03 ` [PATCH v5 2/8] img/app: Use separate service to create directories Demi Marie Obenour
2025-07-21 9:21 ` Alyssa Ross
2025-07-22 23:48 ` Demi Marie Obenour
2025-07-20 18:04 ` [PATCH v5 3/8] img/app: Fix permissions of /tmp/.X11-unix Demi Marie Obenour
2025-07-20 18:05 ` [PATCH v5 4/8] img/app: Create other X11 directories Demi Marie Obenour
2025-07-21 9:23 ` Alyssa Ross
2025-07-21 19:03 ` Demi Marie Obenour
2025-07-20 18:06 ` [PATCH v5 5/8] img/app: Be explicit about directory modes Demi Marie Obenour
2025-07-20 18:08 ` [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot Demi Marie Obenour
2025-07-21 9:23 ` Alyssa Ross
2025-07-20 18:10 ` [PATCH v5 7/8] host/rootfs: " Demi Marie Obenour
2025-07-20 18:11 ` [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-21 9:42 ` Alyssa Ross
2025-07-21 19:09 ` Demi Marie Obenour
2025-07-26 10:11 ` Alyssa Ross
2025-07-21 19:10 ` Demi Marie Obenour
2025-07-24 22:15 ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-24 22:30 ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
2025-07-26 10:46 ` Alyssa Ross
2025-07-24 22:32 ` [PATCH v6 2/5] img/app: " Demi Marie Obenour
2025-07-24 22:33 ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
2025-07-26 11:20 ` Alyssa Ross
2025-07-26 11:26 ` Alyssa Ross
2025-07-24 22:35 ` [PATCH v6 4/5] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-26 10:24 ` Alyssa Ross
2025-07-27 20:13 ` Demi Marie Obenour
2025-07-24 22:36 ` [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-26 11:29 ` Alyssa Ross
2025-07-26 10:57 ` [PATCH v6 0/5] Sound support in Spectrum VMs Alyssa Ross
2025-07-28 5:57 ` [PATCH v7 0/2] " Demi Marie Obenour
2025-07-28 6:01 ` [PATCH v7 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-28 6:03 ` [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-28 6:18 ` Demi Marie Obenour
2025-07-28 23:13 ` [PATCH v8 0/2] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-29 0:32 ` [PATCH v9 " Demi Marie Obenour
2025-07-29 0:33 ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-29 12:44 ` Alyssa Ross
2025-07-29 0:33 ` [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-29 13:08 ` Alyssa Ross
2025-07-29 21:17 ` Demi Marie Obenour
2025-07-30 8:10 ` Alyssa Ross
2025-07-30 9:59 ` [PATCH v10] " Demi Marie Obenour
2025-07-31 9:12 ` Alyssa Ross
2025-07-31 9:40 ` Alyssa Ross
2025-07-31 17:06 ` [PATCH v11] " Demi Marie Obenour
2025-08-01 17:53 ` Alyssa Ross
2025-08-02 7:54 ` Alyssa Ross
2025-07-28 23:13 ` [PATCH v8 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-28 23:19 ` Demi Marie Obenour
2025-07-28 23:13 ` [PATCH v8 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-29 12:41 ` [PATCH v7 0/2] Sound support in Spectrum VMs Alyssa Ross
2025-07-24 22:23 ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=25197dbe-d642-4283-80b7-be66dcc4f4eb@gmail.com \
--to=demiobenour@gmail.com \
--cc=devel@spectrum-os.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://spectrum-os.org/git/crosvm
https://spectrum-os.org/git/doc
https://spectrum-os.org/git/mktuntap
https://spectrum-os.org/git/nixpkgs
https://spectrum-os.org/git/spectrum
https://spectrum-os.org/git/ucspi-vsock
https://spectrum-os.org/git/www
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).