diff --git a/apps/nesinit/src/services.rs b/apps/nesinit/src/services.rs index dc38ee1c..d754181d 100644 --- a/apps/nesinit/src/services.rs +++ b/apps/nesinit/src/services.rs @@ -193,6 +193,9 @@ pub const SERVICE_GID: u32 = 1000; /// workload, which is the property [`crate::ticket::Untrusted`] depends on. pub const AUDIO_DIR: &str = "/run/pipewire"; +/// The PulseAudio socket PipeWire serves, beside its own in [`AUDIO_DIR`]. +pub const PULSE_SOCKET: &str = "/run/pipewire/pulse-native"; + /// Where a service's runtime sockets live. pub const RUNTIME_DIR: &str = "/run/user/1000"; @@ -354,6 +357,26 @@ pub const STACK: &[Service] = &[ ready: Some("/run/pipewire/pipewire-0"), ready_name: None, }, + Service { + name: "pipewire-pulse", + argv: &["/usr/bin/pipewire-pulse"], + env: &[ + ("XDG_RUNTIME_DIR", RUNTIME_DIR), + ("PIPEWIRE_RUNTIME_DIR", AUDIO_DIR), + ("DBUS_SESSION_BUS_ADDRESS", "unix:path=/run/user/1000/bus"), + ], + user: Some((SERVICE_UID, SERVICE_GID)), + // Most desktop applications and games speak PulseAudio, not PipeWire. + cost: "applications that speak PulseAudio play no sound", + required: false, + // Beside PipeWire's socket and open to the workload the same way: its + // address is set in pipewire-pulse.conf.d and handed to the workload as + // PULSE_SERVER. + umask: Some(0), + realtime: true, + ready: Some(PULSE_SOCKET), + ready_name: None, + }, Service { name: "wireplumber", argv: &["/usr/bin/wireplumber"], diff --git a/apps/nesinit/src/workload.rs b/apps/nesinit/src/workload.rs index 1b58e27b..8b82c519 100644 --- a/apps/nesinit/src/workload.rs +++ b/apps/nesinit/src/workload.rs @@ -1005,6 +1005,8 @@ const GRAPHICS: &[(&str, &str)] = &[ // reach and both are told where. Without this a game renders and plays // silently, having looked under its own uid and found nothing. ("PIPEWIRE_RUNTIME_DIR", crate::services::AUDIO_DIR), + // And the same for PulseAudio, which most applications speak. + ("PULSE_SERVER", concat!("unix:", "/run/pipewire/pulse-native")), ]; /// Mount one share where the descriptor says to put it. diff --git a/build/Containerfile b/build/Containerfile index ed5d32b2..f4dc4f45 100644 --- a/build/Containerfile +++ b/build/Containerfile @@ -393,7 +393,7 @@ RUN pacman -Syu --noconfirm --needed \ libglvnd libdrm libepoxy libxxf86vm libinput wayland \ expat zlib llvm-libs lm_sensors elfutils libva shaderc vulkan-icd-loader \ pixman libxkbcommon xcb-util-keysyms xorg-xwayland \ - pipewire pipewire-audio wireplumber opus \ + pipewire pipewire-audio pipewire-pulse wireplumber opus \ python libunwind sdl2-compat \ python-xlib python-urllib3 python-pyzstd \ libvpx speex libtheora libvdpau jsoncpp \ @@ -685,22 +685,30 @@ RUN umu-run --help >/dev/null 2>&1 \ || { echo "umu-run does not start; run it by hand to see the module or interpreter it lacks" >&2; \ umu-run --help >&2 || true; exit 1; } -# The 32-bit C library, and nothing else 32-bit. A launcher's process helper +# The 32-bit C library and GL dispatcher, and nothing else 32-bit. A launcher's process helper # is still a 32-bit program even in a 64-bit client -- Steam's `reaper`, which # every game is started through, needs only libc and libpthread -- and with no # 32-bit loader every launch exits 255 before the game runs, with no message. -# No graphics stack comes with it: games bring their own runtime. +# The launcher also preloads its 32-bit overlay into that helper, and the overlay +# links libGL.so.1: libglvnd's dispatcher satisfies the link, and Mesa is +# assumed rather than installed because the helper never draws. No graphics +# driver comes with it: games bring their own runtime. RUN printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf \ && pacman -Sy --noconfirm --needed lib32-glibc \ + && pacman -S --noconfirm --needed \ + --assume-installed lib32-mesa --assume-installed lib32-opengl-driver \ + lib32-libglvnd \ && pacman -Scc --noconfirm \ - && test -x /usr/lib32/ld-linux.so.2 + && test -x /usr/lib32/ld-linux.so.2 \ + && test -e /usr/lib32/libGL.so.1 # No other 32-bit libraries. The distribution's umu package would have brought a # long list of them for the container runtime this image never runs, which is # why it is built instead of installed; this is the check that stays true if # somebody swaps it back. -RUN ! pacman -Qq | grep '^lib32-' | grep -qvx 'lib32-glibc' \ - || { echo "a lib32 package other than lib32-glibc is installed:" >&2; \ +RUN ! pacman -Qq | grep '^lib32-' \ + | grep -qvxE 'lib32-(glibc|libglvnd|libx11|libxext|libxcb|libxau|libxdmcp)' \ + || { echo "a lib32 package beyond the helper's libc and GL dispatcher is installed:" >&2; \ pacman -Qq | grep '^lib32-' >&2; exit 1; } # ─────────────────────────────────────────────────────────── diff --git a/build/etc/pipewire/pipewire-pulse.conf.d/50-nestri.conf b/build/etc/pipewire/pipewire-pulse.conf.d/50-nestri.conf new file mode 100644 index 00000000..9df03e8d --- /dev/null +++ b/build/etc/pipewire/pipewire-pulse.conf.d/50-nestri.conf @@ -0,0 +1,5 @@ +# Serve PulseAudio beside PipeWire's own socket, where the workload -- another +# user -- can reach it. The workload is told this address in PULSE_SERVER. +pulse.properties = { + server.address = [ "unix:/run/pipewire/pulse-native" ] +}