From e355b40c66279bd62411c388f26ffde26ebdee3c Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:34:50 +0100 Subject: [PATCH 1/3] Add `format-bluetooth` support to wireplumber module --- src/modules/wireplumber.cpp | 44 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index a43ad29b..4b25f817 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -14,16 +14,16 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val mixer_api_(nullptr), def_nodes_api_(nullptr), default_node_name_(nullptr), - default_source_name_(nullptr), pending_plugins_(0), muted_(false), - source_muted_(false), volume_(0.0), - source_volume_(0.0), min_step_(0.0), node_id_(0), + type_(nullptr), source_node_id_(0), - type_(nullptr) { + source_muted_(false), + source_volume_(0.0), + default_source_name_(nullptr) { waybar::modules::Wireplumber::modules.push_back(this); wp_init(WP_INIT_PIPEWIRE); @@ -418,10 +418,33 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() { auto waybar::modules::Wireplumber::update() -> void { auto format = format_; std::string tooltipFormat; + std::string format_name = "format"; + + // Handle sink bluetooth state + const std::string name = default_node_name_ != nullptr ? default_node_name_ : ""; + + auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos; + if (bt) { + // format = + // config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format; + format_name += "-bluetooth"; + label_.get_style_context()->add_class("bluetooth"); + } else { + label_.get_style_context()->remove_class("bluetooth"); + } // Handle sink mute state if (muted_) { - format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; + // if (bt) + // format = config_["format-bluetooth-muted"].isString() + // ? config_["format-bluetooth-muted"].asString() + // : format; + // else + // format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; + // Check muted bluetooth format exists, otherwise fall back to default muted format. + if (format_name != "format" && !config_[format_name + "-muted"].isString()) + format_name = "format"; + format_name += "-muted"; label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("sink-muted"); } else { @@ -441,13 +464,10 @@ auto waybar::modules::Wireplumber::update() -> void { // Get the state and apply state-specific format if available auto state = getState(vol); - if (!state.empty()) { - std::string format_name = muted_ ? "format-muted" : "format"; - std::string state_format_name = format_name + "-" + state; - if (config_[state_format_name].isString()) { - format = config_[state_format_name].asString(); - } - } + if (!state.empty() && config_[format_name + "-" + state].isString()) + format = config_[format_name + "-" + state].asString(); + else if (config_[format_name].isString()) + format = config_[format_name].asString(); // Prepare source format string (similar to PulseAudio) std::string format_source = "{volume}%"; From 644a3575416e457286ffb691d0ee941f52d5a175 Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:27:45 +0200 Subject: [PATCH 2/3] [wireplumber] Add support for device.form-factor --- include/modules/wireplumber.hpp | 2 + src/modules/wireplumber.cpp | 72 ++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index 0565704f..9b7d5a7b 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -33,6 +33,7 @@ class Wireplumber : public ALabel { static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self); bool handleScroll(GdkEventScroll* e) override; + std::vector getWPIcon(); static std::list modules; @@ -54,6 +55,7 @@ class Wireplumber : public ALabel { bool source_muted_; double source_volume_; gchar* default_source_name_; + std::string form_factor_; }; } // namespace waybar::modules diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 4b25f817..d4962e68 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -23,7 +23,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val source_node_id_(0), source_muted_(false), source_volume_(0.0), - default_source_name_(nullptr) { + default_source_name_(nullptr), + form_factor_("") { waybar::modules::Wireplumber::modules.push_back(this); wp_init(WP_INIT_PIPEWIRE); @@ -97,6 +98,36 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* : description != nullptr ? description : "Unknown node name"; spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); + + // find form-factor only if sink + if (g_strcmp0(self->type_, "Audio/Sink") == 0) { + const auto* devid = wp_properties_get(properties, "device.id"); + spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); + + auto* devproxy = static_cast( + wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, + "bound-id", "=s", devid, nullptr)); + + if (devproxy == nullptr) { + auto err = fmt::format("Object '{}' not found\n", devid); + spdlog::error("[{}]: {}", self->name_, err); + throw std::runtime_error(err); + } + + g_autoptr(WpProperties) devprop = + WP_IS_PIPEWIRE_OBJECT(devproxy) != 0 + ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(devproxy)) + : wp_properties_new_empty(); + devprop = wp_properties_ensure_unique_owner(devprop); + + if (const auto* ff = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(devproxy), "device.form-factor")) { + self->form_factor_ = ff; + spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); + } else { + self->form_factor_ = ""; + } + } } void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self, @@ -358,6 +389,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) { "=s", self->type_, nullptr); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", "Audio/Source", nullptr); + wp_object_manager_add_interest(om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", + "=s", "Audio/Device", nullptr); } void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, @@ -415,6 +448,33 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() { this); } +static const std::array ports = { + "headphone", "speaker", "headset", "hands-free", "portable", "car", "hifi", +}; + +std::vector waybar::modules::Wireplumber::getWPIcon() { + std::vector res; + if (muted_) { + res.emplace_back(node_name_ + "-muted"); + } + res.push_back(node_name_); + res.push_back(source_name_); + std::transform(form_factor_.begin(), form_factor_.end(), form_factor_.begin(), ::tolower); + for (auto const& port : ports) { + if (form_factor_.find(port) != std::string::npos) { + if (muted_) { + res.emplace_back(port + "-muted"); + } + res.push_back(port); + break; + } + } + if (muted_) { + res.emplace_back("default-muted"); + } + return res; +} + auto waybar::modules::Wireplumber::update() -> void { auto format = format_; std::string tooltipFormat; @@ -485,10 +545,10 @@ auto waybar::modules::Wireplumber::update() -> void { std::string formatted_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol)); - std::string markup = - fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); + std::string markup = fmt::format( + fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol, getWPIcon())), fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -499,7 +559,7 @@ auto waybar::modules::Wireplumber::update() -> void { if (!tooltipFormat.empty()) { label_.set_tooltip_text(fmt::format( fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), + fmt::arg("icon", getIcon(vol, getWPIcon())), fmt::arg("format_source", formatted_source), fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); } else { label_.set_tooltip_text(node_name_); From deb8a56eb1cc284925c1f238b04c1e011293a6a7 Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:40:56 +0200 Subject: [PATCH 3/3] [wireplumber] remove old version (bluetooth) and unnecessary checks (form-factor) --- src/modules/wireplumber.cpp | 45 +++++++++---------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index d4962e68..cec7968d 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -99,34 +99,19 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* : "Unknown node name"; spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); - // find form-factor only if sink - if (g_strcmp0(self->type_, "Audio/Sink") == 0) { - const auto* devid = wp_properties_get(properties, "device.id"); - spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); + // find form-factor + const auto* devid = wp_properties_get(properties, "device.id"); + spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); - auto* devproxy = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, - "bound-id", "=s", devid, nullptr)); + auto* dev = static_cast(wp_object_manager_lookup( + self->om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=s", devid, nullptr)); - if (devproxy == nullptr) { - auto err = fmt::format("Object '{}' not found\n", devid); - spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); - } - - g_autoptr(WpProperties) devprop = - WP_IS_PIPEWIRE_OBJECT(devproxy) != 0 - ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(devproxy)) - : wp_properties_new_empty(); - devprop = wp_properties_ensure_unique_owner(devprop); - - if (const auto* ff = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(devproxy), "device.form-factor")) { - self->form_factor_ = ff; - spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); - } else { - self->form_factor_ = ""; - } + if (const auto* ff = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(dev), "device.form-factor")) { + self->form_factor_ = ff; + spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); + } else { + self->form_factor_ = ""; } } @@ -485,8 +470,6 @@ auto waybar::modules::Wireplumber::update() -> void { auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos; if (bt) { - // format = - // config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format; format_name += "-bluetooth"; label_.get_style_context()->add_class("bluetooth"); } else { @@ -495,12 +478,6 @@ auto waybar::modules::Wireplumber::update() -> void { // Handle sink mute state if (muted_) { - // if (bt) - // format = config_["format-bluetooth-muted"].isString() - // ? config_["format-bluetooth-muted"].asString() - // : format; - // else - // format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; // Check muted bluetooth format exists, otherwise fall back to default muted format. if (format_name != "format" && !config_[format_name + "-muted"].isString()) format_name = "format";