feat(audio): allow routing individual app output to a specific device - #3865
feat(audio): allow routing individual app output to a specific device#3865oldirtty wants to merge 3 commits into
Conversation
0bbd40e to
98835ac
Compare
[P1] target.object is written with the wrong PipeWire identifiersrc/pipewire/pipewire_service.cpp:2078-2081 This is not theoretical: the active PipeWire graph here contains sinks with id=94, object.serial=744, id=83, object.serial=225, and id=77, object.serial=226. Writing 94 as target.object therefore identifies the [P2] Routing state is not read from the metadata being modifiedsrc/pipewire/pipewire_service.cpp:169-183, 1613 Consequently, the routing button/checkmark is stale or wrong when:
Track target.object metadata by stream subject and resolve its serial/name to the current sink. The metadata event should be the authoritative state; the stream’s creation-time target.object property is a [P2] Failed metadata writes are still presented as successfulsrc/pipewire/pipewire_service.cpp:2065-2088 Return without changing local state on a negative result. Prefer updating displayed state from the metadata callback described above rather than maintaining a parallel optimistic copy |
98835ac to
1500843
Compare
|
P1: now writes the sink's object.serial as Spa:Id, falling back to node.name if serial is unavailable. P2 (stale state): added onTargetObjectMetadata, subscribed to target.object on the default metadata object, this is now the single source of truth for routing state. P2 (failed writes): moveProgramOutput no longer mutates local state on write, only the metadata callback does. Tested manually with pwvucontrol (move + reset to default), state stays in sync now. |
Summary
Adds per-application audio output routing to the Audio tab's "Application Volumes" section. Each row now has a routing button next to the mute button that opens a menu listing the system default plus all available output devices, letting a single app's stream be pinned to a specific sink instead of always following the system default.
PipeWireService::moveProgramOutput(programStreamId, targetSinkId)writestarget.object(asSpa:Id) on the "default" WirePlumber metadata, the same mechanism pavucontrol and pwvucontrol use to move streams.AudioNode::targetObjectwas added so the UI can reflect the current routing (checkmark in the menu, outlined routing button when an app is pinned to a non-default device).Motivation
Lets a single app play through a different output than the rest of the system (e.g. notifications on the monitor speakers while music stays on headphones) without a separate app like pavucontrol/pwvucontrol.
Type of Change
Related Issue
Closes #3553
Testing
just formatjust buildjust test, everything okManual Coverage
Manually verified with
pw-metadata -mthattarget.objectis written correctly (Spa:Id, numeric sink id) and confirmed viawpctl statusthat the stream actually moves. Tested with multiple simultaneous output devices (wired + Bluetooth), switching a single app between them while other apps stayed on the default device.Screenshots / Videos
Checklist
CONTRIBUTING.md.just formatwith clang-format v22+ installed, or this PR has no code changes.assets/translations/en.json, or this PR adds no new user-facing strings.Additional Notes
Known limitation: when a stream is routed through an intermediate virtual sink, such as EasyEffects sink, WirePlumber's stream-restore mechanism stores and reapplies EasyEffects' own target for that app, so routing the app directly gets silently reverted back to the EasyEffects sink. To change the final output in that setup, the "EasyEffects Sink" node itself needs to be routed instead of the individual app. Out of scope for this PR.