From 71a53eb79d78a98d6388678c29bf46baf32ae25c Mon Sep 17 00:00:00 2001 From: Rowan Leeder Date: Wed, 25 Sep 2024 03:16:14 +1000 Subject: [PATCH 01/38] Issue-3092 Add source support to wireplumber module - Adds microphone support etc to the wireplumber module. The existing module hardcodes the selected node type to "Audio/Sink". This feature allows the user to override this via `"node-type": "Audio/Source"`. - Unlike the pulseaudio module, this change does not try to see the module manage both input and output. The same effect can be achieved by running two instances of the wireplumber module. This approach: - Works around some of the complexity overhead that seem to have caused similar PRs to stall. - Using separate module instances also allows both the microphone and speaker levels to be controlled with a scroll wheel. This is something a unified module like pulseaudio struggles with. - Similarly, separate instances allows the source volume level to be exposed as the state. Ie- the linear-gradient css patterns can be applied to both input and output. --- include/modules/wireplumber.hpp | 3 ++- man/waybar-wireplumber.5.scd | 27 +++++++++++++++++++++++++++ src/modules/wireplumber.cpp | 17 +++++++++++------ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index 6255b95f..eb39653a 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -18,7 +18,7 @@ class Wireplumber : public ALabel { private: void asyncLoadRequiredApiModules(); - void prepare(); + 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); @@ -44,6 +44,7 @@ class Wireplumber : public ALabel { double min_step_; uint32_t node_id_{0}; std::string node_name_; + gchar* type_; }; } // namespace waybar::modules diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index 9c26ebaf..ae78f184 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber. typeof: string ++ This format is used when the sound is muted. +*node-type*: ++ + typeof: string ++ + default: *Audio/Sink* ++ + The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc. + *tooltip*: ++ typeof: bool ++ default: *true* ++ @@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber. # EXAMPLES +## Basic: + ``` "wireplumber": { "format": "{volume}%", @@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber. } ``` +## Separate Sink and Source Widgets + +``` +"wireplumber#sink": { + "format": "{volume}% {icon}", + "format-muted": "", + "format-icons": ["", "", ""], + "on-click": "helvum", + "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "scroll-step": 5 +}, +"wireplumber#source": { + "node-type": "Audio/Source", + "format": "{volume}% ", + "format-muted": "", + "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle", + "scroll-step": 5 +} +``` + # STYLE - *#wireplumber* diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index eddc3e6b..d6d1e594 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -16,13 +16,17 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val muted_(false), volume_(0.0), min_step_(0.0), - node_id_(0) { + node_id_(0), + type_(nullptr) { wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(nullptr, nullptr, nullptr); apis_ = g_ptr_array_new_with_free_func(g_object_unref); om_ = wp_object_manager_new(); - prepare(); + type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str() + : "Audio/Sink"); + + prepare(this); spdlog::debug("[{}]: connecting to pipewire...", name_); @@ -46,6 +50,7 @@ waybar::modules::Wireplumber::~Wireplumber() { g_clear_object(&mixer_api_); g_clear_object(&def_nodes_api_); g_free(default_node_name_); + g_free(type_); } void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { @@ -138,7 +143,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); uint32_t defaultNodeId; - g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId); if (!isValidNodeId(defaultNodeId)) { spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, @@ -200,9 +205,9 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir throw std::runtime_error("Mixer api is not loaded\n"); } - g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink", + g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_, &self->default_node_name_); - g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_); if (self->default_node_name_ != nullptr) { spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", @@ -246,7 +251,7 @@ void waybar::modules::Wireplumber::activatePlugins() { void waybar::modules::Wireplumber::prepare() { spdlog::debug("[{}]: preparing object manager", name_); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", - "=s", "Audio/Sink", nullptr); + "=s", self->type_, nullptr); } void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, From 2dfef1c213fe008e3520b168ba96f59db6d55d58 Mon Sep 17 00:00:00 2001 From: Rowan Leeder Date: Wed, 25 Sep 2024 04:03:31 +1000 Subject: [PATCH 02/38] Issue-3092 Add node type to wireplumber logs - The module only fetches nodes for "node-type". This causes the 'onMixerChanged' log to spam whenever two or more wireplumber modules were registered on different nodes. To reduce this the unknown node warning will now only print if the node is not the focus of any current module. --- include/modules/wireplumber.hpp | 2 + src/modules/wireplumber.cpp | 81 ++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index eb39653a..e0033e8a 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -32,6 +32,8 @@ class Wireplumber : public ALabel { bool handleScroll(GdkEventScroll* e) override; + static std::list modules; + WpCore* wp_core_; GPtrArray* apis_; WpObjectManager* om_; diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index d6d1e594..c25b3b51 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -4,6 +4,8 @@ bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } +std::list waybar::modules::Wireplumber::modules; + waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) : ALabel(config, "wireplumber", id, "{volume}%"), wp_core_(nullptr), @@ -18,6 +20,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val min_step_(0.0), node_id_(0), type_(nullptr) { + waybar::modules::Wireplumber::modules.push_back(this); + wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(nullptr, nullptr, nullptr); apis_ = g_ptr_array_new_with_free_func(g_object_unref); @@ -28,14 +32,14 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val prepare(this); - spdlog::debug("[{}]: connecting to pipewire...", name_); + spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_); if (wp_core_connect(wp_core_) == 0) { - spdlog::error("[{}]: Could not connect to PipeWire", name_); + spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_); throw std::runtime_error("Could not connect to PipeWire\n"); } - spdlog::debug("[{}]: connected!", name_); + spdlog::debug("[{}]: {} connected!", name_, type_); g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); @@ -43,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val } waybar::modules::Wireplumber::~Wireplumber() { + waybar::modules::Wireplumber::modules.remove(this); wp_core_disconnect(wp_core_); g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_object(&om_); @@ -54,10 +59,11 @@ waybar::modules::Wireplumber::~Wireplumber() { } void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { - spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id); + spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id); if (!isValidNodeId(id)) { - spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id); + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_, + id, self->type_); return; } @@ -85,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self->node_name_ = nick != nullptr ? nick : description != nullptr ? description : "Unknown node name"; - spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_); + spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); } void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { @@ -93,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se GVariant* variant = nullptr; if (!isValidNodeId(id)) { - spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id); + spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_, + id, self->type_); return; } @@ -114,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se } void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { - spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id); - g_autoptr(WpNode) node = static_cast(wp_object_manager_lookup( self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr)); if (node == nullptr) { - spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id); + // log a warning only if no other widget is targeting the id. + // this reduces log spam when multiple instances of the module are used on different node types. + if (id != self->node_id_) { + for (auto const& module : waybar::modules::Wireplumber::modules) { + if (module->node_id_ == id) { + return; + } + } + } + + spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_, + self->type_, id); return; } @@ -128,26 +144,27 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* if (self->node_id_ != id) { spdlog::debug( - "[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not " - "the default node: {} with id: {}", - self->name_, id, name, self->default_node_name_, self->node_id_); + "[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is " + "not the default node: {} with id: {}", + self->name_, self->type_, id, name, self->default_node_name_, self->node_id_); return; } - spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}", - self->name_, id, name); + spdlog::debug( + "[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}", + self->name_, self->type_, id, name); updateVolume(self, id); } void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) { - spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); + spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_); uint32_t defaultNodeId; g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId); if (!isValidNodeId(defaultNodeId)) { - spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, - defaultNodeId); + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_, + defaultNodeId, self->type_); return; } @@ -156,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir "=u", defaultNodeId, nullptr)); if (node == nullptr) { - spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_, - defaultNodeId); + spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_, + self->type_, defaultNodeId); return; } @@ -165,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})", - self->name_, defaultNodeName, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: " + "{})", + self->name_, self->type_, defaultNodeName, defaultNodeId); if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 && self->node_id_ == defaultNodeId) { spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). " - "Ignoring.", - self->name_, self->default_node_name_, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: " + "{}). Ignoring.", + self->name_, self->type_, self->default_node_name_, defaultNodeId); return; } spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})", - self->name_, defaultNodeName, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})", + self->name_, self->type_, defaultNodeName, defaultNodeId); g_free(self->default_node_name_); self->default_node_name_ = g_strdup(defaultNodeName); @@ -210,8 +228,9 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_); if (self->default_node_name_ != nullptr) { - spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", - self->name_, self->default_node_name_, self->node_id_); + spdlog::debug( + "[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}", + self->name_, self->type_, self->default_node_name_, self->node_id_); } updateVolume(self, self->node_id_); @@ -248,8 +267,8 @@ void waybar::modules::Wireplumber::activatePlugins() { } } -void waybar::modules::Wireplumber::prepare() { - spdlog::debug("[{}]: preparing object manager", name_); +void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) { + spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", self->type_, nullptr); } From a26ed50d0f42657bf09c69c996238aa71d670c69 Mon Sep 17 00:00:00 2001 From: Bruno Andreotti Date: Fri, 7 Feb 2025 14:39:07 -0300 Subject: [PATCH 03/38] Add support for vertical bars in privacy module --- include/modules/privacy/privacy.hpp | 2 +- include/modules/privacy/privacy_item.hpp | 4 ++-- src/factory.cpp | 2 +- src/modules/privacy/privacy.cpp | 9 +++++---- src/modules/privacy/privacy_item.cpp | 19 ++++++++++++++----- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index d7656d31..6179098c 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -13,7 +13,7 @@ namespace waybar::modules::privacy { class Privacy : public AModule { public: - Privacy(const std::string &, const Json::Value &, const std::string &pos); + Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos); auto update() -> void override; void onPrivacyNodesChanged(); diff --git a/include/modules/privacy/privacy_item.hpp b/include/modules/privacy/privacy_item.hpp index 836bd994..f5f572c0 100644 --- a/include/modules/privacy/privacy_item.hpp +++ b/include/modules/privacy/privacy_item.hpp @@ -17,8 +17,8 @@ namespace waybar::modules::privacy { class PrivacyItem : public Gtk::Revealer { public: PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes, const std::string &pos, const uint icon_size, - const uint transition_duration); + std::list *nodes, Gtk::Orientation orientation, + const std::string &pos, const uint icon_size, const uint transition_duration); enum PrivacyNodeType privacy_type; diff --git a/src/factory.cpp b/src/factory.cpp index 6c2313e3..1483397d 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -140,7 +140,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, #endif #ifdef HAVE_PIPEWIRE if (ref == "privacy") { - return new waybar::modules::privacy::Privacy(id, config_[name], pos); + return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos); } #endif #ifdef HAVE_MPRIS diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 97996c33..48bba888 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT; using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; -Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos) +Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation, + const std::string& pos) : AModule(config, "privacy", id), nodes_screenshare(), nodes_audio_in(), nodes_audio_out(), visibility_conn(), - box_(Gtk::ORIENTATION_HORIZONTAL, 0) { + box_(orientation, 0) { box_.set_name(name_); event_box_.add(box_); @@ -67,8 +68,8 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st auto iter = typeMap.find(type); if (iter != typeMap.end()) { auto& [nodePtr, nodeType] = iter->second; - auto* item = Gtk::make_managed(module, nodeType, nodePtr, pos, iconSize, - transition_duration); + auto* item = Gtk::make_managed(module, nodeType, nodePtr, orientation, pos, + iconSize, transition_duration); box_.add(*item); } } diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index a38b95a4..54e61b43 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -11,8 +11,9 @@ namespace waybar::modules::privacy { PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes_, const std::string &pos, - const uint icon_size, const uint transition_duration) + std::list *nodes_, Gtk::Orientation orientation, + const std::string &pos, const uint icon_size, + const uint transition_duration) : Gtk::Revealer(), privacy_type(privacy_type_), nodes(nodes_), @@ -40,16 +41,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac // Set the reveal transition to not look weird when sliding in if (pos == "modules-left") { - set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT); + set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL + ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT + : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN); } else if (pos == "modules-center") { set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE); } else if (pos == "modules-right") { - set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT); + set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL + ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT + : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP); } set_transition_duration(transition_duration); box_.set_name("privacy-item"); - box_.add(icon_); + + // We use `set_center_widget` instead of `add` to make sure the icon is + // centered even if the orientation is vertical + box_.set_center_widget(icon_); + icon_.set_pixel_size(icon_size); add(box_); From 937b62ea9a1cd32474855c1241d1dd95f91c0a6e Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:21:08 +0100 Subject: [PATCH 04/38] add SNI custom icon manager --- include/modules/sni/icon_manager.hpp | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 include/modules/sni/icon_manager.hpp diff --git a/include/modules/sni/icon_manager.hpp b/include/modules/sni/icon_manager.hpp new file mode 100644 index 00000000..614d42d9 --- /dev/null +++ b/include/modules/sni/icon_manager.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include +#include + +class IconManager { + public: + static IconManager& instance() { + static IconManager instance; + return instance; + } + + void setIconsConfig(const Json::Value& icons_config) { + if (icons_config.isObject()) { + for (const auto& key : icons_config.getMemberNames()) { + std::string app_name = key; + const Json::Value& icon_value = icons_config[key]; + + if (icon_value.isString()) { + std::string icon_path = icon_value.asString(); + icons_map_[app_name] = icon_path; + } + } + } else { + spdlog::warn("Invalid icon config format."); + } + } + + std::string getIconForApp(const std::string& app_name) const { + auto it = icons_map_.find(app_name); + if (it != icons_map_.end()) { + return it->second; + } + return ""; + } + + private: + IconManager() = default; + std::unordered_map icons_map_; +}; From 78d5c3ef3a3bd78b92eaae2a67cb624d65ab3bf5 Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:21:34 +0100 Subject: [PATCH 05/38] init custom icons from config per tray --- src/modules/sni/tray.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index a2c56808..abd23ebd 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -1,4 +1,5 @@ #include "modules/sni/tray.hpp" +#include "modules/sni/icon_manager.hpp" #include @@ -20,6 +21,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) box_.set_spacing(config_["spacing"].asUInt()); } nb_hosts_ += 1; + if (config_["icons"].isObject()) { + IconManager::instance().setIconsConfig(config_["icons"]); + } dp.emit(); } From d1998de47a0e51688d29f8d08e1b1477315ccbbd Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:22:10 +0100 Subject: [PATCH 06/38] add setCustomIcon and try to apply such when ID is known --- include/modules/sni/item.hpp | 1 + src/modules/sni/item.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index ebc08d45..c5e86d37 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -62,6 +62,7 @@ class Item : public sigc::trackable { void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setStatus(const Glib::ustring& value); + void setCustomIcon(const std::string& id); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index b3e84885..978e8b07 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,4 +1,5 @@ #include "modules/sni/item.hpp" +#include "modules/sni/icon_manager.hpp" #include #include @@ -7,6 +8,7 @@ #include #include +#include #include "gdk/gdk.h" #include "util/format.hpp" @@ -138,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { category = get_variant(value); } else if (name == "Id") { id = get_variant(value); + setCustomIcon(id); } else if (name == "Title") { title = get_variant(value); if (tooltip.text.empty()) { @@ -199,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) { style->add_class(lower); } +void Item::setCustomIcon(const std::string& id) { + std::string custom_icon = IconManager::instance().getIconForApp(id); + if (!custom_icon.empty()) { + if (std::filesystem::exists(custom_icon)) { + Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); + icon_name = ""; // icon_name has priority over pixmap + icon_pixmap = custom_pixbuf; + } else { // if file doesn't exist it's most likely an icon_name + icon_name = custom_icon; + } + } +} + void Item::getUpdatedProperties() { auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)}); From ddf5b3e07b4b5c9484f7bb5ca81e90973e5ade27 Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:30:08 +0100 Subject: [PATCH 07/38] add tray icons docs --- man/waybar-tray.5.scd | 6 +++++- resources/config.jsonc | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index 381d593d..dec5347f 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -47,7 +47,11 @@ Addressed by *tray* ``` "tray": { "icon-size": 21, - "spacing": 10 + "spacing": 10, + "icons": { + "blueman": "bluetooth", + "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png" + } } ``` diff --git a/resources/config.jsonc b/resources/config.jsonc index 6ac1aa50..67d5ff5b 100644 --- a/resources/config.jsonc +++ b/resources/config.jsonc @@ -104,7 +104,11 @@ }, "tray": { // "icon-size": 21, - "spacing": 10 + "spacing": 10, + // "icons": { + // "blueman": "bluetooth", + // "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png" + // } }, "clock": { // "timezone": "America/New_York", From 212c676251d44939d1f89dc3abbb50e8f75f8588 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 26 Feb 2025 15:59:33 +0530 Subject: [PATCH 08/38] Provide an option to show ipv4 or ipv6 or both of them --- include/modules/network.hpp | 5 ++++ man/waybar-network.5.scd | 2 +- src/modules/network.cpp | 54 ++++++++++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index df0ba9c3..dcf7d499 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -16,6 +16,8 @@ #include "util/rfkill.hpp" #endif +enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 }; + namespace waybar::modules { class Network : public ALabel { @@ -50,6 +52,7 @@ class Network : public ALabel { std::optional> readBandwidthUsage(); int ifid_; + ip_addr_pref addr_pref_; struct sockaddr_nl nladdr_ = {0}; struct nl_sock* sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr; @@ -73,9 +76,11 @@ class Network : public ALabel { bool carrier_; std::string ifname_; std::string ipaddr_; + std::string ipaddr6_; std::string gwaddr_; std::string netmask_; int cidr_; + int cidr6_; int32_t signal_strength_dbm_; uint8_t signal_strength_; std::string signal_strength_app_; diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index ee409d0a..c2dfc544 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -24,7 +24,7 @@ Addressed by *network* *family*: ++ typeof: string ++ default: *ipv4* ++ - The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. + The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both. *format*: ++ typeof: string ++ diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 393b4296..ce40340c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() { waybar::modules::Network::Network(const std::string &id, const Json::Value &config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), + addr_pref_(IPV4), efd_(-1), ev_fd_(-1), want_route_dump_(false), @@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf dump_in_progress_(false), is_p2p_(false), cidr_(0), + cidr6_(0), signal_strength_dbm_(0), signal_strength_(0), #ifdef WANT_RFKILL @@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf // the module start with no text, but the event_box_ is shown. label_.set_markup(""); + if (config_["family"] == "ipv6") { + addr_pref_ = IPV6; + } else if (config["family"] == "ipv4_6") { + addr_pref_ = IPV4_6; + } + auto bandwidth = readBandwidthUsage(); if (bandwidth.has_value()) { bandwidth_down_total_ = (*bandwidth).first; @@ -270,7 +278,7 @@ const std::string waybar::modules::Network::getNetworkState() const { return "disconnected"; } if (!carrier_) return "disconnected"; - if (ipaddr_.empty()) return "linked"; + if (ipaddr_.empty() && ipaddr6_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; } @@ -316,11 +324,22 @@ auto waybar::modules::Network::update() -> void { } getState(signal_strength_); + std::string final_ipaddr_; + if (addr_pref_ == ip_addr_pref::IPV4) { + final_ipaddr_ = ipaddr_; + } else if (addr_pref_ == ip_addr_pref::IPV6) { + final_ipaddr_ = ipaddr6_; + } else if (addr_pref_ == ip_addr_pref::IPV4_6) { + final_ipaddr_ = ipaddr_; + final_ipaddr_ += " | "; + final_ipaddr_ += ipaddr6_; + } + auto text = fmt::format( fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), + fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -352,8 +371,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), - fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), + fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -394,10 +414,12 @@ void waybar::modules::Network::clearIface() { essid_.clear(); bssid_.clear(); ipaddr_.clear(); + ipaddr6_.clear(); gwaddr_.clear(); netmask_.clear(); carrier_ = false; cidr_ = 0; + cidr6_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; signal_strength_app_.clear(); @@ -516,12 +538,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } + if ((ifa->ifa_family != AF_INET && net->addr_pref_ == ip_addr_pref::IPV4) || + (ifa->ifa_family != AF_INET6 && net->addr_pref_ == ip_addr_pref::IPV6)) { + return NL_OK; + } + // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { return NL_OK; } - for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { switch (ifa_rta->rta_type) { case IFA_ADDRESS: @@ -529,8 +555,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { case IFA_LOCAL: char ipaddr[INET6_ADDRSTRLEN]; if (!is_del_event) { - net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); - net->cidr_ = ifa->ifa_prefixlen; + if ((net->addr_pref_ == ip_addr_pref::IPV4 || + net->addr_pref_ == ip_addr_pref::IPV4_6) && + net->cidr_ == 0 && ifa->ifa_family == AF_INET) { + net->ipaddr_ = + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); + net->cidr_ = ifa->ifa_prefixlen; + } else if ((net->addr_pref_ == ip_addr_pref::IPV6 || + net->addr_pref_ == ip_addr_pref::IPV4_6) && + net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) { + net->ipaddr6_ = + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); + net->cidr6_ = ifa->ifa_prefixlen; + } + switch (ifa->ifa_family) { case AF_INET: { struct in_addr netmask; @@ -551,7 +589,9 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); } else { net->ipaddr_.clear(); + net->ipaddr6_.clear(); net->cidr_ = 0; + net->cidr6_ = 0; net->netmask_.clear(); spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), From 8bd0285c889244c7c730053c111c8a2431dc64e0 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 26 Feb 2025 16:06:58 +0530 Subject: [PATCH 09/38] Remove redundant if condition --- src/modules/network.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index ce40340c..2f5d19c8 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -538,11 +538,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } - if ((ifa->ifa_family != AF_INET && net->addr_pref_ == ip_addr_pref::IPV4) || - (ifa->ifa_family != AF_INET6 && net->addr_pref_ == ip_addr_pref::IPV6)) { - return NL_OK; - } - // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { From 26a344b131d23caf972955fd7aac9550a5082d27 Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 10:01:40 -0700 Subject: [PATCH 10/38] feat(hyprland): support createworkspacev2 --- src/modules/hyprland/workspaces.cpp | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index ef057d6d..03a7c44e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -322,7 +322,7 @@ void Workspaces::onEvent(const std::string &ev) { onSpecialWorkspaceActivated(payload); } else if (eventName == "destroyworkspace") { onWorkspaceDestroyed(payload); - } else if (eventName == "createworkspace") { + } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); } else if (eventName == "focusedmon") { onMonitorFocused(payload); @@ -362,18 +362,31 @@ void Workspaces::onWorkspaceDestroyed(std::string const &payload) { } } -void Workspaces::onWorkspaceCreated(std::string const &workspaceName, +void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { - spdlog::debug("Workspace created: {}", workspaceName); - auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); + spdlog::debug("Workspace created: {}", payload); + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + int workspaceId = std::stoi(workspaceIdStr); - if (!isWorkspaceIgnored(workspaceName)) { - auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); + auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + + auto workspaceIgnored = isWorkspaceIgnored(workspaceName); + if (!workspaceIgnored) { + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); for (Json::Value workspaceJson : workspacesJson) { - std::string name = workspaceJson["name"].asString(); - if (name == workspaceName) { + int currentId = workspaceJson["id"].asInt(); + std::string currentName = workspaceJson["name"].asString(); + if (currentId == workspaceId) { + // The workspace may have been renamed since creation + // Check if the configured ignore rules apply to the new name + workspaceIgnored = isWorkspaceIgnored(currentName); + if (workspaceIgnored) { + break; + } + if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && - (showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { + (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { for (Json::Value const &rule : workspaceRules) { auto ruleWorkspaceName = rule.isMember("defaultName") ? rule["defaultName"].asString() @@ -388,11 +401,13 @@ void Workspaces::onWorkspaceCreated(std::string const &workspaceName, break; } } else { - extendOrphans(workspaceJson["id"].asInt(), clientsData); + extendOrphans(workspaceId, clientsData); } } - } else { - spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName); + } + if (workspaceIgnored) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, + workspaceName); } } From 0c6ca8321c9d083c3a2ae73f0f94b2193a1619ef Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 11:27:40 -0700 Subject: [PATCH 11/38] feat(hyprland): support destroyworkspacev2 --- src/modules/hyprland/workspaces.cpp | 81 +++++++++++++++++------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 03a7c44e..894d77a8 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -180,7 +180,7 @@ void Workspaces::initializeWorkspaces() { // if the workspace rules changed since last initialization, make sure we reset everything: for (auto &workspace : m_workspaces) { - m_workspacesToRemove.push_back(workspace->name()); + m_workspacesToRemove.push_back(std::to_string(workspace->id())); } // get all current workspaces @@ -320,7 +320,7 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceActivated(payload); } else if (eventName == "activespecial") { onSpecialWorkspaceActivated(payload); - } else if (eventName == "destroyworkspace") { + } else if (eventName == "destroyworkspacev2") { onWorkspaceDestroyed(payload); } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); @@ -357,8 +357,10 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { - if (!isDoubleSpecial(payload)) { - m_workspacesToRemove.push_back(payload); + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + if (!isDoubleSpecial(workspaceName)) { + m_workspacesToRemove.push_back(workspaceIdStr); } } @@ -366,24 +368,21 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload); std::string workspaceIdStr = payload.substr(0, payload.find(',')); - std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); int workspaceId = std::stoi(workspaceIdStr); + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); - auto workspaceIgnored = isWorkspaceIgnored(workspaceName); - if (!workspaceIgnored) { - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); - for (Json::Value workspaceJson : workspacesJson) { - int currentId = workspaceJson["id"].asInt(); - std::string currentName = workspaceJson["name"].asString(); - if (currentId == workspaceId) { - // The workspace may have been renamed since creation - // Check if the configured ignore rules apply to the new name - workspaceIgnored = isWorkspaceIgnored(currentName); - if (workspaceIgnored) { - break; - } + for (Json::Value workspaceJson : workspacesJson) { + int currentId = workspaceJson["id"].asInt(); + if (currentId == workspaceId) { + std::string name = workspaceJson["name"].asString(); + // This workspace name is more up-to-date than the one in the event payload. + if (isWorkspaceIgnored(name)) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, + name); + break; + } if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { @@ -397,18 +396,13 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, } } - m_workspacesToCreate.emplace_back(workspaceJson, clientsData); - break; - } - } else { - extendOrphans(workspaceId, clientsData); + m_workspacesToCreate.emplace_back(workspaceJson, clientsData); + break; } + } else { + extendOrphans(workspaceId, clientsData); } } - if (workspaceIgnored) { - spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, - workspaceName); - } } void Workspaces::onWorkspaceMoved(std::string const &payload) { @@ -700,17 +694,38 @@ auto Workspaces::registerIpc() -> void { } void Workspaces::removeWorkspacesToRemove() { - for (const auto &workspaceName : m_workspacesToRemove) { - removeWorkspace(workspaceName); + for (const auto &workspaceString: m_workspacesToRemove) { + removeWorkspace(workspaceString); } m_workspacesToRemove.clear(); } -void Workspaces::removeWorkspace(std::string const &name) { - spdlog::debug("Removing workspace {}", name); +void Workspaces::removeWorkspace(std::string const &workspaceString) { + spdlog::debug("Removing workspace {}", workspaceString); + + int id = -100; // workspace IDs range from -99 upwards, so -100 is a good "invalid" value + std::string name; + + // TODO: we need to support workspace selectors here + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors + try { + id = std::stoi(workspaceString); + } catch (const std::exception &e) { + if (workspaceString.starts_with("special:")) { + name = workspaceString.substr(8); + } else if (workspaceString.starts_with("name:")) { + name = workspaceString.substr(5); + } else { + name = workspaceString; + } + } + auto workspace = std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &x) { - return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); + if (name.empty()) { + return id == x->id(); + } + return name == x->name(); }); if (workspace == m_workspaces.end()) { @@ -719,7 +734,7 @@ void Workspaces::removeWorkspace(std::string const &name) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace {}", name); + spdlog::trace("Not removing config persistent workspace {}", (*workspace)->name()); return; } From 17cee0d8766c4dc8a5d39c429ef1d0820ec0a41c Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 11:27:40 -0700 Subject: [PATCH 12/38] feat(hyprland): support workspacev2 --- include/modules/hyprland/workspaces.hpp | 2 +- src/modules/hyprland/workspaces.cpp | 78 +++++++++++++++++++++---- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 3f0252c8..364e55dd 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -138,7 +138,7 @@ class Workspaces : public AModule, public EventHandler { bool m_withIcon; uint64_t m_monitorId; - std::string m_activeWorkspaceName; + int m_activeWorkspaceId; std::string m_activeSpecialWorkspaceName; std::vector> m_workspaces; std::vector> m_workspacesToCreate; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 894d77a8..03ab4c56 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -39,7 +39,13 @@ Workspaces::~Workspaces() { } void Workspaces::init() { +<<<<<<< HEAD m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); +======= + m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) initializeWorkspaces(); dp.emit(); @@ -306,6 +312,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c workspaceData["persistent-rule"] = true; m_workspacesToCreate.emplace_back(workspaceData, clientsJson); } else { + // This can be any workspace selector. m_workspacesToRemove.emplace_back(workspace); } } @@ -316,7 +323,7 @@ void Workspaces::onEvent(const std::string &ev) { std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string payload = ev.substr(eventName.size() + 2); - if (eventName == "workspace") { + if (eventName == "workspacev2") { onWorkspaceActivated(payload); } else if (eventName == "activespecial") { onSpecialWorkspaceActivated(payload); @@ -348,7 +355,8 @@ void Workspaces::onEvent(const std::string &ev) { } void Workspaces::onWorkspaceActivated(std::string const &payload) { - m_activeWorkspaceName = payload; + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + m_activeWorkspaceId = std::stoi(workspaceIdStr); } void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { @@ -409,7 +417,13 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace +<<<<<<< HEAD m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); +======= + m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (allOutputs()) return; @@ -432,9 +446,6 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { std::string newName = payload.substr(payload.find(',') + 1); for (auto &workspace : m_workspaces) { if (workspace->id() == workspaceId) { - if (workspace->name() == m_activeWorkspaceName) { - m_activeWorkspaceName = newName; - } workspace->setName(newName); break; } @@ -444,7 +455,16 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) { spdlog::trace("Monitor focused: {}", payload); - m_activeWorkspaceName = payload.substr(payload.find(',') + 1); + + std::string workspaceName = payload.substr(payload.find(',') + 1); + + // TODO this will be in the payload when we upgrade to focusedmonv2 + for (auto &workspace : m_workspaces) { + if (workspace->name() == workspaceName) { + m_activeWorkspaceId = workspace->id(); + break; + } + } for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { @@ -672,6 +692,7 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa } auto Workspaces::registerIpc() -> void { +<<<<<<< HEAD m_ipc.registerForIPC("workspace", this); m_ipc.registerForIPC("activespecial", this); m_ipc.registerForIPC("createworkspace", this); @@ -684,6 +705,33 @@ auto Workspaces::registerIpc() -> void { m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("urgent", this); m_ipc.registerForIPC("configreloaded", this); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + gIPC->registerForIPC("workspace", this); + gIPC->registerForIPC("activespecial", this); + gIPC->registerForIPC("createworkspacev2", this); + gIPC->registerForIPC("destroyworkspacev2", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("moveworkspace", this); + gIPC->registerForIPC("renameworkspace", this); + gIPC->registerForIPC("openwindow", this); + gIPC->registerForIPC("closewindow", this); + gIPC->registerForIPC("movewindow", this); + gIPC->registerForIPC("urgent", this); + gIPC->registerForIPC("configreloaded", this); +======= + gIPC->registerForIPC("workspacev2", this); + gIPC->registerForIPC("activespecial", this); + gIPC->registerForIPC("createworkspacev2", this); + gIPC->registerForIPC("destroyworkspacev2", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("moveworkspace", this); + gIPC->registerForIPC("renameworkspace", this); + gIPC->registerForIPC("openwindow", this); + gIPC->registerForIPC("closewindow", this); + gIPC->registerForIPC("movewindow", this); + gIPC->registerForIPC("urgent", this); + gIPC->registerForIPC("configreloaded", this); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (windowRewriteConfigUsesTitle()) { spdlog::info( @@ -703,14 +751,16 @@ void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspace(std::string const &workspaceString) { spdlog::debug("Removing workspace {}", workspaceString); - int id = -100; // workspace IDs range from -99 upwards, so -100 is a good "invalid" value + int id; std::string name; - // TODO: we need to support workspace selectors here - // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors try { + // If this succeeds, we have a workspace ID. id = std::stoi(workspaceString); } catch (const std::exception &e) { + // TODO: At some point we want to support all workspace selectors + // This is just a subset. + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors if (workspaceString.starts_with("special:")) { name = workspaceString.substr(8); } else if (workspaceString.starts_with("name:")) { @@ -734,7 +784,7 @@ void Workspaces::removeWorkspace(std::string const &workspaceString) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace {}", (*workspace)->name()); + spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), (*workspace)->name()); return; } @@ -894,9 +944,15 @@ void Workspaces::updateWorkspaceStates() { const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - workspace->setActive(workspace->name() == m_activeWorkspaceName || + workspace->setActive(workspace->id() == m_activeWorkspaceId || workspace->name() == m_activeSpecialWorkspaceName); +<<<<<<< HEAD if (workspace->isActive() && workspace->isUrgent()) { +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { +======= + if (workspace->id() == m_activeWorkspaceId && workspace->isUrgent()) { +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) workspace->setUrgent(false); } workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), From 9f71de5227620dc2d884cfda946c8d7e5966337a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Mar 2025 00:11:29 +0000 Subject: [PATCH 13/38] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/9d3ae807ebd2981d593cddd0080856873139aa40?narHash=sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9%2BWC4%3D' (2025-01-29) → 'github:NixOS/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index c4bb5dee..e64cbe2c 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1738142207, - "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", + "lastModified": 1740560979, + "narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", + "rev": "5135c59491985879812717f4c9fea69604e7f26f", "type": "github" }, "original": { From 4a6c417ef5e3b511b40f13057b401cabe90ff197 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Tue, 4 Mar 2025 19:09:21 +0530 Subject: [PATCH 14/38] Add format replacements For cidr6, netmask6 --- include/modules/network.hpp | 1 + man/waybar-network.5.scd | 8 ++++++-- src/modules/network.cpp | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index dcf7d499..5fd0c180 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -79,6 +79,7 @@ class Network : public ALabel { std::string ipaddr6_; std::string gwaddr_; std::string netmask_; + std::string netmask6_; int cidr_; int cidr6_; int32_t signal_strength_dbm_; diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index c2dfc544..15f15395 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -155,9 +155,13 @@ Addressed by *network* *{gwaddr}*: The default gateway for the interface -*{netmask}*: The subnetmask corresponding to the IP. +*{netmask}*: The subnetmask corresponding to the IP(V4). -*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. +*{netmask6}*: The subnetmask corresponding to the IP(V6). + +*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation. + +*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation. *{essid}*: Name (SSID) of the wireless network. diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 2f5d19c8..716cd497 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -339,8 +339,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), - fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), + fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), + fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), @@ -371,9 +372,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), - fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), + fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), + fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -417,6 +418,7 @@ void waybar::modules::Network::clearIface() { ipaddr6_.clear(); gwaddr_.clear(); netmask_.clear(); + netmask6_.clear(); carrier_ = false; cidr_ = 0; cidr6_ = 0; @@ -571,14 +573,14 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); } case AF_INET6: { - struct in6_addr netmask; + struct in6_addr netmask6; for (int i = 0; i < 16; i++) { int v = (i + 1) * 8 - ifa->ifa_prefixlen; if (v < 0) v = 0; if (v > 8) v = 8; - netmask.s6_addr[i] = ~0 << v; + netmask6.s6_addr[i] = ~0 << v; } - net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); + net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr)); } } spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); @@ -588,6 +590,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->cidr_ = 0; net->cidr6_ = 0; net->netmask_.clear(); + net->netmask6_.clear(); spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), ifa->ifa_prefixlen); From f7b4451564dd860bfacfc293d0bbd414e6bb4e54 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Wed, 26 Feb 2025 18:34:18 -0700 Subject: [PATCH 15/38] fix(hyprland): support additional v2 events --- include/modules/hyprland/workspaces.hpp | 15 +- src/modules/hyprland/workspaces.cpp | 421 ++++++++++++------------ 2 files changed, 220 insertions(+), 216 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 364e55dd..6b33baea 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +56,7 @@ class Workspaces : public AModule, public EventHandler { static Json::Value createMonitorWorkspaceData(std::string const& name, std::string const& monitor); - void removeWorkspace(std::string const& name); + void removeWorkspace(std::string const& workspaceString); void setUrgentWorkspace(std::string const& windowaddress); // Config @@ -74,10 +75,11 @@ class Workspaces : public AModule, public EventHandler { void onWorkspaceActivated(std::string const& payload); void onSpecialWorkspaceActivated(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload); - void onWorkspaceCreated(std::string const& workspaceName, + void onWorkspaceCreated(std::string const& payload, Json::Value const& clientsData = Json::Value::nullRef); void onWorkspaceMoved(std::string const& payload); void onWorkspaceRenamed(std::string const& payload); + static std::optional parseWorkspaceId(std::string const& workspaceIdStr); // monitor events void onMonitorFocused(std::string const& payload); @@ -93,11 +95,18 @@ class Workspaces : public AModule, public EventHandler { int windowRewritePriorityFunction(std::string const& window_rule); + // event payload management + template + static std::string makePayload(Args const&... args); + static std::pair splitDoublePayload(std::string const& payload); + static std::tuple splitTriplePayload( + std::string const& payload); + // Update methods void doUpdate(); void removeWorkspacesToRemove(); void createWorkspacesToCreate(); - static std::vector getVisibleWorkspaces(); + static std::vector getVisibleWorkspaces(); void updateWorkspaceStates(); bool updateWindowsToCreate(); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 03ab4c56..c7d397e3 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -39,13 +40,7 @@ Workspaces::~Workspaces() { } void Workspaces::init() { -<<<<<<< HEAD - m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); -======= - m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); initializeWorkspaces(); dp.emit(); @@ -55,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name, std::string const &monitor) { spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); Json::Value workspaceData; - try { - // numbered persistent workspaces get the name as ID - workspaceData["id"] = name == "special" ? -99 : std::stoi(name); - } catch (const std::exception &e) { - // named persistent workspaces start with ID=0 - workspaceData["id"] = 0; + + auto workspaceId = parseWorkspaceId(name); + if (!workspaceId.has_value()) { + workspaceId = 0; } + workspaceData["id"] = *workspaceId; workspaceData["name"] = name; workspaceData["monitor"] = monitor; workspaceData["windows"] = 0; @@ -74,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = std::find_if( - m_workspaces.begin(), m_workspaces.end(), - [workspaceName](std::unique_ptr const &w) { + auto workspace = + std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr const &w) { return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || workspaceName == w->name(); }); @@ -164,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_ fmt::arg("title", window_title)); } -std::vector Workspaces::getVisibleWorkspaces() { - std::vector visibleWorkspaces; +std::vector Workspaces::getVisibleWorkspaces() { + std::vector visibleWorkspaces; auto monitors = IPC::inst().getSocket1JsonReply("monitors"); for (const auto &monitor : monitors) { auto ws = monitor["activeWorkspace"]; - if (ws.isObject() && ws["name"].isString()) { - visibleWorkspaces.push_back(ws["name"].asString()); + if (ws.isObject() && ws["id"].isInt()) { + visibleWorkspaces.push_back(ws["id"].asInt()); } auto sws = monitor["specialWorkspace"]; auto name = sws["name"].asString(); - if (sws.isObject() && sws["name"].isString() && !name.empty()) { - visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); + if (sws.isObject() && sws["id"].isInt() && !name.empty()) { + visibleWorkspaces.push_back(sws["id"].asInt()); } } return visibleWorkspaces; @@ -253,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs int amount = value.asInt(); spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); for (int i = 0; i < amount; i++) { - persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); + persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1)); } } } else if (value.isArray() && !value.empty()) { @@ -331,21 +324,21 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceDestroyed(payload); } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); - } else if (eventName == "focusedmon") { + } else if (eventName == "focusedmonv2") { onMonitorFocused(payload); - } else if (eventName == "moveworkspace") { + } else if (eventName == "moveworkspacev2") { onWorkspaceMoved(payload); } else if (eventName == "openwindow") { onWindowOpened(payload); } else if (eventName == "closewindow") { onWindowClosed(payload); - } else if (eventName == "movewindow") { + } else if (eventName == "movewindowv2") { onWindowMoved(payload); } else if (eventName == "urgent") { setUrgentWorkspace(payload); } else if (eventName == "renameworkspace") { onWorkspaceRenamed(payload); - } else if (eventName == "windowtitle") { + } else if (eventName == "windowtitlev2") { onWindowTitleEvent(payload); } else if (eventName == "configreloaded") { onConfigReloaded(); @@ -355,8 +348,11 @@ void Workspaces::onEvent(const std::string &ev) { } void Workspaces::onWorkspaceActivated(std::string const &payload) { - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - m_activeWorkspaceId = std::stoi(workspaceIdStr); + const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload); + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (workspaceId.has_value()) { + m_activeWorkspaceId = *workspaceId; + } } void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { @@ -365,50 +361,54 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + const auto [workspaceId, workspaceName] = splitDoublePayload(payload); if (!isDoubleSpecial(workspaceName)) { - m_workspacesToRemove.push_back(workspaceIdStr); + m_workspacesToRemove.push_back(workspaceId); } } -void Workspaces::onWorkspaceCreated(std::string const &payload, - Json::Value const &clientsData) { +void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload); - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - int workspaceId = std::stoi(workspaceIdStr); - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); - auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + const auto [workspaceIdStr, _] = splitDoublePayload(payload); + + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; + } + + auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); + auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); for (Json::Value workspaceJson : workspacesJson) { - int currentId = workspaceJson["id"].asInt(); - if (currentId == workspaceId) { - std::string name = workspaceJson["name"].asString(); + const auto currentId = workspaceJson["id"].asInt(); + if (currentId == *workspaceId) { + std::string workspaceName = workspaceJson["name"].asString(); // This workspace name is more up-to-date than the one in the event payload. - if (isWorkspaceIgnored(name)) { - spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, - name); + if (isWorkspaceIgnored(workspaceName)) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId, + workspaceName); break; } - if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && - (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { - for (Json::Value const &rule : workspaceRules) { - auto ruleWorkspaceName = rule.isMember("defaultName") - ? rule["defaultName"].asString() - : rule["workspaceString"].asString(); - if (ruleWorkspaceName == workspaceName) { - workspaceJson["persistent-rule"] = rule["persistent"].asBool(); - break; - } + if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && + (showSpecial() || !workspaceName.starts_with("special")) && + !isDoubleSpecial(workspaceName)) { + for (Json::Value const &rule : workspaceRules) { + auto ruleWorkspaceName = rule.isMember("defaultName") + ? rule["defaultName"].asString() + : rule["workspaceString"].asString(); + if (ruleWorkspaceName == workspaceName) { + workspaceJson["persistent-rule"] = rule["persistent"].asBool(); + break; } + } m_workspacesToCreate.emplace_back(workspaceJson, clientsData); break; } } else { - extendOrphans(workspaceId, clientsData); + extendOrphans(*workspaceId, clientsData); } } } @@ -417,35 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace -<<<<<<< HEAD - m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); -======= - m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt(); if (allOutputs()) return; - std::string workspaceName = payload.substr(0, payload.find(',')); - std::string monitorName = payload.substr(payload.find(',') + 1); + const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload); + + const auto subPayload = makePayload(workspaceIdStr, workspaceName); if (m_bar.output->name == monitorName) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); - onWorkspaceCreated(workspaceName, clientsData); + onWorkspaceCreated(subPayload, clientsData); } else { - spdlog::debug("Removing workspace because it was moved to another monitor: {}"); - onWorkspaceDestroyed(workspaceName); + spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload); + onWorkspaceDestroyed(subPayload); } } void Workspaces::onWorkspaceRenamed(std::string const &payload) { spdlog::debug("Workspace renamed: {}", payload); - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); - std::string newName = payload.substr(payload.find(',') + 1); + const auto [workspaceIdStr, newName] = splitDoublePayload(payload); + + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; + } + for (auto &workspace : m_workspaces) { - if (workspace->id() == workspaceId) { + if (workspace->id() == *workspaceId) { workspace->setName(newName); break; } @@ -456,19 +455,18 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) { spdlog::trace("Monitor focused: {}", payload); - std::string workspaceName = payload.substr(payload.find(',') + 1); + const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload); - // TODO this will be in the payload when we upgrade to focusedmonv2 - for (auto &workspace : m_workspaces) { - if (workspace->name() == workspaceName) { - m_activeWorkspaceId = workspace->id(); - break; - } + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; } + m_activeWorkspaceId = *workspaceId; + for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { - if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { - auto name = monitor["specialWorkspace"]["name"].asString(); + if (monitor["name"].asString() == monitorName) { + const auto name = monitor["specialWorkspace"]["name"].asString(); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); } } @@ -507,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) { void Workspaces::onWindowMoved(std::string const &payload) { spdlog::trace("Window moved: {}", payload); updateWindowCount(); - size_t lastCommaIdx = 0; - size_t nextCommaIdx = payload.find(','); - std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx); - - std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx); + auto [windowAddress, _, workspaceName] = splitTriplePayload(payload); std::string windowRepr; @@ -547,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { spdlog::trace("Window title changed: {}", payload); std::optional> inserter; + const auto [windowAddress, _] = splitDoublePayload(payload); + // If the window was an orphan, rename it at the orphan's vector - if (m_orphanWindowMap.contains(payload)) { + if (m_orphanWindowMap.contains(windowAddress)) { inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; } else { - auto windowWorkspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), - [payload](auto &workspace) { return workspace->containsWindow(payload); }); + auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) { + return workspace->containsWindow(windowAddress); + }); // If the window exists on a workspace, rename it at the workspace's window // map @@ -562,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { (*windowWorkspace)->insertWindow(std::move(wcp)); }; } else { - auto queuedWindow = std::find_if( - m_windowsToCreate.begin(), m_windowsToCreate.end(), - [payload](auto &windowPayload) { return windowPayload.getAddress() == payload; }); + auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) { + return windowPayload.getAddress() == payload; + }); // If the window was queued, rename it in the queue if (queuedWindow != m_windowsToCreate.end()) { @@ -692,57 +688,29 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa } auto Workspaces::registerIpc() -> void { -<<<<<<< HEAD - m_ipc.registerForIPC("workspace", this); + m_ipc.registerForIPC("workspacev2", this); m_ipc.registerForIPC("activespecial", this); - m_ipc.registerForIPC("createworkspace", this); - m_ipc.registerForIPC("destroyworkspace", this); - m_ipc.registerForIPC("focusedmon", this); - m_ipc.registerForIPC("moveworkspace", this); + m_ipc.registerForIPC("createworkspacev2", this); + m_ipc.registerForIPC("destroyworkspacev2", this); + m_ipc.registerForIPC("focusedmonv2", this); + m_ipc.registerForIPC("moveworkspacev2", this); m_ipc.registerForIPC("renameworkspace", this); m_ipc.registerForIPC("openwindow", this); m_ipc.registerForIPC("closewindow", this); - m_ipc.registerForIPC("movewindow", this); + m_ipc.registerForIPC("movewindowv2", this); m_ipc.registerForIPC("urgent", this); m_ipc.registerForIPC("configreloaded", this); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - gIPC->registerForIPC("workspace", this); - gIPC->registerForIPC("activespecial", this); - gIPC->registerForIPC("createworkspacev2", this); - gIPC->registerForIPC("destroyworkspacev2", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("moveworkspace", this); - gIPC->registerForIPC("renameworkspace", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("urgent", this); - gIPC->registerForIPC("configreloaded", this); -======= - gIPC->registerForIPC("workspacev2", this); - gIPC->registerForIPC("activespecial", this); - gIPC->registerForIPC("createworkspacev2", this); - gIPC->registerForIPC("destroyworkspacev2", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("moveworkspace", this); - gIPC->registerForIPC("renameworkspace", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("urgent", this); - gIPC->registerForIPC("configreloaded", this); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (windowRewriteConfigUsesTitle()) { spdlog::info( - "Registering for Hyprland's 'windowtitle' events because a user-defined window " + "Registering for Hyprland's 'windowtitlev2' events because a user-defined window " "rewrite rule uses the 'title' field."); - m_ipc.registerForIPC("windowtitle", this); + m_ipc.registerForIPC("windowtitlev2", this); } } void Workspaces::removeWorkspacesToRemove() { - for (const auto &workspaceString: m_workspacesToRemove) { + for (const auto &workspaceString : m_workspacesToRemove) { removeWorkspace(workspaceString); } m_workspacesToRemove.clear(); @@ -751,32 +719,27 @@ void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspace(std::string const &workspaceString) { spdlog::debug("Removing workspace {}", workspaceString); - int id; - std::string name; + // If this succeeds, we have a workspace ID. + const auto workspaceId = parseWorkspaceId(workspaceString); - try { - // If this succeeds, we have a workspace ID. - id = std::stoi(workspaceString); - } catch (const std::exception &e) { - // TODO: At some point we want to support all workspace selectors - // This is just a subset. - // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors - if (workspaceString.starts_with("special:")) { - name = workspaceString.substr(8); - } else if (workspaceString.starts_with("name:")) { - name = workspaceString.substr(5); - } else { - name = workspaceString; - } + std::string name; + // TODO: At some point we want to support all workspace selectors + // This is just a subset. + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors + if (workspaceString.starts_with("special:")) { + name = workspaceString.substr(8); + } else if (workspaceString.starts_with("name:")) { + name = workspaceString.substr(5); + } else { + name = workspaceString; } - auto workspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &x) { - if (name.empty()) { - return id == x->id(); - } - return name == x->name(); - }); + const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr &x) { + if (workspaceId.has_value()) { + return *workspaceId == x->id(); + } + return name == x->name(); + }); if (workspace == m_workspaces.end()) { // happens when a workspace on another monitor is destroyed @@ -784,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &workspaceString) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), (*workspace)->name()); + spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), + (*workspace)->name()); return; } @@ -808,62 +772,63 @@ void Workspaces::setCurrentMonitorId() { } void Workspaces::sortWorkspaces() { - std::sort(m_workspaces.begin(), m_workspaces.end(), - [&](std::unique_ptr &a, std::unique_ptr &b) { - // Helper comparisons - auto isIdLess = a->id() < b->id(); - auto isNameLess = a->name() < b->name(); + std::ranges::sort( // + m_workspaces, [&](std::unique_ptr &a, std::unique_ptr &b) { + // Helper comparisons + auto isIdLess = a->id() < b->id(); + auto isNameLess = a->name() < b->name(); - switch (m_sortBy) { - case SortMethod::ID: - return isIdLess; - case SortMethod::NAME: - return isNameLess; - case SortMethod::NUMBER: - try { - return std::stoi(a->name()) < std::stoi(b->name()); - } catch (const std::invalid_argument &) { - // Handle the exception if necessary. - break; - } - case SortMethod::DEFAULT: - default: - // Handle the default case here. - // normal -> named persistent -> named -> special -> named special + switch (m_sortBy) { + case SortMethod::ID: + return isIdLess; + case SortMethod::NAME: + return isNameLess; + case SortMethod::NUMBER: + try { + return std::stoi(a->name()) < std::stoi(b->name()); + } catch (const std::invalid_argument &) { + // Handle the exception if necessary. + break; + } + case SortMethod::DEFAULT: + default: + // Handle the default case here. + // normal -> named persistent -> named -> special -> named special - // both normal (includes numbered persistent) => sort by ID - if (a->id() > 0 && b->id() > 0) { - return isIdLess; - } + // both normal (includes numbered persistent) => sort by ID + if (a->id() > 0 && b->id() > 0) { + return isIdLess; + } - // one normal, one special => normal first - if ((a->isSpecial()) ^ (b->isSpecial())) { - return b->isSpecial(); - } + // one normal, one special => normal first + if ((a->isSpecial()) ^ (b->isSpecial())) { + return b->isSpecial(); + } - // only one normal, one named - if ((a->id() > 0) ^ (b->id() > 0)) { - return a->id() > 0; - } + // only one normal, one named + if ((a->id() > 0) ^ (b->id() > 0)) { + return a->id() > 0; + } - // both special - if (a->isSpecial() && b->isSpecial()) { - // if one is -99 => put it last - if (a->id() == -99 || b->id() == -99) { - return b->id() == -99; - } - // both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1) - return isNameLess; - } - - // sort non-special named workspaces by name (ID <= -1377) - return isNameLess; - break; + // both special + if (a->isSpecial() && b->isSpecial()) { + // if one is -99 => put it last + if (a->id() == -99 || b->id() == -99) { + return b->id() == -99; } + // both are 0 (not yet named persistents) / named specials + // (-98 <= ID <= -1) + return isNameLess; + } - // Return a default value if none of the cases match. - return isNameLess; // You can adjust this to your specific needs. - }); + // sort non-special named workspaces by name (ID <= -1377) + return isNameLess; + break; + } + + // Return a default value if none of the cases match. + return isNameLess; // You can adjust this to your specific needs. + }); for (size_t i = 0; i < m_workspaces.size(); ++i) { m_box.reorder_child(m_workspaces[i]->button(), i); @@ -941,22 +906,17 @@ bool Workspaces::updateWindowsToCreate() { } void Workspaces::updateWorkspaceStates() { - const std::vector visibleWorkspaces = getVisibleWorkspaces(); + const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - workspace->setActive(workspace->id() == m_activeWorkspaceId || - workspace->name() == m_activeSpecialWorkspaceName); -<<<<<<< HEAD + workspace->setActive( + workspace->id() == m_activeWorkspaceId || + (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName)); if (workspace->isActive() && workspace->isUrgent()) { -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { -======= - if (workspace->id() == m_activeWorkspaceId && workspace->isUrgent()) { ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) workspace->setUrgent(false); } - workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), - workspace->name()) != visibleWorkspaces.end()); + workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) != + visibleWorkspaces.end()); std::string &workspaceIcon = m_iconsMap[""]; if (m_withIcon) { workspaceIcon = workspace->selectIcon(m_iconsMap); @@ -994,4 +954,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { return 0; } +template +std::string Workspaces::makePayload(Args const &...args) { + std::ostringstream result; + bool first = true; + ((result << (first ? "" : ",") << args, first = false), ...); + return result.str(); +} + +std::pair Workspaces::splitDoublePayload(std::string const &payload) { + const std::string part1 = payload.substr(0, payload.find(',')); + const std::string part2 = payload.substr(part1.size() + 1); + return {part1, part2}; +} + +std::tuple Workspaces::splitTriplePayload( + std::string const &payload) { + const size_t firstComma = payload.find(','); + const size_t secondComma = payload.find(',', firstComma + 1); + + const std::string part1 = payload.substr(0, firstComma); + const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1)); + const std::string part3 = payload.substr(secondComma + 1); + + return {part1, part2, part3}; +} + +std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) { + try { + return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); + } catch (std::exception const &e) { + spdlog::error("Failed to parse workspace ID: {}", e.what()); + return std::nullopt; + } +} + } // namespace waybar::modules::hyprland From 5e4dac1c0aebd6c4ad1f358f09e1cfd06a95d529 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 5 Mar 2025 15:27:28 +0530 Subject: [PATCH 16/38] Newline as a seperator when displaying IPv4 and 6 at the same time --- src/modules/network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 716cd497..0a77c00e 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void { final_ipaddr_ = ipaddr6_; } else if (addr_pref_ == ip_addr_pref::IPV4_6) { final_ipaddr_ = ipaddr_; - final_ipaddr_ += " | "; + final_ipaddr_ += '\n'; final_ipaddr_ += ipaddr6_; } From 906170400efd29b8151cbbac7e744485b48457a2 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 5 Mar 2025 16:53:56 -0800 Subject: [PATCH 17/38] cffi: always return config values as JSON Previously, string JSON values were special cased to be provided as bare strings, which means that CFFI modules have to either know what type each value is expected to be, or use a heuristic such as trying to decode and then treating the value as a string on failure. Instead, we can always return JSON, and let the downstream consumer handle deserialising the value into whatever type is expected. The new behaviour is gated on a new ABI version 2: modules built against version 1 will continue to get the old behaviour. --- resources/custom_modules/cffi_example/main.c | 4 ++-- .../custom_modules/cffi_example/waybar_cffi_module.h | 10 ++++++++-- src/modules/cffi.cpp | 12 ++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/resources/custom_modules/cffi_example/main.c b/resources/custom_modules/cffi_example/main.c index ba2c8cf4..7618de58 100644 --- a/resources/custom_modules/cffi_example/main.c +++ b/resources/custom_modules/cffi_example/main.c @@ -17,7 +17,7 @@ void onclicked(GtkButton* button) { } // You must -const size_t wbcffi_version = 1; +const size_t wbcffi_version = 2; void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries, size_t config_entries_len) { @@ -67,4 +67,4 @@ void wbcffi_refresh(void* instance, int signal) { void wbcffi_doaction(void* instance, const char* name) { printf("cffi_example inst=%p: doAction(%s)\n", instance, name); -} \ No newline at end of file +} diff --git a/resources/custom_modules/cffi_example/waybar_cffi_module.h b/resources/custom_modules/cffi_example/waybar_cffi_module.h index a7886bea..c1a82f59 100644 --- a/resources/custom_modules/cffi_example/waybar_cffi_module.h +++ b/resources/custom_modules/cffi_example/waybar_cffi_module.h @@ -7,7 +7,7 @@ extern "C" { #endif -/// Waybar ABI version. 1 is the latest version +/// Waybar ABI version. 2 is the latest version extern const size_t wbcffi_version; /// Private Waybar CFFI module @@ -35,7 +35,13 @@ typedef struct { typedef struct { /// Entry key const char* key; - /// Entry value as string. JSON object and arrays are serialized. + /// Entry value + /// + /// In ABI version 1, this may be either a bare string if the value is a + /// string, or the JSON representation of any other JSON object as a string. + /// + /// From ABI version 2 onwards, this is always the JSON representation of the + /// value as a string. const char* value; } wbcffi_config_entry; diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index e560659b..5c095f46 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co } // Fetch functions - if (*wbcffi_version == 1) { + if (*wbcffi_version == 1 || *wbcffi_version == 2) { // Mandatory functions hooks_.init = reinterpret_cast(dlsym(handle, "wbcffi_init")); if (!hooks_.init) { @@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co const auto& keys = config.getMemberNames(); for (size_t i = 0; i < keys.size(); i++) { const auto& value = config[keys[i]]; - if (value.isConvertibleTo(Json::ValueType::stringValue)) { - config_entries_stringstor.push_back(config[keys[i]].asString()); + if (*wbcffi_version == 1) { + if (value.isConvertibleTo(Json::ValueType::stringValue)) { + config_entries_stringstor.push_back(value.asString()); + } else { + config_entries_stringstor.push_back(value.toStyledString()); + } } else { - config_entries_stringstor.push_back(config[keys[i]].toStyledString()); + config_entries_stringstor.push_back(value.toStyledString()); } } From f631d5eaf90f0e366d1ae73911cbc0cf67ad6ffa Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Wed, 5 Mar 2025 22:44:55 -0600 Subject: [PATCH 18/38] nix/default: disable version check Downstream added version check, causes this flake to fail building. --- nix/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/default.nix b/nix/default.nix index a9ff180b..fc564f0a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -27,6 +27,8 @@ in # downstream patch should not affect upstream patches = []; + # nixpkgs checks version, no need when building locally + nativeInstallCheckInputs = []; buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ pkgs.wireplumber From 6fd859c0c4230cb45fe4503446ba548d09d64158 Mon Sep 17 00:00:00 2001 From: Angelo Dureghello Date: Sat, 22 Mar 2025 18:33:03 +0100 Subject: [PATCH 19/38] add login-proxy option There are cases where systemd-logind is not used/running. Result is that bcklight module will not run. Add an option that, when set to false, allows backlight module to work without systemd-logind. --- meson.build | 4 ++++ meson_options.txt | 1 + src/util/backlight_backend.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/meson.build b/meson.build index 5524b49c..1bc1f656 100644 --- a/meson.build +++ b/meson.build @@ -333,6 +333,10 @@ if get_option('niri') ) endif +if get_option('login-proxy') + add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp') +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += files('src/modules/network.cpp') diff --git a/meson_options.txt b/meson_options.txt index 303ef038..d83fe01f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -20,3 +20,4 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('niri', type: 'boolean', description: 'Enable support for niri') +option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface') diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 41236462..863896d5 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -150,6 +150,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, throw std::runtime_error("No backlight found"); } +#ifdef HAVE_LOGIN_PROXY // Connect to the login interface login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", @@ -160,6 +161,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); } +#endif udev_thread_ = [this] { std::unique_ptr udev{udev_new()}; From 4ba1947a50d9af7578dc4febdc99bfdc9f88ec8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Mon, 24 Mar 2025 15:30:57 +0100 Subject: [PATCH 20/38] fix(FreeBSD): Use dev.cpu temperature sysctl --- src/modules/temperature.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 89d3db2d..93f427ee 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; - if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) != 0) { - throw std::runtime_error(fmt::format( - "sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone)); + // First, try with dev.cpu + if ( (sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0) || + (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0) ) { + auto temperature_c = ((float)temp - 2732) / 10; + return temperature_c; } - auto temperature_c = ((float)temp - 2732) / 10; - return temperature_c; + + throw std::runtime_error(fmt::format( + "sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone)); #else // Linux std::ifstream temp(file_path_); @@ -148,4 +152,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) { bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); -} \ No newline at end of file +} From 567ae16a680dfc8196f493d4318e9d0cd36e2580 Mon Sep 17 00:00:00 2001 From: tea Date: Fri, 28 Mar 2025 09:42:48 +0100 Subject: [PATCH 21/38] fix incorrect type for `weeks-pos` in waybar-clock man page --- man/waybar-clock.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index f0a88d24..50a5fc07 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe :[ 3 :[ Relevant for *mode=year*. Count of months per row |[ *weeks-pos* -:[ integer +:[ string :[ :[ The position where week numbers should be displayed. Disabled when is empty. Possible values: left|right From 9d2b137594313f8bfd79e6c6c2ecfef2f798e035 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Mon, 31 Mar 2025 18:36:12 +0200 Subject: [PATCH 22/38] fix manpage for backlight/slider --- man/waybar-backlight-slider.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-backlight-slider.5.scd b/man/waybar-backlight-slider.5.scd index 8d8353c3..c6f027a1 100644 --- a/man/waybar-backlight-slider.5.scd +++ b/man/waybar-backlight-slider.5.scd @@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki ``` "modules-right": [ - "backlight-slider", + "backlight/slider", ], "backlight/slider": { "min": 0, From c0b8c4d46865bedf721988f73d64b76e41cd6f41 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Apr 2025 00:12:37 +0000 Subject: [PATCH 23/38] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26) → 'github:NixOS/nixpkgs/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e64cbe2c..27290894 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740560979, - "narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=", + "lastModified": 1743315132, + "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5135c59491985879812717f4c9fea69604e7f26f", + "rev": "52faf482a3889b7619003c0daec593a1912fddc1", "type": "github" }, "original": { From c5bc3bc59a19fdc77c4066ec5132617289206d0f Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:11:33 -0500 Subject: [PATCH 24/38] hyprland/workspaces: fix crash --- src/modules/hyprland/workspaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index c7d397e3..770947ea 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -578,7 +578,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { return client["address"].asString() == jsonWindowAddress; }); - if (!client->empty()) { + if (client != clientsData.end() && !client->empty()) { (*inserter)({*client}); } } From 91ef6e51ed5e24d89827d9e8c127a96fef4a2c60 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:23:42 -0500 Subject: [PATCH 25/38] hyprland/workspaces: range find lint cleanup --- src/modules/hyprland/workspaces.cpp | 45 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 770947ea..0e225935 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -79,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, const auto keys = workspace_data.getMemberNames(); const auto *k = "persistent-rule"; - if (std::find(keys.begin(), keys.end(), k) != keys.end()) { + if (std::ranges::find(keys, k) != keys.end()) { spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName, workspace_data[k].asBool() ? "true" : "false"); (*workspace)->setPersistentRule(workspace_data[k].asBool()); } k = "persistent-config"; - if (std::find(keys.begin(), keys.end(), k) != keys.end()) { + if (std::ranges::find(keys, k) != keys.end()) { spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName, workspace_data[k].asBool() ? "true" : "false"); (*workspace)->setPersistentConfig(workspace_data[k].asBool()); @@ -231,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs std::vector persistentWorkspacesToCreate; const std::string currentMonitor = m_bar.output->name; - const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end(); + const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end(); for (const std::string &key : keys) { // only add if either: // 1. key is the current monitor name @@ -573,10 +573,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); std::string jsonWindowAddress = fmt::format("0x{}", payload); - auto client = - std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) { - return client["address"].asString() == jsonWindowAddress; - }); + auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) { + return client["address"].asString() == jsonWindowAddress; + }); if (client != clientsData.end() && !client->empty()) { (*inserter)({*client}); @@ -760,9 +759,9 @@ void Workspaces::setCurrentMonitorId() { // get monitor ID from name (used by persistent workspaces) m_monitorId = 0; auto monitors = m_ipc.getSocket1JsonReply("monitors"); - auto currentMonitor = std::find_if( - monitors.begin(), monitors.end(), - [this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; }); + auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) { + return m["name"].asString() == m_bar.output->name; + }); if (currentMonitor == monitors.end()) { spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name); } else { @@ -846,9 +845,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { } } - auto workspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), - [workspaceId](std::unique_ptr &x) { return x->id() == workspaceId; }); + auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr &x) { + return x->id() == workspaceId; + }); if (workspace != m_workspaces.end()) { workspace->get()->setUrgent(); } @@ -862,11 +861,10 @@ auto Workspaces::update() -> void { void Workspaces::updateWindowCount() { const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - auto workspaceJson = - std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) { - return x["name"].asString() == workspace->name() || - (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); - }); + auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) { + return x["name"].asString() == workspace->name() || + (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); + }); uint32_t count = 0; if (workspaceJson != workspacesJson.end()) { try { @@ -921,12 +919,11 @@ void Workspaces::updateWorkspaceStates() { if (m_withIcon) { workspaceIcon = workspace->selectIcon(m_iconsMap); } - auto updatedWorkspace = std::find_if( - updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) { - auto wNameRaw = w["name"].asString(); - auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; - return wName == workspace->name(); - }); + auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) { + auto wNameRaw = w["name"].asString(); + auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; + return wName == workspace->name(); + }); if (updatedWorkspace != updatedWorkspaces.end()) { workspace->setOutput((*updatedWorkspace)["monitor"].asString()); } From 84162ec60437756fd3ad089ba1b8596bdad64b63 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:48:01 -0500 Subject: [PATCH 26/38] .github/workflows/clang-format: bump github action --- .github/workflows/clang-format.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 3c62819e..b5bfbe36 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,7 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: DoozyX/clang-format-lint-action@v0.16.2 + # TODO: bump to clang 19 release + # - uses: DoozyX/clang-format-lint-action@v0.18.2 + - uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809 name: clang-format with: source: "." From 5ff6b0ad0ff1072a15fb7ed16a5595d083e48661 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:56:32 -0500 Subject: [PATCH 27/38] .github/workflows: tweak job names They didn't seem to correspond to the workflow, properly. Making triggering them locally weird. --- .github/workflows/clang-format.yml | 2 +- .github/workflows/clang-tidy.yml.bak | 2 +- .github/workflows/freebsd.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index b5bfbe36..0fad47c4 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - build: + lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/clang-tidy.yml.bak b/.github/workflows/clang-tidy.yml.bak index a39bd23d..ec67fb7e 100644 --- a/.github/workflows/clang-tidy.yml.bak +++ b/.github/workflows/clang-tidy.yml.bak @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - build: + lint: runs-on: ubuntu-latest container: image: alexays/waybar:debian diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index bbb97198..ca0dcbc8 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - clang: + build: # Run actions in a FreeBSD VM on the ubuntu runner # https://github.com/actions/runner/issues/385 - for FreeBSD runner support runs-on: ubuntu-latest From 9ca52a48c86c362c28aa3b9102192756daf82a16 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Sun, 6 Apr 2025 09:46:06 +0200 Subject: [PATCH 28/38] wireplumber: fix potential nullpointer deref --- src/modules/wireplumber.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index c25b3b51..106ca403 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -299,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r gboolean success = FALSE; g_autoptr(GError) error = nullptr; - success = wp_core_load_component_finish(self->wp_core_, res, nullptr); + success = wp_core_load_component_finish(self->wp_core_, res, &error); if (success == FALSE) { spdlog::error("[{}]: mixer API load failed", self->name_); From e92b0a86b5595a3c130f9c2fded724c71ac89d35 Mon Sep 17 00:00:00 2001 From: Clemens Horn Date: Mon, 7 Apr 2025 20:33:18 +0200 Subject: [PATCH 29/38] wlr/taskbar: find icon by title as fallback --- src/modules/wlr/taskbar.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 30e4ee48..3c188dd0 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -382,9 +382,40 @@ std::string Task::state_string(bool shortened) const { return res.substr(0, res.size() - 1); } -void Task::handle_title(const char *title) { - title_ = title; - hide_if_ignored(); +void Task::handle_title(const char* title) { + title_ = title; + hide_if_ignored(); + + // Skip if we already have app info or no title + if (app_info_ || title_.empty()) { + return; + } + + // Try exact title match + app_info_ = get_desktop_app_info(title_); + + // Try lowercase version if still needed + if (!app_info_) { + std::string lower_title = title_; + std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower); + app_info_ = get_desktop_app_info(lower_title); + } + + // If we found a match, update name and icon + if (app_info_) { + name_ = app_info_->get_display_name(); + spdlog::info("Found desktop file via title fallback: {}", name_); + + if (with_icon_) { + const int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; + for (auto& icon_theme : tbar_->icon_themes()) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { + icon_.show(); + break; + } + } + } + } } void Task::set_minimize_hint() { From addf44d9457ae6f17e73cb4b428b7d3be242390c Mon Sep 17 00:00:00 2001 From: Clemens Horn Date: Mon, 7 Apr 2025 20:51:35 +0200 Subject: [PATCH 30/38] test --- src/modules/wlr/taskbar.cpp | 59 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 3c188dd0..1169425a 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -383,39 +383,38 @@ std::string Task::state_string(bool shortened) const { } void Task::handle_title(const char* title) { - title_ = title; - hide_if_ignored(); + if (title_.empty()) { + spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_)); + } else { + spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title)); + } + title_ = title; + hide_if_ignored(); - // Skip if we already have app info or no title - if (app_info_ || title_.empty()) { - return; + if (!with_icon_ && !with_name_ || app_info_) { + return; + } + + set_app_info_from_app_id_list(title_); + name_ = app_info_ ? app_info_->get_display_name() : title; + + if (!with_icon_) { + return; + } + + int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; + bool found = false; + for (auto &icon_theme : tbar_->icon_themes()) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { + found = true; + break; } + } - // Try exact title match - app_info_ = get_desktop_app_info(title_); - - // Try lowercase version if still needed - if (!app_info_) { - std::string lower_title = title_; - std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower); - app_info_ = get_desktop_app_info(lower_title); - } - - // If we found a match, update name and icon - if (app_info_) { - name_ = app_info_->get_display_name(); - spdlog::info("Found desktop file via title fallback: {}", name_); - - if (with_icon_) { - const int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; - for (auto& icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { - icon_.show(); - break; - } - } - } - } + if (found) + icon_.show(); + else + spdlog::debug("Couldn't find icon for {}", title_); } void Task::set_minimize_hint() { From afb1ee54221838c98c8583ec6da8fa4ad7913aa9 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 11 Apr 2025 14:05:39 -0500 Subject: [PATCH 31/38] audio_backend: fix crash Getting crashes when called before we have proper information. --- src/util/audio_backend.cpp | 138 +++++++++++++++++++++++++++++-------- 1 file changed, 110 insertions(+), 28 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 807b5dc7..860168fd 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -24,6 +24,8 @@ AudioBackend::AudioBackend(std::function on_updated_cb, private_construc source_volume_(0), source_muted_(false), on_updated_cb_(std::move(on_updated_cb)) { + // Initialize pa_volume_ with safe defaults + pa_cvolume_init(&pa_volume_); mainloop_ = pa_threaded_mainloop_new(); if (mainloop_ == nullptr) { throw std::runtime_error("pa_mainloop_new() failed."); @@ -131,7 +133,12 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { auto *backend = static_cast(data); if (success != 0) { - pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); + if ((backend->context_ != nullptr) && + pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { + pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); + } + } else { + spdlog::debug("Volume modification failed"); } } @@ -180,11 +187,20 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } if (backend->current_sink_name_ == i->name) { - backend->pa_volume_ = i->volume; - float volume = - static_cast(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM}; - backend->sink_idx_ = i->index; - backend->volume_ = std::round(volume * 100.0F); + // Safely copy the volume structure + if (pa_cvolume_valid(&i->volume) != 0) { + backend->pa_volume_ = i->volume; + float volume = + static_cast(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM}; + backend->sink_idx_ = i->index; + backend->volume_ = std::round(volume * 100.0F); + } else { + spdlog::error("Invalid volume structure received from PulseAudio"); + // Initialize with safe defaults + pa_cvolume_init(&backend->pa_volume_); + backend->volume_ = 0; + } + backend->muted_ = i->mute != 0; backend->desc_ = i->description; backend->monitor_ = i->monitor_source_name; @@ -230,43 +246,109 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo } void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { - double volume_tick = static_cast(PA_VOLUME_NORM) / 100; - pa_cvolume pa_volume = pa_volume_; + // Early return if context is not ready + if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { + spdlog::error("PulseAudio context not ready"); + return; + } + // Prepare volume structure + pa_cvolume pa_volume; + + pa_cvolume_init(&pa_volume); + + // Use existing volume structure if valid, otherwise create a safe default + if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { + pa_volume = pa_volume_; + } else { + // Set stereo as a safe default + pa_volume.channels = 2; + spdlog::debug("Using default stereo volume structure"); + } + + // Set the volume safely volume = std::clamp(volume, min_volume, max_volume); - pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); + pa_volume_t vol = volume * (static_cast(PA_VOLUME_NORM) / 100); + // Set all channels to the same volume manually to avoid pa_cvolume_set + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = vol; + } + + // Apply the volume change pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) { - double volume_tick = static_cast(PA_VOLUME_NORM) / 100; - pa_volume_t change = volume_tick; - pa_cvolume pa_volume = pa_volume_; + // Early return if context is not ready + if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { + spdlog::error("PulseAudio context not ready"); + return; + } + // Prepare volume structure + pa_cvolume pa_volume; + pa_cvolume_init(&pa_volume); + + // Use existing volume structure if valid, otherwise create a safe default + if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { + pa_volume = pa_volume_; + } else { + // Set stereo as a safe default + pa_volume.channels = 2; + spdlog::debug("Using default stereo volume structure"); + + // Initialize all channels to current volume level + double volume_tick = static_cast(PA_VOLUME_NORM) / 100; + pa_volume_t vol = volume_ * volume_tick; + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = vol; + } + + // No need to continue with volume change if we had to create a new structure + pa_threaded_mainloop_lock(mainloop_); + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + pa_threaded_mainloop_unlock(mainloop_); + return; + } + + // Calculate volume change + double volume_tick = static_cast(PA_VOLUME_NORM) / 100; + pa_volume_t change; max_volume = std::min(max_volume, static_cast(PA_VOLUME_UI_MAX)); - if (change_type == ChangeType::Increase) { - if (volume_ < max_volume) { - if (volume_ + step > max_volume) { - change = round((max_volume - volume_) * volume_tick); - } else { - change = round(step * volume_tick); - } - pa_cvolume_inc(&pa_volume, change); + if (change_type == ChangeType::Increase && volume_ < max_volume) { + // Calculate how much to increase + if (volume_ + step > max_volume) { + change = round((max_volume - volume_) * volume_tick); + } else { + change = round(step * volume_tick); } - } else if (change_type == ChangeType::Decrease) { - if (volume_ > 0) { - if (volume_ - step < 0) { - change = round(volume_ * volume_tick); - } else { - change = round(step * volume_tick); - } - pa_cvolume_dec(&pa_volume, change); + + // Manually increase each channel's volume + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX); } + } else if (change_type == ChangeType::Decrease && volume_ > 0) { + // Calculate how much to decrease + if (volume_ - step < 0) { + change = round(volume_ * volume_tick); + } else { + change = round(step * volume_tick); + } + + // Manually decrease each channel's volume + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0; + } + } else { + // No change needed + return; } + + // Apply the volume change pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_threaded_mainloop_unlock(mainloop_); From 7e845f506e6fe666e6f5ba9e2907511adb373740 Mon Sep 17 00:00:00 2001 From: Almarhoon Ibraheem Date: Sat, 12 Apr 2025 18:31:34 +0300 Subject: [PATCH 32/38] sway workspace: fix workspace button not shown in nested layouts --- src/modules/sway/workspaces.cpp | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index dec5cddf..b8ed73d4 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) { return name; } +bool is_focused_recursive(const Json::Value& node) { + // If a workspace has a focused container then get_tree will say + // that the workspace itself isn't focused. Therefore we need to + // check if any of its nodes are focused as well. + // some layouts like tabbed have many nested nodes + // all nested nodes must be checked for focused flag + if (node["focused"].asBool()) { + return true; + } + + for (const auto& child : node["nodes"]) { + if (is_focused_recursive(child)) { + return true; + } + } + + for (const auto& child : node["floating_nodes"]) { + if (is_focused_recursive(child)) { + return true; + } + } + + return false; +} + void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { if (config_["current-only"].asBool()) { - // If a workspace has a focused container then get_tree will say - // that the workspace itself isn't focused. Therefore we need to - // check if any of its nodes are focused as well. - bool focused = node["focused"].asBool() || - std::any_of(node["nodes"].begin(), node["nodes"].end(), - [](const auto &child) { return child["focused"].asBool(); }); - - if (focused) { + if (is_focused_recursive(node)) { button.show(); } else { button.hide(); From 252e4f78bfb18d515c99c55afbe47a3987deb2d7 Mon Sep 17 00:00:00 2001 From: Kaiyang Wu Date: Sun, 13 Apr 2025 22:11:41 -0700 Subject: [PATCH 33/38] fix: support libcava 0.10.4 Signed-off-by: Kaiyang Wu --- include/modules/cava.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp index b4d2325b..1a88c7b7 100644 --- a/include/modules/cava.hpp +++ b/include/modules/cava.hpp @@ -5,7 +5,16 @@ namespace cava { extern "C" { +// Need sdl_glsl output feature to be enabled on libcava +#ifndef SDL_GLSL +#define SDL_GLSL +#endif + #include + +#ifdef SDL_GLSL +#undef SDL_GLSL +#endif } } // namespace cava From e85025f8052fbf25730c4153548677222d935490 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Tue, 15 Apr 2025 16:33:07 +0300 Subject: [PATCH 34/38] libCava bump: 0.10.4 --- meson.build | 2 +- subprojects/cava.wrap | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 1bc1f656..7f9854d5 100644 --- a/meson.build +++ b/meson.build @@ -486,7 +486,7 @@ if get_option('experimental') endif cava = dependency('cava', - version : '>=0.10.3', + version : '>=0.10.4', required: get_option('cava'), fallback : ['cava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap index f0309bf5..f220207c 100644 --- a/subprojects/cava.wrap +++ b/subprojects/cava.wrap @@ -1,7 +1,7 @@ [wrap-file] -directory = cava-0.10.3 -source_url = https://github.com/LukashonakV/cava/archive/0.10.3.tar.gz -source_filename = cava-0.10.3.tar.gz -source_hash = aab0a4ed3f999e8461ad9de63ef8a77f28b6b2011f7dd0c69ba81819d442f6f9 +directory = cava-0.10.4 +source_url = https://github.com/LukashonakV/cava/archive/0.10.4.tar.gz +source_filename = cava-0.10.4.tar.gz +source_hash =7bc1c1f9535f2bcc5cd2ae8a2434a2e3a05f5670b1c96316df304137ffc65756 [provide] cava = cava_dep From bf4f3ab064a4b8aa3b37868a1a6a8abede373e6e Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 15 Apr 2025 12:06:41 -0500 Subject: [PATCH 35/38] nix: cava bump --- nix/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index fc564f0a..2e621272 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,12 +5,12 @@ }: let libcava = rec { - version = "0.10.3"; + version = "0.10.4"; src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; - rev = version; - hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg="; + tag = version; + hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM="; }; }; in From 5c48373cfedb32300a51bb0e567fb9eeedc48253 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:43:47 -0500 Subject: [PATCH 36/38] flake.nix: add treefmt formatter Easier to format everything properly. --- flake.nix | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/flake.nix b/flake.nix index 571c4934..d19d4a1f 100644 --- a/flake.nix +++ b/flake.nix @@ -45,12 +45,41 @@ # overrides for local development nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ + nixfmt-rfc-style clang-tools gdb ]); }; }); + formatter = genSystems ( + pkgs: + pkgs.treefmt.withConfig { + settings = [ + { + formatter = { + clang-format = { + options = [ "-i" ]; + command = lib.getExe' pkgs.clang-tools "clang-format"; + excludes = []; + includes = [ + "*.c" + "*.cpp" + "*.h" + "*.hpp" + ]; + }; + nixfmt = { + command = lib.getExe pkgs.nixfmt-rfc-style; + includes = [ "*.nix" ]; + }; + }; + tree-root-file = ".git/index"; + } + ]; + } + ); + overlays = { default = self.overlays.waybar; waybar = final: prev: { From 55f52c345775535649d8861b83a0e936a207830c Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 15 Apr 2025 14:56:28 -0500 Subject: [PATCH 37/38] treewide: clang and nix format --- default.nix | 19 +++---- flake.nix | 106 ++++++++++++++++++++++-------------- nix/default.nix | 53 +++++++++--------- src/modules/sni/item.cpp | 12 ++-- src/modules/sni/tray.cpp | 3 +- src/modules/temperature.cpp | 8 +-- src/modules/wlr/taskbar.cpp | 2 +- 7 files changed, 113 insertions(+), 90 deletions(-) diff --git a/default.nix b/default.nix index 2cccff28..6466507b 100644 --- a/default.nix +++ b/default.nix @@ -1,10 +1,9 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).defaultNix +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } +) { src = ./.; }).defaultNix diff --git a/flake.nix b/flake.nix index d19d4a1f..7c7a2281 100644 --- a/flake.nix +++ b/flake.nix @@ -9,48 +9,68 @@ }; }; - outputs = { self, nixpkgs, ... }: + outputs = + { self, nixpkgs, ... }: let inherit (nixpkgs) lib; - genSystems = func: lib.genAttrs [ - "x86_64-linux" - "aarch64-linux" - ] - (system: func (import nixpkgs { - inherit system; - overlays = with self.overlays; [ - waybar - ]; - })); + genSystems = + func: + lib.genAttrs + [ + "x86_64-linux" + "aarch64-linux" + ] + ( + system: + func ( + import nixpkgs { + inherit system; + overlays = with self.overlays; [ + waybar + ]; + } + ) + ); - mkDate = longDate: (lib.concatStringsSep "-" [ - (builtins.substring 0 4 longDate) - (builtins.substring 4 2 longDate) - (builtins.substring 6 2 longDate) - ]); + mkDate = + longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); in { - devShells = genSystems - (pkgs: - { - default = - pkgs.mkShell - { - name = "waybar-shell"; + devShells = genSystems (pkgs: { + default = pkgs.mkShell { + name = "waybar-shell"; - # inherit attributes from upstream nixpkgs derivation - inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget - depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget - depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps; + # inherit attributes from upstream nixpkgs derivation + inherit (pkgs.waybar) + buildInputs + depsBuildBuild + depsBuildBuildPropagated + depsBuildTarget + depsBuildTargetPropagated + depsHostHost + depsHostHostPropagated + depsTargetTarget + depsTargetTargetPropagated + propagatedBuildInputs + propagatedNativeBuildInputs + strictDeps + ; - # overrides for local development - nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ - nixfmt-rfc-style - clang-tools - gdb - ]); - }; - }); + # overrides for local development + nativeBuildInputs = + pkgs.waybar.nativeBuildInputs + ++ (with pkgs; [ + nixfmt-rfc-style + clang-tools + gdb + ]); + }; + }); formatter = genSystems ( pkgs: @@ -61,7 +81,7 @@ clang-format = { options = [ "-i" ]; command = lib.getExe' pkgs.clang-tools "clang-format"; - excludes = []; + excludes = [ ]; includes = [ "*.c" "*.cpp" @@ -87,11 +107,15 @@ waybar = prev.waybar; # take the first "version: '...'" from meson.build version = - (builtins.head (builtins.split "'" - (builtins.elemAt - (builtins.split " version: '" (builtins.readFile ./meson.build)) - 2))) - + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + (builtins.head ( + builtins.split "'" ( + builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2 + ) + )) + + "+date=" + + (mkDate (self.lastModifiedDate or "19700101")) + + "_" + + (self.shortRev or "dirty"); }; }; }; diff --git a/nix/default.nix b/nix/default.nix index 2e621272..4cfd75c0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,7 +1,8 @@ -{ lib -, pkgs -, waybar -, version +{ + lib, + pkgs, + waybar, + version, }: let libcava = rec { @@ -14,31 +15,29 @@ let }; }; in -(waybar.overrideAttrs ( - oldAttrs: { - inherit version; +(waybar.overrideAttrs (oldAttrs: { + inherit version; - src = lib.cleanSourceWith { - filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; - src = lib.cleanSource ../.; - }; + src = lib.cleanSourceWith { + filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; + src = lib.cleanSource ../.; + }; - mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; + mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; - # downstream patch should not affect upstream - patches = []; - # nixpkgs checks version, no need when building locally - nativeInstallCheckInputs = []; + # downstream patch should not affect upstream + patches = [ ]; + # nixpkgs checks version, no need when building locally + nativeInstallCheckInputs = [ ]; - buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ - pkgs.wireplumber - ]; + buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ + pkgs.wireplumber + ]; - postUnpack = '' - pushd "$sourceRoot" - cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} - patchShebangs . - popd - ''; - } -)) + postUnpack = '' + pushd "$sourceRoot" + cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} + patchShebangs . + popd + ''; +})) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 978e8b07..9dc13158 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,16 +1,16 @@ #include "modules/sni/item.hpp" -#include "modules/sni/icon_manager.hpp" #include #include #include #include +#include #include #include -#include #include "gdk/gdk.h" +#include "modules/sni/icon_manager.hpp" #include "util/format.hpp" #include "util/gtk_icon.hpp" @@ -206,10 +206,10 @@ void Item::setCustomIcon(const std::string& id) { std::string custom_icon = IconManager::instance().getIconForApp(id); if (!custom_icon.empty()) { if (std::filesystem::exists(custom_icon)) { - Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); - icon_name = ""; // icon_name has priority over pixmap - icon_pixmap = custom_pixbuf; - } else { // if file doesn't exist it's most likely an icon_name + Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); + icon_name = ""; // icon_name has priority over pixmap + icon_pixmap = custom_pixbuf; + } else { // if file doesn't exist it's most likely an icon_name icon_name = custom_icon; } } diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index abd23ebd..f657c855 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -1,8 +1,9 @@ #include "modules/sni/tray.hpp" -#include "modules/sni/icon_manager.hpp" #include +#include "modules/sni/icon_manager.hpp" + namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 93f427ee..a3e1c1ee 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -115,10 +115,10 @@ float waybar::modules::Temperature::getTemperature() { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; // First, try with dev.cpu - if ( (sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) == 0) || - (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) == 0) ) { + if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) == + 0) || + (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0)) { auto temperature_c = ((float)temp - 2732) / 10; return temperature_c; } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 1169425a..8e3b2542 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -382,7 +382,7 @@ std::string Task::state_string(bool shortened) const { return res.substr(0, res.size() - 1); } -void Task::handle_title(const char* title) { +void Task::handle_title(const char *title) { if (title_.empty()) { spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_)); } else { From ba8ea3d952c95311a25758a5e9f5420f83e58236 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 24 Apr 2025 09:29:40 +0000 Subject: [PATCH 38/38] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30) → 'github:NixOS/nixpkgs/8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7?narHash=sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo%3D' (2025-04-23) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 27290894..480f004f 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743315132, - "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", + "lastModified": 1745391562, + "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "52faf482a3889b7619003c0daec593a1912fddc1", + "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", "type": "github" }, "original": {