patches and low-level development discussion
 help / color / mirror / code / Atom feed
* [PATCH v3] Run PipeWire and WirePlumber in the VMs
@ 2025-07-11  2:44 Demi Marie Obenour
  2025-07-14 14:54 ` Alyssa Ross
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
  0 siblings, 2 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-11  2:44 UTC (permalink / raw)
  To: Spectrum OS Development

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-11  2:44 [PATCH v3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-14 14:54 ` Alyssa Ross
  2025-07-15 20:22   ` Demi Marie Obenour
                     ` (2 more replies)
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
  1 sibling, 3 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-14 14:54 UTC (permalink / raw)
  To: Demi Marie Obenour; +Cc: Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 26094 bytes --]

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)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-14 14:54 ` Alyssa Ross
@ 2025-07-15 20:22   ` Demi Marie Obenour
  2025-07-16 10:26     ` Alyssa Ross
  2025-07-16 21:16   ` Demi Marie Obenour
  2025-07-17  5:53   ` Demi Marie Obenour
  2 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-15 20:22 UTC (permalink / raw)
  To: Alyssa Ross; +Cc: Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 3423 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-15 20:22   ` Demi Marie Obenour
@ 2025-07-16 10:26     ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-16 10:26 UTC (permalink / raw)
  To: Demi Marie Obenour; +Cc: Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 3655 bytes --]

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-14 14:54 ` Alyssa Ross
  2025-07-15 20:22   ` Demi Marie Obenour
@ 2025-07-16 21:16   ` Demi Marie Obenour
  2025-07-16 21:27     ` Demi Marie Obenour
  2025-07-17  5:53   ` Demi Marie Obenour
  2 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-16 21:16 UTC (permalink / raw)
  To: Alyssa Ross; +Cc: Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 3604 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-16 21:16   ` Demi Marie Obenour
@ 2025-07-16 21:27     ` Demi Marie Obenour
  2025-07-18 12:16       ` Alyssa Ross
  0 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-16 21:27 UTC (permalink / raw)
  To: Alyssa Ross; +Cc: Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 3751 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-14 14:54 ` Alyssa Ross
  2025-07-15 20:22   ` Demi Marie Obenour
  2025-07-16 21:16   ` Demi Marie Obenour
@ 2025-07-17  5:53   ` Demi Marie Obenour
  2025-07-18 10:02     ` Alyssa Ross
  2 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-17  5:53 UTC (permalink / raw)
  To: Alyssa Ross; +Cc: Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 4657 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v4 0/3] Sound support in Spectrum VMs
  2025-07-11  2:44 [PATCH v3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
  2025-07-14 14:54 ` Alyssa Ross
@ 2025-07-18  2:07 ` Demi Marie Obenour
  2025-07-18  2:13   ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
                     ` (3 more replies)
  1 sibling, 4 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-18  2:07 UTC (permalink / raw)
  To: Spectrum OS Development


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)


^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-18  2:13   ` Demi Marie Obenour
  2025-07-18 11:14     ` Alyssa Ross
  2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-18  2:13 UTC (permalink / raw)
  To: Spectrum OS Development

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v4 2/3] Fix permissions on /tmp
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-18  2:13   ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
@ 2025-07-18  2:13   ` Demi Marie Obenour
  2025-07-18 11:51     ` Alyssa Ross
                       ` (2 more replies)
  2025-07-18  2:14   ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
  3 siblings, 3 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-18  2:13 UTC (permalink / raw)
  To: Spectrum OS Development

/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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-18  2:13   ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
  2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
@ 2025-07-18  2:14   ` Demi Marie Obenour
  2025-07-18 11:27     ` Alyssa Ross
  2025-07-19  8:06     ` Alyssa Ross
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
  3 siblings, 2 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-18  2:14 UTC (permalink / raw)
  To: Spectrum OS Development

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-17  5:53   ` Demi Marie Obenour
@ 2025-07-18 10:02     ` Alyssa Ross
  2025-07-18 10:19       ` Alyssa Ross
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 10:02 UTC (permalink / raw)
  To: Demi Marie Obenour; +Cc: Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 5652 bytes --]

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…

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-18 10:02     ` Alyssa Ross
@ 2025-07-18 10:19       ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 10:19 UTC (permalink / raw)
  To: Demi Marie Obenour; +Cc: Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 6286 bytes --]

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…

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes
  2025-07-18  2:13   ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
