Skip to content

feat(widgets/volume): add "Hide When Inactive" toggle to volume widget - #3927

Open
Aron-dxd wants to merge 3 commits into
noctalia-dev:mainfrom
Aron-dxd:feature/hide-volume-widget
Open

feat(widgets/volume): add "Hide When Inactive" toggle to volume widget#3927
Aron-dxd wants to merge 3 commits into
noctalia-dev:mainfrom
Aron-dxd:feature/hide-volume-widget

Conversation

@Aron-dxd

Copy link
Copy Markdown

Summary

This pull request adds an optional toggle to hide the volume widget when it is not in use. When enabled, the widget will hide itself unless the device(output or input) is currently in use. In the case of inputs, it's hidden unless some application is capturing audio from a microphone. For outputs, the widget is hidden unless any application is currently playing audio.

Motivation

An input volume widget that is always visible even when nothing is capturing audio adds clutter to a bar for those who want only relevant controls. This lets people configure the widget to be hidden when it isn't relevant.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Build / packaging

Related Issue

Closes #3917

Testing

  • Ran just format and just build. Both passed.
  • just test passes 79/80. process_test fails in the same manner on a clean main
  • Built and ran the debug binary directly.

Manual Coverage

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Tested with different bar positions and density settings
  • Tested at different interface scaling values
  • Tested with multiple monitors

Screenshots / Videos

Checklist

  • This PR is ready for review, or it is marked as Draft.
  • I read and followed the relevant guidance in CONTRIBUTING.md.
  • I ran just format with clang-format v22+ installed, or this PR has no code changes.
  • I ran the relevant build or test commands, or explained why they were not run.
  • I self-reviewed the changes.
  • I checked for new warnings or errors.
  • I will update end-user documentation after merge, or this PR does not change user-facing configuration or behavior.
  • I added or updated assets/translations/en.json, or this PR adds no new user-facing strings.
  • I did not edit non-English translation files unless this PR is explicitly for translation tooling, an import/export sync, or a maintainer-requested locale change.
  • I used the existing canonical names for config keys, IPC names, paths, and identifiers.

Additional Notes

The original issue only asked for a toggle in the case of microphone. A single setting for when a device is not in use in both input and output cases seemed more consistent than adding a setting that silently does nothing for output widgets or conditionally rendering the toggle only when input is selected. I also didn't find any widgets that do conditional rendering of a setting based on the value of another.

This PR also only checks capture activity and not device presence. A mic that is connected but idle will be treated the same as no mic present. In both cases, the widget will be hidden when the toggle is enabled.

@Aron-dxd Aron-dxd changed the title Add "Hide When Inactive" toggle to volume widget feat(widget/volume): add "Hide When Inactive" toggle to volume widget Aug 14, 2026
@Aron-dxd Aron-dxd changed the title feat(widget/volume): add "Hide When Inactive" toggle to volume widget feat(widgets/volume): add "Hide When Inactive" toggle to volume widget Aug 14, 2026
@ItsLemmy

Copy link
Copy Markdown
Collaborator

[P1] Required clang-tidy check fails

src/shell/bar/widgets/volume_widget.cpp:113

isActive = std::any_of(captures.begin(), captures.end(), ...);

The repository’s required scoped lint reports:

error: use a ranges version of this algorithm
[modernize-use-ranges,-warnings-as-errors]

Use:

isActive = std::ranges::any_of(captures, [](const auto& capture) {
return capture.kind == PrivacyCaptureKind::Microphone;
});

[P2] Output stream existence does not mean active playback

src/shell/bar/widgets/volume_widget.cpp:117

isActive = !m_audio->state().programOutputs.empty();

programOutputs is the application-volume inventory. PipeWireService::rebuildState() adds every
classified Stream/Output/Audio node without considering its pw_node_info::state. Applications can
retain idle or suspended streams after playback stops, so hide_when_inactive = true can leave the
output widget visible indefinitely.

PipeWire distinguishes RUNNING, IDLE, and SUSPENDED node states, but NodeData/AudioNode currently
discard that state. Either:

  • retain node state and count only running output streams; or
  • scope this setting to input widgets rather than advertising incorrect output behavior.

Reference: PipeWire node states
(https://docs.pipewire.org/group__pw__node.html#gac493839c0b0a1d0b935969f2e990a2df).

@ItsLemmy
ItsLemmy marked this pull request as draft August 15, 2026 04:57
@Aron-dxd

Copy link
Copy Markdown
Author

Fixed the warning and added PipeWire node state to AudioNode. Now check is only for RUNNING streams.

@Aron-dxd
Aron-dxd marked this pull request as ready for review August 15, 2026 19:26
@ItsLemmy

Copy link
Copy Markdown
Collaborator

The prior clang-tidy finding is fixed. The output check now tests AudioNodeState::Running, but the
state snapshot is not refreshed when playback transitions.

[P1] Rebuild AudioState when node state changes

src/pipewire/pipewire_service.cpp:1154-1164

onNodeInfo() writes the latest PipeWire state into nd.nodeState, but the rebuildState() condition
at lines 1239–1243 does not consider whether that state changed.

For an already-classified Stream/Output/Audio, a state-only callback has:

  • wasStreamReady == true
  • filterPropsChanged == false
  • wasProgramStream == isStream
  • neither privacy condition set

Therefore rebuildState() is skipped. m_audio->state().programOutputs retains its old state, so the
widget can:

  • remain visible after playback becomes idle or suspended; or
  • remain hidden when an existing idle stream starts running.

Capture the previous state, detect a transition, and include that transition in the rebuild
condition. Alternatively, handle PW_NODE_CHANGE_MASK_STATE explicitly. The externally exposed
AudioNode::nodeState must be synchronized before consumers inspect it.

@ItsLemmy
ItsLemmy marked this pull request as draft August 15, 2026 22:29
@Aron-dxd

Copy link
Copy Markdown
Author

Can you check now?

On a side note, when I was on google meet, the stream stayed in the Running state even after ending the call(without closing the meet tab). But that is probably something with browsers/google meet.

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.

[FEATURE] Option to hide input volume panel widget when microphone not in use

2 participants