diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 80aa431af..ec99eb2c4 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -24,6 +24,11 @@ class AIconLabel : public ALabel { bool label_contains_icon{false}; bool iconEnabled() const; + bool setTooltipMarkup (const Glib::ustring& markup) override; + + private: + std::string iconLabel_; + std::string cleanLabel_; }; } // namespace waybar diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 948289aea..5dbb70717 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -32,8 +32,14 @@ class ALabel : public AModule { bool alt_ = false; std::string default_format_; - bool setLabelMarkup(const Glib::ustring& markup); - bool setTooltipMarkup(const Glib::ustring& markup); + // 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_; + + virtual bool setLabelMarkup(const Glib::ustring& markup); + virtual bool setTooltipMarkup(const Glib::ustring& markup); // resolveTooltipFormat() / resolveFormat() are inherited from AModule. @@ -88,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_; }; diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp index 7f78c1650..75bbc5f00 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 8fef19852..5b49b9617 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 43cb6cab1..46069f45e 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 0e4051879..fb9278ae6 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 a74ffda2c..7e8946097 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 d65ef84d0..9bb10c382 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/man/waybar.5.scd.in b/man/waybar.5.scd.in index ff2af1bc3..029f67b2f 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: diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index a54266ac1..66942a743 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -2,6 +2,7 @@ #include #include + #include #include @@ -74,36 +75,38 @@ 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); } 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 && label_.get_label().c_str() != cleanLabel_) { + 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); } } @@ -112,6 +115,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); } diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index ef9a13b68..246f6c37a 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 836a0d71f..0f7056ccb 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 05b30ca28..5a88b3894 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 { @@ -197,7 +197,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { toggleStatus(0); timeout = config_["timeout"].asDouble(); } - ALabel::handleToggle(e); + AIconLabel::handleToggle(e); return true; } @@ -206,7 +206,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) { @@ -221,7 +221,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 88b711d32..355a738ee 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); @@ -126,7 +126,7 @@ auto Inhibitor::update() -> void { label_.set_tooltip_markup(status_text); } - return ALabel::update(); + return AIconLabel::update(); } auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { @@ -142,7 +142,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 610c38304..2d2f1fd91 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 149222108..b6c93688c 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 @@ -496,7 +496,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 4b410cd31..488d28796 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() {