@ 2025-07-18 11:14     ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 11:14 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1298 bytes --]

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.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-18  2:14   ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-18 11:27     ` Alyssa Ross
  2025-07-18 17:59       ` Demi Marie Obenour
  2025-07-19  8:06     ` Alyssa Ross
  1 sibling, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 11:27 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 3533 bytes --]

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??

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 2/3] Fix permissions on /tmp
  2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
@ 2025-07-18 11:51     ` Alyssa Ross
  2025-07-18 11:51     ` Alyssa Ross
  2025-07-18 11:53     ` Alyssa Ross
  2 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 11:51 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

This patch has been committed as 306350cf1cd85c3f3c890c076e989a2cd5ca6d9e,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=306350cf1cd85c3f3c890c076e989a2cd5ca6d9e.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 2/3] Fix permissions on /tmp
  2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
  2025-07-18 11:51     ` Alyssa Ross
@ 2025-07-18 11:51     ` Alyssa Ross
  2025-07-18 11:53     ` Alyssa Ross
  2 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 11:51 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

This patch has been committed as 65ac62b2b170d9fb1dd47190073a97346467152f,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=65ac62b2b170d9fb1dd47190073a97346467152f.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 2/3] Fix permissions on /tmp
  2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
  2025-07-18 11:51     ` Alyssa Ross
  2025-07-18 11:51     ` Alyssa Ross
@ 2025-07-18 11:53     ` Alyssa Ross
  2 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 11:53 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 511 bytes --]

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.)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v3] Run PipeWire and WirePlumber in the VMs
  2025-07-16 21:27     ` Demi Marie Obenour
@ 2025-07-18 12:16       ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-18 12:16 UTC (permalink / raw)
  To: Demi Marie Obenour; +Cc: Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1225 bytes --]

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.)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-18 11:27     ` Alyssa Ross
@ 2025-07-18 17:59       ` Demi Marie Obenour
  2025-07-19  9:22         ` Alyssa Ross
  0 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-18 17:59 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 5247 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-18  2:14   ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
  2025-07-18 11:27     ` Alyssa Ross
@ 2025-07-19  8:06     ` Alyssa Ross
  2025-07-19 20:03       ` Demi Marie Obenour
  1 sibling, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-19  8:06 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1378 bytes --]

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?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-18 17:59       ` Demi Marie Obenour
@ 2025-07-19  9:22         ` Alyssa Ross
  2025-07-19 20:05           ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-19  9:22 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 4844 bytes --]

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.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-19  8:06     ` Alyssa Ross
@ 2025-07-19 20:03       ` Demi Marie Obenour
  2025-07-19 20:07         ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-19 20:03 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 1721 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-19  9:22         ` Alyssa Ross
@ 2025-07-19 20:05           ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-19 20:05 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 5215 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-19 20:03       ` Demi Marie Obenour
@ 2025-07-19 20:07         ` Demi Marie Obenour
  2025-07-20  7:50           ` Alyssa Ross
  0 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-19 20:07 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 2026 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs
  2025-07-19 20:07         ` Demi Marie Obenour
