Skip to content

fix(linux): prevent SIGABRT crash when setting volume to 0% - #54

Merged
Horuse merged 1 commit into
mainfrom
fix/issue-53-speaker-zero-volume-crash
Sep 26, 2026
Merged

Horuse merged 1 commit into
mainfrom
fix/issue-53-speaker-zero-volume-crash

Conversation

@Horuse

@Horuse Horuse commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Fixes a SIGABRT crash on Linux when a device volume is set to 0%.

set_device_volume skipped channel initialization when scalar <= 0, so an empty ChannelVolumes::default() (0 channels) was passed to set_sink_volume_by_name / set_source_volume_by_name. libpulse rejects it client-side (pa_cvolume_valid fails) and returns a NULL operation, which trips an assertion in libpulse-binding and aborts the process.

Changes in src-tauri/src/audio/volume/linux.rs:

  • New channel_volumes_for(scalar) always returns a valid ChannelVolumes: <= 0 and NaN map to Volume::MUTED on all channels, positive values keep the existing cubic mapping.
  • set_device_volume returns false if the volumes are invalid instead of handing them to PulseAudio.
  • resolve_device rejects empty / whitespace names (including an empty monitor: suffix and an empty default sink/source from the server), since an empty name also makes libpulse return a NULL operation.
  • Unit tests for channel_volumes_for (zero, negative, NaN, positive) and device_volume_from.

All Linux volume writes go through set_device_volume, so the fix covers output devices (speakers), input devices (microphones) and monitor: sources (system / app audio). There is no per-app sink-input / source-output volume control on Linux, so no other call sites are affected.

Why is this the right approach?

The crash comes from building an invalid ChannelVolumes, so the fix makes the builder always produce a valid one instead of catching the failure later. Muting is expressed as Volume::MUTED on every channel plus the existing set_*_mute_by_name call, which is what PulseAudio expects for 0%. Validating the device name in resolve_device closes the other client-side path that returns a NULL operation. No new dependencies.

Checklist

  • Diff is limited to the change - no unrelated edits
  • bun run check passes
  • cargo check --manifest-path src-tauri/Cargo.toml passes
  • bun run format leaves the tree clean
  • Generated TS types are committed with the Rust change (if any)
  • No new dependency without a reason in the PR description
  • I read the RT audio path section of docs/CONCEPT.md and confirmed this change adds no allocations, locks, or syscalls to cpal / SCK callbacks or DspWorker::run

Platform coverage

  • Developed on: macOS 14
  • Tested on: macOS 14, Fedora 44
  • What I did to test: moved the volume slider to 0%
  • Not tested: Windows

Per-OS file touched: src-tauri/src/audio/volume/linux.rs only. macOS (volume/macos.rs) and Windows (volume/windows.rs) are not affected.

Related

ChannelVolumes::default() has 0 channels and is rejected as invalid by PulseAudio (pa_cvolume_valid returns false). When setting volume to 0%, the previous code skipped channel volume initialization, passing the empty structure to set_sink_volume_by_name / set_source_volume_by_name. PulseAudio returned a NULL operation pointer, triggering an assertion panic in libpulse-binding and SIGABRT.

Now channel_volumes_for guarantees valid ChannelVolumes with CHANNELS_MAX channels set to Volume::MUTED whenever scalar <= 0 or is NaN, and device names are validated against empty strings.
@Horuse
Horuse merged commit e8ee676 into main Sep 26, 2026
9 checks passed
@Horuse
Horuse deleted the fix/issue-53-speaker-zero-volume-crash branch September 26, 2026 14:49
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.

[crash] Native crash: SIGABRT when setting Speaker volume to 0%

1 participant