Demi Marie Obenour writes: > WirePlumber is completely overkill as a session manager here, and > ideally a trivial session manager would be used instead. PipeWire is > configured to listen on the PulseAudio socket, so PulseAudio > compatibility works. pw-record and pw-play both work, and if PulseAudio > is installed paplay and parecord also work. This does install a lot of > unnecessary files into the VMs, which will hopefully be removed > later as part of a debloating effort. > > Signed-off-by: Demi Marie Obenour > --- > img/app/Makefile | 18 +- > img/app/default.nix | 3 + > img/app/etc/mdev.conf | 1 + > img/app/etc/pipewire/pipewire.conf | 199 ++++++++++++++++++ > .../etc/s6-rc/app/dependencies.d/wireplumber | 0 > .../etc/s6-rc/pipewire/dependencies.d/dbus | 0 > img/app/etc/s6-rc/pipewire/notification-fd | 1 + > .../s6-rc/pipewire/notification-fd.license | 2 + > img/app/etc/s6-rc/pipewire/run | 23 ++ > img/app/etc/s6-rc/pipewire/type | 1 + > img/app/etc/s6-rc/pipewire/type.license | 2 + > .../etc/s6-rc/wireplumber/dependencies.d/dbus | 0 > .../s6-rc/wireplumber/dependencies.d/pipewire | 0 > 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 + > .../wireplumber.conf.d/99_spectrum.conf | 39 ++++ > 17 files changed, 293 insertions(+), 3 deletions(-) > create mode 100644 img/app/etc/pipewire/pipewire.conf > create mode 100644 img/app/etc/s6-rc/app/dependencies.d/wireplumber > create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/dbus > 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/wireplumber/dependencies.d/dbus > create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire > 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.d/99_spectrum.conf > > diff --git a/img/app/Makefile b/img/app/Makefile > index 4b4d64f81d99a01eebe777f3737fef813ebb6d3f..69bc9afc0756b0628ddc1fe53913a75b45b3213b 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.d/99_spectrum.conf > + Sorting again! (Don't worry about it too much — if it was the only issue I'd just fix it myself when applying the patch.) > VM_DIRS = dev run proc sys tmp \ > etc/s6-linux-init/run-image/service \ > etc/s6-linux-init/run-image/user \ > @@ -85,6 +88,7 @@ build/rootfs.erofs: ../../scripts/make-erofs.sh $(PACKAGES_FILE) $(VM_FILES) $(V > VM_S6_RC_FILES = \ > 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/notification-fd \ > @@ -98,9 +102,16 @@ VM_S6_RC_FILES = \ > etc/s6-rc/mdevd/type \ > etc/s6-rc/ok-all/contents \ > etc/s6-rc/ok-all/type \ > + etc/s6-rc/pipewire/notification-fd \ > + etc/s6-rc/pipewire/run \ > + etc/s6-rc/pipewire/type \ > 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/wayland-proxy-virtwl/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 $@) > @@ -137,7 +148,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 \ This unrelated change is still here. > @@ -147,6 +158,7 @@ run-qemu: $(imgdir)/appvm/blk/root.img start-vhost-user-net start-virtiofsd > -chardev socket,id=virtiofsd,path=build/virtiofsd.sock \ > -device vhost-user-fs-pci,chardev=virtiofsd,tag=virtiofs0 \ > -device virtio-gpu-rutabaga-pci,cross-domain=on,hostmem=8G \ > + -audio driver=pipewire,model=virtio \ > -object memory-backend-memfd,id=mem,size=256M,share=on \ > -numa node,memdev=mem \ > -device vhost-vsock-pci,guest-cid=3 \ > diff --git a/img/app/default.nix b/img/app/default.nix > index 740643ac41f6473cdb6f6b0fd1f5f47f4493240d..d3eed1f0accdc8968d1ba5bdec74ab597789082f 100644 > --- a/img/app/default.nix > +++ b/img/app/default.nix > @@ -48,6 +48,9 @@ let > pkgs.xwayland > pkgs.xdg-desktop-portal > pkgs.xdg-desktop-portal-gtk > + # Depends on pulseaudio libs > + pkgs.pipewire > + pkgs.wireplumber > ]; > })).fhsenv; > in > diff --git a/img/app/etc/mdev.conf b/img/app/etc/mdev.conf > index f2101e1f683c49808358b25520080c59ed2afa8e..0e4a1a088522c05da9e0ce15fe135c40d6cf3064 100644 > --- a/img/app/etc/mdev.conf > +++ b/img/app/etc/mdev.conf > @@ -5,3 +5,4 @@ > $INTERFACE=.* 0:0 660 ! +/etc/mdev/iface > $MODALIAS=virtio:d0000001Av.* 0:0 660 ! +/etc/mdev/virtiofs > dri/card0 0:0 660 +background { /etc/mdev/listen card0 } > +snd/controlC0 0:0 660 +background { /etc/mdev/listen controlC0 } > diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf > new file mode 100644 > index 0000000000000000000000000000000000000000..e5a413a409f46e7fe176102bbd6780db14f85dba > --- /dev/null > +++ b/img/app/etc/pipewire/pipewire.conf > @@ -0,0 +1,199 @@ > +# SPDX-License-Identifier: MIT > + > +# Copyright © 2018 Wim Taymans > +# Copyright © 2025 Demi Marie Obenour > +# > +# 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. > + > +# This file is based on the upstream default configuration. This can be > +# found in upstream GitLab or in any distro with a recent version of PipeWire. > +# The following changes have been made: > +# > +# - Conditions that have known values in Spectrum VMs are omitted. > +# - Modules for hardware devices Spectrum VMs don't have are not loaded. > +# - The PulseAudio emulation server is loaded. > +# - Settings for VMs are applied unconditionally. > +# - Most comments in the upstream files have been removed. > +# - Device nodes for virtio-sound devices have been added. > +# - Integration with udev and logind is removed. > +context.properties = { > + # Upstream defaults. > + link.max-buffers = 16 > + core.daemon = true > + core.name = pipewire-0 > + # Account for running in a VM > + default.clock.min-quantum = 1024 > +} > + > +# Upstream defaults, with support for AVB, V4L2, libcamera > +# bluez, Vulkan, JACK, and video conversion omitted. > +context.spa-libs = { > + audio.convert.* = audioconvert/libspa-audioconvert > + api.alsa.* = alsa/libspa-alsa > + support.* = support/libspa-support > +} > + > +context.modules = [ > + # Upstream defaults > + { name = libpipewire-module-rt > + args = { nice.level = -11, rt.prio = 88 } > + } > + { name = libpipewire-module-protocol-native } > + { name = libpipewire-module-metadata } > + { name = libpipewire-module-spa-device-factory } > + { name = libpipewire-module-spa-node-factory } > + { name = libpipewire-module-client-node } > + { name = libpipewire-module-access } > + { name = libpipewire-module-client-device } > + { name = libpipewire-module-portal } > + { name = libpipewire-module-adapter } > + { name = libpipewire-module-link-factory } > + { name = libpipewire-module-session-manager } > + > + # Load the PulseAudio server into PipeWire. > + # This avoids needing a separate pipewire-pulse > + # process. The args are those used when running > + # in a VM. > + { name = libpipewire-module-protocol-pulse > + args = { > + server.address = [ "unix:native" ] > + pulse.min.quantum = 1024/48000 > + } > + } > +] > + > +context.objects = [ > + # Upstream defaults > + { 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 > + } > + } > + { 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 > + } > + } > + > + # Spectrum doesn't use udev, so device nodes must be created statically. > + # Creating them with pw-cli works as long as pw-cli is running, but > + # the nodes are destroyed when pw-cli exits. > + { factory = adapter > + args = { > + 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,0,0" > + api.alsa.pcm.card = 0, > + api.alsa.pcm.stream = "playback" > + audio.allowed-rates = [ ] > + audio.channels = 2 > + audio.format = "S32" > + audio.position = "FL,FR" > + audio.rate = 48000 > + factory.name = "api.alsa.pcm.sink" > + media.class = "Audio/Sink" > + node.name = "alsa_output.pci-0000_00_01.0.analog-stereo" > + node.suspend-on-idle = true > + } > + } > + { factory = adapter > + args = { > + 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,0,0" > + api.alsa.pcm.card = 0, > + api.alsa.pcm.stream = "capture" > + audio.allowed-rates = [ ] > + audio.channels = 2 > + audio.format = "S32" > + audio.position = "FL,FR" > + audio.rate = 48000 > + factory.name = "api.alsa.pcm.source" > + media.class = "Audio/Source" > + node.name = "alsa_input.pci-0000_00_01.0.analog-stereo" > + node.suspend-on-idle = true > + } > + } > +] > + > +# Load the modules that are in the default config *except* > +# for ones whose job is to maintain state. > +pulse.cmd = [ > + { cmd = "load-module" args = "module-always-sink" flags = [ ] } > + { cmd = "load-module" args = "module-device-manager" flags = [ ] } > +] > + > +# More default stuff. > +pulse.rules = [ > + { > + 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 ] } > + } > + { > + matches = [ { application.process.binary = "firefox" } ] > + actions = { quirks = [ remove-capture-dont-move ] } > + } > + { > + matches = [ { application.name = "~speech-dispatcher.*" } ] > + actions = { > + update-props = { > + pulse.min.req = 512/48000 > + pulse.min.quantum = 512/48000 > + pulse.idle.timeout = 5 > + } > + } > + } > +] > + > +context.exec = [] > 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/pipewire/dependencies.d/dbus b/img/app/etc/s6-rc/pipewire/dependencies.d/dbus > new file mode 100644 > index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 > 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 > diff --git a/img/app/etc/s6-rc/pipewire/run b/img/app/etc/s6-rc/pipewire/run > new file mode 100644 > index 0000000000000000000000000000000000000000..c5cf090fb4779e0f3ede1782ada5c95ce5b25702 > --- /dev/null > +++ b/img/app/etc/s6-rc/pipewire/run > @@ -0,0 +1,23 @@ > +#!/bin/execlineb -P > +# SPDX-License-Identifier: EUPL-1.2+ > +# SPDX-FileCopyrightText: 2023-2024 Alyssa Ross > +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour > + > +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 > + > +# Wait for sound devices to be available > +if { /etc/mdev/wait controlC0 } > + > +# 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 > 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/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/run b/img/app/etc/s6-rc/wireplumber/run > new file mode 100644 > index 0000000000000000000000000000000000000000..d58f1971c7387c896256a91ad0c92386a02fd9e2 > --- /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 > +wireplumber --profile spectrum > 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 > diff --git a/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf > new file mode 100644 > index 0000000000000000000000000000000000000000..ff2f464395aaf7c8f0a739b0f01552c4ee987740 > --- /dev/null > +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf > @@ -0,0 +1,39 @@ > +# SPDX-License-Identifier: MIT > + > +# Copyright © 2019-2021 Collabora Ltd. Which part of this is © Collabora? It looks like you've written it yourself. I like this approach better though. :) > +# > +# 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. > + > +# Disable various stuff Spectrum doesn't need and which causes errors. > +wireplumber.profiles = { > + spectrum = { > + inherits = [ main-embedded ] > + hardware.video-capture = disabled > + hardware.bluetooth = disabled > + support.settings = disabled > + check.no-media-session = disabled > + policy.standard = required > + } > +} > + > +# Default to 100% sink volume. The host will adjust this as needed. > +wireplumber.settings = { > + device.routes.default-sink-volume = 1.0 > +} > -- > Sincerely, > Demi Marie Obenour (she/her/hers)