src/mainwindow.cc src/minimalstreamwidget.cc src/minimalstreamwidget.h

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Jun 23 13:58:46 UTC 2025


 src/mainwindow.cc          |   34 +++++++++++++++++-----
 src/minimalstreamwidget.cc |   67 ++++++++++++++++++++++++++++++++++++---------
 src/minimalstreamwidget.h  |   10 +++++-
 3 files changed, 88 insertions(+), 23 deletions(-)

New commits:
commit ce48aec45ffc551c6456f74a5134f23153f322f5
Author: Arun Raghavan <arun at asymptotic.io>
Date:   Fri Jun 6 11:50:12 2025 +0530

    Decay volume meters on monitor stream suspend
    
    If the stream is suspended mid peak, the volume meter just hangs at the
    last level. This can happen with a idle suspend timeout of 0 on
    PulseAudio, or just by default on PipeWire.
    
    When that happens, we attach to the frame clock and decay to zero in a
    second.
    
    Fixes: https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/issues/174

diff --git a/src/mainwindow.cc b/src/mainwindow.cc
index fa4b48e..c6ca964 100644
--- a/src/mainwindow.cc
+++ b/src/mainwindow.cc
@@ -626,7 +626,7 @@ static void suspended_callback(pa_stream *s, void *userdata) {
     MainWindow *w = static_cast<MainWindow*>(userdata);
 
     if (pa_stream_is_suspended(s))
-        w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1);
+        w->updateVolumeMeter(pa_stream_get_device_index(s), pa_stream_get_monitor_stream(s), -1);
 }
 
 static void read_callback(pa_stream *s, size_t length, void *userdata) {
@@ -1091,13 +1091,15 @@ void MainWindow::updateDeviceInfo(const pa_ext_device_restore_info &info) {
 
 
 void MainWindow::updateVolumeMeter(uint32_t source_index, uint32_t sink_input_idx, double v) {
+    MinimalStreamWidget *sw = NULL;
 
     if (sink_input_idx != PA_INVALID_INDEX) {
         SinkInputWidget *w;
 
         if (sinkInputWidgets.count(sink_input_idx)) {
             w = sinkInputWidgets[sink_input_idx];
-            w->updatePeak(v);
+            sw = static_cast<MinimalStreamWidget*>(w);
+            goto done;
         }
 
     } else {
@@ -1105,22 +1107,38 @@ void MainWindow::updateVolumeMeter(uint32_t source_index, uint32_t sink_input_id
         for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) {
             SinkWidget* w = i->second;
 
-            if (w->monitor_index == source_index)
-                w->updatePeak(v);
+            if (w->monitor_index == source_index) {
+                sw = static_cast<MinimalStreamWidget*>(w);
+                goto done;
+            }
         }
 
         for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) {
             SourceWidget* w = i->second;
 
-            if (w->index == source_index)
-                w->updatePeak(v);
+            if (w->index == source_index) {
+                sw = static_cast<MinimalStreamWidget*>(w);
+                goto done;
+            }
         }
 
         for (std::map<uint32_t, SourceOutputWidget*>::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) {
             SourceOutputWidget* w = i->second;
 
-            if (w->sourceIndex() == source_index)
-                w->updatePeak(v);
+            if (w->sourceIndex() == source_index) {
+                sw = static_cast<MinimalStreamWidget*>(w);
+                goto done;
+            }
+        }
+    }
+
+done:
+    if (sw) {
+        if (v == -1) {
+            sw->decayToZero();
+        } else {
+            sw->stopDecay();
+            sw->updatePeak(v);
         }
     }
 }
diff --git a/src/minimalstreamwidget.cc b/src/minimalstreamwidget.cc
index 92e5f08..0a85538 100644
--- a/src/minimalstreamwidget.cc
+++ b/src/minimalstreamwidget.cc
@@ -36,7 +36,9 @@ MinimalStreamWidget::MinimalStreamWidget(BaseObjectType* cobject) :
     peak(NULL),
     updating(false),
     volumeMeterEnabled(false),
-    volumeMeterVisible(true) {
+    volumeMeterVisible(true),
+    decayTickId(0),
+    decayLastFrameTime(-1) {
 }
 
 MinimalStreamWidget::~MinimalStreamWidget() {
@@ -61,22 +63,27 @@ void MinimalStreamWidget::init() {
     peakProgressBar.hide();
 }
 
-#define DECAY_STEP (1.0 / PEAKS_RATE)
+void MinimalStreamWidget::stopDecay() {
+    if (decayTickId) {
+        remove_tick_callback(decayTickId);
+        decayTickId = 0;
+    }
+}
 
-void MinimalStreamWidget::updatePeak(double v) {
-    if (lastPeak >= DECAY_STEP)
-        if (v < lastPeak - DECAY_STEP)
-            v = lastPeak - DECAY_STEP;
+void MinimalStreamWidget::updatePeak(double v, double decayStep) {
+    if (lastPeak >= decayStep)
+        if (v < lastPeak - decayStep)
+            v = lastPeak - decayStep;
 
     lastPeak = v;
 
-      if (v >= 0) {
-          peakProgressBar.set_sensitive(TRUE);
-          peakProgressBar.set_fraction(v);
-      } else {
-          peakProgressBar.set_sensitive(FALSE);
-          peakProgressBar.set_fraction(0);
-      }
+    if (v >= 0) {
+        peakProgressBar.set_sensitive(TRUE);
+        peakProgressBar.set_fraction(v);
+    } else {
+        peakProgressBar.set_sensitive(FALSE);
+        peakProgressBar.set_fraction(0);
+    }
 
     enableVolumeMeter();
 }
@@ -98,6 +105,40 @@ void MinimalStreamWidget::setVolumeMeterVisible(bool v) {
             peakProgressBar.show();
         }
     } else {
+        stopDecay();
         peakProgressBar.hide();
     }
 }
+
+bool MinimalStreamWidget::decayOnTick(const Glib::RefPtr<Gdk::FrameClock>& frameClock) {
+    auto frameTime = frameClock->get_frame_time();
+
+    if (lastPeak == 0) {
+        decayTickId = 0;
+        return false;
+    }
+
+    // Scale elapsed time (µs) so we decay in at most 1 second
+    if (frameTime != decayLastFrameTime)
+        updatePeak(0, (frameTime - decayLastFrameTime) / 1000000.0);
+
+    decayLastFrameTime = frameTime;
+
+    return true;
+}
+
+void MinimalStreamWidget::decayToZero() {
+    if (decayTickId)
+        stopDecay();
+
+    auto frameClock = get_frame_clock();
+
+    if (!frameClock) {
+        /* Widget isn't visible, set all the way to 0 */
+        updatePeak(0, 1.0);
+        return;
+    }
+
+    decayLastFrameTime = frameClock->get_frame_time();
+    decayTickId = add_tick_callback(sigc::mem_fun(*this, &MinimalStreamWidget::decayOnTick));
+}
diff --git a/src/minimalstreamwidget.h b/src/minimalstreamwidget.h
index 0f7275f..15a65e6 100644
--- a/src/minimalstreamwidget.h
+++ b/src/minimalstreamwidget.h
@@ -25,6 +25,7 @@
 #include <pulse/pulseaudio.h>
 
 #define PEAKS_RATE 144
+#define DECAY_STEP (1.0 / PEAKS_RATE)
 
 class MinimalStreamWidget : public Gtk::Box {
 public:
@@ -50,17 +51,22 @@ public:
 
     bool volumeMeterEnabled;
     void enableVolumeMeter();
-    void updatePeak(double v);
+    void updatePeak(double v, double decayStep = DECAY_STEP);
     void setVolumeMeterVisible(bool v);
 
+    void decayToZero();
+    void stopDecay();
+
 protected:
     /* Subclasses must call this after the constructor to finalize the initial
      * layout. */
     void init();
+    bool decayOnTick(const Glib::RefPtr<Gdk::FrameClock>& frame_clock);
 
 private :
     bool volumeMeterVisible;
-
+    guint decayTickId;
+    gint64 decayLastFrameTime;
 };
 
 #endif



More information about the pulseaudio-commits mailing list