@ 2025-07-20  7:50           ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-20  7:50 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1199 bytes --]

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!

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v5 0/8] Sound support in Spectrum VMs
  2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
                     ` (2 preceding siblings ...)
  2025-07-18  2:14   ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-20 17:58   ` Demi Marie Obenour
  2025-07-20 18:02     ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
                       ` (9 more replies)
  3 siblings, 10 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 17:58 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp"
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-20 18:02     ` Demi Marie Obenour
  2025-07-21  9:34       ` Alyssa Ross
  2025-07-20 18:03     ` [PATCH v5 2/8] img/app: Use separate service to create directories Demi Marie Obenour
                       ` (8 subsequent siblings)
  9 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:02 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 2/8] img/app: Use separate service to create directories
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-20 18:02     ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
@ 2025-07-20 18:03     ` Demi Marie Obenour
  2025-07-21  9:21       ` Alyssa Ross
  2025-07-20 18:04     ` [PATCH v5 3/8] img/app: Fix permissions of /tmp/.X11-unix Demi Marie Obenour
                       ` (7 subsequent siblings)
  9 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:03 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 3/8] img/app: Fix permissions of /tmp/.X11-unix
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-20 18:02     ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
  2025-07-20 18:03     ` [PATCH v5 2/8] img/app: Use separate service to create directories Demi Marie Obenour
@ 2025-07-20 18:04     ` Demi Marie Obenour
  2025-07-20 18:05     ` [PATCH v5 4/8] img/app: Create other X11 directories Demi Marie Obenour
                       ` (6 subsequent siblings)
  9 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:04 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 4/8] img/app: Create other X11 directories
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (2 preceding siblings ...)
  2025-07-20 18:04     ` [PATCH v5 3/8] img/app: Fix permissions of /tmp/.X11-unix Demi Marie Obenour
