From 929fc16994c413923af31d8b4a3d302315943a47 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 23 Jun 2021 00:27:30 -0700 Subject: [PATCH 1/6] fix(tray): ignore unused WindowId property --- include/modules/sni/item.hpp | 1 - src/modules/sni/item.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 3cbd0b7..11402c1 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -30,7 +30,6 @@ class Item : public sigc::trackable { std::string status; std::string title; - int32_t window_id; std::string icon_name; Glib::RefPtr icon_pixmap; Glib::RefPtr icon_theme; diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 6fca472..0ccba19 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -105,8 +105,6 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { title = get_variant(value); } else if (name == "Status") { status = get_variant(value); - } else if (name == "WindowId") { - window_id = get_variant(value); } else if (name == "IconName") { icon_name = get_variant(value); } else if (name == "IconPixmap") { From 4b6253e81001e285c7e09a7196b3dad98d9aa783 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 23 Jun 2021 00:27:42 -0700 Subject: [PATCH 2/6] refactor(tray): infer changed properties from signal name Comparing two GVariants is too expensive; let's collect the set of properties updated by each signal and apply them unconditionally. --- include/modules/sni/item.hpp | 5 +++- src/modules/sni/item.cpp | 46 ++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 11402c1..fbb5163 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -11,6 +11,9 @@ #include #include +#include +#include + namespace waybar::modules::SNI { class Item : public sigc::trackable { @@ -64,7 +67,7 @@ class Item : public sigc::trackable { Glib::RefPtr proxy_; Glib::RefPtr cancellable_; - bool update_pending_; + std::set update_pending_; }; } // namespace waybar::modules::SNI diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 0ccba19..d3eed4a 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,8 +1,11 @@ #include "modules/sni/item.hpp" + #include #include #include + #include +#include template <> struct fmt::formatter : formatter { @@ -40,8 +43,7 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf object_path(op), icon_size(16), effective_icon_size(0), - icon_theme(Gtk::IconTheme::create()), - update_pending_(false) { + icon_theme(Gtk::IconTheme::create()) { if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } @@ -148,8 +150,6 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } void Item::getUpdatedProperties() { - update_pending_ = false; - auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)}); proxy_->call("org.freedesktop.DBus.Properties.GetAll", @@ -166,10 +166,7 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { auto properties = properties_variant.get(); for (const auto& [name, value] : properties) { - Glib::VariantBase old_value; - proxy_->get_cached_property(old_value, name); - if (!old_value || !value.equal(old_value)) { - proxy_->set_cached_property(name, value); + if (update_pending_.count(name.raw())) { setProperty(name, const_cast(value)); } } @@ -181,18 +178,37 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { } catch (const std::exception& err) { spdlog::warn("Failed to update properties: {}", err.what()); } + update_pending_.clear(); } +/** + * Mapping from a signal name to a set of possibly changed properties. + * Commented signals are not handled by the tray module at the moment. + */ +static const std::map> signal2props = { + {"NewTitle", {"Title"}}, + {"NewIcon", {"IconName", "IconPixmap"}}, + // {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, + // {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, + {"NewIconThemePath", {"IconThemePath"}}, + {"NewToolTip", {"ToolTip"}}, + {"NewStatus", {"Status"}}, + // {"XAyatanaNewLabel", {"XAyatanaLabel"}}, +}; + void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { spdlog::trace("Tray item '{}' got signal {}", id, signal_name); - if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) { - /* Debounce signals and schedule update of all properties. - * Based on behavior of Plasma dataengine for StatusNotifierItem. - */ - update_pending_ = true; - Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), - UPDATE_DEBOUNCE_TIME); + auto changed = signal2props.find(signal_name.raw()); + if (changed != signal2props.end()) { + if (update_pending_.empty()) { + /* Debounce signals and schedule update of all properties. + * Based on behavior of Plasma dataengine for StatusNotifierItem. + */ + Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), + UPDATE_DEBOUNCE_TIME); + } + update_pending_.insert(changed->second.begin(), changed->second.end()); } } From 84a8f79bbe18ec17036f7816d29cc6c5df20b0de Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 23 Jun 2021 23:47:35 -0700 Subject: [PATCH 3/6] feat(tray): implement tooltips (text only) for tray items --- include/modules/sni/item.hpp | 6 ++++++ src/modules/sni/item.cpp | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index fbb5163..fc4596e 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -16,6 +16,11 @@ namespace waybar::modules::SNI { +struct ToolTip { + Glib::ustring icon_name; + Glib::ustring text; +}; + class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&); @@ -41,6 +46,7 @@ class Item : public sigc::trackable { std::string attention_movie_name; std::string icon_theme_path; std::string menu; + ToolTip tooltip; DbusmenuGtkMenu* dbus_menu = nullptr; Gtk::Menu* gtk_menu = nullptr; /** diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index d3eed4a..f092183 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -81,7 +82,6 @@ void Item::proxyReady(Glib::RefPtr& result) { return; } this->updateImage(); - // this->event_box.set_tooltip_text(this->title); } catch (const Glib::Error& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); @@ -91,10 +91,24 @@ void Item::proxyReady(Glib::RefPtr& result) { } template -T get_variant(Glib::VariantBase& value) { +T get_variant(const Glib::VariantBase& value) { return Glib::VariantBase::cast_dynamic>(value).get(); } +template <> +ToolTip get_variant(const Glib::VariantBase& value) { + ToolTip result; + // Unwrap (sa(iiay)ss) + auto container = value.cast_dynamic(value); + result.icon_name = get_variant(container.get_child(0)); + result.text = get_variant(container.get_child(2)); + auto description = get_variant(container.get_child(3)); + if (!description.empty()) { + result.text = fmt::format("{}\n{}", result.text, description); + } + return result; +} + void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); @@ -122,7 +136,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } else if (name == "AttentionMovieName") { attention_movie_name = get_variant(value); } else if (name == "ToolTip") { - // TODO: tooltip + tooltip = get_variant(value); + if (!tooltip.text.empty()) { + event_box.set_tooltip_markup(tooltip.text); + } } else if (name == "IconThemePath") { icon_theme_path = get_variant(value); if (!icon_theme_path.empty()) { @@ -172,7 +189,6 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { } this->updateImage(); - // this->event_box.set_tooltip_text(this->title); } catch (const Glib::Error& err) { spdlog::warn("Failed to update properties: {}", err.what()); } catch (const std::exception& err) { From 1418f96e4653aad2e8f0be25453ac022632cbe32 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 30 Jun 2021 22:17:27 -0700 Subject: [PATCH 4/6] feat(tray): fallback to Title for items without ToolTip --- src/modules/sni/item.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index f092183..d4c4872 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -119,6 +119,9 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { id = get_variant(value); } else if (name == "Title") { title = get_variant(value); + if (tooltip.text.empty()) { + event_box.set_tooltip_markup(title); + } } else if (name == "Status") { status = get_variant(value); } else if (name == "IconName") { From 245f7f4b11731c158f15703750b85dd6c87d660f Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 26 Jun 2021 16:33:25 -0700 Subject: [PATCH 5/6] feat(tray): handle scroll events --- include/modules/sni/item.hpp | 6 ++++ man/waybar-tray.5.scd | 4 +++ src/modules/sni/item.cpp | 54 +++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index fc4596e..e51d3c7 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -70,6 +70,12 @@ class Item : public sigc::trackable { static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); void makeMenu(); bool handleClick(GdkEventButton* const& /*ev*/); + bool handleScroll(GdkEventScroll* const&); + + // smooth scrolling threshold + gdouble scroll_threshold_ = 0; + gdouble distance_scrolled_x_ = 0; + gdouble distance_scrolled_y_ = 0; Glib::RefPtr proxy_; Glib::RefPtr cancellable_; diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index cd0e93f..5446a56 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -16,6 +16,10 @@ Addressed by *tray* typeof: integer ++ Defines the size of the tray icons. +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + *spacing*: ++ typeof: integer ++ Defines the spacing between the tray icons. diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index d4c4872..9a77d24 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -48,9 +48,13 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } + if (config["smooth-scrolling-threshold"].isNumeric()) { + scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble(); + } event_box.add(image); - event_box.add_events(Gdk::BUTTON_PRESS_MASK); + event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); + event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); cancellable_ = Gio::Cancellable::create(); @@ -403,4 +407,52 @@ bool Item::handleClick(GdkEventButton* const& ev) { return false; } +bool Item::handleScroll(GdkEventScroll* const& ev) { + int dx = 0, dy = 0; + switch (ev->direction) { + case GDK_SCROLL_UP: + dy = -1; + break; + case GDK_SCROLL_DOWN: + dy = 1; + break; + case GDK_SCROLL_LEFT: + dx = -1; + break; + case GDK_SCROLL_RIGHT: + dx = 1; + break; + case GDK_SCROLL_SMOOTH: + distance_scrolled_x_ += ev->delta_x; + distance_scrolled_y_ += ev->delta_y; + // check against the configured threshold and ensure that the absolute value >= 1 + if (distance_scrolled_x_ > scroll_threshold_) { + dx = (int)lround(std::max(distance_scrolled_x_, 1.0)); + distance_scrolled_x_ = 0; + } else if (distance_scrolled_x_ < -scroll_threshold_) { + dx = (int)lround(std::min(distance_scrolled_x_, -1.0)); + distance_scrolled_x_ = 0; + } + if (distance_scrolled_y_ > scroll_threshold_) { + dy = (int)lround(std::max(distance_scrolled_y_, 1.0)); + distance_scrolled_y_ = 0; + } else if (distance_scrolled_y_ < -scroll_threshold_) { + dy = (int)lround(std::min(distance_scrolled_y_, -1.0)); + distance_scrolled_y_ = 0; + } + break; + } + if (dx != 0) { + auto parameters = Glib::VariantContainerBase::create_tuple( + {Glib::Variant::create(dx), Glib::Variant::create("horizontal")}); + proxy_->call("Scroll", parameters); + } + if (dy != 0) { + auto parameters = Glib::VariantContainerBase::create_tuple( + {Glib::Variant::create(dy), Glib::Variant::create("vertical")}); + proxy_->call("Scroll", parameters); + } + return true; +} + } // namespace waybar::modules::SNI From a5fe6f40b8d1439b8c9794e30ba97d92fabc4715 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 2 Jul 2021 19:44:10 -0700 Subject: [PATCH 6/6] feat(tray): handle Status property On the `Passive` value of `Status` tray items would be hidden unless `show-passive-items` is set to true. On the `NeedsAttention` value of `Status` tray items will have a `.needs-attention` CSS class. --- include/modules/sni/item.hpp | 4 +++- man/waybar-tray.5.scd | 8 ++++++++ resources/style.css | 9 +++++++++ src/modules/sni/item.cpp | 24 ++++++++++++++++++++++-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index e51d3c7..1f1503e 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -35,7 +35,6 @@ class Item : public sigc::trackable { Gtk::EventBox event_box; std::string category; std::string id; - std::string status; std::string title; std::string icon_name; @@ -59,6 +58,7 @@ class Item : public sigc::trackable { private: void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); + void setStatus(const Glib::ustring& value); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, @@ -76,6 +76,8 @@ class Item : public sigc::trackable { gdouble scroll_threshold_ = 0; gdouble distance_scrolled_x_ = 0; gdouble distance_scrolled_y_ = 0; + // visibility of items with Status == Passive + bool show_passive_ = false; Glib::RefPtr proxy_; Glib::RefPtr cancellable_; diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index 5446a56..c664594 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -16,6 +16,11 @@ Addressed by *tray* typeof: integer ++ Defines the size of the tray icons. +*show-passive-items*: ++ + typeof: bool ++ + default: false ++ + Defines visibility of the tray icons with *Passive* status. + *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. @@ -41,3 +46,6 @@ Addressed by *tray* # STYLE - *#tray* +- *#tray > .passive* +- *#tray > .active* +- *#tray > .needs-attention* diff --git a/resources/style.css b/resources/style.css index 63ea871..32dce42 100644 --- a/resources/style.css +++ b/resources/style.css @@ -195,6 +195,15 @@ label:focus { background-color: #2980b9; } +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; + background-color: #eb4d4b; +} + #idle_inhibitor { background-color: #2d3436; } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 9a77d24..267cf63 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -51,10 +51,15 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf if (config["smooth-scrolling-threshold"].isNumeric()) { scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble(); } + if (config["show-passive-items"].isBool()) { + show_passive_ = config["show-passive-items"].asBool(); + } event_box.add(image); event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); + // initial visibility + event_box.set_visible(show_passive_); cancellable_ = Gio::Cancellable::create(); @@ -81,7 +86,7 @@ void Item::proxyReady(Glib::RefPtr& result) { this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal)); - if (this->id.empty() || this->category.empty() || this->status.empty()) { + if (this->id.empty() || this->category.empty()) { spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); return; } @@ -127,7 +132,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { event_box.set_tooltip_markup(title); } } else if (name == "Status") { - status = get_variant(value); + setStatus(get_variant(value)); } else if (name == "IconName") { icon_name = get_variant(value); } else if (name == "IconPixmap") { @@ -173,6 +178,21 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } +void Item::setStatus(const Glib::ustring& value) { + Glib::ustring lower = value.lowercase(); + event_box.set_visible(show_passive_ || lower.compare("passive") != 0); + + auto style = event_box.get_style_context(); + for (const auto& class_name : style->list_classes()) { + style->remove_class(class_name); + } + if (lower.compare("needsattention") == 0) { + // convert status to dash-case for CSS + lower = "needs-attention"; + } + style->add_class(lower); +} + void Item::getUpdatedProperties() { auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)});