Skip to content

pipewire: show QEMU audio streams in Application Volumes#2986

Merged
ItsLemmy merged 1 commit into
noctalia-dev:mainfrom
Gustav0ar:fix/qemu-audio-app-volume
Jun 15, 2026
Merged

pipewire: show QEMU audio streams in Application Volumes#2986
ItsLemmy merged 1 commit into
noctalia-dev:mainfrom
Gustav0ar:fix/qemu-audio-app-volume

Conversation

@Gustav0ar

@Gustav0ar Gustav0ar commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Disclaimer

I know that this is a very edge cases and is intended for people who uses QEMU VMs and want the audio to appear on the "Application Volumes" widget. This will solve the below issues for me and I'll be really grateful if this is merged.

  1. !nd.targetObject.empty() causes the QEMU VMs to be filtered out from the list
  2. Show proper VM name instead of generic QEMU process name

** This change was made with the help of Claude Fable 5. **

Summary

QEMU's PipeWire audio stream (libvirt <audio type="pipewire">) is a Stream/Output/Audio node with target.object set to the libvirt <input>/<output> name (e.g. "Windows 11"). Noctalia's isProgramOutputNode filter deliberately excluded any node with a non-empty target.object as a "loopback/filter endpoint", so QEMU streams were hidden from Application Volumes even though pavucontrol and wpctl show them. This makes the QEMU volume uncontrollable from the panel while the VM is playing audio.

Two changes:

  1. Drop the !nd.targetObject.empty() exclusion in isProgramOutputNode. node.link-group and node.passive remain excluded (those still correctly hide Wine/Proton link-grouped stubs and real loopback/filter endpoints). Only target.object is relaxed. The comment is updated to match the new behaviour and to explain why virtual streams that feed another endpoint (QEMU → VM HDA) are user-controllable application volumes.

  2. Promote target.object to applicationName in refreshNodeIdentity for any Stream/Output/Audio node whose node.name starts with qemu-system- (covering every QEMU system emulator binary: qemu-system-x86_64, qemu-system-aarch64, qemu-system-riscv64, qemu-system-arm, qemu-system-ppc64, ...). QEMU's PipeWire backend does not expose application.name / application.id from process metadata, so without this the stream would show as the bare process name. When the libvirt <input>/<output> has a name attribute, that name is used; when it does not, the stream falls back to the generic label "QEMU". The block synthesises a stable applicationId of org.qemu.vm.<name> so the stream doesn't collide with any other process's identity.

The block runs unconditionally for any matching node — not gated on whether applicationName is already set — because refreshNodeIdentity is called from both onClientInfo (which fires before the node's target.object is populated, and would otherwise pin applicationName to "QEMU" for the lifetime of the stream) and onNodeInfo (which fills in target.object later). QEMU's PipeWire backend never sets application.name on its own, so there is no other source of application identity for this stream that we could be clobbering.

Reproduction

On a libvirt-managed QEMU VM with <audio type="pipewire"> and a <sound> device, open the audio panel's "Application Volumes". The QEMU stream is missing. The same stream is visible in pavucontrol and listed in wpctl status as a Stream.

pw-cli info <id> on the QEMU Stream/Output/Audio node confirms:

  • node.name = "qemu-system-<arch>"
  • target.object = "<libvirt <input>/<output> name>"
  • application.name and application.id not set

Verification

  • meson setup build && ninja -C build — 688/688 targets compile and link cleanly.
  • meson test -C build — 22/22 pass.
  • The change is contained to one file (src/pipewire/pipewire_service.cpp, +31 / −3).

Result

image

based on QEMU config:

<domain>
  ...
  <devices>
    ...
    <audio id="1" type="pipewire" runtimeDir="/run/user/1000">
        <input mixingEngine="no" name="Windows 11"/>
        <output mixingEngine="no" name="Windows 11"/>
    </audio>
    ...
  </devices>
  ...
</domain>

Risk

With this change, any PipeWire node with target.object set (e.g. a virtual sink feeding another virtual sink) will now appear in Application Volumes. On the reporter's system there are zero such nodes besides QEMU; if a future use case needs to keep hiding a particular category of target.object streams, that should be added as a follow-up with a more specific predicate.

Copilot AI review requested due to automatic review settings June 11, 2026 11:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates PipeWire stream classification and identity handling to better match wpctl/pavucontrol behavior, especially for QEMU/libvirt PipeWire audio streams.

Changes:

  • Treat target.object streams as user-controllable “program output nodes” (no longer excluded).
  • Add a QEMU/libvirt-specific fallback to derive applicationName (and sometimes applicationId) from target.object when metadata is missing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pipewire/pipewire_service.cpp Outdated
Comment thread src/pipewire/pipewire_service.cpp Outdated
Comment thread src/pipewire/pipewire_service.cpp Outdated
@Gustav0ar Gustav0ar force-pushed the fix/qemu-audio-app-volume branch 4 times, most recently from dcff959 to 3a1002d Compare June 11, 2026 12:01
@Gustav0ar Gustav0ar force-pushed the fix/qemu-audio-app-volume branch from 3a1002d to 47b33c2 Compare June 11, 2026 12:05
@ItsLemmy ItsLemmy merged commit 3309b4c into noctalia-dev:main Jun 15, 2026
@ItsLemmy

Copy link
Copy Markdown
Collaborator

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants