@@ -21,7 +21,8 @@ VolumeWidget::VolumeWidget(PipeWireService* audio, EasyEffectsService* easyEffec
2121 m_muteColor(options.muteColor), m_glyphOverride(std::move(options.glyph)),
2222 m_muteGlyphOverride(std::move(options.muteGlyph)),
2323 m_effectsProfileGlyphs(std::move(options.effectsProfileGlyphs)),
24- m_customImage(widget_custom_image::fromConfig(options.customImage, options.customImageColorize)) {}
24+ m_customImage(widget_custom_image::fromConfig(options.customImage, options.customImageColorize)),
25+ m_hideWhenInactive(options.hideWhenInactive && options.device == VolumeWidgetTarget::Input) {}
2526
2627void VolumeWidget::create () {
2728 auto area = ui::inputArea ({});
@@ -94,6 +95,16 @@ void VolumeWidget::doLayout(Renderer& renderer, float containerWidth, float cont
9495
9596void VolumeWidget::doUpdate (Renderer& renderer) { syncState (renderer); }
9697
98+ void VolumeWidget::syncWidgetVisibility (bool showWidget) {
99+ if (Node* rootNode = root (); rootNode != nullptr ) {
100+ if (rootNode->visible () != showWidget || rootNode->participatesInLayout () != showWidget) {
101+ rootNode->setVisible (showWidget);
102+ rootNode->setParticipatesInLayout (showWidget);
103+ requestUpdate ();
104+ }
105+ }
106+ }
107+
97108void VolumeWidget::syncState (Renderer& renderer) {
98109 if (m_audio == nullptr || (m_glyph == nullptr && m_image == nullptr ) || m_label == nullptr ) {
99110 return ;
@@ -106,18 +117,32 @@ void VolumeWidget::syncState(Renderer& renderer) {
106117 m_target == VolumeWidgetTarget::Input ? AudioEffectsProfileKind::Input : AudioEffectsProfileKind::Output;
107118 const std::string effectsProfile =
108119 m_easyEffects != nullptr ? m_easyEffects->activeEffectsProfile (kind) : std::string{};
120+ // Reuses the privacy signal: a microphone capture exists while an application is linked to a
121+ // source. Skipped entirely for the output widget, where m_hideWhenInactive is always false.
122+ const bool micActive =
123+ m_hideWhenInactive && std::ranges::any_of (m_audio->privacyState ().captures , [](const PrivacyCapture& capture) {
124+ return capture.kind == PrivacyCaptureKind::Microphone;
125+ });
109126
110127 if (volume == m_lastVolume
111128 && muted == m_lastMuted
112129 && m_isVertical == m_lastVertical
113- && effectsProfile == m_lastEffectsProfile) {
130+ && effectsProfile == m_lastEffectsProfile
131+ && micActive == m_lastMicActive) {
114132 return ;
115133 }
116134
117135 m_lastVolume = volume;
118136 m_lastMuted = muted;
119137 m_lastVertical = m_isVertical;
120138 m_lastEffectsProfile = effectsProfile;
139+ m_lastMicActive = micActive;
140+
141+ const bool showWidget = !m_hideWhenInactive || micActive;
142+ syncWidgetVisibility (showWidget);
143+ if (!showWidget) {
144+ return ;
145+ }
121146
122147 if (m_image != nullptr ) {
123148 widget_custom_image::sync (
0 commit comments