@ 2025-07-20 18:05     ` Demi Marie Obenour
  2025-07-21  9:23       ` Alyssa Ross
  2025-07-20 18:06     ` [PATCH v5 5/8] img/app: Be explicit about directory modes Demi Marie Obenour
                       ` (5 subsequent siblings)
  9 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:05 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 5/8] img/app: Be explicit about directory modes
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (3 preceding siblings ...)
  2025-07-20 18:05     ` [PATCH v5 4/8] img/app: Create other X11 directories Demi Marie Obenour
@ 2025-07-20 18:06     ` Demi Marie Obenour
  2025-07-20 18:08     ` [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot Demi Marie Obenour
                       ` (4 subsequent siblings)
  9 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:06 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (4 preceding siblings ...)
  2025-07-20 18:06     ` [PATCH v5 5/8] img/app: Be explicit about directory modes Demi Marie Obenour
@ 2025-07-20 18:08     ` Demi Marie Obenour
  2025-07-21  9:23       ` Alyssa Ross
  2025-07-20 18:10     ` [PATCH v5 7/8] host/rootfs: " Demi Marie Obenour
                       ` (3 subsequent siblings)
  9 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:08 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 7/8] host/rootfs: create /run/user and /run/wait very early in boot
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (5 preceding siblings ...)
  2025-07-20 18:08     ` [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot Demi Marie Obenour
@ 2025-07-20 18:10     ` Demi Marie Obenour
  2025-07-20 18:11     ` [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
                       ` (2 subsequent siblings)
  9 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:10 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (6 preceding siblings ...)
  2025-07-20 18:10     ` [PATCH v5 7/8] host/rootfs: " Demi Marie Obenour
@ 2025-07-20 18:11     ` Demi Marie Obenour
  2025-07-21  9:42       ` Alyssa Ross
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-24 22:23     ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
  9 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-20 18:11 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 2/8] img/app: Use separate service to create directories
  2025-07-20 18:03     ` [PATCH v5 2/8] img/app: Use separate service to create directories Demi Marie Obenour
@ 2025-07-21  9:21       ` Alyssa Ross
  2025-07-22 23:48         ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-21  9:21 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 5796 bytes --]

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)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 4/8] img/app: Create other X11 directories
  2025-07-20 18:05     ` [PATCH v5 4/8] img/app: Create other X11 directories Demi Marie Obenour
@ 2025-07-21  9:23       ` Alyssa Ross
  2025-07-21 19:03         ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-21  9:23 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1043 bytes --]

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)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot
  2025-07-20 18:08     ` [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot Demi Marie Obenour
@ 2025-07-21  9:23       ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-21  9:23 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 2497 bytes --]

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)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp"
  2025-07-20 18:02     ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
@ 2025-07-21  9:34       ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-21  9:34 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development; +Cc: Alyssa Ross

This patch has been committed as 3063693e0bd5ad17b7786f4c86d66b12a5a62a1d,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=3063693e0bd5ad17b7786f4c86d66b12a5a62a1d.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-20 18:11     ` [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-21  9:42       ` Alyssa Ross
  2025-07-21 19:09         ` Demi Marie Obenour
  2025-07-21 19:10         ` Demi Marie Obenour
  0 siblings, 2 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-21  9:42 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 15674 bytes --]

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?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 4/8] img/app: Create other X11 directories
  2025-07-21  9:23       ` Alyssa Ross
@ 2025-07-21 19:03         ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-21 19:03 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 555 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-21  9:42       ` Alyssa Ross
@ 2025-07-21 19:09         ` Demi Marie Obenour
  2025-07-26 10:11           ` Alyssa Ross
  2025-07-21 19:10         ` Demi Marie Obenour
  1 sibling, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-21 19:09 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 17096 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-21  9:42       ` Alyssa Ross
  2025-07-21 19:09         ` Demi Marie Obenour
@ 2025-07-21 19:10         ` Demi Marie Obenour
  1 sibling, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-21 19:10 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 17096 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 2/8] img/app: Use separate service to create directories
  2025-07-21  9:21       ` Alyssa Ross
@ 2025-07-22 23:48         ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-22 23:48 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 5487 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v6 0/5] Sound support in Spectrum VMs
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (7 preceding siblings ...)
  2025-07-20 18:11     ` [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-24 22:15     ` Demi Marie Obenour
  2025-07-24 22:30       ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
                         ` (6 more replies)
  2025-07-24 22:23     ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
  9 siblings, 7 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:15 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image
  2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
                       ` (8 preceding siblings ...)
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-24 22:23     ` Demi Marie Obenour
  9 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:23 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-24 22:30       ` Demi Marie Obenour
  2025-07-26 10:46         ` Alyssa Ross
  2025-07-24 22:32       ` [PATCH v6 2/5] img/app: " Demi Marie Obenour
                         ` (5 subsequent siblings)
  6 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:30 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross


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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v6 2/5] img/app: Create /run/user and /run/wait via run-image
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-24 22:30       ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
@ 2025-07-24 22:32       ` Demi Marie Obenour
  2025-07-24 22:33       ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
                         ` (4 subsequent siblings)
  6 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:32 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v6 3/5] img/app: tell mount(8) to create directories
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
  2025-07-24 22:30       ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
  2025-07-24 22:32       ` [PATCH v6 2/5] img/app: " Demi Marie Obenour
@ 2025-07-24 22:33       ` Demi Marie Obenour
  2025-07-26 11:20         ` Alyssa Ross
  2025-07-26 11:26         ` Alyssa Ross
  2025-07-24 22:35       ` [PATCH v6 4/5] img/app: Create needed directories in early boot Demi Marie Obenour
                         ` (3 subsequent siblings)
  6 siblings, 2 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:33 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v6 4/5] img/app: Create needed directories in early boot
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
                         ` (2 preceding siblings ...)
  2025-07-24 22:33       ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
@ 2025-07-24 22:35       ` Demi Marie Obenour
  2025-07-26 10:24         ` Alyssa Ross
  2025-07-24 22:36       ` [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
                         ` (2 subsequent siblings)
  6 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:35 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
                         ` (3 preceding siblings ...)
  2025-07-24 22:35       ` [PATCH v6 4/5] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-24 22:36       ` Demi Marie Obenour
  2025-07-26 11:29         ` Alyssa Ross
  2025-07-26 10:57       ` [PATCH v6 0/5] Sound support in Spectrum VMs Alyssa Ross
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
  6 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-24 22:36 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-21 19:09         ` Demi Marie Obenour
