Skip to content

Commit d9f2a43

Browse files
authored
Merge pull request #5165 from Alexays/fix/crash-batch3
fix: mpris resume SIGSEGV (#5124), wireplumber async UAF (#3974), stale reload batch state (#4129)
2 parents 54db664 + c19abf3 commit d9f2a43

4 files changed

Lines changed: 39 additions & 1 deletion

File tree

include/modules/wireplumber.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class Wireplumber : public ALabel {
3636
std::vector<std::string> getWPIcon();
3737

3838
static std::list<waybar::modules::Wireplumber*> modules;
39+
// Returns true while `self` is still a live module. Async load/activation callbacks use this to
40+
// avoid dereferencing a `self` that was destroyed before the callback fired (see #3974).
41+
static bool isModuleAlive(waybar::modules::Wireplumber* self);
3942

4043
uint32_t resolvePhysicalSink(uint32_t start_id);
4144
uint32_t findPlaybackNodeId(const gchar* description);

src/client.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,15 @@ void waybar::Client::bindInterfaces() {
286286
// Clear stale outputs from previous run
287287
outputs_.clear();
288288

289+
// Also drop any batch state that was left pending from the previous run. On
290+
// reload the GApplication is swapped but the default main context (and its
291+
// queued PRIORITY_HIGH_IDLE createBarsBatch source) survives; pending_outputs_
292+
// would then hold dangling waybar_output* into the just-cleared outputs_ list,
293+
// which createBarsBatch's address comparison can mis-match if the freed slot is
294+
// reused. Reset so the next run schedules its batch from a clean state (#4129).
295+
pending_outputs_.clear();
296+
bars_scheduled_ = false;
297+
289298
// add existing outputs and subscribe to updates
290299
for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) {
291300
auto monitor = gdk_display->get_monitor(i);

src/modules/mpris/mpris.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
406406
if (mpris->player_ == "playerctld") {
407407
mpris->dp.emit();
408408
} else if (mpris->player_ == player_name->name) {
409+
// Don't touch GTK widgets directly from the playerctl callback: on resume
410+
// from suspend this can run in a re-entrant / torn-down state and crash in
411+
// Gtk::Widget::set_visible. Only update state + emit; update() (on the main
412+
// thread) hides the module when there is no player. See #5124.
409413
mpris->player = nullptr;
410-
mpris->event_box_.set_visible(false);
411414
mpris->dp.emit();
412415
}
413416
}

src/modules/wireplumber.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
1010

1111
std::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;
1212

13+
// Async load/activation callbacks (onDefaultNodesApiLoaded, onMixerApiLoaded, onPluginActivated)
14+
// are handed a raw `self` pointer with no GCancellable, and WirePlumber has no way to withdraw an
15+
// in-flight callback. If the module is destroyed before such a callback fires (e.g. an output/bar
16+
// is removed while a component load is still pending, or during an audio route transition), the
17+
// callback would dereference a freed `self`. The destructor removes `this` from this registry
18+
// before any teardown, so a missing entry means `self` is dangling and the callback must bail out
19+
// without touching it. See https://github.com/Alexays/Waybar/issues/3974.
20+
bool waybar::modules::Wireplumber::isModuleAlive(waybar::modules::Wireplumber* self) {
21+
return std::find(modules.begin(), modules.end(), self) != modules.end();
22+
}
23+
1324
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
1425
: ALabel(config, "wireplumber", id, "{volume}%"),
1526
wp_core_(nullptr),
@@ -387,6 +398,10 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
387398

388399
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
389400
waybar::modules::Wireplumber* self) {
401+
if (!isModuleAlive(self)) {
402+
return;
403+
}
404+
390405
const auto* pluginName = wp_plugin_get_name(WP_PLUGIN(p));
391406
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, pluginName);
392407
g_autoptr(GError) error = nullptr;
@@ -432,6 +447,10 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
432447

433448
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
434449
waybar::modules::Wireplumber* self) {
450+
if (!isModuleAlive(self)) {
451+
return;
452+
}
453+
435454
gboolean success = FALSE;
436455
g_autoptr(GError) error = nullptr;
437456

@@ -453,6 +472,10 @@ void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncRe
453472

454473
void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* res,
455474
waybar::modules::Wireplumber* self) {
475+
if (!isModuleAlive(self)) {
476+
return;
477+
}
478+
456479
gboolean success = FALSE;
457480
g_autoptr(GError) error = nullptr;
458481

0 commit comments

Comments
 (0)