The WirePlumber module registers three async callbacks (onDefaultNodesApiLoaded, onMixerApiLoaded, onPluginActivated) that receive a raw self pointer with a NULL GCancellable. WirePlumber cannot withdraw an in-flight callback, so if the module is destroyed before a queued callback fires (e.g. a temporary output/bar is removed while a component load is still pending, or during an audio route transition), the callback dereferences the freed self, causing heap corruption / a crash. Guard each of these callbacks with isModuleAlive(), which checks the existing static modules registry. The destructor already removes this from the registry before any teardown, so a missing entry means self is dangling and the callback bails out without touching it. A GCancellable cannot fix this cleanly here: every callback dereferences self on its first line, and wp_core_load_component completes via a WpTransition (not a GTask), so the cancellable is not recoverable from the GAsyncResult either. The liveness check must not touch self at all. Fixes #3974.
72 lines
2.5 KiB
C++
72 lines
2.5 KiB
C++
#pragma once
|
|
|
|
#include <fmt/format.h>
|
|
#include <wp/wp.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
#include "ALabel.hpp"
|
|
|
|
namespace waybar::modules {
|
|
|
|
class Wireplumber : public ALabel {
|
|
public:
|
|
Wireplumber(const std::string&, const Json::Value&);
|
|
virtual ~Wireplumber();
|
|
auto update() -> void override;
|
|
|
|
private:
|
|
void asyncLoadRequiredApiModules();
|
|
void prepare(waybar::modules::Wireplumber* self);
|
|
void activatePlugins();
|
|
static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
|
|
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 onObjectManagerInstalled(waybar::modules::Wireplumber* self);
|
|
static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id);
|
|
static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
|
|
|
|
bool handleScroll(GdkEventScroll* e) override;
|
|
std::vector<std::string> getWPIcon();
|
|
|
|
static std::list<waybar::modules::Wireplumber*> modules;
|
|
// Returns true while `self` is still a live module. Async load/activation callbacks use this to
|
|
// avoid dereferencing a `self` that was destroyed before the callback fired (see #3974).
|
|
static bool isModuleAlive(waybar::modules::Wireplumber* self);
|
|
|
|
uint32_t resolvePhysicalSink(uint32_t start_id);
|
|
uint32_t findPlaybackNodeId(const gchar* description);
|
|
uint32_t get_linked_sink_id(WpObjectManager* om, uint32_t from_node_id);
|
|
uint32_t get_linked_node_from_output_ports(WpObjectManager* om, uint32_t from_node_id);
|
|
|
|
WpCore* wp_core_;
|
|
GPtrArray* apis_;
|
|
WpObjectManager* om_;
|
|
WpPlugin* mixer_api_;
|
|
WpPlugin* def_nodes_api_;
|
|
gchar* default_node_name_;
|
|
uint32_t pending_plugins_;
|
|
bool muted_;
|
|
double volume_;
|
|
double min_step_;
|
|
uint32_t node_id_{0};
|
|
std::string node_name_;
|
|
std::string source_name_;
|
|
gchar* type_;
|
|
uint32_t source_node_id_;
|
|
bool source_muted_;
|
|
double source_volume_;
|
|
gchar* default_source_name_;
|
|
bool only_physical_;
|
|
bool resolved_physical_;
|
|
std::string form_factor_;
|
|
};
|
|
|
|
} // namespace waybar::modules
|