@ 2025-07-26 10:11           ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 10:11 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 3744 bytes --]

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.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 4/5] img/app: Create needed directories in early boot
  2025-07-24 22:35       ` [PATCH v6 4/5] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-26 10:24         ` Alyssa Ross
  2025-07-27 20:13           ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 10:24 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 2234 bytes --]

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…

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image
  2025-07-24 22:30       ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
@ 2025-07-26 10:46         ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 10:46 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development; +Cc: Alyssa Ross

This patch has been committed as b3cb2511d3612a1d58246fabdc6c4fbb21886c4c,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=b3cb2511d3612a1d58246fabdc6c4fbb21886c4c.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 0/5] Sound support in Spectrum VMs
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
                         ` (4 preceding siblings ...)
  2025-07-24 22:36       ` [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-26 10:57       ` Alyssa Ross
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
  6 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 10:57 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1219 bytes --]

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 3/5] img/app: tell mount(8) to create directories
  2025-07-24 22:33       ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
@ 2025-07-26 11:20         ` Alyssa Ross
  2025-07-26 11:26         ` Alyssa Ross
  1 sibling, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 11:20 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development; +Cc: Alyssa Ross

This patch has been committed as 560fd878ba1bbd8df0fe28488e72948f28940948,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=560fd878ba1bbd8df0fe28488e72948f28940948.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 3/5] img/app: tell mount(8) to create directories
  2025-07-24 22:33       ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
  2025-07-26 11:20         ` Alyssa Ross
@ 2025-07-26 11:26         ` Alyssa Ross
  1 sibling, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 11:26 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 555 bytes --]

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.)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-24 22:36       ` [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-26 11:29         ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-26 11:29 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 22029 bytes --]

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)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v6 4/5] img/app: Create needed directories in early boot
  2025-07-26 10:24         ` Alyssa Ross
@ 2025-07-27 20:13           ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-27 20:13 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 2817 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v7 0/2] Sound support in Spectrum VMs
  2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
                         ` (5 preceding siblings ...)
  2025-07-26 10:57       ` [PATCH v6 0/5] Sound support in Spectrum VMs Alyssa Ross
@ 2025-07-28  5:57       ` Demi Marie Obenour
  2025-07-28  6:01         ` [PATCH v7 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
                           ` (5 more replies)
  6 siblings, 6 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28  5:57 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v7 1/2] img/app: Create needed directories in early boot
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
@ 2025-07-28  6:01         ` Demi Marie Obenour
  2025-07-28  6:03         ` [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
                           ` (4 subsequent siblings)
  5 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28  6:01 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
  2025-07-28  6:01         ` [PATCH v7 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-28  6:03         ` Demi Marie Obenour
  2025-07-28  6:18           ` Demi Marie Obenour
  2025-07-28 23:13         ` [PATCH v8 0/2] Sound support in Spectrum VMs Demi Marie Obenour
                           ` (3 subsequent siblings)
  5 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28  6:03 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-28  6:03         ` [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-28  6:18           ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28  6:18 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross


[-- Attachment #1.1.1: Type: text/plain, Size: 24156 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v8 0/2] Sound support in Spectrum VMs
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
  2025-07-28  6:01         ` [PATCH v7 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
  2025-07-28  6:03         ` [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-28 23:13         ` Demi Marie Obenour
  2025-07-29  0:32           ` [PATCH v9 " Demi Marie Obenour
  2025-07-28 23:13         ` [PATCH v8 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
                           ` (2 subsequent siblings)
  5 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28 23:13 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v8 1/2] img/app: Create needed directories in early boot
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
                           ` (2 preceding siblings ...)
  2025-07-28 23:13         ` [PATCH v8 0/2] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-28 23:13         ` Demi Marie Obenour
  2025-07-28 23:19           ` Demi Marie Obenour
  2025-07-28 23:13         ` [PATCH v8 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
  2025-07-29 12:41         ` [PATCH v7 0/2] Sound support in Spectrum VMs Alyssa Ross
  5 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28 23:13 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v8 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
                           ` (3 preceding siblings ...)
  2025-07-28 23:13         ` [PATCH v8 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-28 23:13         ` Demi Marie Obenour
  2025-07-29 12:41         ` [PATCH v7 0/2] Sound support in Spectrum VMs Alyssa Ross
  5 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28 23:13 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v8 1/2] img/app: Create needed directories in early boot
  2025-07-28 23:13         ` [PATCH v8 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-28 23:19           ` Demi Marie Obenour
  0 siblings, 0 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-28 23:19 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross


[-- Attachment #1.1.1: Type: text/plain, Size: 3619 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v9 0/2] Sound support in Spectrum VMs
  2025-07-28 23:13         ` [PATCH v8 0/2] Sound support in Spectrum VMs Demi Marie Obenour
@ 2025-07-29  0:32           ` Demi Marie Obenour
  2025-07-29  0:33             ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
                               ` (2 more replies)
  0 siblings, 3 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-29  0:32 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v9 1/2] img/app: Create needed directories in early boot
  2025-07-29  0:32           ` [PATCH v9 " Demi Marie Obenour
@ 2025-07-29  0:33             ` Demi Marie Obenour
  2025-07-29 12:44               ` Alyssa Ross
  2025-07-29  0:33             ` [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
  2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
  2 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-29  0:33 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)


^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-29  0:32           ` [PATCH v9 " Demi Marie Obenour
  2025-07-29  0:33             ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-29  0:33             ` Demi Marie Obenour
  2025-07-29 13:08               ` Alyssa Ross
  2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
  2 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-29  0:33 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Demi Marie Obenour, Alyssa Ross

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)

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v7 0/2] Sound support in Spectrum VMs
  2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
                           ` (4 preceding siblings ...)
  2025-07-28 23:13         ` [PATCH v8 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-29 12:41         ` Alyssa Ross
  5 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-29 12:41 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 969 bytes --]

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!

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v9 1/2] img/app: Create needed directories in early boot
  2025-07-29  0:33             ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
@ 2025-07-29 12:44               ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-29 12:44 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 1086 bytes --]

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 }

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-29  0:33             ` [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-29 13:08               ` Alyssa Ross
  2025-07-29 21:17                 ` Demi Marie Obenour
  0 siblings, 1 reply; 81+ messages in thread
From: Alyssa Ross @ 2025-07-29 13:08 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 25940 bytes --]

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
> +}

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-29 13:08               ` Alyssa Ross
@ 2025-07-29 21:17                 ` Demi Marie Obenour
  2025-07-30  8:10                   ` Alyssa Ross
  0 siblings, 1 reply; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-29 21:17 UTC (permalink / raw)
  To: Alyssa Ross, Spectrum OS Development


[-- Attachment #1.1.1: Type: text/plain, Size: 10757 bytes --]

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)

[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-29 21:17                 ` Demi Marie Obenour
@ 2025-07-30  8:10                   ` Alyssa Ross
  0 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-30  8:10 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 4856 bytes --]

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.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v10] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-29  0:32           ` [PATCH v9 " Demi Marie Obenour
  2025-07-29  0:33             ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
  2025-07-29  0:33             ` [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
@ 2025-07-30  9:59             ` Demi Marie Obenour
  2025-07-31  9:12               ` Alyssa Ross
                                 ` (2 more replies)
  2 siblings, 3 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-30  9:59 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross, Demi Marie Obenour

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)


^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v10] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
@ 2025-07-31  9:12               ` Alyssa Ross
  2025-07-31  9:40               ` Alyssa Ross
  2025-07-31 17:06               ` [PATCH v11] " Demi Marie Obenour
  2 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-31  9:12 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 4107 bytes --]

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.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v10] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
  2025-07-31  9:12               ` Alyssa Ross
@ 2025-07-31  9:40               ` Alyssa Ross
  2025-07-31 17:06               ` [PATCH v11] " Demi Marie Obenour
  2 siblings, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-07-31  9:40 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 562 bytes --]

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. :)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH v11] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
  2025-07-31  9:12               ` Alyssa Ross
  2025-07-31  9:40               ` Alyssa Ross
@ 2025-07-31 17:06               ` Demi Marie Obenour
  2025-08-01 17:53                 ` Alyssa Ross
  2025-08-02  7:54                 ` Alyssa Ross
  2 siblings, 2 replies; 81+ messages in thread
