Merge pull request #5176 from Alexays/fix/review-lowsev

fix: resource-leak / correctness cleanups from the 0.15.0..master review (low severity)
This commit is contained in:
Alexis Rouillard
2026-07-05 10:37:59 +02:00
committed by GitHub
3 changed files with 107 additions and 60 deletions
+5
View File
@@ -79,13 +79,17 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st
// Make the GtkBuilder and check for errors in his parsing // Make the GtkBuilder and check for errors in his parsing
if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {
g_object_unref(builder);
throw std::runtime_error("Error found in the file " + menuFile); throw std::runtime_error("Error found in the file " + menuFile);
} }
menu_ = gtk_builder_get_object(builder, "menu"); menu_ = gtk_builder_get_object(builder, "menu");
if (menu_ == nullptr) { if (menu_ == nullptr) {
g_object_unref(builder);
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
} }
// Keep the menu alive after dropping the transient GtkBuilder.
g_object_ref(menu_);
submenus_ = std::map<std::string, GtkMenuItem*>(); submenus_ = std::map<std::string, GtkMenuItem*>();
menuActionsMap_ = std::map<std::string, std::string>(); menuActionsMap_ = std::map<std::string, std::string>();
@@ -98,6 +102,7 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)menuActionsMap_[key].c_str()); (gpointer)menuActionsMap_[key].c_str());
} }
g_object_unref(builder);
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
} }
+9 -4
View File
@@ -123,8 +123,9 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
} }
submenus_[key] = GTK_MENU_ITEM(item); submenus_[key] = GTK_MENU_ITEM(item);
menuActionsMap_[key] = it->asString(); menuActionsMap_[key] = it->asString();
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), g_signal_connect_data(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)g_strdup(menuActionsMap_[key].c_str())); g_strdup(menuActionsMap_[key].c_str()), (GClosureNotify)g_free,
(GConnectFlags)0);
} }
g_object_unref(builder); g_object_unref(builder);
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
@@ -183,7 +184,9 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) { if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) {
static bool warned = false; static bool warned = false;
if (!warned) { if (!warned) {
spdlog::warn("format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", \"max\": N}"); spdlog::warn(
"format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", "
"\"max\": N}");
warned = true; warned = true;
} }
continue; continue;
@@ -229,7 +232,9 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) { if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) {
static bool warned = false; static bool warned = false;
if (!warned) { if (!warned) {
spdlog::warn("format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", \"max\": N}"); spdlog::warn(
"format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", "
"\"max\": N}");
warned = true; warned = true;
} }
continue; continue;
+72 -35
View File
@@ -4,6 +4,7 @@
#include <pulse/def.h> #include <pulse/def.h>
#include <pulse/error.h> #include <pulse/error.h>
#include <pulse/introspect.h> #include <pulse/introspect.h>
#include <pulse/operation.h>
#include <pulse/subscribe.h> #include <pulse/subscribe.h>
#include <pulse/volume.h> #include <pulse/volume.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@@ -104,16 +105,17 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) {
} }
break; break;
case PA_CONTEXT_READY: case PA_CONTEXT_READY:
pa_context_get_server_info(c, serverInfoCb, data); if (auto* o = pa_context_get_server_info(c, serverInfoCb, data)) pa_operation_unref(o);
pa_context_set_subscribe_callback(c, subscribeCb, data); pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, if (auto* o = pa_context_subscribe(c,
static_cast<enum pa_subscription_mask>( static_cast<enum pa_subscription_mask>(
static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) | static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) | static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) | static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) | static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)), static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
nullptr, nullptr); nullptr, nullptr))
pa_operation_unref(o);
break; break;
case PA_CONTEXT_FAILED: case PA_CONTEXT_FAILED:
if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) { if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
@@ -164,15 +166,18 @@ void AudioBackend::subscribeCb(pa_context* context, pa_subscription_event_type_t
return; return;
} }
if (facility == PA_SUBSCRIPTION_EVENT_SERVER) { if (facility == PA_SUBSCRIPTION_EVENT_SERVER) {
pa_context_get_server_info(context, serverInfoCb, data); if (auto* o = pa_context_get_server_info(context, serverInfoCb, data)) pa_operation_unref(o);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); if (auto* o = pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data))
pa_operation_unref(o);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { } else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
pa_context_get_sink_info_list(context, sinkInfoCb, data); if (auto* o = pa_context_get_sink_info_list(context, sinkInfoCb, data)) pa_operation_unref(o);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); if (auto* o = pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data))
pa_operation_unref(o);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
pa_context_get_source_info_list(context, sourceInfoCb, data); if (auto* o = pa_context_get_source_info_list(context, sourceInfoCb, data))
pa_operation_unref(o);
} }
} }
@@ -184,7 +189,9 @@ void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) {
if (success != 0) { if (success != 0) {
if ((backend->context_ != nullptr) && if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); if (auto* o = pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_,
sinkInfoCb, data))
pa_operation_unref(o);
} }
} else { } else {
spdlog::debug("Volume modification failed"); spdlog::debug("Volume modification failed");
@@ -196,8 +203,9 @@ void AudioBackend::sourceVolumeModifyCb(pa_context* c, int success, void* data)
if (success != 0) { if (success != 0) {
if ((backend->context_ != nullptr) && if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_source_info_by_index(backend->context_, backend->source_idx_, sourceInfoCb, if (auto* o = pa_context_get_source_info_by_index(backend->context_, backend->source_idx_,
data); sourceInfoCb, data))
pa_operation_unref(o);
} }
} else { } else {
spdlog::debug("Source volume modification failed"); spdlog::debug("Source volume modification failed");
@@ -231,13 +239,28 @@ void AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, in
} }
} }
if (const auto mapping = backend->sink_mapping_.find(backend->current_sink_name_); // Resolve which sink to report deterministically, independent of the order in
// which PulseAudio enumerates sinks during a sink_info_list sweep.
//
// When the default sink has an explicit mapping, the mapped target sink is the
// definitive selection ("the sink named by the value is considered to be the
// current sink instead of the one named by the key"): only that target sink may
// write the reported volume/mute state. The mapping is keyed on the stable
// default_sink_name (set once per server-info update) rather than on the mutable
// current_sink_name_. Previously the mapping override and the "pick a running
// sink" fallback both mutated current_sink_name_ mid-sweep, so the mapping could
// be overridden by whichever running sink happened to be enumerated, and the
// reported volume depended on enumeration order.
if (const auto mapping = backend->sink_mapping_.find(backend->default_sink_name);
mapping != backend->sink_mapping_.end()) { mapping != backend->sink_mapping_.end()) {
if (i->name == mapping->second) { if (i->name != mapping->second) {
// A mapping is in effect but this is not the mapped target sink; ignore it so
// it can never override the target's reported volume.
return;
}
backend->current_sink_name_ = i->name; backend->current_sink_name_ = i->name;
} backend->current_sink_running_ = (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
} } else {
backend->default_sink_running_ = backend->default_sink_name == i->name && backend->default_sink_running_ = backend->default_sink_name == i->name &&
(i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE); (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
@@ -255,6 +278,7 @@ void AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, in
backend->current_sink_name_ = i->name; backend->current_sink_name_ = i->name;
backend->current_sink_running_ = true; backend->current_sink_running_ = true;
} }
}
if (backend->current_sink_name_ == i->name) { if (backend->current_sink_name_ == i->name) {
// Safely copy the volume structure // Safely copy the volume structure
@@ -317,8 +341,8 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo
backend->default_sink_name = i->default_sink_name ? i->default_sink_name : ""; backend->default_sink_name = i->default_sink_name ? i->default_sink_name : "";
backend->default_source_name_ = i->default_source_name ? i->default_source_name : ""; backend->default_source_name_ = i->default_source_name ? i->default_source_name : "";
pa_context_get_sink_info_list(context, sinkInfoCb, data); if (auto* o = pa_context_get_sink_info_list(context, sinkInfoCb, data)) pa_operation_unref(o);
pa_context_get_source_info_list(context, sourceInfoCb, data); if (auto* o = pa_context_get_source_info_list(context, sourceInfoCb, data)) pa_operation_unref(o);
} }
uint16_t AudioBackend::getVolume(PulseaudioTarget target) const { uint16_t AudioBackend::getVolume(PulseaudioTarget target) const {
@@ -368,10 +392,13 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m
// Apply the volume change // Apply the volume change
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
if (is_source) { if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume,
this); sourceVolumeModifyCb, this))
pa_operation_unref(o);
} else { } else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this))
pa_operation_unref(o);
} }
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
@@ -412,10 +439,13 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
// No need to continue with volume change if we had to create a new structure // No need to continue with volume change if we had to create a new structure
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
if (is_source) { if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume,
this); sourceVolumeModifyCb, this))
pa_operation_unref(o);
} else { } else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this))
pa_operation_unref(o);
} }
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
return; return;
@@ -458,10 +488,13 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
// Apply the volume change // Apply the volume change
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
if (is_source) { if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, if (auto* o = pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume,
this); sourceVolumeModifyCb, this))
pa_operation_unref(o);
} else { } else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); if (auto* o = pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this))
pa_operation_unref(o);
} }
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
@@ -478,8 +511,9 @@ void AudioBackend::toggleSinkMute() {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = !muted_; muted_ = !muted_;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr, if (auto* o = pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_),
nullptr); nullptr, nullptr))
pa_operation_unref(o);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
@@ -487,8 +521,9 @@ void AudioBackend::toggleSinkMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = mute; muted_ = mute;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr, if (auto* o = pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_),
nullptr); nullptr, nullptr))
pa_operation_unref(o);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
@@ -496,8 +531,9 @@ void AudioBackend::toggleSourceMute() {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = !source_muted_; source_muted_ = !source_muted_;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_), if (auto* o = pa_context_set_source_mute_by_index(
nullptr, nullptr); context_, source_idx_, static_cast<int>(source_muted_), nullptr, nullptr))
pa_operation_unref(o);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
@@ -505,8 +541,9 @@ void AudioBackend::toggleSourceMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = mute; source_muted_ = mute;
pa_threaded_mainloop_lock(mainloop_); pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_), if (auto* o = pa_context_set_source_mute_by_index(
nullptr, nullptr); context_, source_idx_, static_cast<int>(source_muted_), nullptr, nullptr))
pa_operation_unref(o);
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }