Repository navigation
Conversation
Volume mixers list cpal streams as `cpal-playback-<pid>` and PulseAudio clients as `cpal-pulseaudio-<pid>`, with no icon or role. The pid in the name also defeats stream-restore, which keys a stream's volume by its media name. Add `ApplicationInfo`, `StreamMetadata` and `StreamRole`, host-agnostic hints, and take them on the PipeWire and PulseAudio hosts: - `Host::with_application` names the application: PipeWire's `application.name` and `application.icon-name` on the stream and its client, PulseAudio's client name and the stream's `application.name` and `application.icon_name`. - `Host::set_stream_metadata` sets `media.name` and `media.role` on the streams built afterwards, with the role in each server's vocabulary (`Communication` on PipeWire, `phone` on PulseAudio). Export `PulseAudioHost` from `cpal::platform`, as `PipeWireHost` is.
crmne
added a commit
to crmne/fastframe
that referenced
this pull request
Oct 20, 2026
No cpal release can name streams, so fastframe-audio now gets cpal from crmne/cpal's stream-metadata-0.18 branch at fcd8b6e: cpal 0.18.2 with RustAudio/cpal#1409, the one fork dependency the maintainer approved. It is carried until a cpal release includes #1409 or its successor, then goes back to crates.io. AGENTS.md records the exception, and MIGRATION.md and the crate README tell apps that also get cpal 0.18 from crates.io (through kira, for example) to patch it to the same rev, so they build one cpal. On PipeWire and PulseAudio every host the process makes now names the app, and the host a stream opens from names the stream: its media name (the app's when the options give none) and its role. The connection kept to PipeWire's PulseAudio server is named too, and the first choice of the sound server makes the named host directly, so no unnamed `cpal-pulseaudio-<pid>` client is left in the mixer. Names lose any NUL byte, which the servers cannot take.
crmne
added a commit
to crmne/chatwithwork-desktop
that referenced
this pull request
Oct 20, 2026
On PipeWire and PulseAudio, dictation now shows as "Dictation" under "Chat with Work" with the cww-app icon the Linux packages install, instead of cpal-pulseaudio-<pid>. It has no stream role: it isn't call audio. fastframe-audio now gets cpal from crmne/cpal (RustAudio/cpal#1409), the only cpal in the tree, so cargo deny allows that source.
crmne
added a commit
to crmne/spotifast
that referenced
this pull request
Oct 20, 2026
On PipeWire and PulseAudio the stream showed as cpal-playback-<pid>, with the client as cpal-pulseaudio-<pid>. fastframe-audio 0.14 names the app and its streams: Spotifast now calls set_app_info at startup, before any output is listed or opened, with its name and the desktop entry's icon, and its output's media name is "Spotify playback", fixed while the stream is open. No role is set, so the mixer remembers the volume for Spotifast rather than one shared by every music player. Every fastframe crate moves to v0.14.0 together. fastframe-audio gets cpal from crmne/cpal (cpal 0.18.2 with RustAudio/cpal#1409); rodio 0.21 gets cpal 0.18.2 from crates.io, so [patch.crates-io] points it at the same fork and the build has one cpal. The Nix vendor hash follows the lockfile. Checked on a private PipeWire 1.6 with WirePlumber and pipewire-pulse: pw-dump shows the client and node with application.name Spotifast, application.icon-name spotifast and media.name Spotify playback, and pactl list sink-inputs shows the same.
Contributor
This has been on my todo list for a few days, please expect a fix relatively soon |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Volume mixers (
pavucontrol, GNOME and KDE sound settings) list cpal streams with cpal's own names:media.nameiscpal-playback-<pid>/cpal-capture-<pid>, with noapplication.nameor icon on the node and nomedia.role.cpal-pulseaudio-<pid>, and the stream has no name, icon or role.Besides looking wrong, the pid defeats stream-restore: pipewire-pulse keys a PipeWire-host stream's remembered volume by
sink-input-by-media-name:cpal-playback-<pid>, and a PulseAudio-host stream's bysink-input-by-application-name:cpal-pulseaudio-<pid>, so no volume is ever restored across runs. This is the request in #1186 (closed) and the "naming and metadata" row of the proposal in #1334.Change
Three host-agnostic value types in the crate root, which hosts treat as hints:
The PipeWire and PulseAudio hosts take them through host-specific configuration, following
set_connect_automatically:Host::with_application(ApplicationInfo) -> Result<Host, Error>. It is a constructor because PulseAudio names the application by its connection, whichHost::newopens.Host::set_stream_metadata(StreamMetadata) -> Result<(), Error>applies to streams built from the host's devices afterwards.Both return
ErrorKind::InvalidInputfor a value containing a NUL byte.PulseAudioHostis now exported fromcpal::platform, asPipeWireHostalready is, so apps can construct it.application.nameon the stream and on its core/clientSetClientName) and the stream'sapplication.nameapplication.icon-nameon the stream and on its clientapplication.icon_name(pulseaudio-rs'sClient::from_envtakes only a name)media.nameand thepw_streamname (defaults to cpal'scpal-playback-<pid>as before)media.namein the stream proplist (unset by default, as before)media.role:Music,Movie,Game,Communication,Notification,Production,Accessibilitymedia.role:music,video,game,phone,event,production,a11yWith nothing set, behaviour is unchanged.
Relation to #1334
#1329 was parked to wait for the extension API in #1334, and I know this PR runs into the same thing. I've tried to keep the parts that should outlast that API apart from the parts that won't:
name_hint-style stream-builder knob and a host-builder knob would both pass down. They hold typed data, not raw key/value pairs, so the role works on more than one backend (WASAPI'sAUDIO_STREAM_CATEGORYand AAudio's usage could map from the same enum later).If you'd rather wait for #1334, the mapping and tests can move onto it unchanged. I'm also glad to rename or reshape the types to fit that design.
Not in this PR
pw_stream_update_properties; PulseAudio'sSET_PLAYBACK_STREAM_NAME/UPDATE_*_STREAM_PROPLIST, which pulseaudio-rs exposes asPlaybackStream::set_name), and a music player wants it for the track title. It needs aStreamTraitmethod, which is a core API decision, so I left it out.ApplicationInfo::namewould fit it, but it belongs with the host builder.Breaking changes
pulseaudio::Device::SinkandDevice::Source(reachable throughDeviceInner) have a newidentityfield, so exhaustive patterns on them outside cpal break. This is noted under Changed in the changelog.Testing
Unit tests cover the property mapping on both hosts and the NUL check (
host::pipewire::device::test::identity_properties,host::pulseaudio::tests::identity_props,stream_metadata::tests::rejects_nul_bytes).I ran a small program that opens one silent output or input on each host, with and without names, against PipeWire 1.6.9 and pipewire-pulse ("PulseAudio (on PipeWire 1.6.9)") on Arch Linux, and read the results back with
pw-dumpandpactl list sink-inputs/source-outputs/clients.PipeWire host, output, before:
PipeWire host, output, with
ApplicationInfo { name: "Spotifast", icon_name: Some("spotifast") }andStreamMetadata { name: Some("Some Song"), role: Some(Music) }:PipeWire host, input, with
ZapFast/Voice call/Communication:PulseAudio host, output, before:
PulseAudio host, output and input, named as above:
Checks run locally:
cargo fmt --all -- --check;cargo clippy --all -- -D warningswith default features and with--all-features;cargo clippy --target aarch64-apple-darwin --all-features -- -D warnings;cargo test --workspacewith default and all features;cargo check --examples --all-features;cargo +1.85.0 check --features pipewire;DOCS_RS=1 RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc --all-features --no-deps.Unverified
pulseaudioitself.cfgas Linux, but I haven't built or run them.cargo clippy --target x86_64-pc-windows-msvcstops on an unusedframes_to_duration, and plain master does the same, so this change doesn't cause it. CI's ASIO build has not been run.pavucontrol. All the evidence above comes from server properties.