From: Demi Marie Obenour @ 2025-07-31 17:06 UTC (permalink / raw)
  To: Spectrum OS Development; +Cc: Alyssa Ross, Demi Marie Obenour

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)


^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH v11] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-31 17:06               ` [PATCH v11] " Demi Marie Obenour
@ 2025-08-01 17:53                 ` Alyssa Ross
  2025-08-02  7:54                 ` Alyssa Ross
  1 sibling, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-08-01 17:53 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development
  Cc: Alyssa Ross, Demi Marie Obenour

This patch has been committed as 6c945fc8b81ab15866567816007dee3e5793cf4d,
which can be viewed online at
https://spectrum-os.org/git/spectrum/commit/?id=6c945fc8b81ab15866567816007dee3e5793cf4d.

This is an automated message.  Send comments/questions/requests to:
Alyssa Ross <hi@alyssa.is>

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH v11] img/app: Run PipeWire and WirePlumber in the VMs
  2025-07-31 17:06               ` [PATCH v11] " Demi Marie Obenour
  2025-08-01 17:53                 ` Alyssa Ross
@ 2025-08-02  7:54                 ` Alyssa Ross
  1 sibling, 0 replies; 81+ messages in thread
From: Alyssa Ross @ 2025-08-02  7:54 UTC (permalink / raw)
  To: Demi Marie Obenour, Spectrum OS Development

[-- Attachment #1: Type: text/plain, Size: 262 bytes --]

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.)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

end of thread, other threads:[~2025-08-02  7:55 UTC | newest]

Thread overview: 81+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-11  2:44 [PATCH v3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-14 14:54 ` Alyssa Ross
2025-07-15 20:22   ` Demi Marie Obenour
2025-07-16 10:26     ` Alyssa Ross
2025-07-16 21:16   ` Demi Marie Obenour
2025-07-16 21:27     ` Demi Marie Obenour
2025-07-18 12:16       ` Alyssa Ross
2025-07-17  5:53   ` Demi Marie Obenour
2025-07-18 10:02     ` Alyssa Ross
2025-07-18 10:19       ` Alyssa Ross
2025-07-18  2:07 ` [PATCH v4 0/3] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-18  2:13   ` [PATCH v4 1/3] Rebuild the root filesystem when the makefile changes Demi Marie Obenour
2025-07-18 11:14     ` Alyssa Ross
2025-07-18  2:13   ` [PATCH v4 2/3] Fix permissions on /tmp Demi Marie Obenour
2025-07-18 11:51     ` Alyssa Ross
2025-07-18 11:51     ` Alyssa Ross
2025-07-18 11:53     ` Alyssa Ross
2025-07-18  2:14   ` [PATCH v4 3/3] Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-18 11:27     ` Alyssa Ross
2025-07-18 17:59       ` Demi Marie Obenour
2025-07-19  9:22         ` Alyssa Ross
2025-07-19 20:05           ` Demi Marie Obenour
2025-07-19  8:06     ` Alyssa Ross
2025-07-19 20:03       ` Demi Marie Obenour
2025-07-19 20:07         ` Demi Marie Obenour
2025-07-20  7:50           ` Alyssa Ross
2025-07-20 17:58   ` [PATCH v5 0/8] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-20 18:02     ` [PATCH v5 1/8] Revert "img/app: fix permissions on /tmp" Demi Marie Obenour
2025-07-21  9:34       ` Alyssa Ross
2025-07-20 18:03     ` [PATCH v5 2/8] img/app: Use separate service to create directories Demi Marie Obenour
2025-07-21  9:21       ` Alyssa Ross
2025-07-22 23:48         ` Demi Marie Obenour
2025-07-20 18:04     ` [PATCH v5 3/8] img/app: Fix permissions of /tmp/.X11-unix Demi Marie Obenour
2025-07-20 18:05     ` [PATCH v5 4/8] img/app: Create other X11 directories Demi Marie Obenour
2025-07-21  9:23       ` Alyssa Ross
2025-07-21 19:03         ` Demi Marie Obenour
2025-07-20 18:06     ` [PATCH v5 5/8] img/app: Be explicit about directory modes Demi Marie Obenour
2025-07-20 18:08     ` [PATCH v5 6/8] img/app: create /run/user and /run/wait very early in boot Demi Marie Obenour
2025-07-21  9:23       ` Alyssa Ross
2025-07-20 18:10     ` [PATCH v5 7/8] host/rootfs: " Demi Marie Obenour
2025-07-20 18:11     ` [PATCH v5 8/8] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-21  9:42       ` Alyssa Ross
2025-07-21 19:09         ` Demi Marie Obenour
2025-07-26 10:11           ` Alyssa Ross
2025-07-21 19:10         ` Demi Marie Obenour
2025-07-24 22:15     ` [PATCH v6 0/5] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-24 22:30       ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour
2025-07-26 10:46         ` Alyssa Ross
2025-07-24 22:32       ` [PATCH v6 2/5] img/app: " Demi Marie Obenour
2025-07-24 22:33       ` [PATCH v6 3/5] img/app: tell mount(8) to create directories Demi Marie Obenour
2025-07-26 11:20         ` Alyssa Ross
2025-07-26 11:26         ` Alyssa Ross
2025-07-24 22:35       ` [PATCH v6 4/5] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-26 10:24         ` Alyssa Ross
2025-07-27 20:13           ` Demi Marie Obenour
2025-07-24 22:36       ` [PATCH v6 5/5] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-26 11:29         ` Alyssa Ross
2025-07-26 10:57       ` [PATCH v6 0/5] Sound support in Spectrum VMs Alyssa Ross
2025-07-28  5:57       ` [PATCH v7 0/2] " Demi Marie Obenour
2025-07-28  6:01         ` [PATCH v7 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-28  6:03         ` [PATCH v7 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-28  6:18           ` Demi Marie Obenour
2025-07-28 23:13         ` [PATCH v8 0/2] Sound support in Spectrum VMs Demi Marie Obenour
2025-07-29  0:32           ` [PATCH v9 " Demi Marie Obenour
2025-07-29  0:33             ` [PATCH v9 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-29 12:44               ` Alyssa Ross
2025-07-29  0:33             ` [PATCH v9 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-29 13:08               ` Alyssa Ross
2025-07-29 21:17                 ` Demi Marie Obenour
2025-07-30  8:10                   ` Alyssa Ross
2025-07-30  9:59             ` [PATCH v10] " Demi Marie Obenour
2025-07-31  9:12               ` Alyssa Ross
2025-07-31  9:40               ` Alyssa Ross
2025-07-31 17:06               ` [PATCH v11] " Demi Marie Obenour
2025-08-01 17:53                 ` Alyssa Ross
2025-08-02  7:54                 ` Alyssa Ross
2025-07-28 23:13         ` [PATCH v8 1/2] img/app: Create needed directories in early boot Demi Marie Obenour
2025-07-28 23:19           ` Demi Marie Obenour
2025-07-28 23:13         ` [PATCH v8 2/2] img/app: Run PipeWire and WirePlumber in the VMs Demi Marie Obenour
2025-07-29 12:41         ` [PATCH v7 0/2] Sound support in Spectrum VMs Alyssa Ross
2025-07-24 22:23     ` [PATCH v6 1/5] host/rootfs: Create /run/user and /run/wait via run-image Demi Marie Obenour

Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).