Files
punktfunk/scripts/60-punktfunk-dualsense.conf
enricobuehler f24eb02692 fix(packaging): the DualSense driver-priority guard has to be 0, and cover the capture node
The shipped WirePlumber policy sets `priority.driver = 1` on a DS5's ALSA sink and says it
"keeps the pad from ever driving the graph". Read against PipeWire's own recalc, it does not:
`priority_driver` is unsigned and `pw_context_recalc_graph` skips a driver only when it is
`<= 0`. At 1 the pad is merely LAST in the ordering — and last is still elected whenever
nothing above it qualifies, which on a punktfunk host is the ordinary in-session state,
because claiming our own sink as the default output leaves the box's real card idle. With
`node.always-process` on the same node it is also permanently runnable, i.e. permanently
eligible.

Zero is the value that means excluded. The pad keeps driving the streams actually linked to
it — a driver always drives its own group, priority orders the election and nothing else —
so GE-Proton's haptics are unaffected.

The second rule covers the capture side of the same cards. That node is what clocked a
reporter's desktop audio for a whole session: in the Pro Audio profile it carries
`priority.driver = 2600`, never suspends, and had nothing linked to it at all — its only
function on that machine was to clock other people's graphs. The `alsa_output` matches never
touched it. Only the priority is set there; holding a device open is about the playback node
GE opens raw, and an always-processing microphone is not something this host should ask for.

Both of these are belt to the braces of the host-side fix — a capture group that carries its
own driver cannot be handed one — but they are worth having on their own: they are what stops
a pad from clocking anything else on the box, including a build that predates it.
2026-08-18 17:47:00 +02:00

63 lines
3.3 KiB
Plaintext

# WirePlumber policy for DualSense sound cards on a punktfunk host (virtual usbip pads AND
# physically plugged pads — the failure modes are identical).
#
# 1. `node.always-process` + no suspend: PipeWire must HOLD the pad's ALSA device open at all
# times. GE-Proton's DS5 haptic router opens the sink's backing `hw:` device RAW whenever it
# is free — and then its own path re-probe EBUSYs against its own handle, invalidates the
# stream, and spins a refresh loop at 100 Hz (haptics dead, speaker dead). On SteamOS, where
# that code was developed, PipeWire always holds the device, so GE lands on its well-tested
# Pulse-routing fallback immediately. This rule reproduces that environment. Field-diagnosed
# 2026-08-18 (Spider-Man Remastered, GE-Proton 11-5).
#
# 2. `priority.driver = 0`: an always-processing node is a permanent graph-driver candidate, and
# a USB pad's audio clock (virtual or real) must never clock somebody else's graph — the day
# this was diagnosed, the virtual pad's clock drove the desktop capture to 50 % delivery, and
# a reporter's pad forwarded over VirtualHere did the same to a whole 15-minute session.
#
# ⚠ It must be ZERO, not 1. PipeWire's `pw_context_recalc_graph` skips a driver only when its
# `priority.driver` is `<= 0` (`priority_driver` is unsigned); at 1 the node is merely LAST in
# the ordering, and last is still elected whenever nothing above it qualifies — which is the
# normal in-session state on a punktfunk host, because claiming our own stream sink as the
# default output leaves the box's real card idle. Zero keeps the pad driving its own linked
# streams (a driver always drives its own group) while removing it from the election for
# everyone else's.
#
# The `alsa_input` rule below is the same key on the pad's capture side. That node is what
# clocked the reporter's desktop audio for a whole session: in the Pro Audio profile it
# carries `priority.driver = 2600` and never suspends, and nothing was linked to it at all —
# its only function on that machine was to clock other people's graphs.
#
# Install: /usr/share/wireplumber/wireplumber.conf.d/ (the user instance reads the shared dirs).
monitor.alsa.rules = [
{
matches = [
# Both product-string spellings a DS5 family pad ships with: newer firmware / Edge say
# "DualSense[ Edge] Wireless Controller", earlier firmware says just "Wireless Controller".
{ node.name = "~alsa_output.usb-Sony_Interactive_Entertainment_DualSense.*" }
{ node.name = "~alsa_output.usb-Sony_Interactive_Entertainment_Wireless_Controller.*" }
]
actions = {
update-props = {
session.suspend-timeout-seconds = 0
node.pause-on-idle = false
node.always-process = true
priority.driver = 0
}
}
}
{
matches = [
# The capture side of the same cards. Only the driver priority: holding the device open is
# about the playback node GE-Proton opens raw, and an always-processing microphone is not
# something this host has any business asking for.
{ node.name = "~alsa_input.usb-Sony_Interactive_Entertainment_DualSense.*" }
{ node.name = "~alsa_input.usb-Sony_Interactive_Entertainment_Wireless_Controller.*" }
]
actions = {
update-props = {
priority.driver = 0
}
}
}
]