From a591611922e72fdb67e6073ff54b30f68db95faa Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sun, 5 Jul 2026 22:53:33 +0700 Subject: [PATCH 1/6] Change module parent into AIconLabel modules affected: cpu, idle_inhibitor, inhibitor, memory, network, temperature, custom --- include/modules/cpu.hpp | 4 ++-- include/modules/idle_inhibitor.hpp | 4 ++-- include/modules/inhibitor.hpp | 4 ++-- include/modules/memory.hpp | 4 ++-- include/modules/network.hpp | 4 ++-- include/modules/temperature.hpp | 4 ++-- src/modules/cpu.cpp | 4 ++-- src/modules/custom.cpp | 4 ++-- src/modules/idle_inhibitor.cpp | 10 +++++----- src/modules/inhibitor.cpp | 6 +++--- src/modules/memory/common.cpp | 4 ++-- src/modules/network.cpp | 4 ++-- src/modules/temperature.cpp | 4 ++-- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp index 7f78c1650e..75bbc5f004 100644 --- a/include/modules/cpu.hpp +++ b/include/modules/cpu.hpp @@ -9,12 +9,12 @@ #include #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Cpu : public ALabel { +class Cpu : public AIconLabel { public: Cpu(const std::string&, const Json::Value&); virtual ~Cpu() = default; diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 8fef198522..5b49b96171 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -2,7 +2,7 @@ #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "bar.hpp" #include "client.hpp" @@ -10,7 +10,7 @@ struct ext_idle_notification_v1; namespace waybar::modules { -class IdleInhibitor : public ALabel { +class IdleInhibitor : public AIconLabel { sigc::connection timeout_; ext_idle_notification_v1* idle_notification_; uint32_t idle_timeout_ms_; diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp index 43cb6cab1b..46069f45eb 100644 --- a/include/modules/inhibitor.hpp +++ b/include/modules/inhibitor.hpp @@ -4,12 +4,12 @@ #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "bar.hpp" namespace waybar::modules { -class Inhibitor : public ALabel { +class Inhibitor : public AIconLabel { public: Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Inhibitor(); diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp index 0e40518790..fb9278ae64 100644 --- a/include/modules/memory.hpp +++ b/include/modules/memory.hpp @@ -5,12 +5,12 @@ #include #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Memory : public ALabel { +class Memory : public AIconLabel { public: Memory(const std::string&, const Json::Value&); virtual ~Memory() = default; diff --git a/include/modules/network.hpp b/include/modules/network.hpp index a74ffda2c5..7e8946097e 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -11,7 +11,7 @@ #include #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "util/sleeper_thread.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" @@ -23,7 +23,7 @@ enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 }; namespace waybar::modules { -class Network : public ALabel { +class Network : public AIconLabel { public: Network(const std::string&, const Json::Value&); virtual ~Network(); diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp index d65ef84d01..9bb10c3825 100644 --- a/include/modules/temperature.hpp +++ b/include/modules/temperature.hpp @@ -4,12 +4,12 @@ #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Temperature : public ALabel { +class Temperature : public AIconLabel { public: Temperature(const std::string&, const Json::Value&); virtual ~Temperature() = default; diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index ef9a13b681..246f6c37a6 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -14,7 +14,7 @@ #endif waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) - : ALabel(config, "cpu", id, "{usage}%", 10) { + : AIconLabel(config, "cpu", id, "{usage}%", 10) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -66,5 +66,5 @@ auto waybar::modules::Cpu::update() -> void { } // Call parent update - ALabel::update(); + AIconLabel::update(); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 64577509ec..1c288dc03b 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -180,13 +180,13 @@ void waybar::modules::Custom::handleEvent() { } bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) { - auto ret = ALabel::handleScroll(e); + auto ret = AIconLabel::handleScroll(e); handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { - auto ret = ALabel::handleToggle(e); + auto ret = AIconLabel::handleToggle(e); handleEvent(); return ret; } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 170701cea5..43fbad74fc 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -10,7 +10,7 @@ long waybar::modules::IdleInhibitor::deactivationTime = time(nullptr); waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true), + : AIconLabel(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), idle_notification_(nullptr), @@ -99,7 +99,7 @@ auto waybar::modules::IdleInhibitor::update() -> void { fmt::arg("icon", getIcon(0, status_text))); label_.get_style_context()->add_class(status_text); // Call parent update - ALabel::update(); + AIconLabel::update(); } auto waybar::modules::IdleInhibitor::refresh(int sig) -> void { @@ -195,7 +195,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { toggleStatus(0); timeout = config_["timeout"].asDouble(); } - ALabel::handleToggle(e); + AIconLabel::handleToggle(e); return true; } @@ -204,7 +204,7 @@ bool waybar::modules::IdleInhibitor::handleScroll(GdkEventScroll* e) { // "dynamic-timeout" (singular) key spellings. if (!(config_["dynamic-timeouts"].asBool() || config_["dynamic-timeout"].asBool())) { // Delegate to the base handler so any configured on-scroll-* command still runs. - return ALabel::handleScroll(e); + return AIconLabel::handleScroll(e); } auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { @@ -219,7 +219,7 @@ bool waybar::modules::IdleInhibitor::handleScroll(GdkEventScroll* e) { } deactivationTime = time(nullptr) + timeout * 60; - ALabel::handleScroll(e); + AIconLabel::handleScroll(e); return true; } diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index 43c1f83e72..6683c1a045 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -99,7 +99,7 @@ auto getInhibitors(const Json::Value& config) -> std::string { namespace waybar::modules { Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "inhibitor", id, "{status}", true), + : AIconLabel(config, "inhibitor", id, "{status}", true), dbus_(::dbus()), inhibitors_(::getInhibitors(config)) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); @@ -127,7 +127,7 @@ auto Inhibitor::update() -> void { label_.set_tooltip_markup(status_text); } - return ALabel::update(); + return AIconLabel::update(); } auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { @@ -143,7 +143,7 @@ auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { } } - return ALabel::handleToggle(e); + return AIconLabel::handleToggle(e); } } // namespace waybar::modules diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 610c383041..2d2f1fd916 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -12,7 +12,7 @@ const std::unordered_map kUnits = { } waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) - : ALabel(config, "memory", id, "{}%", 30) { + : AIconLabel(config, "memory", id, "{}%", 30) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -91,5 +91,5 @@ auto waybar::modules::Memory::update() -> void { event_box_.hide(); } // Call parent update - ALabel::update(); + AIconLabel::update(); } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index d3adfbe59b..c3a9d94319 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -103,7 +103,7 @@ uint32_t waybar::modules::Network::readLinkSpeed() const { } waybar::modules::Network::Network(const std::string& id, const Json::Value& config) - : ALabel(config, "network", id, DEFAULT_FORMAT, 60) { + : AIconLabel(config, "network", id, DEFAULT_FORMAT, 60) { // Start with some "text" in the module's label_. update() will then // update it. Since the text should be different, update() will be able // to show or hide the event_box_. This is to work around the case where @@ -497,7 +497,7 @@ auto waybar::modules::Network::update() -> void { } // Call parent update - ALabel::update(); + AIconLabel::update(); } // https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495 diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 4b410cd31a..488d287963 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -10,7 +10,7 @@ #endif waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config) - : ALabel(config, "temperature", id, "{temperatureC}°C", 10) { + : AIconLabel(config, "temperature", id, "{temperatureC}°C", 10) { #if defined(__FreeBSD__) // FreeBSD uses sysctlbyname instead of read from a file #else @@ -160,7 +160,7 @@ auto waybar::modules::Temperature::update() -> void { fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp))); // Call parent update - ALabel::update(); + AIconLabel::update(); } float waybar::modules::Temperature::getTemperature() { From 98882b98c4ad48f0d06251e18eae67f0d4f8f2df Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sun, 5 Jul 2026 23:25:30 +0700 Subject: [PATCH 2/6] AIconLabel: apply tooltip to box_ instead of just label_ ALabel: make last_label_markup_ and last_tooltip_markup_ protected, as AIconLabel might need it --- include/AIconLabel.hpp | 1 + include/ALabel.hpp | 13 ++++++------- src/AIconLabel.cpp | 14 +++++++++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 80aa431afc..adf97cbea2 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -24,6 +24,7 @@ class AIconLabel : public ALabel { bool label_contains_icon{false}; bool iconEnabled() const; + bool setTooltipMarkup(const Glib::ustring& markup); }; } // namespace waybar diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 3e3690b101..30b27f5c58 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -31,6 +31,12 @@ class ALabel : public AModule { bool alt_ = false; std::string default_format_; + // Raw UTF-8 bytes, not Glib::ustring: ustring::operator== collates with + // g_utf8_collate(), which gives private-use codepoints (nerd-font icons) + // no collation weight, so two different icons compare equal. + std::optional last_label_markup_; + std::optional last_tooltip_markup_; + bool setLabelMarkup(const Glib::ustring& markup); bool setTooltipMarkup(const Glib::ustring& markup); @@ -85,13 +91,6 @@ class ALabel : public AModule { std::map submenus_; std::map menuActionsMap_; static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); - - private: - // Raw UTF-8 bytes, not Glib::ustring: ustring::operator== collates with - // g_utf8_collate(), which gives private-use codepoints (nerd-font icons) - // no collation weight, so two different icons compare equal. - std::optional last_label_markup_; - std::optional last_tooltip_markup_; }; } // namespace waybar diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index a54266ac16..8480e77d78 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -2,6 +2,7 @@ #include #include + #include #include @@ -74,7 +75,7 @@ std::tuple AIconLabel::extractIcon(const std::string& label_result = std::regex_replace(input, clean_label_pattern, ""); } } catch (const std::exception& e) { - spdlog::warn("Error while parsing icon from label. {}", e.what()); + spdlog::warn("Error while parsing icon from label. {}", e.what()); } return std::make_tuple(icon_result, label_result); @@ -112,6 +113,17 @@ auto AIconLabel::update() -> void { ALabel::update(); } +bool AIconLabel::setTooltipMarkup(const Glib::ustring& markup) { + if (last_tooltip_markup_ == markup) { + return false; + } + + box_.set_tooltip_markup(markup); + + last_tooltip_markup_ = markup; + return true; +} + bool AIconLabel::iconEnabled() const { return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false); } From 8d05e2810212cfb51e7fbec12799ab3a09eeede9 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sun, 5 Jul 2026 23:40:32 +0700 Subject: [PATCH 3/6] Document support for Rofi dmenu icons in labels --- man/waybar.5.scd.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 78dc433ec6..65928bd15c 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -313,6 +313,10 @@ When positioning Waybar on the left or right side of the screen, sometimes it's Valid options for the "rotate" property are: 0, 90, 180, and 270. +## Encode icon name in label string +You can use Rofi’s extended dmenu protocol to use system icon as module's icon. String in "format" and/or "format-icons" property may contain Rofi's extended dmenu protocol which is substring that prefixed with \0icon\1f followed by named icon or path to an image and ended with \n. Because of config file written in JSON then it should written \\0icon\\1f\\n in "format" and/or "format-icon" property. + + ## Swapping icon and label If a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the "swap-icon-label" property, taking a boolean. Example: From 758f18481f1436dff0c673a65a0bab98184b471f Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 8 Jul 2026 08:13:49 +0700 Subject: [PATCH 4/6] Store the extracted icon and label strings as class members to avoid re-extraction during UI updates. --- include/AIconLabel.hpp | 4 ++++ src/AIconLabel.cpp | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index adf97cbea2..d91a2a8e83 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -25,6 +25,10 @@ class AIconLabel : public ALabel { bool iconEnabled() const; bool setTooltipMarkup(const Glib::ustring& markup); + + private: + std::string iconLabel_; + std::string cleanLabel_; }; } // namespace waybar diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 8480e77d78..db5f4f78d7 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -82,29 +82,31 @@ std::tuple AIconLabel::extractIcon(const std::string& } auto AIconLabel::update() -> void { - label_contains_icon = false; - - auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str()); - label_contains_icon = iconLabel.length() > 0; + if (label_.get_label().length() > 0) { + auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str()); + label_contains_icon = iconLabel.length() > 0; + iconLabel_ = iconLabel; + cleanLabel_ = cleanLabel; + } if (label_contains_icon) { - label_.set_markup(cleanLabel); + label_.set_markup(cleanLabel_); - if (iconLabel.front() == '/') { + if (iconLabel_.front() == '/') { try { int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); - auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size); + auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel_, scaled_icon_size, scaled_icon_size); auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); image_.set(surface); image_.set_visible(true); } catch (const Glib::Exception& e) { - spdlog::warn("Failed to load embedded icon {}: {}", iconLabel, std::string(e.what())); + spdlog::warn("Failed to load embedded icon {}: {}", iconLabel_, std::string(e.what())); image_.set_visible(false); } } else { - image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID); + image_.set_from_icon_name(iconLabel_, Gtk::ICON_SIZE_INVALID); image_.set_visible(true); } } From 5d47c44c5c5003ae27abbeaced87716ff276a7ba Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 8 Jul 2026 09:05:16 +0700 Subject: [PATCH 5/6] - Mark ALabel setTooltipMarkup and setLabelMarkup as virtual so AIconLabel can implement their own method - Optimize iconLabel update --- include/AIconLabel.hpp | 2 +- include/ALabel.hpp | 4 ++-- src/AIconLabel.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index d91a2a8e83..ec99eb2c4d 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -24,7 +24,7 @@ class AIconLabel : public ALabel { bool label_contains_icon{false}; bool iconEnabled() const; - bool setTooltipMarkup(const Glib::ustring& markup); + bool setTooltipMarkup (const Glib::ustring& markup) override; private: std::string iconLabel_; diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 30b27f5c58..8352ce5816 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -37,8 +37,8 @@ class ALabel : public AModule { std::optional last_label_markup_; std::optional last_tooltip_markup_; - bool setLabelMarkup(const Glib::ustring& markup); - bool setTooltipMarkup(const Glib::ustring& markup); + virtual bool setLabelMarkup(const Glib::ustring& markup); + virtual bool setTooltipMarkup(const Glib::ustring& markup); // resolveTooltipFormat() / resolveFormat() are inherited from AModule. diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index db5f4f78d7..66942a7435 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -82,7 +82,7 @@ std::tuple AIconLabel::extractIcon(const std::string& } auto AIconLabel::update() -> void { - if (label_.get_label().length() > 0) { + if (label_.get_label().length() > 0 && label_.get_label().c_str() != cleanLabel_) { auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str()); label_contains_icon = iconLabel.length() > 0; iconLabel_ = iconLabel; From b45c42445b479f4f806f7ed6d44110b73751eb85 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Thu, 30 Jul 2026 22:47:57 +0700 Subject: [PATCH 6/6] Mark ALabel setTooltipMarkup and setLabelMarkup as virtual so AIconLabel can implement their own method --- include/ALabel.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 770eff1578..5dbb707171 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -94,11 +94,6 @@ class ALabel : public AModule { static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); private: - // Raw UTF-8 bytes, not Glib::ustring: ustring::operator== collates with - // g_utf8_collate(), which gives private-use codepoints (nerd-font icons) - // no collation weight, so two different icons compare equal. - std::optional last_label_markup_; - std::optional last_tooltip_markup_; Glib::RefPtr active_tooltip_; };