[PATCH v3] Run PipeWire and WirePlumber in the VMs
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> --- Changes since v2: Enable PipeWire's PulseAudio emulation. img/app/Makefile | 19 +- img/app/default.nix | 3 + img/app/etc/fstab | 5 +- .../pipewire.conf.d/90_enable_pulseaudio.conf | 172 ++++++++++++++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../app/dependencies.d/wireplumber.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 + 26 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf 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/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 diff --git a/img/app/Makefile b/img/app/Makefile index f818e91..8144518 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -53,7 +53,8 @@ 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.d/90_enable_pulseaudio.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 @@ -83,22 +84,34 @@ build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) bu 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 \ 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 $@) diff --git a/img/app/default.nix b/img/app/default.nix index 740643a..d3eed1f 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/fstab b/img/app/etc/fstab index a95088b..40aa3bd 100644 --- a/img/app/etc/fstab +++ b/img/app/etc/fstab @@ -1,7 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> proc /proc proc defaults 0 0 -devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 +devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 -tmpfs /tmp tmpfs defaults 0 0 +tmpfs /tmp tmpfs defaults,mode=1755 0 0 +tmpfs /run tmpfs defaults 0 0 diff --git a/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf b/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf new file mode 100644 index 0000000..a5b86a6 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans + +# 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. + +# PulseAudio config file for PipeWire version "1.4.2" # +# +# Copy and edit this file in /etc/pipewire for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.modules = [ + { name = libpipewire-module-protocol-pulse + args = { + # contents of pulse.properties can also be placed here + # to have config per server. + } + } +] + +# Extra commands can be executed here. +# load-module : loads a module with args and flags +# args = "<module-name> <module-args>" +# ( flags = [ nofail ] ) +# ( condition = [ { <key1> = <value1>, ... } ... ] ) +# conditions will check the pulse.properties key/values. +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 } ] } + #{ cmd = "load-module" args = "module-switch-on-connect" } + #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] } +] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + #dither.noise = 0 +} + +pulse.properties = { + # the addresses this server listens on + server.address = [ + "unix:native" + #"unix:/tmp/something" # absolute paths may be used + #"tcp:4713" # IPv4 and IPv6 on all addresses + #"tcp:[::]:9999" # IPv6 on all addresses + #"tcp:127.0.0.1:8888" # IPv4 on a single address + # + #{ address = "tcp:4713" # address + # max-clients = 64 # maximum number of clients + # listen-backlog = 32 # backlog in the server listen queue + # client.access = "restricted" # permissions for clients + #} + ] + #server.dbus-name = "org.pulseaudio.Server" + #pulse.allow-module-loading = true + #pulse.min.req = 128/48000 # 2.7ms + #pulse.default.req = 960/48000 # 20 milliseconds + #pulse.min.frag = 128/48000 # 2.7ms + #pulse.default.frag = 96000/48000 # 2 seconds + #pulse.default.tlength = 96000/48000 # 2 seconds + #pulse.min.quantum = 128/48000 # 2.7ms + #pulse.idle.timeout = 0 # don't pause after underruns + #pulse.default.format = F32 + #pulse.default.position = [ FL FR ] +} + +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 = [ + { + matches = [ + { + # all keys must match the value. ! negates. ~ starts regex. + #client.name = "Firefox" + #application.process.binary = "teams" + #application.name = "~speech-dispatcher.*" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + # Possible quirks:" + # force-s16-info forces sink and source info as S16 format + # remove-capture-dont-move removes the capture DONT_MOVE flag + # block-source-volume blocks updates to source volume + # block-sink-volume blocks updates to sink volume + #quirks = [ ] + } + } + { + # 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 + } + } + } + #{ + # matches = [ { application.process.binary = "Discord" } ] + # actions = { quirks = [ block-source-volume ] } + #} +] 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 0000000..e69de29 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 0000000..c4a0586 --- /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/directories/type b/img/app/etc/s6-rc/directories/type new file mode 100644 index 0000000..bdd22a1 --- /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 0000000..c4a0586 --- /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 0000000..2396fad --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..7ed6ff8 --- /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 0000000..c4a0586 --- /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 0000000..c0b55a1 --- /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 0000000..5883cff --- /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 0000000..c4a0586 --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 7b80343..c1e0e08 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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..e721d8d --- /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 0000000..5883cff --- /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 0000000..c4a0586 --- /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> base-commit: 9090caebe25310caa80a13787ef58b1f81658a78 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log: N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0 Can we set something in a config file or something to disable this extra stuff?
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v2: Enable PipeWire's PulseAudio emulation.
img/app/Makefile | 19 +- img/app/default.nix | 3 + img/app/etc/fstab | 5 +- .../pipewire.conf.d/90_enable_pulseaudio.conf | 172 ++++++++++++++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../app/dependencies.d/wireplumber.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 + 26 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf 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/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
diff --git a/img/app/Makefile b/img/app/Makefile index f818e91..8144518 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -53,7 +53,8 @@ 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.d/90_enable_pulseaudio.conf
Keep lists alphabetical please!
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 @@ -83,22 +84,34 @@ build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) bu 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 \ 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 $@) diff --git a/img/app/default.nix b/img/app/default.nix index 740643a..d3eed1f 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/fstab b/img/app/etc/fstab index a95088b..40aa3bd 100644 --- a/img/app/etc/fstab +++ b/img/app/etc/fstab @@ -1,7 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> proc /proc proc defaults 0 0 -devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 +devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 -tmpfs /tmp tmpfs defaults 0 0 +tmpfs /tmp tmpfs defaults,mode=1755 0 0 +tmpfs /run tmpfs defaults 0 0
Still think it would be nice to do the directory permissions a separate patch, since it's not strictly related to PipeWire. (And again, /run is provided by s6-linux-init, so we don't need to create it again — or is there some advantage to still having it listed in fstab?)
diff --git a/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf b/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf new file mode 100644 index 0000000..a5b86a6 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf.d/90_enable_pulseaudio.conf @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans
REUSE should detect the copyright line below fine, so the SPDX-FileCopyrightText header shouldn't be necessary. (It's probably a bug if not.)
+ +# 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. +#
I recently added a check for trailing whitespace (release/checks/whitespace.nix), and it doesn't like this license header.
+# 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. + +# PulseAudio config file for PipeWire version "1.4.2" # +# +# Copy and edit this file in /etc/pipewire for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.modules = [ + { name = libpipewire-module-protocol-pulse + args = { + # contents of pulse.properties can also be placed here + # to have config per server. + } + } +] + +# Extra commands can be executed here. +# load-module : loads a module with args and flags +# args = "<module-name> <module-args>" +# ( flags = [ nofail ] ) +# ( condition = [ { <key1> = <value1>, ... } ... ] ) +# conditions will check the pulse.properties key/values. +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 } ] } + #{ cmd = "load-module" args = "module-switch-on-connect" } + #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] }
There's a lot of commented out stuff in this file. Elsewhere in Spectrum, I've generally left that sort of thing out, to keep the file as short and easy to review as possible. Upstream is always available as a reference when more stuff needs to be enabled.
+] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + #dither.noise = 0 +} + +pulse.properties = { + # the addresses this server listens on + server.address = [ + "unix:native" + #"unix:/tmp/something" # absolute paths may be used + #"tcp:4713" # IPv4 and IPv6 on all addresses + #"tcp:[::]:9999" # IPv6 on all addresses + #"tcp:127.0.0.1:8888" # IPv4 on a single address + # + #{ address = "tcp:4713" # address + # max-clients = 64 # maximum number of clients + # listen-backlog = 32 # backlog in the server listen queue + # client.access = "restricted" # permissions for clients + #} + ] + #server.dbus-name = "org.pulseaudio.Server" + #pulse.allow-module-loading = true + #pulse.min.req = 128/48000 # 2.7ms + #pulse.default.req = 960/48000 # 20 milliseconds + #pulse.min.frag = 128/48000 # 2.7ms + #pulse.default.frag = 96000/48000 # 2 seconds + #pulse.default.tlength = 96000/48000 # 2 seconds + #pulse.min.quantum = 128/48000 # 2.7ms + #pulse.idle.timeout = 0 # don't pause after underruns + #pulse.default.format = F32 + #pulse.default.position = [ FL FR ] +} + +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 = [ + { + matches = [ + { + # all keys must match the value. ! negates. ~ starts regex. + #client.name = "Firefox" + #application.process.binary = "teams" + #application.name = "~speech-dispatcher.*" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + # Possible quirks:" + # force-s16-info forces sink and source info as S16 format + # remove-capture-dont-move removes the capture DONT_MOVE flag + # block-source-volume blocks updates to source volume + # block-sink-volume blocks updates to sink volume + #quirks = [ ] + } + } + { + # 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 + } + } + } + #{ + # matches = [ { application.process.binary = "Discord" } ] + # actions = { quirks = [ block-source-volume ] } + #} +] 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 0000000..e69de29 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 0000000..c4a0586 --- /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/directories/type b/img/app/etc/s6-rc/directories/type new file mode 100644 index 0000000..bdd22a1 --- /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 0000000..c4a0586 --- /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 0000000..2396fad --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..7ed6ff8 --- /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 0000000..c4a0586 --- /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 0000000..c0b55a1 --- /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 0000000..5883cff --- /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 0000000..c4a0586 --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 7b80343..c1e0e08 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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..e69de29 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 0000000..c4a0586 --- /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 0000000..e721d8d --- /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 0000000..5883cff --- /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 0000000..c4a0586 --- /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>
base-commit: 9090caebe25310caa80a13787ef58b1f81658a78 -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff? I can disable the Bluetooth and libcamera SPA plugins, but it would be better to build PipeWire and WirePlumber without support for various stuff that Spectrum VMs will never need. Is this feasible? -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff? I can disable the Bluetooth and libcamera SPA plugins, but it would be better to build PipeWire and WirePlumber without support for various stuff that Spectrum VMs will never need. Is this feasible?
Yes, but I'd rather leave build changes to later, as there's a cost to maintaining overrides of Nixpkgs packages, so the longer we can get away with not having to do that, the better
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff? I'm working on it, but I ran into a problem: I can't install vim in the guest! Adding it to the list of packages doesn't work; I still don't have a Vim binary in the guest. Without that, I have to rebuild the VM every time I want to make a change, which makes it very hard to iterate while I figure out how to make PipeWire talk to an emulated virtio-sound device QEMU provides. -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/16/25 17:16, Demi Marie Obenour wrote:
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff? I'm working on it, but I ran into a problem: I can't install vim in the guest! Adding it to the list of packages doesn't work; I still don't have a Vim binary in the guest. Without that, I have to rebuild the VM every time I want to make a change, which makes it very hard to iterate while I figure out how to make PipeWire talk to an emulated virtio-sound device QEMU provides.
Deleting the build directory causes the image to be rebuilt. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/16/25 17:16, Demi Marie Obenour wrote:
I'm working on it, but I ran into a problem: I can't install vim in the guest! Adding it to the list of packages doesn't work; I still don't have a Vim binary in the guest. Without that, I have to rebuild the VM every time I want to make a change, which makes it very hard to iterate while I figure out how to make PipeWire talk to an emulated virtio-sound device QEMU provides.
Deleting the build directory causes the image to be rebuilt.
I've pushed a change that causes a rebuild if the list of packages has changed. You'll still need to exit and re-enter the Nix shell (or use something like direnv that automatically refreshes that for you), but there's no longer a need to delete the build directory after that. We could also implement rebuilds when other environment variables from Nix change. (The reason I think this change makes sense, but not your Makefile dependency patch, is that this only needs a dependency to be added to specific targets that use that value, and because it's solving an integration problem between Nix and Make, rather than trying to change normal Make functionality.)
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff?
I started work on a v4 patch, but before sending it I decided to test that not only did PipeWire and WirePlumber start, but also that I could actually play sound to a virtio-sound device I had attached to the VM. This failed rather miserably, and I finally figured out the culprit: PipeWire relies on udev for device discovery, and without udev it doesn't detect any devices. It appears possible to create devices in PipeWire via the PipeWire CLI or via the configuration file. The former would allow them to be created by mdevd, while the latter would be appropriate if the devices were known to be ready before PipeWire started. I strongly recommend using either systemd-udevd (with systemd) or eudev (without systemd) in the host and in any VM that will have real hardware attached to it. udev rules are where all the upstream work on uevent processing is happening, so it fits with Spectrum's "upstream first" mindset. Something simpler like mdevd might be appropriate for VMs that will only ever see virtual hardware with known behavior and do not need to deal with the quirks of real hardware. For the host, I think systemd is probably the right solution. It's complicated, but that is because it solves a complicated problem. Without systemd, Spectrum will wind up needing to reimplement a lot of configuration that has already been written for systemd. Also, systemd has fantastic TPM support. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff?
I started work on a v4 patch, but before sending it I decided to test that not only did PipeWire and WirePlumber start, but also that I could actually play sound to a virtio-sound device I had attached to the VM. This failed rather miserably, and I finally figured out the culprit: PipeWire relies on udev for device discovery, and without udev it doesn't detect any devices.
It appears possible to create devices in PipeWire via the PipeWire CLI or via the configuration file. The former would allow them to be created by mdevd, while the latter would be appropriate if the devices were known to be ready before PipeWire started.
I strongly recommend using either systemd-udevd (with systemd) or eudev (without systemd) in the host and in any VM that will have real hardware attached to it. udev rules are where all the upstream work on uevent processing is happening, so it fits with Spectrum's "upstream first" mindset. Something simpler like mdevd might be appropriate for VMs that will only ever see virtual hardware with known behavior and do not need to deal with the quirks of real hardware.
No real objection to udev in VMs. On the host I'm less sure, because ideally the host shouldn't really be interacting with hardware very much, so it comes down to whether there are things in scope for the host where the kernel doesn't do the right thing by default.
For the host, I think systemd is probably the right solution. It's complicated, but that is because it solves a complicated problem. Without systemd, Spectrum will wind up needing to reimplement a lot of configuration that has already been written for systemd. Also, systemd has fantastic TPM support.
systemd is very modular. I'm not sure you need much of the rest of systemd to use the TPM stuff, for example. The value I'd see in systemd would be all the sandboxing stuff for services. It's annoying because none of that has to be part of a single unified service manager either — it could largely easily be done by modular chainloaders — but given nobody is implementing it outside of systemd there might not be a reasonable other choice. So I'm not really opposed to this either, and I guess it probably means we need udev on the host anyway. I don't know when I'd be able to prioritise making the switch, though…
Alyssa Ross <hi@alyssa.is> writes:
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/14/25 10:54, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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.
Yeah, I saw this in the log:
N 14:27:54.810067 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:588:on_component_loaded: <WpCore:0x28d95d30> optional component 'support.logind [module: libwireplumber-module-logind]' failed to load: failed to start systemd logind monitor: -2 (No such file or directory) N 14:27:54.811299 wp-internal-comp-l ../lib/wp/private/internal-comp-loader.c:640:wp_component_array_load_task_execute_step: <WpCore:0x28d95d30> skipping component 'monitor.bluez.seat-monitoring [virtual]' because some of its dependencies were not loaded E 14:27:54.851210 spa.dbus ../spa/plugins/support/dbus.c:333:impl_connection_get: Failed to connect to system bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory E 14:27:54.852143 spa.bluez5 ../spa/plugins/bluez5/bluez5-dbus.c:6632:impl_init: failed to get dbus connection N 14:27:54.855904 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.enum.dbus' could not be loaded; is it installed? N 14:27:54.856657 s-monitors bluez.lua:411:createMonitor: PipeWire's BlueZ SPA plugin is missing or broken. Bluetooth devices will not be supported. E 14:27:54.859091 spa.bluez5.midi ../spa/plugins/bluez5/midi-enum.c:805:impl_init: Creating GDBus connection failed: Could not connect: No such file or directory N 14:27:54.859810 wp-device ../lib/wp/device.c:710:wp_spa_device_new_from_spa_factory: SPA handle 'api.bluez5.midi.enum' could not be loaded; is it installed? N 14:27:54.860524 s-monitors bluez-midi.lua:95:createMonitor: PipeWire's BlueZ MIDI SPA missing or broken. Bluetooth not supported. E 14:27:54.861346 spa.bluez5.midi.no ../spa/plugins/bluez5/midi-node.c:1989:impl_init: failed to get dbus connection: Could not connect: No such file or directory E 14:27:54.862435 pw.resource ../src/pipewire/resource.c:255:pw_resource_errorf_id: can't create node: Input/output error W 14:27:54.863275 wp-node ../lib/wp/node.c:913:wp_impl_node_new_from_pw_factory: failed to create node from factory 'spa-node-factory' N 14:27:54.863965 s-monitors bluez-midi.lua:130:createServers: Failed to create BLE MIDI server. [0:00:01.314658075] [123] INFO IPAManager ipa_manager.cpp:137 libcamera is not installed. Adding '/nix/store/src/ipa' to the IPA search path [0:00:01.321611889] [123] INFO Camera camera_manager.cpp:326 libcamera v0.5.0
Can we set something in a config file or something to disable this extra stuff?
I started work on a v4 patch, but before sending it I decided to test that not only did PipeWire and WirePlumber start, but also that I could actually play sound to a virtio-sound device I had attached to the VM. This failed rather miserably, and I finally figured out the culprit: PipeWire relies on udev for device discovery, and without udev it doesn't detect any devices.
It appears possible to create devices in PipeWire via the PipeWire CLI or via the configuration file. The former would allow them to be created by mdevd, while the latter would be appropriate if the devices were known to be ready before PipeWire started.
I strongly recommend using either systemd-udevd (with systemd) or eudev (without systemd) in the host and in any VM that will have real hardware attached to it. udev rules are where all the upstream work on uevent processing is happening, so it fits with Spectrum's "upstream first" mindset. Something simpler like mdevd might be appropriate for VMs that will only ever see virtual hardware with known behavior and do not need to deal with the quirks of real hardware.
No real objection to udev in VMs. On the host I'm less sure, because ideally the host shouldn't really be interacting with hardware very much, so it comes down to whether there are things in scope for the host where the kernel doesn't do the right thing by default.
For the host, I think systemd is probably the right solution. It's complicated, but that is because it solves a complicated problem. Without systemd, Spectrum will wind up needing to reimplement a lot of configuration that has already been written for systemd. Also, systemd has fantastic TPM support.
systemd is very modular. I'm not sure you need much of the rest of systemd to use the TPM stuff, for example. The value I'd see in systemd would be all the sandboxing stuff for services. It's annoying because none of that has to be part of a single unified service manager either — it could largely easily be done by modular chainloaders — but given nobody is implementing it outside of systemd there might not be a reasonable other choice. So I'm not really opposed to this either, and I guess it probably means we need udev on the host anyway. I don't know when I'd be able to prioritise making the switch, though…
Although at that point we're introducing lots of sockets and global buses and things that we've so far been very keen to avoid on the host. I'm really not sure all that additional surface is actually a good tradeoff compared to doing what defense in depth sandboxing measures we can easily do ourselves. The set of software that has to run on the code is very tightly constrained, so it's a very different situation to what we'd find in VMs, where we need to be compatible with arbitrary stuff…
This provides sound support in Spectrum VMs. The first patch is a workaround for a make footgun and the second is a trivial permissions fix. The third patch is where all the interesting stuff is. Demi Marie Obenour (3): Rebuild the root filesystem when the makefile changes Fix permissions on /tmp Run PipeWire and WirePlumber in the VMs img/app/Makefile | 30 +- img/app/default.nix | 18 + img/app/etc/fstab | 4 +- 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 ++++++++++++++++++ 31 files changed, 1072 insertions(+), 19 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 base-commit: 89b80752a275a4ca1818a49d86f27cca9e323389 -- Sincerely, Demi Marie Obenour (she/her/hers)
I spent hours figuring out why files were not found in the VMs. Turns out that 'make' was no rerunning the script to rebuild the root filesystem. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/img/app/Makefile b/img/app/Makefile index f818e914908038cdf14532d1b7b44a1c37c1b270..d81337f99e2e83cf03ac73b9fc96fae6ce118537 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -71,7 +71,7 @@ build/fifo: build/empty: mkdir -p $@ -build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) build/empty build/fifo +build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) build/empty build/fifo Makefile ( \ cat $$PACKAGES ;\ for file in $(VM_FILES) $(VM_LINKS); do printf '%s\n%s\n' $$file $$file; done ;\ base-commit: 89b80752a275a4ca1818a49d86f27cca9e323389 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
I spent hours figuring out why files were not found in the VMs. Turns out that 'make' was no rerunning the script to rebuild the root filesystem.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/img/app/Makefile b/img/app/Makefile index f818e914908038cdf14532d1b7b44a1c37c1b270..d81337f99e2e83cf03ac73b9fc96fae6ce118537 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -71,7 +71,7 @@ build/fifo: build/empty: mkdir -p $@
-build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) build/empty build/fifo +build/rootfs.erofs: ../../scripts/make-erofs.sh $(VM_FILES) $(VM_BUILD_FILES) build/empty build/fifo Makefile ( \ cat $$PACKAGES ;\ for file in $(VM_FILES) $(VM_LINKS); do printf '%s\n%s\n' $$file $$file; done ;\
I'm not sure about this one — there's no reason this target in particular needs to depend on the Makefile, but others don't. A change to the Makefile could cause any target to be outdated. I think it's just a normal part of working with make that you have to do a clean build after changing the Makefile.
/tmp should always be 1755 and stuff will break if it is not. Also fix a whitespace issue that had no functional impact. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/fstab | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/img/app/etc/fstab b/img/app/etc/fstab index a95088b0552a05b059e86ff970fddef33f02b944..964d2131a84a8db5ed6613b61f14830416244b86 100644 --- a/img/app/etc/fstab +++ b/img/app/etc/fstab @@ -1,7 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> proc /proc proc defaults 0 0 -devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 +devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 -tmpfs /tmp tmpfs defaults 0 0 +tmpfs /tmp tmpfs defaults,mode=1755 0 0 -- Sincerely, Demi Marie Obenour (she/her/hers)
This patch has been committed as 306350cf1cd85c3f3c890c076e989a2cd5ca6d9e, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=306350cf1cd85c3f3c890c076e98.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
This patch has been committed as 65ac62b2b170d9fb1dd47190073a97346467152f, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=65ac62b2b170d9fb1dd47190073a.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
Demi Marie Obenour <demiobenour@gmail.com> writes:
/tmp should always be 1755 and stuff will break if it is not.
Also fix a whitespace issue that had no functional impact.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
As you'll see from the automated messages, I've applied this, but I split it into two commits, and I tweaked the commit messages to conform to the normal conventions. (You can get a good idea of them by looking at past commit messages for the area you're working in.)
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)
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 ++++++++++++++++++
The PipeWire and WirePlumber config files are still very big. Is it really not possible to make them smaller and rely on defaults where possible? With a whole big config like this, it's difficult to see what Spectrum-specific stuff is going on.
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
Do these all conflict with PipeWire/WirePlumber/PulseAudio??
On 7/18/25 07:27, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 ++++++++++++++++++
The PipeWire and WirePlumber config files are still very big. Is it really not possible to make them smaller and rely on defaults where possible? With a whole big config like this, it's difficult to see what Spectrum-specific stuff is going on.
Both of these files are *smaller* than their upstream counterparts. The main Spectrum-specific stuff is: - Nodes are statically set up. This is because PipeWire relies on udev to enumerate device nodes, and Spectrum VMs don't have udev. - A lot of stuff in the upstream configuration files has been removed. Spectrum's configs should be *smaller* than their upstream counterparts. Neither PipeWire nor WirePlumber has much in the way of a default configuration. Without a configuration file, neither PipeWire nor WirePlumber does anything useful. Overriding the defaults and using the upstream configuration file might be possible, but it seemed unlikely when I looked. Spectrum VMs are more like embedded systems than the desktop systems the defaults are made for. I recommend not including any of the upstream configuration files and only an allowlist of SPA plugins and PipeWire and WirePlumber modules. Stuff like Bluetooth can also be excluded. Just because upstream nixpkgs pulls it in as a dependency does not mean the configuration in the VM actually needs it. Ideally, the various plugins and modules would all be separate build outputs with their own dependencies, and ‘pipewire’ and ‘wireplumber’ would be meta-packages that pull in all of them.
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
Do these all conflict with PipeWire/WirePlumber/PulseAudio??
Nope. This should have been a separate patch. It is purely to save disk space and build time by excluding stuff that we definitely have no use for. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/18/25 07:27, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 ++++++++++++++++++
The PipeWire and WirePlumber config files are still very big. Is it really not possible to make them smaller and rely on defaults where possible? With a whole big config like this, it's difficult to see what Spectrum-specific stuff is going on.
Both of these files are *smaller* than their upstream counterparts. The main Spectrum-specific stuff is:
- Nodes are statically set up. This is because PipeWire relies on udev to enumerate device nodes, and Spectrum VMs don't have udev.
- A lot of stuff in the upstream configuration files has been removed. Spectrum's configs should be *smaller* than their upstream counterparts.
Neither PipeWire nor WirePlumber has much in the way of a default configuration. Without a configuration file, neither PipeWire nor WirePlumber does anything useful. Overriding the defaults and using the upstream configuration file might be possible, but it seemed unlikely when I looked. Spectrum VMs are more like embedded systems than the desktop systems the defaults are made for.
Okay, but there's still stuff that could definitely be trimmed. The comments explaining the syntax of the config file, and the default values, for example. I think they get in the way more than they help because they make the actual non-default settings very sparse. Similarly we probably don't need conditional overrides that are "only applied when running in a VM".
I recommend not including any of the upstream configuration files and only an allowlist of SPA plugins and PipeWire and WirePlumber modules. Stuff like Bluetooth can also be excluded. Just because upstream nixpkgs pulls it in as a dependency does not mean the configuration in the VM actually needs it. Ideally, the various plugins and modules would all be separate build outputs with their own dependencies, and ‘pipewire’ and ‘wireplumber’ would be meta-packages that pull in all of them.
That's an interesting idea. I think it would probably be feasible? And wouldn't share the same maintenance concerns as lots of overrides.
On 7/19/25 05:22, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/18/25 07:27, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 ++++++++++++++++++
The PipeWire and WirePlumber config files are still very big. Is it really not possible to make them smaller and rely on defaults where possible? With a whole big config like this, it's difficult to see what Spectrum-specific stuff is going on.
Both of these files are *smaller* than their upstream counterparts. The main Spectrum-specific stuff is:
- Nodes are statically set up. This is because PipeWire relies on udev to enumerate device nodes, and Spectrum VMs don't have udev.
- A lot of stuff in the upstream configuration files has been removed. Spectrum's configs should be *smaller* than their upstream counterparts.
Neither PipeWire nor WirePlumber has much in the way of a default configuration. Without a configuration file, neither PipeWire nor WirePlumber does anything useful. Overriding the defaults and using the upstream configuration file might be possible, but it seemed unlikely when I looked. Spectrum VMs are more like embedded systems than the desktop systems the defaults are made for.
Okay, but there's still stuff that could definitely be trimmed. The comments explaining the syntax of the config file, and the default values, for example. I think they get in the way more than they help because they make the actual non-default settings very sparse. Similarly we probably don't need conditional overrides that are "only applied when running in a VM".
I'll fix this in v5 by only including comments explaining the differences from upstream and removing the conditional overrides.
I recommend not including any of the upstream configuration files and only an allowlist of SPA plugins and PipeWire and WirePlumber modules. Stuff like Bluetooth can also be excluded. Just because upstream nixpkgs pulls it in as a dependency does not mean the configuration in the VM actually needs it. Ideally, the various plugins and modules would all be separate build outputs with their own dependencies, and ‘pipewire’ and ‘wireplumber’ would be meta-packages that pull in all of them.
That's an interesting idea. I think it would probably be feasible? And wouldn't share the same maintenance concerns as lots of overrides.
I'll add that to the list of debloating tasks. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
diff --git a/img/app/Makefile b/img/app/Makefile index d81337f99e2e83cf03ac73b9fc96fae6ce118537..691db75c287294104bb2fa332e002792bfa61f6e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -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 \
Why this change?
-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
Let's group this with the fs and gpu arguments? More similar to them than the console. Also, would it be better to use the pa driver, so it can be tested on systems that don't have PipeWire yet? There shouldn't be any downside, right?
On 7/19/25 04:06, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
diff --git a/img/app/Makefile b/img/app/Makefile index d81337f99e2e83cf03ac73b9fc96fae6ce118537..691db75c287294104bb2fa332e002792bfa61f6e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -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 \
Why this change?
Otherwise the script fails with TCG, but it seems to fail anyway due to rutabaga issues in what is probably an untested configuration. I'll get another machine to do testing with KVM enabled.
-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
Let's group this with the fs and gpu arguments? More similar to them than the console. Also, would it be better to use the pa driver, so it can be tested on systems that don't have PipeWire yet? There shouldn't be any downside, right?
Will fix in v5. -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/19/25 16:03, Demi Marie Obenour wrote:
On 7/19/25 04:06, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
diff --git a/img/app/Makefile b/img/app/Makefile index d81337f99e2e83cf03ac73b9fc96fae6ce118537..691db75c287294104bb2fa332e002792bfa61f6e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -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 \
Why this change?
Otherwise the script fails with TCG, but it seems to fail anyway due to rutabaga issues in what is probably an untested configuration. I'll get another machine to do testing with KVM enabled.
-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
Let's group this with the fs and gpu arguments? More similar to them than the console. Also, would it be better to use the pa driver, so it can be tested on systems that don't have PipeWire yet? There shouldn't be any downside, right?
Will fix in v5.
I wrote too soon: the pa driver fails if PipeWire is in use, as it expects "$XDG_RUNTIME_DIR/native/pid" to exist and pipewire-pulse doesn't create it. Qubes OS doesn't alter pipewire-pulse so it isn't the cause. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/19/25 16:03, Demi Marie Obenour wrote:
On 7/19/25 04:06, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
@@ -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
Let's group this with the fs and gpu arguments? More similar to them than the console. Also, would it be better to use the pa driver, so it can be tested on systems that don't have PipeWire yet? There shouldn't be any downside, right?
Will fix in v5.
I wrote too soon: the pa driver fails if PipeWire is in use, as it expects "$XDG_RUNTIME_DIR/native/pid" to exist and pipewire-pulse doesn't create it. Qubes OS doesn't alter pipewire-pulse so it isn't the cause.
Okay — worked on my testing on NixOS, but doesn't sound worth it if it's going to be even less portable than PipeWire!
The first patch reverts a completely wrong patch I sent earlier. Patches 2 through 7 clean up directory creation in the boot scripts, and patch 8 actually adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a QEMU VM, as there is no sound support in Cloud Hypervisor and crosvm's sound support requires CrAS or Android. Running CrAS might actually not be a bad idea; if nothing else, I expect it to have significantly better code quality than PipeWire. CrAS does rely on udev, though, so I don't see a way around udev on the host. Demi Marie Obenour (8): Revert "img/app: fix permissions on /tmp" img/app: Use separate service to create directories img/app: Fix permissions of /tmp/.X11-unix img/app: Create other X11 directories img/app: Be explicit about directory modes img/app: create /run/user and /run/wait very early in boot host/rootfs: create /run/user and /run/wait very early in boot img/app: Run PipeWire and WirePlumber in the VMs host/rootfs/etc/mdev/listen | 1 - host/rootfs/etc/mdev/wait | 1 - host/rootfs/etc/s6-linux-init/scripts/rc.init | 1 + host/rootfs/etc/s6-rc/weston/run | 6 +- img/app/Makefile | 22 +- img/app/default.nix | 3 + img/app/etc/fstab | 2 +- img/app/etc/mdev.conf | 3 + img/app/etc/mdev/listen | 1 - img/app/etc/mdev/wait | 1 - img/app/etc/pipewire/pipewire.conf | 199 +++++++ img/app/etc/s6-linux-init/scripts/rc.init | 2 +- .../etc/s6-rc/app/dependencies.d/directories | 0 .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../etc/s6-rc/dbus/dependencies.d/directories | 0 img/app/etc/s6-rc/directories/type | 1 + img/app/etc/s6-rc/directories/type.license | 2 + img/app/etc/s6-rc/directories/up | 10 + .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + img/app/etc/s6-rc/pipewire/type | 1 + img/app/etc/s6-rc/pipewire/type.license | 2 + .../dependencies.d/directories | 0 img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 - .../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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 33 files changed, 816 insertions(+), 24 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/wireplumber create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories 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/mdevd 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/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 base-commit: ec60920e1e37295f93f1295fe6f441735bba2e41 -- Sincerely, Demi Marie Obenour (she/her/hers)
The default permissions of 1777 are correct, as `stat /tmp` on any Unix-like system will show. Using 1755 is nonsensical: it means that items in the directory can only be moved or deleted by their owners, but also that they can only be moved or deleted by the owner of the directory. In this case, that owner is root, who can ignore permissions anyway. This reverts commit 306350cf1cd85c3f3c890c076e989a2cd5ca6d9e. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/fstab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/img/app/etc/fstab b/img/app/etc/fstab index 964d2131a84a8db5ed6613b61f14830416244b86..4a290d9710d177af0b787e67fd95a97fed4160c1 100644 --- a/img/app/etc/fstab +++ b/img/app/etc/fstab @@ -4,4 +4,4 @@ proc /proc proc defaults 0 0 devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 -tmpfs /tmp tmpfs defaults,mode=1755 0 0 +tmpfs /tmp tmpfs defaults 0 0 base-commit: ec60920e1e37295f93f1295fe6f441735bba2e41 -- Sincerely, Demi Marie Obenour (she/her/hers)
This patch has been committed as 3063693e0bd5ad17b7786f4c86d66b12a5a62a1d, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=3063693e0bd5ad17b7786f4c86d6.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
The app, D-Bus, and wayland-proxy-virtwl all rely on /run/user and /run/user/0 to exist. The app and wayland-proxy-virtwl also relies on /tmp/.X11-unix existing. Move the directory creation to a oneshot service that other services rely on. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 5 +++++ img/app/etc/s6-rc/app/dependencies.d/directories | 0 img/app/etc/s6-rc/dbus/dependencies.d/directories | 0 img/app/etc/s6-rc/directories/type | 1 + img/app/etc/s6-rc/directories/type.license | 2 ++ img/app/etc/s6-rc/directories/up | 10 ++++++++++ .../wayland-proxy-virtwl/dependencies.d/directories | 0 img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 ----------- 8 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 img/app/etc/s6-rc/app/dependencies.d/directories create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories 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/wayland-proxy-virtwl/dependencies.d/directories diff --git a/img/app/Makefile b/img/app/Makefile index 80ef37ddf0c44636813725bd499e4ea5fad03c06..e11be09a3c6ca801d9211e49b58e3d05d57e344e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -82,12 +82,16 @@ 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/directories \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ 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 \ @@ -96,6 +100,7 @@ VM_S6_RC_FILES = \ etc/s6-rc/mdevd/type \ etc/s6-rc/ok-all/contents \ etc/s6-rc/ok-all/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 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/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/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..0ce4a06a37077cef8881d45382a81950e164560f --- /dev/null +++ b/img/app/etc/s6-rc/directories/up @@ -0,0 +1,10 @@ +#!/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 /run/user /tmp/.X11-unix } +if { mkdir -m 0700 /run/user/0 } 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/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 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
The app, D-Bus, and wayland-proxy-virtwl all rely on /run/user and /run/user/0 to exist. The app and wayland-proxy-virtwl also relies on /tmp/.X11-unix existing. Move the directory creation to a oneshot service that other services rely on.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 5 +++++ img/app/etc/s6-rc/app/dependencies.d/directories | 0 img/app/etc/s6-rc/dbus/dependencies.d/directories | 0 img/app/etc/s6-rc/directories/type | 1 + img/app/etc/s6-rc/directories/type.license | 2 ++ img/app/etc/s6-rc/directories/up | 10 ++++++++++ .../wayland-proxy-virtwl/dependencies.d/directories | 0 img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 ----------- 8 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 img/app/etc/s6-rc/app/dependencies.d/directories create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories 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/wayland-proxy-virtwl/dependencies.d/directories
diff --git a/img/app/Makefile b/img/app/Makefile index 80ef37ddf0c44636813725bd499e4ea5fad03c06..e11be09a3c6ca801d9211e49b58e3d05d57e344e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -82,12 +82,16 @@ 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/directories \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ 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 \ @@ -96,6 +100,7 @@ VM_S6_RC_FILES = \ etc/s6-rc/mdevd/type \ etc/s6-rc/ok-all/contents \ etc/s6-rc/ok-all/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 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/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/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..0ce4a06a37077cef8881d45382a81950e164560f --- /dev/null +++ b/img/app/etc/s6-rc/directories/up @@ -0,0 +1,10 @@ +#!/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 /run/user /tmp/.X11-unix } +if { mkdir -m 0700 /run/user/0 }
Since you've basically rewritten this, let's reset the copyright here. We could also create /run/user in VM_DIRS in the Makefile, like we already do for /run/service — probably good to keep that consistent when we don't have reasons not to like a special mode.
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/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 -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/21/25 05:21, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
The app, D-Bus, and wayland-proxy-virtwl all rely on /run/user and /run/user/0 to exist. The app and wayland-proxy-virtwl also relies on /tmp/.X11-unix existing. Move the directory creation to a oneshot service that other services rely on.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 5 +++++ img/app/etc/s6-rc/app/dependencies.d/directories | 0 img/app/etc/s6-rc/dbus/dependencies.d/directories | 0 img/app/etc/s6-rc/directories/type | 1 + img/app/etc/s6-rc/directories/type.license | 2 ++ img/app/etc/s6-rc/directories/up | 10 ++++++++++ .../wayland-proxy-virtwl/dependencies.d/directories | 0 img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 ----------- 8 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 img/app/etc/s6-rc/app/dependencies.d/directories create mode 100644 img/app/etc/s6-rc/dbus/dependencies.d/directories 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/wayland-proxy-virtwl/dependencies.d/directories
diff --git a/img/app/Makefile b/img/app/Makefile index 80ef37ddf0c44636813725bd499e4ea5fad03c06..e11be09a3c6ca801d9211e49b58e3d05d57e344e 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -82,12 +82,16 @@ 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/directories \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ 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 \ @@ -96,6 +100,7 @@ VM_S6_RC_FILES = \ etc/s6-rc/mdevd/type \ etc/s6-rc/ok-all/contents \ etc/s6-rc/ok-all/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 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/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/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..0ce4a06a37077cef8881d45382a81950e164560f --- /dev/null +++ b/img/app/etc/s6-rc/directories/up @@ -0,0 +1,10 @@ +#!/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 /run/user /tmp/.X11-unix } +if { mkdir -m 0700 /run/user/0 }
Since you've basically rewritten this, let's reset the copyright here.
We could also create /run/user in VM_DIRS in the Makefile, like we already do for /run/service — probably good to keep that consistent when we don't have reasons not to like a special mode.
For v6, my plan is: - Create /run/user and /run/wait in the Makefile via VM_DIRs. Make the corresponding change on the host too. - Have /run/user/0 be a separate tmpfs mount created in /etc/fstab. This is for consistency with systemd-based systems, which automatically create such a mount. If you prefer, I can use mkdir in rc.init instead. - Create the X11 directories (/tmp/.*-unix) in rc.init before starting any services. Should these be a single patch or in separate ones? I know that it is normally best to separate them out, but these patches are all tiny compared to the giant patch that follows them. -- Sincerely, Demi Marie Obenour (she/her/hers)
1777 is the stock default. In the future it should only be writeable by the user running the X server, but that is for when Spectrum VMs have non-root users. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-rc/directories/up | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up index 0ce4a06a37077cef8881d45382a81950e164560f..e76a6785bc5ef644787142c2ebb40193d4d10fb4 100644 --- a/img/app/etc/s6-rc/directories/up +++ b/img/app/etc/s6-rc/directories/up @@ -6,5 +6,6 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2022 Unikie -if { mkdir /run/user /tmp/.X11-unix } +if { mkdir -m 1777 /tmp/.X11-unix } +if { mkdir -m 0755 /run/user } if { mkdir -m 0700 /run/user/0 } -- Sincerely, Demi Marie Obenour (she/her/hers)
On my system /tmp/.X11-unix, /tmp/.ICE-unix, /tmp/.XIM-unix, and /tmp/.font-unix all exist and have 1777 permissions. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-rc/directories/up | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up index e76a6785bc5ef644787142c2ebb40193d4d10fb4..be60547cc7ba562d3220fabbe5bca630aa4b1f1a 100644 --- a/img/app/etc/s6-rc/directories/up +++ b/img/app/etc/s6-rc/directories/up @@ -6,6 +6,6 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2022 Unikie -if { mkdir -m 1777 /tmp/.X11-unix } +if { mkdir -m 1777 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix } if { mkdir -m 0755 /run/user } if { mkdir -m 0700 /run/user/0 } -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On my system /tmp/.X11-unix, /tmp/.ICE-unix, /tmp/.XIM-unix, and /tmp/.font-unix all exist and have 1777 permissions.
And the idea here is that they need to exist for compatibility, or…?
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-rc/directories/up | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up index e76a6785bc5ef644787142c2ebb40193d4d10fb4..be60547cc7ba562d3220fabbe5bca630aa4b1f1a 100644 --- a/img/app/etc/s6-rc/directories/up +++ b/img/app/etc/s6-rc/directories/up @@ -6,6 +6,6 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2022 Unikie
-if { mkdir -m 1777 /tmp/.X11-unix } +if { mkdir -m 1777 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix } if { mkdir -m 0755 /run/user } if { mkdir -m 0700 /run/user/0 } -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/21/25 05:23, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
On my system /tmp/.X11-unix, /tmp/.ICE-unix, /tmp/.XIM-unix, and /tmp/.font-unix all exist and have 1777 permissions.
And the idea here is that they need to exist for compatibility, or…?
I don't know if they need to exist, but I'm also no expert on X11 and I really want to avoid any unexpected breakage that would be horrible to debug. If they are not needed, having them is harmless. -- Sincerely, Demi Marie Obenour (she/her/hers)
No functional change intended. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-linux-init/scripts/rc.init | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index b46afb73b8e4b56e0d3a8e1da5c05b14812bce36..a9e43aae685597b94ebcd431d4bb560f2d02a92e 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -4,7 +4,7 @@ if { s6-rc-init -c /etc/s6-rc /run/service } -if { mkdir -p /dev/pts /dev/shm } +if { mkdir -p -m 0755 /dev/pts /dev/shm } if { modprobe overlay } if { mount -a } -- Sincerely, Demi Marie Obenour (she/her/hers)
There's no real reason not to use the existing call to mkdir for that. This allows getting rid of calls to mkdir from directories/up, mdev/listen, and mdev/wait, so the overall diffstat is negative. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/mdev/listen | 1 - img/app/etc/mdev/wait | 1 - img/app/etc/s6-linux-init/scripts/rc.init | 2 +- img/app/etc/s6-rc/directories/up | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/img/app/etc/mdev/listen b/img/app/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/img/app/etc/mdev/listen +++ b/img/app/etc/mdev/listen @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/img/app/etc/mdev/wait b/img/app/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/img/app/etc/mdev/wait +++ b/img/app/etc/mdev/wait @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index a9e43aae685597b94ebcd431d4bb560f2d02a92e..d8b71affa1752c6fd7455412cde9c76fa0752713 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -4,7 +4,7 @@ if { s6-rc-init -c /etc/s6-rc /run/service } -if { mkdir -p -m 0755 /dev/pts /dev/shm } +if { mkdir -p -m 0755 /dev/pts /dev/shm /run/user /run/wait } if { modprobe overlay } if { mount -a } diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up index be60547cc7ba562d3220fabbe5bca630aa4b1f1a..d2688029151d622eba1a2d9d06e4435e4ff632e6 100644 --- a/img/app/etc/s6-rc/directories/up +++ b/img/app/etc/s6-rc/directories/up @@ -7,5 +7,4 @@ # SPDX-FileCopyrightText: 2022 Unikie if { mkdir -m 1777 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix } -if { mkdir -m 0755 /run/user } if { mkdir -m 0700 /run/user/0 } -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
There's no real reason not to use the existing call to mkdir for that. This allows getting rid of calls to mkdir from directories/up, mdev/listen, and mdev/wait, so the overall diffstat is negative.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/mdev/listen | 1 - img/app/etc/mdev/wait | 1 - img/app/etc/s6-linux-init/scripts/rc.init | 2 +- img/app/etc/s6-rc/directories/up | 1 - 4 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/img/app/etc/mdev/listen b/img/app/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/img/app/etc/mdev/listen +++ b/img/app/etc/mdev/listen @@ -4,7 +4,6 @@
foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} }
diff --git a/img/app/etc/mdev/wait b/img/app/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/img/app/etc/mdev/wait +++ b/img/app/etc/mdev/wait @@ -4,7 +4,6 @@
foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} }
diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index a9e43aae685597b94ebcd431d4bb560f2d02a92e..d8b71affa1752c6fd7455412cde9c76fa0752713 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -4,7 +4,7 @@
if { s6-rc-init -c /etc/s6-rc /run/service }
-if { mkdir -p -m 0755 /dev/pts /dev/shm } +if { mkdir -p -m 0755 /dev/pts /dev/shm /run/user /run/wait }
Let's do this with run-image in the Makefile as well.
if { modprobe overlay } if { mount -a }
diff --git a/img/app/etc/s6-rc/directories/up b/img/app/etc/s6-rc/directories/up index be60547cc7ba562d3220fabbe5bca630aa4b1f1a..d2688029151d622eba1a2d9d06e4435e4ff632e6 100644 --- a/img/app/etc/s6-rc/directories/up +++ b/img/app/etc/s6-rc/directories/up @@ -7,5 +7,4 @@ # SPDX-FileCopyrightText: 2022 Unikie
if { mkdir -m 1777 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix } -if { mkdir -m 0755 /run/user } if { mkdir -m 0700 /run/user/0 } -- Sincerely, Demi Marie Obenour (she/her/hers)
It's easier to just ensure that these directories always exist. This allows getting rid of calls to mkdir from directories/up, mdev/listen, and mdev/wait, so the overall diffstat is negative. Also use mkdir -m instead of a separate call to foreground, and use -p instead of ignoring all errors. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- host/rootfs/etc/mdev/listen | 1 - host/rootfs/etc/mdev/wait | 1 - host/rootfs/etc/s6-linux-init/scripts/rc.init | 1 + host/rootfs/etc/s6-rc/weston/run | 6 +----- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/host/rootfs/etc/mdev/listen b/host/rootfs/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/host/rootfs/etc/mdev/listen +++ b/host/rootfs/etc/mdev/listen @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/mdev/wait b/host/rootfs/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/host/rootfs/etc/mdev/wait +++ b/host/rootfs/etc/mdev/wait @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/s6-linux-init/scripts/rc.init b/host/rootfs/etc/s6-linux-init/scripts/rc.init index 674fd38cc76837c7be25a5ef060f0f4d4b786394..d7c4b62a91c40e10e2a773bd33c0e36dc85bd378 100755 --- a/host/rootfs/etc/s6-linux-init/scripts/rc.init +++ b/host/rootfs/etc/s6-linux-init/scripts/rc.init @@ -5,6 +5,7 @@ if { s6-rc-init -c /etc/s6-rc /run/service } if { mount --make-shared /run } +if { mkdir -m 0755 /run/user /run/wait } if { mount -a --mkdir } s6-rc change ok-all diff --git a/host/rootfs/etc/s6-rc/weston/run b/host/rootfs/etc/s6-rc/weston/run index 2512c011575591eefb110d6e3586517f28ba7064..0d3c819a8fecca6cc6b825a6e791899c12806de2 100644 --- a/host/rootfs/etc/s6-rc/weston/run +++ b/host/rootfs/etc/s6-rc/weston/run @@ -4,11 +4,7 @@ unexport WAYLAND_DISPLAY -foreground { mkdir /run/user } -foreground { - umask 077 - mkdir /run/user/0 -} +if { mkdir -p -m 0700 /run/user/0 } unexport ? backtick USER { id -un } -- Sincerely, Demi Marie Obenour (she/her/hers)
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 bring a log of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 17 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 3 + img/app/etc/pipewire/pipewire.conf | 199 +++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + 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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 18 files changed, 794 insertions(+), 2 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/directories create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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 diff --git a/img/app/Makefile b/img/app/Makefile index e11be09a3c6ca801d9211e49b58e3d05d57e344e..734a76f018dfc1deaa9bf2cfbd4fa0d6885f0546 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 @@ -84,6 +87,7 @@ VM_S6_RC_FILES = \ etc/s6-rc/app/dependencies.d/dbus \ etc/s6-rc/app/dependencies.d/directories \ 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 \ @@ -100,10 +104,18 @@ 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/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/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 $@) @@ -150,6 +162,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..80efb77c68cd533b4c46e9d15966807a3ff084dd 100644 --- a/img/app/etc/mdev.conf +++ b/img/app/etc/mdev.conf @@ -5,3 +5,6 @@ $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/pcmC0D0p 0:0 660 +background { /etc/mdev/listen pcmC0D0p } +snd/pcmC0D0c 0:0 660 +background { /etc/mdev/listen pcmC0D0c } +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..cbea25d2c7ca274db8a3c8772439b0d3a8279f13 --- /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" + 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" + 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/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/mdevd b/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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 <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..2b3c001fb4f8b00e39aeeefe28ad6a4f7e4760a4 --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/run @@ -0,0 +1,25 @@ +#!/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 + +# Wait for sound devices to be available +if { /etc/mdev/wait pcmC0D0p } +if { /etc/mdev/wait pcmC0D0c } +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 <demiobenour@gmail.com> 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..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..68871e93083572976bba29b44f07eb35b86c0275 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf @@ -0,0 +1,536 @@ +# 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. + +# 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 WirePlumber. +# 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. +# - Settings for VMs are applied unconditionally. +# - Most comments in the upstream files have been removed. +# - Integration with udev and logind is removed. +# - RT scheduling for the data thread is enabled. +# - The settings are those appropriate for a system-wide instance. + +context.spa-libs = { + api.alsa.* = alsa/libspa-alsa + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +# Upstream default, with RT scheduling enabled. +context.modules = [ + { + name = libpipewire-module-rt + args = { nice.level = -11, rt.prio = 88 } + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-metadata } +] + +wireplumber.profiles = { + 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 = [ + { + name = export-core, type = built-in + provides = support.export-core + } + { + name = libpipewire-module-client-node, type = pw-module + provides = pw.client-node + wants = [ support.export-core ] + } + { + name = libpipewire-module-client-device, type = pw-module + provides = pw.client-device + wants = [ support.export-core ] + } + { + name = libpipewire-module-spa-node-factory, type = pw-module + provides = pw.node-factory.spa + requires = [ pw.client-node ] + } + { + name = libpipewire-module-adapter, type = pw-module + provides = pw.node-factory.adapter + requires = [ pw.client-node ] + } + { + name = libwireplumber-module-settings, type = module + arguments = { metadata.name = sm-settings } + provides = metadata.sm-settings + } + { + name = settings-instance, type = built-in + arguments = { metadata.name = sm-settings } + provides = support.settings + after = [ metadata.sm-settings ] + } + { + name = libwireplumber-module-lua-scripting, type = module + provides = support.lua-scripting + } + { + name = libwireplumber-module-standard-event-source, type = module + provides = support.standard-event-source + } + { + 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 + } + { + name = libwireplumber-module-default-nodes-api, type = module + provides = api.default-nodes + } + { + name = libwireplumber-module-mixer-api, type = module + provides = api.mixer + } + { + name = metadata.lua, type = script/lua + arguments = { metadata.name = default } + provides = metadata.default + } + { + name = metadata.lua, type = script/lua + arguments = { metadata.name = filters } + provides = metadata.filters + } + { + name = sm-objects.lua, type = script/lua + provides = metadata.sm-objects + } + { + name = session-services.lua, type = script/lua + provides = support.session-services + } + { + name = monitors/alsa.lua, type = script/lua + provides = monitor.alsa + requires = [ support.export-core, pw.client-device ] + } + { + name = client/access-default.lua, type = script/lua + provides = script.client.access-default + } + { + type = virtual, provides = policy.client.access + wants = [ script.client.access-default ] + } + { + 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 ] + } + { + 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 ] + } + { + 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 ] + } + { + 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/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 ] + } + { + 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 ] + } + { + 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 ] + } + { + type = virtual, provides = hardware.audio + wants = [ monitor.alsa ] + } +] + +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)
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 bring a log of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 17 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 3 + img/app/etc/pipewire/pipewire.conf | 199 +++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + 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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 18 files changed, 794 insertions(+), 2 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/directories create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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
diff --git a/img/app/Makefile b/img/app/Makefile index e11be09a3c6ca801d9211e49b58e3d05d57e344e..734a76f018dfc1deaa9bf2cfbd4fa0d6885f0546 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
Sorting
diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..cbea25d2c7ca274db8a3c8772439b0d3a8279f13 --- /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 +}
Any reason not to enable JACK?
+ +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 + } + }
So does upstream not load it into PipeWire?
+] + +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" + 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" + 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 = []
I like this a lot better now, thanks!
diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd b/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
Probably not necessary, given we wait for the device anyway?
diff --git a/img/app/etc/wireplumber/wireplumber.conf b/img/app/etc/wireplumber/wireplumber.conf new file mode 100644 index 0000000000000000000000000000000000000000..68871e93083572976bba29b44f07eb35b86c0275 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf @@ -0,0 +1,536 @@ +# 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. + +# 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 WirePlumber. +# 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. +# - Settings for VMs are applied unconditionally. +# - Most comments in the upstream files have been removed. +# - Integration with udev and logind is removed. +# - RT scheduling for the data thread is enabled. +# - The settings are those appropriate for a system-wide instance. + +context.spa-libs = { + api.alsa.* = alsa/libspa-alsa + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +# Upstream default, with RT scheduling enabled. +context.modules = [ + { + name = libpipewire-module-rt + args = { nice.level = -11, rt.prio = 88 } + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-metadata } +] + +wireplumber.profiles = { + 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
It's enabled by default?
On 7/21/25 05:42, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 bring a log of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 17 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 3 + img/app/etc/pipewire/pipewire.conf | 199 +++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + 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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 18 files changed, 794 insertions(+), 2 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/directories create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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
diff --git a/img/app/Makefile b/img/app/Makefile index e11be09a3c6ca801d9211e49b58e3d05d57e344e..734a76f018dfc1deaa9bf2cfbd4fa0d6885f0546 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
Sorting
Will fix in v6. Does Unix or Vim `sort` give the order you want?
diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..cbea25d2c7ca274db8a3c8772439b0d3a8279f13 --- /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 +}
Any reason not to enable JACK?
JACK is for when PipeWire uses JACK as a backend, which in a Spectrum VM will never happen.
+ +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 + } + }
So does upstream not load it into PipeWire?
Upstream’s “minimal” configuration has that as an option. The default configuration for desktops is to use a separate pipewire-pulse process, but that wastes memory. Once every daemon is running as its own user, there could potentially be a very minor security win to doing this, as pipewire-pulse does not need access to any physical hardware. However, this assumes that the PipeWire native protocol is secure and that the program in the VM cannot escalate its privileges via a kernel vulnerability, neither of which are good assumptions.
+] + +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" + 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" + 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 = []
I like this a lot better now, thanks!
You're welcome!
diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd b/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
Probably not necessary, given we wait for the device anyway?
Indeed so.
diff --git a/img/app/etc/wireplumber/wireplumber.conf b/img/app/etc/wireplumber/wireplumber.conf new file mode 100644 index 0000000000000000000000000000000000000000..68871e93083572976bba29b44f07eb35b86c0275 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf @@ -0,0 +1,536 @@ +# 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. + +# 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 WirePlumber. +# 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. +# - Settings for VMs are applied unconditionally. +# - Most comments in the upstream files have been removed. +# - Integration with udev and logind is removed. +# - RT scheduling for the data thread is enabled. +# - The settings are those appropriate for a system-wide instance. + +context.spa-libs = { + api.alsa.* = alsa/libspa-alsa + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +# Upstream default, with RT scheduling enabled. +context.modules = [ + { + name = libpipewire-module-rt + args = { nice.level = -11, rt.prio = 88 } + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-metadata } +] + +wireplumber.profiles = { + 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
It's enabled by default?
Bluetooth is indeed enabled by default in the default configuration. I expect this to be a no-op with the stripped down configuration I am using here, but I will need to do some testing before I remove it. -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/21/25 05:42, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 bring a log of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 17 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 3 + img/app/etc/pipewire/pipewire.conf | 199 +++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + 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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 18 files changed, 794 insertions(+), 2 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/directories create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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
diff --git a/img/app/Makefile b/img/app/Makefile index e11be09a3c6ca801d9211e49b58e3d05d57e344e..734a76f018dfc1deaa9bf2cfbd4fa0d6885f0546 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
Sorting
Will fix in v6. Does Unix or Vim `sort` give the order you want?
I'd say LC_ALL=C.UTF-8 sort would probably be canonical, but usually I do M-x sort-lines in Emacs, and I don't think our paths have really been complicated enough to diverge between any of those so far.
On 7/21/25 05:42, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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 bring a log of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 17 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 3 + img/app/etc/pipewire/pipewire.conf | 199 +++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../s6-rc/pipewire/dependencies.d/directories | 0 .../etc/s6-rc/pipewire/dependencies.d/mdevd | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 25 + 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 + img/app/etc/wireplumber/wireplumber.conf | 536 ++++++++++++++++++ 18 files changed, 794 insertions(+), 2 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/directories create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/mdevd 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
diff --git a/img/app/Makefile b/img/app/Makefile index e11be09a3c6ca801d9211e49b58e3d05d57e344e..734a76f018dfc1deaa9bf2cfbd4fa0d6885f0546 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
Sorting
Will fix in v6. Does Unix or Vim `sort` give the order you want?
diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..cbea25d2c7ca274db8a3c8772439b0d3a8279f13 --- /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 +}
Any reason not to enable JACK?
JACK is for when PipeWire uses JACK as a backend, which in a Spectrum VM will never happen.
+ +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 + } + }
So does upstream not load it into PipeWire?
Upstream’s “minimal” configuration has that as an option. The default configuration for desktops is to use a separate pipewire-pulse process, but that wastes memory. Once every daemon is running as its own user, there could potentially be a very minor security win to doing this, as pipewire-pulse does not need access to any physical hardware. However, this assumes that the PipeWire native protocol is secure and that the program in the VM cannot escalate its privileges via a kernel vulnerability, neither of which are good assumptions.
+] + +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" + 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" + 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 = []
I like this a lot better now, thanks!
You're welcome!
diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd b/img/app/etc/s6-rc/pipewire/dependencies.d/mdevd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
Probably not necessary, given we wait for the device anyway?
Indeed so.
diff --git a/img/app/etc/wireplumber/wireplumber.conf b/img/app/etc/wireplumber/wireplumber.conf new file mode 100644 index 0000000000000000000000000000000000000000..68871e93083572976bba29b44f07eb35b86c0275 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf @@ -0,0 +1,536 @@ +# 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. + +# 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 WirePlumber. +# 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. +# - Settings for VMs are applied unconditionally. +# - Most comments in the upstream files have been removed. +# - Integration with udev and logind is removed. +# - RT scheduling for the data thread is enabled. +# - The settings are those appropriate for a system-wide instance. + +context.spa-libs = { + api.alsa.* = alsa/libspa-alsa + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +# Upstream default, with RT scheduling enabled. +context.modules = [ + { + name = libpipewire-module-rt + args = { nice.level = -11, rt.prio = 88 } + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-metadata } +] + +wireplumber.profiles = { + 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
It's enabled by default?
Bluetooth is indeed enabled by default in the default configuration. I expect this to be a no-op with the stripped down configuration I am using here, but I will need to do some testing before I remove it. -- Sincerely, Demi Marie Obenour (she/her/hers)
Patches 1 through 4 clean up directory creation in the boot scripts, and patch 5 actually adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a VM, as Spectrum OS has no sound on the host yet. --- Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. Demi Marie Obenour (5): host/rootfs: Create /run/user and /run/wait via run-image img/app: Create /run/user and /run/wait via run-image img/app: tell mount(8) to create directories img/app: Create needed directories in early boot img/app: Run PipeWire and WirePlumber in the VMs host/rootfs/Makefile | 2 + host/rootfs/etc/mdev/listen | 1 - host/rootfs/etc/mdev/wait | 1 - host/rootfs/etc/s6-rc/weston/run | 1 - img/app/Makefile | 22 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/mdev/listen | 1 - img/app/etc/mdev/wait | 1 - img/app/etc/pipewire/pipewire.conf | 199 ++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 11 +- .../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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 11 - .../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 ++++ 25 files changed, 307 insertions(+), 22 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 base-commit: f2f6d7d2a36fd37910335654b8fc69fb01934cb3 -- Sincerely, Demi Marie Obenour (she/her/hers)
This allows code to just assume that they exist, avoiding unneeded calls to mkdir. It also ensures that the directories are created before anything that relies on them runs. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- host/rootfs/Makefile | 2 ++ host/rootfs/etc/mdev/listen | 1 - host/rootfs/etc/mdev/wait | 1 - host/rootfs/etc/s6-rc/weston/run | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/host/rootfs/Makefile b/host/rootfs/Makefile index 08e42032da519b6ca73770b466d67776caa823e1..f677fe580f2e2be58113457e63468d97f49a49f6 100644 --- a/host/rootfs/Makefile +++ b/host/rootfs/Makefile @@ -90,8 +90,10 @@ DIRS = \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/instances \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/data \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/env \ + etc/s6-linux-init/run-image/user \ etc/s6-linux-init/run-image/vm/by-id \ etc/s6-linux-init/run-image/vm/by-name \ + etc/s6-linux-init/run-image/wait \ ext \ run \ proc \ diff --git a/host/rootfs/etc/mdev/listen b/host/rootfs/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/host/rootfs/etc/mdev/listen +++ b/host/rootfs/etc/mdev/listen @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/mdev/wait b/host/rootfs/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/host/rootfs/etc/mdev/wait +++ b/host/rootfs/etc/mdev/wait @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/s6-rc/weston/run b/host/rootfs/etc/s6-rc/weston/run index 2512c011575591eefb110d6e3586517f28ba7064..7dca0dab095569c9e7d49df9d245533a7265283e 100644 --- a/host/rootfs/etc/s6-rc/weston/run +++ b/host/rootfs/etc/s6-rc/weston/run @@ -4,7 +4,6 @@ unexport WAYLAND_DISPLAY -foreground { mkdir /run/user } foreground { umask 077 mkdir /run/user/0 base-commit: f2f6d7d2a36fd37910335654b8fc69fb01934cb3 -- Sincerely, Demi Marie Obenour (she/her/hers)
This patch has been committed as b3cb2511d3612a1d58246fabdc6c4fbb21886c4c, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=b3cb2511d3612a1d58246fabdc6c.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
This allows code to just assume that they exist, avoiding unneeded calls to mkdir. It also ensures that the directories are created before anything that relies on them runs. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 4 +++- img/app/etc/mdev/listen | 1 - img/app/etc/mdev/wait | 1 - img/app/etc/s6-rc/wayland-proxy-virtwl/run | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/img/app/Makefile b/img/app/Makefile index 80ef37ddf0c44636813725bd499e4ea5fad03c06..4b4d64f81d99a01eebe777f3737fef813ebb6d3f 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -55,7 +55,9 @@ VM_FILES = \ etc/s6-linux-init/scripts/rc.shutdown.final \ etc/xdg/xdg-desktop-portal/portals.conf VM_DIRS = dev run proc sys tmp \ - etc/s6-linux-init/run-image/service + etc/s6-linux-init/run-image/service \ + etc/s6-linux-init/run-image/user \ + etc/s6-linux-init/run-image/wait VM_FIFOS = etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/fifo # These are separate because they need to be included, but putting diff --git a/img/app/etc/mdev/listen b/img/app/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/img/app/etc/mdev/listen +++ b/img/app/etc/mdev/listen @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/img/app/etc/mdev/wait b/img/app/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/img/app/etc/mdev/wait +++ b/img/app/etc/mdev/wait @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 7b8034368f547cfaf83a81a3b5d73ab864edafff..0715d912953c8a1d326059dfd37c29799fcbb053 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -7,7 +7,6 @@ # SPDX-FileCopyrightText: 2022 Unikie foreground { mkdir /tmp/.X11-unix } -foreground { mkdir /run/user } foreground { umask 077 mkdir /run/user/0 -- Sincerely, Demi Marie Obenour (she/her/hers)
mount(8) will create directories as needed if --mkdir is passed. Use this instead of a separate call to mkdir(1). Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-linux-init/scripts/rc.init | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index b46afb73b8e4b56e0d3a8e1da5c05b14812bce36..c5a59245ff3761e94acb974edde967806fb3b234 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -4,8 +4,7 @@ if { s6-rc-init -c /etc/s6-rc /run/service } -if { mkdir -p /dev/pts /dev/shm } if { modprobe overlay } -if { mount -a } +if { mount -a --mkdir } s6-rc change ok-all -- Sincerely, Demi Marie Obenour (she/her/hers)
This patch has been committed as 560fd878ba1bbd8df0fe28488e72948f28940948, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=560fd878ba1bbd8df0fe28488e72.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
Demi Marie Obenour <demiobenour@gmail.com> writes:
mount(8) will create directories as needed if --mkdir is passed. Use this instead of a separate call to mkdir(1).
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/etc/s6-linux-init/scripts/rc.init | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
(I checked and the same change can't be applied to vm/sys/net, because it's still using Busybox mount. Doesn't seem worth changing just for this, and I suspect we'll unify it with img/app at some point anyway.)
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages: 1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them. 2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so. Also, it creates the various directories used by X11 with restrictive permissions to prevent untrusted code from writing to them, and sets up /run/user/0 to provide $XDG_RUNTIME_DIR. The copyright notice for directory creation is not kept because making four directories with well-known names and permissions is not copyrightable. --- img/app/etc/s6-linux-init/scripts/rc.init | 8 ++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..6f2db32935332793faf47e3c68e42b0afd537a2d 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -7,4 +7,12 @@ if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# /tmp/.*-unix are used by X11 and exist on my machine with 1777 permissions. +# Use mode 0755 because no other user needs access to them. +# Also, I have seen some software use /tmp/user, so create it as well. +if { mkdir -m 0755 /tmp/user /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix } + +# Create $XDG_RUNTIME_DIR +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -1,16 +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 { - umask 077 - mkdir /run/user/0 -} s6-ipcserver-socketbinder -B /run/user/0/wayland-0 fdmove -c 3 0 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages:
1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them.
2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so.
Also, it creates the various directories used by X11 with restrictive permissions to prevent untrusted code from writing to them, and sets up /run/user/0 to provide $XDG_RUNTIME_DIR.
The copyright notice for directory creation is not kept because making four directories with well-known names and permissions is not copyrightable.
Missing S-o-b.
--- img/app/etc/s6-linux-init/scripts/rc.init | 8 ++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..6f2db32935332793faf47e3c68e42b0afd537a2d 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -7,4 +7,12 @@ if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir }
+# /tmp/.*-unix are used by X11 and exist on my machine with 1777 permissions. +# Use mode 0755 because no other user needs access to them.
0755 gives read access to other things — that what we want?
+# Also, I have seen some software use /tmp/user, so create it as well. +if { mkdir -m 0755 /tmp/user /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix }
In general I'd prefer to avoid having anything in the VMs where we don't totally understand what it's for. If we want to create these anyway just to make sure something evil doesn't create them with the wrong owner/permissions before we can, rather than because we know they do something useful that we want, maybe we should create them 0000? But given that this is the guest, I'm not sure that's necessary…
On 7/26/25 06:24, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages:
1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them.
2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so.
Also, it creates the various directories used by X11 with restrictive permissions to prevent untrusted code from writing to them, and sets up /run/user/0 to provide $XDG_RUNTIME_DIR.
The copyright notice for directory creation is not kept because making four directories with well-known names and permissions is not copyrightable.
Missing S-o-b.
Will fix in v7.
--- img/app/etc/s6-linux-init/scripts/rc.init | 8 ++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..6f2db32935332793faf47e3c68e42b0afd537a2d 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -7,4 +7,12 @@ if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir }
+# /tmp/.*-unix are used by X11 and exist on my machine with 1777 permissions. +# Use mode 0755 because no other user needs access to them.
0755 gives read access to other things — that what we want?
Nope. Only stuff that needs access should have it.
+# Also, I have seen some software use /tmp/user, so create it as well. +if { mkdir -m 0755 /tmp/user /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix /tmp/.font-unix }
In general I'd prefer to avoid having anything in the VMs where we don't totally understand what it's for. If we want to create these anyway just to make sure something evil doesn't create them with the wrong owner/permissions before we can, rather than because we know they do something useful that we want, maybe we should create them 0000? But given that this is the guest, I'm not sure that's necessary…
- /tmp/.X11-unix is the X server. - /tmp/.ICE-unix is for Inter-Client Exchange, which is still used. At a minimum, I have seen error messages referring to it. - /tmp/.font-unix is for the obsolete X Font Server. - /tmp/.XIM-unix is presumably for X11 input methods, which are not currently supported. At some point they might need to be supported. -- Sincerely, Demi Marie Obenour (she/her/hers)
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 <demiobenour@gmail.com> --- 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 + 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 \ @@ -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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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. +# +# 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)
Demi Marie Obenour <demiobenour@gmail.com> 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 <demiobenour@gmail.com> --- 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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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)
Demi Marie Obenour <demiobenour@gmail.com> writes:
Patches 1 through 4 clean up directory creation in the boot scripts, and patch 5 actually adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a VM, as Spectrum OS has no sound on the host yet. --- Changes since v5:
- Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes.
Demi Marie Obenour (5): host/rootfs: Create /run/user and /run/wait via run-image img/app: Create /run/user and /run/wait via run-image
FTR: Applied both of these, but squashed them into a single commit.
img/app: tell mount(8) to create directories img/app: Create needed directories in early boot img/app: Run PipeWire and WirePlumber in the VMs
Patch 1 creates directories that PipeWire, WirePlumber, and applications need early in boot to avoid ordering problems. Patch 2 adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a VM, as Spectrum OS has no sound on the host yet. --- Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. Demi Marie Obenour (2): img/app: Create needed directories in early boot img/app: Run PipeWire and WirePlumber in the VMs img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 199 ++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 13 ++ .../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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 - .../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 | 45 ++++ 19 files changed, 311 insertions(+), 12 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 base-commit: 560fd878ba1bbd8df0fe28488e72948f28940948 -- Sincerely, Demi Marie Obenour (she/her/hers)
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages: 1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them. 2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so. The following directories are created under /tmp: - /tmp/.font-unix (used by obsolete X Font Server) is created with mode 0000. - The directories used by X11 are created with 1700 permissions: - /tmp/.X11-unix (X server) - /tmp/.ICE-unix (Inter-Client Exchange) - /tmp/.XIM-unix (X Input Methods) - $XDG_RUNTIME_DIR (/run/user/0) is created with 0700 permissions, as expected by the XDG specification. The copyright notice for directory creation is not kept because making one directories with well-known names and permissions is not copyrightable and the code has been rewritten. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6: - Add missing S-o-b - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. Changes since v5: - Remove "directories" service in favor of creating the directories from rc.init. --- img/app/etc/s6-linux-init/scripts/rc.init | 13 +++++++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..7744286d0282bb8e0cc40973c6a6eae4c9401630 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,23 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# X Font Server is obsolete +if { mkdir -m 0000 /tmp/.font-unix } + +# /tmp/.X11-unix: X11 server +# /tmp/.ICE-unix: X11 Inter-Client Exchange +# /tmp/.XIM-unix: X11 input methods +# Some documentation states sticky bit is required. +if { mkdir -m 1700 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix } + +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -1,16 +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 { - umask 077 - mkdir /run/user/0 -} s6-ipcserver-socketbinder -B /run/user/0/wayland-0 fdmove -c 3 0 base-commit: 560fd878ba1bbd8df0fe28488e72948f28940948 -- Sincerely, Demi Marie Obenour (she/her/hers)
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. If WirePlumber is allowed to enable monitor nodes for the ALSA output device, WirePlumber prefers the monitor over the ALSA input device [1]. This breaks audio recording. To work around this, set node.features.audio.monitor-ports to false. Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up. [1]: https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829 Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6: - Fix spelling errors in commit message. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. The copyright notice on the WirePlumber drop-in was kept because it is based on fragments in the upstream config file and because when in doubt, keeping the copyright notice is always safer. Changes since v5: - Remove "directories" service in favor of creating the directories from rc.init. - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- 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 | 45 ++++ 17 files changed, 298 insertions(+), 2 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..c2186c9eba52207dfaa94f9ac8364a7dba844c34 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + 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 $@) @@ -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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..75672bc4e2acc90dfc481905e6ce71615977dc0d --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 + # Disable monitor ports so WirePlumber cannot select them: + # https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829 + node.features.audio.monitor-ports = false +} -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/28/25 02:03, Demi Marie Obenour wrote:
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.
If WirePlumber is allowed to enable monitor nodes for the ALSA output device, WirePlumber prefers the monitor over the ALSA input device [1]. This breaks audio recording. To work around this, set node.features.audio.monitor-ports to false.
Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up.
[1]: https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6:
- Fix spelling errors in commit message. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead.
The copyright notice on the WirePlumber drop-in was kept because it is based on fragments in the upstream config file and because when in doubt, keeping the copyright notice is always safer.
Changes since v5:
- Remove "directories" service in favor of creating the directories from rc.init. - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- 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 | 45 ++++ 17 files changed, 298 insertions(+), 2 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..c2186c9eba52207dfaa94f9ac8364a7dba844c34 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + 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 $@) @@ -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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..75672bc4e2acc90dfc481905e6ce71615977dc0d --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 + # Disable monitor ports so WirePlumber cannot select them: + # https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829 + node.features.audio.monitor-ports = false +}
Patch v7 1/2 is fine, but this patch (and all of the previous PipeWire patches) have a bug: they do not set the priorities of the input and output device nodes. The result is that it is unspecified which one is selected as the graph driver by PipeWire, and it is also unspecified whether WirePlumber links an application to the source node or the monitor of the sink node. I'll send a v8 with a fixed pipewire.conf. Thanks to George Kiagiadakis for pointing out this problem. -- Sincerely, Demi Marie Obenour (she/her/hers)
Patch 1 creates directories that PipeWire, WirePlumber, and applications need early in boot to avoid ordering problems. Patch 2 adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a VM, as Spectrum OS has no sound on the host yet. --- Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. Demi Marie Obenour (2): img/app: Create needed directories in early boot img/app: Run PipeWire and WirePlumber in the VMs img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 218 ++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 13 ++ .../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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 - .../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 | 42 ++++ 19 files changed, 327 insertions(+), 12 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 base-commit: 560fd878ba1bbd8df0fe28488e72948f28940948 -- Sincerely, Demi Marie Obenour (she/her/hers)
Patch 1 creates directories that PipeWire, WirePlumber, and applications need early in boot to avoid ordering problems. Patch 2 adds working (and tested) support for audio in the VM via PipeWire. This only works when running in a VM, as Spectrum OS has no sound on the host yet. --- Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments. Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. Demi Marie Obenour (2): img/app: Create needed directories in early boot img/app: Run PipeWire and WirePlumber in the VMs img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 229 ++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 13 + .../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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 - .../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 | 42 ++++ 19 files changed, 338 insertions(+), 12 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 base-commit: 560fd878ba1bbd8df0fe28488e72948f28940948 -- Sincerely, Demi Marie Obenour (she/her/hers)
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages: 1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them. 2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so. The following directories are created under /tmp: - /tmp/.font-unix (used by obsolete X Font Server) is created with mode 0000. - The directories used by X11 are created with 1700 permissions: - /tmp/.X11-unix (X server) - /tmp/.ICE-unix (Inter-Client Exchange) - /tmp/.XIM-unix (X Input Methods) - $XDG_RUNTIME_DIR (/run/user/0) is created with 0700 permissions, as expected by the XDG specification. The copyright notice for directory creation is not kept because making one directories with well-known names and permissions is not copyrightable and the code has been rewritten. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6: - Add missing S-o-b - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. Changes since v5: - Remove "directories" service in favor of creating the directories from rc.init. --- img/app/etc/s6-linux-init/scripts/rc.init | 13 +++++++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..7744286d0282bb8e0cc40973c6a6eae4c9401630 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,23 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# X Font Server is obsolete +if { mkdir -m 0000 /tmp/.font-unix } + +# /tmp/.X11-unix: X11 server +# /tmp/.ICE-unix: X11 Inter-Client Exchange +# /tmp/.XIM-unix: X11 input methods +# Some documentation states sticky bit is required. +if { mkdir -m 1700 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix } + +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -1,16 +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 { - umask 077 - mkdir /run/user/0 -} s6-ipcserver-socketbinder -B /run/user/0/wayland-0 fdmove -c 3 0 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..7744286d0282bb8e0cc40973c6a6eae4c9401630 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,23 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
if { s6-rc-init -c /etc/s6-rc /run/service }
if { modprobe overlay } if { mount -a --mkdir }
+# X Font Server is obsolete +if { mkdir -m 0000 /tmp/.font-unix } + +# /tmp/.X11-unix: X11 server +# /tmp/.ICE-unix: X11 Inter-Client Exchange +# /tmp/.XIM-unix: X11 input methods +# Some documentation states sticky bit is required.
Might be nice to mention /which/ documentation?
+if { mkdir -m 1700 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix }
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. Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments. Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 229 ++++++++++++++++++ .../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 | 42 ++++ 17 files changed, 325 insertions(+), 2 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..c2186c9eba52207dfaa94f9ac8364a7dba844c34 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + 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 $@) @@ -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..0dceb81ed46d3c264c6b12d9a1141e1b1b2135b7 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,229 @@ +# 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. + # + # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # The explanation for why it is more important to avoid + # capture xruns than playback xruns comes from past discussions + # that I (Demi Marie Obenour) had with Wim Taymans. + { 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 + priority.driver = 1000 + priority.session = 1000 + } + } + + { 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 + priority.driver = 2000 + priority.session = 2000 + } + } +] + +# 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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +} -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> 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.
Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments.
Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber.
Changes since v6: - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead.
Changes since v5:
- Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 229 ++++++++++++++++++ .../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 | 42 ++++ 17 files changed, 325 insertions(+), 2 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
We have app depending on wireplumber, but no notification-fd for wireplumber. What's actually required here? Does Wireplumber actually need to be running (for whatever definition thereof) by the time an application starts, or does it just need to start at some reasonable point?
diff --git a/img/app/Makefile b/img/app/Makefile index 4b4d64f81d99a01eebe777f3737fef813ebb6d3f..c2186c9eba52207dfaa94f9ac8364a7dba844c34 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + 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 $@) @@ -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..0dceb81ed46d3c264c6b12d9a1141e1b1b2135b7 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,229 @@ +# 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. + # + # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # The explanation for why it is more important to avoid + # capture xruns than playback xruns comes from past discussions + # that I (Demi Marie Obenour) had with Wim Taymans.
Just checking: they're okay with being credited, right? (Important for me to be confident before mentioning anybody in an immutable log.)
+ { 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 + priority.driver = 1000 + priority.session = 1000 + } + } + + { 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 + priority.driver = 2000 + priority.session = 2000 + } + } +] + +# 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 <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..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 <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 + +# Wait for sound devices to be available +if { /etc/mdev/wait controlC0 } + +# Notify readiness. +if { fdmove 1 5 echo } +fdclose 5
We could move this earlier, right? If things connect before the sound devices are available, PipeWire won't have been started yet so the connection will just not be accepted until they are.
+ +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/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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# Copyright © 2019-2021 Collabora Ltd.
Once again I'm surprised any of this is © Collabora…
+# +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +}
On 7/29/25 09:08, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> 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.
Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments.
Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber.
Changes since v6: - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead.
Changes since v5:
- Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 229 ++++++++++++++++++ .../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 | 42 ++++ 17 files changed, 325 insertions(+), 2 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
We have app depending on wireplumber, but no notification-fd for wireplumber. What's actually required here? Does Wireplumber actually need to be running (for whatever definition thereof) by the time an application starts, or does it just need to start at some reasonable point?
The latter. In systemd terms, this is WantedBy=. I will fix this by having ok-all depend on WirePlumber, while the app no longer does.
+ # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # The explanation for why it is more important to avoid + # capture xruns than playback xruns comes from past discussions + # that I (Demi Marie Obenour) had with Wim Taymans.
Just checking: they're okay with being credited, right? (Important for me to be confident before mentioning anybody in an immutable log.)
That's actually not something I had checked. I asked both if they are okay with this. Should I sent V10 without these, or should I wait for a response from them?
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
This dependency wasn't needed and wasn't included in the VMs. v10 will leave it out.
--- /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 <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 + +# Wait for sound devices to be available +if { /etc/mdev/wait controlC0 } + +# Notify readiness. +if { fdmove 1 5 echo } +fdclose 5
We could move this earlier, right? If things connect before the sound devices are available, PipeWire won't have been started yet so the connection will just not be accepted until they are.
That is indeed correct. Will change in v10.
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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# Copyright © 2019-2021 Collabora Ltd.
Once again I'm surprised any of this is © Collabora…
It's out of an abundance of caution. All of this is taken from the wireplumber.conf files in Arch and Nixpkgs, which I believe are just the upstream ones. I suspect you are correct, but it's much better to keep a copyright notice that is not needed than to remove one that actually *is* needed.
+# +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +}
-- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
On 7/29/25 09:08, Alyssa Ross wrote:
Demi Marie Obenour <demiobenour@gmail.com> writes:
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
We have app depending on wireplumber, but no notification-fd for wireplumber. What's actually required here? Does Wireplumber actually need to be running (for whatever definition thereof) by the time an application starts, or does it just need to start at some reasonable point?
The latter. In systemd terms, this is WantedBy=. I will fix this by having ok-all depend on WirePlumber, while the app no longer does.
Yep, that's the right fix.
+ # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # The explanation for why it is more important to avoid + # capture xruns than playback xruns comes from past discussions + # that I (Demi Marie Obenour) had with Wim Taymans.
Just checking: they're okay with being credited, right? (Important for me to be confident before mentioning anybody in an immutable log.)
That's actually not something I had checked. I asked both if they are okay with this. Should I sent V10 without these, or should I wait for a response from them?
Send a new version without it, and then we can add it in later if you get a response. If the discussion was public, an alternative would be to just link to the discussion.
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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# Copyright © 2019-2021 Collabora Ltd.
Once again I'm surprised any of this is © Collabora…
It's out of an abundance of caution. All of this is taken from the wireplumber.conf files in Arch and Nixpkgs, which I believe are just the upstream ones. I suspect you are correct, but it's much better to keep a copyright notice that is not needed than to remove one that actually *is* needed.
Well, keeping it can also cause problems in the event of relicensing, etc. In a marginal case, I'd keep the notice too, but I just think there's no way there's anything copyrightable remaining in this case.
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. Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up. PipeWire and WirePlumber require that "$XDG_RUNTIME_DIR" exist, so create it in rc.init. Having a separate service to create a single directory is not worthwhile. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v9: - Consider PipeWire to be ready as soon as its sockets are listening, rather than waiting for the sound device to be available. - Pull WirePlumber in via ok-all instead of app. This avoids blocking app startup until WirePlumber is started. - Have the app depend on PipeWire now that it no longer depends on WirePlumber. - Delete an unused file. It was meant to indicate that PipeWire depends on dbus, but it actually doesn't, and the file was not included in the VM image anyway. - Do not create any additional directories beyond "$XDG_RUNTIME_DIR" in early boot. This should be done in a separate patch series. - Avoid mentioning old stuff in a commit message. Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments. Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 227 +++++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 4 + img/app/etc/s6-rc/app/dependencies.d/pipewire | 0 img/app/etc/s6-rc/ok-all/contents | 1 + img/app/etc/s6-rc/pipewire/notification-fd | 1 + img/app/etc/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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 4 - img/app/etc/s6-rc/wireplumber/dependencies.d/dbus | 0 .../etc/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 | 42 ++++ 19 files changed, 328 insertions(+), 6 deletions(-) diff --git a/img/app/Makefile b/img/app/Makefile index 4b4d64f81d99a01eebe777f3737fef813ebb6d3f..c347300a326c1429a731e1173fe7599534c43014 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + VM_DIRS = dev run proc sys tmp \ etc/s6-linux-init/run-image/service \ etc/s6-linux-init/run-image/user \ @@ -84,6 +87,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/pipewire \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ etc/s6-rc/app/run \ etc/s6-rc/app/type \ @@ -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 $@) @@ -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..5f1e34145e9dcb1497f7aa7bb0461f0f13931b1a --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,227 @@ +# 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. + # + # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # See <https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829#note_3027666>. + { 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 + priority.driver = 1000 + priority.session = 1000 + } + } + + { 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 + priority.driver = 2000 + priority.session = 2000 + } + } +] + +# 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-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..049603696ab94dee38251bf472814e02cc268ec0 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,14 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/app/dependencies.d/pipewire b/img/app/etc/s6-rc/app/dependencies.d/pipewire new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/ok-all/contents b/img/app/etc/s6-rc/ok-all/contents index 92f3ef1632fee9d5909c95d031f9646c8aafb7de..6788e54384f065890d00d77378ea5c45ec89f61c 100644 --- a/img/app/etc/s6-rc/ok-all/contents +++ b/img/app/etc/s6-rc/ok-all/contents @@ -2,4 +2,5 @@ # SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> # mdevd-coldplug +wireplumber app 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..ab469019ab447e977fd5924d6d12024a11dfe5a6 --- /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 <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 + +# Wait for sound devices to be available +if { /etc/mdev/wait controlC0 } + +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/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..df9173a1de36b92648f88947b63e32b71b85118a 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -7,10 +7,6 @@ # SPDX-FileCopyrightText: 2022 Unikie foreground { mkdir /tmp/.X11-unix } -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/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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +} --- base-commit: 560fd878ba1bbd8df0fe28488e72948f28940948 change-id: 20250729-pipewire-v10-bb873ebb3560 -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> 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.
Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up.
PipeWire and WirePlumber require that "$XDG_RUNTIME_DIR" exist, so create it in rc.init. Having a separate service to create a single directory is not worthwhile.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> ---
This looks good to me now, with the exception of a couple of copyright fixes.
diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..5f1e34145e9dcb1497f7aa7bb0461f0f13931b1a --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,227 @@ +# SPDX-License-Identifier: MIT + +# Copyright © 2018 Wim Taymans +# Copyright © 2025 Demi Marie Obenour
We should keep our own copyright notices in the SPDX-FileCopyrightText format. (It's fine to leave third party ones as they are though.)
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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +}
There is no way there is anything left in this file that is non-trivial creative expression owned by Collabora. It should be licensed CC0 like other configuration files in Spectrum, and the Collabora copyright should be dropped.
Demi Marie Obenour <demiobenour@gmail.com> writes:
@@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \
I've pulled this sorting fix out and committed in separately. :)
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. Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up. PipeWire and WirePlumber require that "$XDG_RUNTIME_DIR" exist, so create it in rc.init. Having a separate service to create a single directory is not worthwhile. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes in v11: - Fix copyright notices - Give credit to George Kiagiadakis and Wim Taymans - Link to v10: https://lore.kernel.org/r/20250730-pipewire-v10-v10-1-0f7a7ee80943@gmail.com Changes since v9: - Consider PipeWire to be ready as soon as its sockets are listening, rather than waiting for the sound device to be available. - Pull WirePlumber in via ok-all instead of app. This avoids blocking app startup until WirePlumber is started. - Have the app depend on PipeWire now that it no longer depends on WirePlumber. - Delete an unused file. It was meant to indicate that PipeWire depends on dbus, but it actually doesn't, and the file was not included in the VM image anyway. - Do not create any additional directories beyond "$XDG_RUNTIME_DIR" in early boot. This should be done in a separate patch series. - Avoid mentioning old stuff in a commit message. Changes since v8: - Add longer explanation for the priority.driver and priority.session values. - Give credit to George Kiagiadakis. - Fix spelling errors in comments. Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Move changes to startup scripts into a single patch. - Remove "directories" service in favor of creating the directories from rc.init. - Use VM_DIRS to create /run/user and /run/wait. - Create /run/user and /run/wait on the host as well, using the same mechanism as for the VM (though with a different variable name in the makefile). - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 14 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 230 +++++++++++++++++++++ img/app/etc/s6-linux-init/scripts/rc.init | 4 + img/app/etc/s6-rc/app/dependencies.d/pipewire | 0 img/app/etc/s6-rc/ok-all/contents | 1 + img/app/etc/s6-rc/pipewire/notification-fd | 1 + img/app/etc/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 + img/app/etc/s6-rc/wayland-proxy-virtwl/run | 4 - img/app/etc/s6-rc/wireplumber/dependencies.d/dbus | 0 .../etc/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 | 20 ++ 19 files changed, 308 insertions(+), 5 deletions(-) diff --git a/img/app/Makefile b/img/app/Makefile index 284266155f3da9dd36a2cd5d97f01f87fcef5175..c347300a326c1429a731e1173fe7599534c43014 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -53,7 +54,9 @@ 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/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + VM_DIRS = dev run proc sys tmp \ etc/s6-linux-init/run-image/service \ etc/s6-linux-init/run-image/user \ @@ -84,6 +87,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/pipewire \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ etc/s6-rc/app/run \ etc/s6-rc/app/type \ @@ -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 $@) @@ -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..6e946b560aa2ff762d42c3e0e3818916500d2883 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,230 @@ +# 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. + # + # PipeWire chooses the node with the highest priority.driver value as + # graph driver, which is the node that decides when the processing graph + # is going to run. If both the capture node and playback node are in + # the same graph, the capture node should be chosen as the driver. This + # is because the driver gets to choose the rate of the graph and so is + # much less likely to xrun. Since capture xruns result in corrupted + # audio recordings, while playback xruns just result in a glitch, it + # is more important to avoid capture xruns. + # + # When there are multiple sources or sinks that could be used, + # WirePlumber links application nodes to the one with the highest + # priority.session value. In the configuration created here, + # there are two valid audio sources: the virtio sound card's + # capture stream and the monitor of its playback stream. The + # capture stream is the correct choice, so its priority.session + # should be higher. + # + # The recommendation to give the capture device higher values + # for priority.driver and priority.session comes from George + # Kiagiadakis of Collabora, who also provided the values + # used (2000 and 1000) and why they must be different. + # See <https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/829#note_3027666>. + # The explanation for why it is more important to avoid + # capture xruns than playback xruns comes from past discussions + # that I (Demi Marie Obenour) had with Wim Taymans. + { 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 + priority.driver = 1000 + priority.session = 1000 + } + } + + { 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 + priority.driver = 2000 + priority.session = 2000 + } + } +] + +# 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-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..049603696ab94dee38251bf472814e02cc268ec0 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,14 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/app/dependencies.d/pipewire b/img/app/etc/s6-rc/app/dependencies.d/pipewire new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/ok-all/contents b/img/app/etc/s6-rc/ok-all/contents index 92f3ef1632fee9d5909c95d031f9646c8aafb7de..6788e54384f065890d00d77378ea5c45ec89f61c 100644 --- a/img/app/etc/s6-rc/ok-all/contents +++ b/img/app/etc/s6-rc/ok-all/contents @@ -2,4 +2,5 @@ # SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> # mdevd-coldplug +wireplumber app 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..ab469019ab447e977fd5924d6d12024a11dfe5a6 --- /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 <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 + +# Wait for sound devices to be available +if { /etc/mdev/wait controlC0 } + +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/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..df9173a1de36b92648f88947b63e32b71b85118a 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -7,10 +7,6 @@ # SPDX-FileCopyrightText: 2022 Unikie foreground { mkdir /tmp/.X11-unix } -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/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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..0a4165c33cea4d33957b76d5de6a14623c75f66c --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +} --- base-commit: 0a19c316f32aa076bebf172d72da3cb3ea3eeabf change-id: 20250729-pipewire-v10-bb873ebb3560 -- Sincerely, Demi Marie Obenour (she/her/hers)
This patch has been committed as 6c945fc8b81ab15866567816007dee3e5793cf4d, which can be viewed online at https://spectrum-os.org/git/spectrum/commit/?id=6c945fc8b81ab15866567816007d.... This is an automated message. Send comments/questions/requests to: Alyssa Ross <hi@alyssa.is>
Demi Marie Obenour <demiobenour@gmail.com> writes:
Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up.
(I fixed the "virtio-user" typo when committing.)
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages: 1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them. 2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so. The following directories are created under /tmp: - /tmp/.font-unix (used by obsolete X Font Server) is created with mode 0000. - The directories used by X11 are created with 1700 permissions: - /tmp/.X11-unix (X server) - /tmp/.ICE-unix (Inter-Client Exchange) - /tmp/.XIM-unix (X Input Methods) - $XDG_RUNTIME_DIR (/run/user/0) is created with 0700 permissions, as expected by the XDG specification. The copyright notice for directory creation is not kept because making one directories with well-known names and permissions is not copyrightable and the code has been rewritten. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6: - Add missing S-o-b - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. Changes since v5: - Remove "directories" service in favor of creating the directories from rc.init. --- img/app/etc/s6-linux-init/scripts/rc.init | 13 +++++++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..7744286d0282bb8e0cc40973c6a6eae4c9401630 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,23 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> if { s6-rc-init -c /etc/s6-rc /run/service } if { modprobe overlay } if { mount -a --mkdir } +# X Font Server is obsolete +if { mkdir -m 0000 /tmp/.font-unix } + +# /tmp/.X11-unix: X11 server +# /tmp/.ICE-unix: X11 Inter-Client Exchange +# /tmp/.XIM-unix: X11 input methods +# Some documentation states sticky bit is required. +if { mkdir -m 1700 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix } + +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -1,16 +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 { - umask 077 - mkdir /run/user/0 -} s6-ipcserver-socketbinder -B /run/user/0/wayland-0 fdmove -c 3 0 -- Sincerely, Demi Marie Obenour (she/her/hers)
On 7/28/25 19:13, Demi Marie Obenour wrote:
This moves various calls to mkdir(1) to very early boot, before any services are running. This has two advantages:
1. These directories are guaranteed to exist. Code can just assume that they are there without checking for them.
2. Malicious code running as an unprivileged user cannot create directories under /tmp before legitimate code has done so.
The following directories are created under /tmp:
- /tmp/.font-unix (used by obsolete X Font Server) is created with mode 0000. - The directories used by X11 are created with 1700 permissions: - /tmp/.X11-unix (X server) - /tmp/.ICE-unix (Inter-Client Exchange) - /tmp/.XIM-unix (X Input Methods) - $XDG_RUNTIME_DIR (/run/user/0) is created with 0700 permissions, as expected by the XDG specification.
The copyright notice for directory creation is not kept because making one directories with well-known names and permissions is not copyrightable and the code has been rewritten.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v6:
- Add missing S-o-b - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages.
Changes since v5:
- Remove "directories" service in favor of creating the directories from rc.init. --- img/app/etc/s6-linux-init/scripts/rc.init | 13 +++++++++++++ img/app/etc/s6-rc/wayland-proxy-virtwl/run | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/img/app/etc/s6-linux-init/scripts/rc.init b/img/app/etc/s6-linux-init/scripts/rc.init index c5a59245ff3761e94acb974edde967806fb3b234..7744286d0282bb8e0cc40973c6a6eae4c9401630 100755 --- a/img/app/etc/s6-linux-init/scripts/rc.init +++ b/img/app/etc/s6-linux-init/scripts/rc.init @@ -1,10 +1,23 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2022 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
if { s6-rc-init -c /etc/s6-rc /run/service }
if { modprobe overlay } if { mount -a --mkdir }
+# X Font Server is obsolete +if { mkdir -m 0000 /tmp/.font-unix } + +# /tmp/.X11-unix: X11 server +# /tmp/.ICE-unix: X11 Inter-Client Exchange +# /tmp/.XIM-unix: X11 input methods +# Some documentation states sticky bit is required. +if { mkdir -m 1700 /tmp/.X11-unix /tmp/.ICE-unix /tmp/.XIM-unix } + +# /run/user/0: "$XDG_RUNTIME_DIR" +if { mkdir -m 0700 /run/user/0 } + s6-rc change ok-all diff --git a/img/app/etc/s6-rc/wayland-proxy-virtwl/run b/img/app/etc/s6-rc/wayland-proxy-virtwl/run index 0715d912953c8a1d326059dfd37c29799fcbb053..c1e0e088c789ab8c5fde7e50c9f4b856fff0e477 100755 --- a/img/app/etc/s6-rc/wayland-proxy-virtwl/run +++ b/img/app/etc/s6-rc/wayland-proxy-virtwl/run @@ -1,16 +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 { - umask 077 - mkdir /run/user/0 -}
s6-ipcserver-socketbinder -B /run/user/0/wayland-0 fdmove -c 3 0
Whoops, forgot to run `git config --global format.thread shallow` before sending. -- Sincerely, Demi Marie Obenour (she/her/hers)
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. Only run-qemu has had a virtio-sound device added, as crosvm and Cloud Hypervisor require a virtio-user sound device and that is more complex to set up. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- Changes since v7: - Give the capture node a higher priority.session than the playback node, so WirePlumber links recording applications to the capture node instead of the monitor of the playback node. - Give the capture node a higher priority.driver than the playback node, so PipeWire has the capture node drive the graph. This is better because capture xruns lead to permanently corrupted data, whereas playback xruns do not. - Re-enable monitor node creation in WirePlumber. Changes since v6: - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead. Changes since v5: - Use a drop-in configuration file for WirePlumber instead of overriding wireplumber.conf. This should be more robust to future WirePlumber changes. --- img/app/Makefile | 16 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 218 ++++++++++++++++++ .../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 | 42 ++++ 17 files changed, 314 insertions(+), 2 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..c2186c9eba52207dfaa94f9ac8364a7dba844c34 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -40,6 +40,7 @@ VM_FILES = \ etc/mdev/virtiofs \ etc/mdev/wait \ etc/passwd \ + etc/pipewire/pipewire.conf \ etc/resolv.conf \ etc/s6-linux-init/env/DBUS_SESSION_BUS_ADDRESS \ etc/s6-linux-init/env/DISPLAY \ @@ -47,13 +48,15 @@ VM_FILES = \ etc/s6-linux-init/env/NIX_XDG_DESKTOP_PORTAL_DIR \ etc/s6-linux-init/env/WAYLAND_DISPLAY \ etc/s6-linux-init/env/XDG_RUNTIME_DIR \ + etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/notification-fd \ etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \ - etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf \ etc/xdg/xdg-desktop-portal/portals.conf + 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 $@) @@ -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..10e1ecfd4888811e2b2c54f8c8d6c1f850e7bc63 --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,218 @@ +# 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 + # Use a lower priority than the capture device so PipeWire prefers + # the capture device over the playback device as the graph driver. + priority.driver = 1000 + # Use a lower priority than the capture device so that WirePlumber + # prefers the capture device over the monitor of the playback + # device as the audio source for applications. + priority.session = 1000 + } + } + + { 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 + # Use a higher priority than the playback device so PipeWire prefers + # the capture device over the playback device as the graph driver. + # This prioritizes avoiding capture xruns over avoiding playback xruns. + # Since capture xruns result in corrupted audio recordings, while + # playback xruns just result in a glitch, it is more importanta to + # avoid capture xruns. + priority.driver = 2000 + # Use a higher priority than the capture device so that WirePlumber + # prefers the capture device over the monitor of the playback + # device as the audio source for applications. + priority.session = 2000 + } + } +] + +# 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 <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..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 <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 + +# 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 <demiobenour@gmail.com> 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 <demiobenour@gmail.com> +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 <demiobenour@gmail.com> 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..277e6019c46582afba12af9b1a27bb16ddd9e804 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2019-2021 Collabora Ltd. +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour + +# 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. + +wireplumber.profiles = { + spectrum = { + # Spectrum VMs are essentially embedded systems, in that they are + # not at all general-purpose. + inherits = [ main-embedded ] + # Disable video and Bluetooth + hardware.video-capture = disabled + hardware.bluetooth = disabled + # Media Session is definitely not running + check.no-media-session = disabled + } +} + +wireplumber.settings = { + # Default to 100% sink volume. The host will adjust this as needed. + device.routes.default-sink-volume = 1.0 +} -- Sincerely, Demi Marie Obenour (she/her/hers)
Demi Marie Obenour <demiobenour@gmail.com> writes:
Changes since v6: - Add missing S-o-b to directory creation patch. - Add comments explaining why each directory needs to be created. - Fix spelling errors in commit messages. - Sort lines in Makefile. - Don't disable support.settings as a comment in wireplumber.conf says to not do that. Instead, tell WirePlumber to not create monitor nodes. This is a workaround for WirePlumber bug 829. - Don't remove "-cpu host" from make run-qemu's QEMU command line. This was needed for local testing with KVM disabled but should not have been submitted upstream. Hardware with KVM support should be used instead.
I would actually quite like it to be possible to test in QEMU without KVM, so that it's easy to do development on Qubes OS, for example. I'd take the change as long as everything still works with KVM as well, it just shouldn't be part of an unrelated change about PipeWire!
This allows code to just assume that they exist, avoiding unneeded calls to mkdir. It also ensures that the directories are created before anything that relies on them runs. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- host/rootfs/Makefile | 2 ++ host/rootfs/etc/mdev/listen | 1 - host/rootfs/etc/mdev/wait | 1 - host/rootfs/etc/s6-rc/weston/run | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/host/rootfs/Makefile b/host/rootfs/Makefile index 08e42032da519b6ca73770b466d67776caa823e1..f677fe580f2e2be58113457e63468d97f49a49f6 100644 --- a/host/rootfs/Makefile +++ b/host/rootfs/Makefile @@ -90,8 +90,10 @@ DIRS = \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/instances \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/data \ etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/env \ + etc/s6-linux-init/run-image/user \ etc/s6-linux-init/run-image/vm/by-id \ etc/s6-linux-init/run-image/vm/by-name \ + etc/s6-linux-init/run-image/wait \ ext \ run \ proc \ diff --git a/host/rootfs/etc/mdev/listen b/host/rootfs/etc/mdev/listen index 8c8d86407d1f131cf28f335c8f1028d731916203..ab50ee8c5ed1139d1129bac56afa7263af150745 100755 --- a/host/rootfs/etc/mdev/listen +++ b/host/rootfs/etc/mdev/listen @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/mdev/wait b/host/rootfs/etc/mdev/wait index 625cd46dc06618309ebaa7230f0bed49e248f3a8..6bddb303d2671ce4e5b8581cd81235d7404916e7 100755 --- a/host/rootfs/etc/mdev/wait +++ b/host/rootfs/etc/mdev/wait @@ -4,7 +4,6 @@ foreground { redirfd -w 2 /dev/null - foreground { mkdir /run/wait } mkfifo /run/wait/${1} } diff --git a/host/rootfs/etc/s6-rc/weston/run b/host/rootfs/etc/s6-rc/weston/run index 2512c011575591eefb110d6e3586517f28ba7064..7dca0dab095569c9e7d49df9d245533a7265283e 100644 --- a/host/rootfs/etc/s6-rc/weston/run +++ b/host/rootfs/etc/s6-rc/weston/run @@ -4,7 +4,6 @@ unexport WAYLAND_DISPLAY -foreground { mkdir /run/user } foreground { umask 077 mkdir /run/user/0 base-commit: f2f6d7d2a36fd37910335654b8fc69fb01934cb3 -- Sincerely, Demi Marie Obenour (she/her/hers)
participants (3)
-
Alyssa Ross -
Alyssa Ross -
Demi Marie Obenour