Skip to content

fix(pipewire): fix volume control for devices without route definitions#570

Open
Rival wants to merge 1 commit intoquickshell-mirror:masterfrom
Rival:master
Open

fix(pipewire): fix volume control for devices without route definitions#570
Rival wants to merge 1 commit intoquickshell-mirror:masterfrom
Rival:master

Conversation

@Rival
Copy link

@Rival Rival commented Feb 15, 2026

Fix multiple issues that prevented volume control from working for certain audio devices (particularly headphones) where the ALSA/Pipewire device has no route definitions.

Backend fixes:

  • Fix division by zero in PwNodeBoundAudio::averageVolume() - returns 0.0f when mVolumes vector is empty instead of causing NaN/Infinity
  • Fix null pointer dereferences in PwVolumeProps::parseSpaPod() by adding null checks for volumesProp, channelsProp, and muteProp before access
  • Add PwDevice::hasRouteDevice() method to check if a routeDevice exists
  • Modify PwNode::shouldUseDevice() to check hasRouteDevice() - this prevents nodes from attempting to use device control when the device has no route definitions, forcing fallback to node-level SPA param control
  • Add forward declaration for PwVolumeProps in device.hpp to avoid circular dependency (device.hpp includes node.hpp which includes device.hpp)

Root cause: Devices like the Sennheiser GSX 1000 (id=56) have routeDevice=0 but no route definitions in routeDeviceIndexes. The original code would skip node-level volume updates in favor of device updates that never came, resulting in no volume events being emitted.

Fix: Nodes now use node-level control when hasRouteDevice() returns false, ensuring volume events are properly emitted via SPA_PROP_Props.

Fix multiple issues that prevented volume control from working for certain
audio devices (particularly headphones) where the ALSA/Pipewire device has
no route definitions.

Backend fixes:
- Fix division by zero in PwNodeBoundAudio::averageVolume() - returns 0.0f
  when mVolumes vector is empty instead of causing NaN/Infinity
- Fix null pointer dereferences in PwVolumeProps::parseSpaPod() by adding
  null checks for volumesProp, channelsProp, and muteProp before access
- Add PwDevice::hasRouteDevice() method to check if a routeDevice exists
- Modify PwNode::shouldUseDevice() to check hasRouteDevice() - this prevents
  nodes from attempting to use device control when the device has no route
  definitions, forcing fallback to node-level SPA param control
- Add forward declaration for PwVolumeProps in device.hpp to avoid
  circular dependency (device.hpp includes node.hpp which includes device.hpp)

Root cause: Devices like the Sennheiser GSX 1000 (id=56) have routeDevice=0
but no route definitions in routeDeviceIndexes. The original code would skip
node-level volume updates in favor of device updates that never came,
resulting in no volume events being emitted.

Fix: Nodes now use node-level control when hasRouteDevice() returns false,
ensuring volume events are properly emitted via SPA_PROP_Props.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@outfoxxed
Copy link
Member

These changes look good, however the device/route management has been somewhat hard to get right in the past. Please make sure volumes persist after a reboot if they do when using pulse tools, as this is the biggest one.

@Rival
Copy link
Author

Rival commented Feb 17, 2026

@outfoxxed Confirmed - rebooted and volumes persisted correctly on both devices, matching the values set before reboot. It seems changes go through the same PipeWire route/device volume mechanisms that wpctl/pactl use, so persistence works as expected.

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.

2 participants

Comments