fix(wireplumber): guard async callbacks by connection generation; wire scroll once

Addresses review on #5168:

- Generational aliasing (blocking): setupConnection()/onReconnectTimeout()
  rebuild wp_core_/om_/pending_plugins_ in place on the same self, but the
  async load/activate callbacks carried no generation, and isModuleAlive()
  only proves self still exists. If PipeWire dropped again while a previous
  connection's async chain was still in flight, a stale completion would run
  against the rebuilt connection (a stray --pending_plugins_, an out-of-order
  install_object_manager), re-creating #2882's stale/blank state. Each async
  call now carries an AsyncCall{self, generation}; connection_generation_ is
  bumped in setupConnection(), and every callback drops out when its
  generation no longer matches (checked after isModuleAlive short-circuits).

- Duplicate scroll handlers: onMixerApiLoaded re-runs on every reconnect and
  connected a new scroll handler each time (dead but accumulating). Moved the
  one-time wiring to the constructor; handleScroll no-ops while mixer_api_ is
  null, so wiring it before the first connect is safe.
This commit is contained in:
Alex
2026-07-05 09:53:11 +02:00
parent 0b88b4ef0c
commit acc7060be6
2 changed files with 46 additions and 17 deletions
+7 -4
View File
@@ -29,10 +29,9 @@ class Wireplumber : public ALabel {
static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
static void updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateSourceName(waybar::modules::Wireplumber* self, uint32_t id); // NEW
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self);
static void onMixerApiLoaded(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onPluginActivated(WpObject* p, GAsyncResult* res, gpointer data);
static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, gpointer data);
static void onMixerApiLoaded(WpObject* p, GAsyncResult* res, gpointer data);
static void onObjectManagerInstalled(waybar::modules::Wireplumber* self);
static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id);
static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
@@ -57,6 +56,10 @@ class Wireplumber : public ALabel {
WpPlugin* def_nodes_api_;
gchar* default_node_name_;
uint32_t pending_plugins_;
// Bumped on every (re)connection. The async load/activate callbacks capture the generation they
// were scheduled under (via their user_data) and no-op if it no longer matches, so a completion
// from a connection that was already torn down cannot corrupt the new generation's state (#2882).
uint32_t connection_generation_{0};
bool muted_;
double volume_;
double min_step_;