From f6d92fd708d0fd87b008e3ed8005bc6b65fb3e05 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 01:08:53 -0600 Subject: [PATCH] fix(sni): render attention and overlay tray icon assets Load attention and overlay pixmaps from item properties, watch the corresponding update signals, and prefer attention artwork while an item is in NeedsAttention state. When an item only exports an attention movie asset, fall back to loading that asset as a static pixbuf so the tray still shows the alert state. Signed-off-by: Austin Horstman --- include/modules/sni/item.hpp | 9 ++- src/modules/sni/item.cpp | 110 +++++++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 26 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 43200fdb..74d54f4c 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -48,7 +48,9 @@ class Item : public sigc::trackable { Glib::RefPtr icon_pixmap; Glib::RefPtr icon_theme; std::string overlay_icon_name; + Glib::RefPtr overlay_icon_pixmap; std::string attention_icon_name; + Glib::RefPtr attention_icon_pixmap; std::string attention_movie_name; std::string icon_theme_path; std::string menu; @@ -76,8 +78,13 @@ class Item : public sigc::trackable { const Glib::VariantContainerBase& arguments); void updateImage(); - Glib::RefPtr extractPixBuf(GVariant* variant); + static Glib::RefPtr extractPixBuf(GVariant* variant); Glib::RefPtr getIconPixbuf(); + Glib::RefPtr getAttentionIconPixbuf(); + Glib::RefPtr getOverlayIconPixbuf(); + Glib::RefPtr loadIconFromNameOrFile(const std::string& name, bool log_failure); + static Glib::RefPtr overlayPixbufs(const Glib::RefPtr&, + const Glib::RefPtr&); Glib::RefPtr getIconByName(const std::string& name, int size); double getScaledIconSize(); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 9820cc62..2f368083 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -195,11 +196,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } else if (name == "OverlayIconName") { overlay_icon_name = get_variant(value); } else if (name == "OverlayIconPixmap") { - // TODO: overlay_icon_pixmap + overlay_icon_pixmap = extractPixBuf(value.gobj()); } else if (name == "AttentionIconName") { attention_icon_name = get_variant(value); } else if (name == "AttentionIconPixmap") { - // TODO: attention_icon_pixmap + attention_icon_pixmap = extractPixBuf(value.gobj()); } else if (name == "AttentionMovieName") { attention_movie_name = get_variant(value); } else if (name == "ToolTip") { @@ -315,8 +316,8 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { static const std::map> signal2props = { {"NewTitle", {"Title"}}, {"NewIcon", {"IconName", "IconPixmap"}}, - // {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, - // {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, + {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, + {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, {"NewIconThemePath", {"IconThemePath"}}, {"NewToolTip", {"ToolTip"}}, {"NewStatus", {"Status"}}, @@ -406,36 +407,24 @@ void Item::updateImage() { pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } + pixbuf = overlayPixbufs(pixbuf, getOverlayIconPixbuf()); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window()); image.set(surface); } Glib::RefPtr Item::getIconPixbuf() { - if (!icon_name.empty()) { - try { - std::ifstream temp(icon_name); - if (temp.is_open()) { - return Gdk::Pixbuf::create_from_file(icon_name); - } - } catch (Glib::Error& e) { - // Ignore because we want to also try different methods of getting an icon. - // - // But a warning is logged, as the file apparently exists, but there was - // a failure in creating a pixbuf out of it. - - spdlog::warn("Item '{}': {}", id, static_cast(e.what())); - } - - try { - // Will throw if it can not find an icon. - return getIconByName(icon_name, getScaledIconSize()); - } catch (Glib::Error& e) { - spdlog::trace("Item '{}': {}", id, static_cast(e.what())); + if (status_ == "needsattention") { + if (auto attention_pixbuf = getAttentionIconPixbuf()) { + return attention_pixbuf; } } - // Return the pixmap only if an icon for the given name could not be found. + if (auto pixbuf = loadIconFromNameOrFile(icon_name, true)) { + return pixbuf; + } + if (icon_pixmap) { return icon_pixmap; } @@ -450,6 +439,77 @@ Glib::RefPtr Item::getIconPixbuf() { return getIconByName("image-missing", getScaledIconSize()); } +Glib::RefPtr Item::getAttentionIconPixbuf() { + if (auto pixbuf = loadIconFromNameOrFile(attention_icon_name, false)) { + return pixbuf; + } + if (auto pixbuf = loadIconFromNameOrFile(attention_movie_name, false)) { + return pixbuf; + } + return attention_icon_pixmap; +} + +Glib::RefPtr Item::getOverlayIconPixbuf() { + if (auto pixbuf = loadIconFromNameOrFile(overlay_icon_name, false)) { + return pixbuf; + } + return overlay_icon_pixmap; +} + +Glib::RefPtr Item::loadIconFromNameOrFile(const std::string& name, bool log_failure) { + if (name.empty()) { + return {}; + } + + try { + std::ifstream temp(name); + if (temp.is_open()) { + return Gdk::Pixbuf::create_from_file(name); + } + } catch (const Glib::Error& e) { + if (log_failure) { + spdlog::warn("Item '{}': {}", id, static_cast(e.what())); + } + } + + try { + return getIconByName(name, getScaledIconSize()); + } catch (const Glib::Error& e) { + if (log_failure) { + spdlog::trace("Item '{}': {}", id, static_cast(e.what())); + } + } + + return {}; +} + +Glib::RefPtr Item::overlayPixbufs(const Glib::RefPtr& base, + const Glib::RefPtr& overlay) { + if (!base || !overlay) { + return base; + } + + auto composed = base->copy(); + if (!composed) { + return base; + } + + int overlay_target_size = + std::max(1, std::min(composed->get_width(), composed->get_height()) / 2); + auto scaled_overlay = overlay->scale_simple(overlay_target_size, overlay_target_size, + Gdk::InterpType::INTERP_BILINEAR); + if (!scaled_overlay) { + return composed; + } + + int dest_x = std::max(0, composed->get_width() - scaled_overlay->get_width()); + int dest_y = std::max(0, composed->get_height() - scaled_overlay->get_height()); + scaled_overlay->composite(composed, dest_x, dest_y, scaled_overlay->get_width(), + scaled_overlay->get_height(), dest_x, dest_y, 1.0, 1.0, + Gdk::InterpType::INTERP_BILINEAR, 255); + return composed; +} + Glib::RefPtr Item::getIconByName(const std::string& name, int request_size) { if (!icon_theme_path.empty()) { auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,