From bc0281ca5321cb6e72ab6d295c790ae10d7eec7e Mon Sep 17 00:00:00 2001 From: scorpion-26 Date: Mon, 18 Mar 2024 02:26:09 +0100 Subject: [PATCH] Rewrite large parts of SNI This is the fix for https://github.com/scorpion-26/gBar/issues/79, but blown out of proportions, because various things kept breaking - Original fix: Add/Remove items instead of cleaning everything everytime something changes. If the dbus-menu was open, it was referencing the old widget causing issues. The new method keeps the original GtkDrawingArea and only replace the buffer/tooltip - Create dbus-menu on widget creation instead of on click. This was actually the fix for https://github.com/scorpion-26/gBar/pull/12#issuecomment-1529143790. The css has also been updated, making the popup finally look good. - With the new item refreshing theme KeePassXC kept deadlocking when the properties changed. To fix it, the dbus properties are now queried asynchronously via 'GetAll' --- css/style.css | 8 + css/style.scss | 15 +- src/SNI.cpp | 695 ++++++++++++++++++++++++------------------------- src/Widget.cpp | 8 + 4 files changed, 366 insertions(+), 360 deletions(-) diff --git a/css/style.css b/css/style.css index a31eeb1..773ccf1 100644 --- a/css/style.css +++ b/css/style.css @@ -4,7 +4,13 @@ } .popup { + background-color: #282a36; color: #50fa7b; + border-radius: 16px; +} + +menu { + padding: 8px 8px 8px 8px; } .bar, tooltip { @@ -388,3 +394,5 @@ highlight { animation-timing-function: linear; animation-fill-mode: forwards; } + +/*# sourceMappingURL=style.css.map */ diff --git a/css/style.scss b/css/style.scss index 5f68b72..1fbbbc5 100644 --- a/css/style.scss +++ b/css/style.scss @@ -22,16 +22,21 @@ $textsize: 16px; font-family: "CaskaydiaCove NF"; } +.popup { + background-color: $bg; + color: #50fa7b; + border-radius: 16px; +} + +menu { + padding: 8px 8px 8px 8px; +} + // debug .cpu-box{ //background-color: #00cc00 } -.popup { - color: #50fa7b; -} - - .bar,tooltip{ background-color: $bg; border-radius: 16px; diff --git a/src/SNI.cpp b/src/SNI.cpp index dd99aaa..53e134a 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -2,7 +2,6 @@ #include "Log.h" #include "Widget.h" #include "Config.h" -#include "Common.h" #ifdef WITH_SNI @@ -11,7 +10,6 @@ #include #include -#include #include #include @@ -35,20 +33,22 @@ namespace SNI std::string menuObjectPath = ""; + Texture* textureWidget = nullptr; EventBox* gtkEvent = nullptr; GtkMenu* dbusMenu = nullptr; int watcherID = -1; int propertyChangeWatcherID = -1; + + bool gathering = false; }; - std::vector items; + std::vector> items; std::unordered_map clientsToQuery; std::unordered_set reloadedNames; - // Gtk stuff, TODO: Allow more than one instance - // Simply removing the gtk_drawing_areas doesn't trigger proper redrawing - // HACK: Make an outer permanent and an inner box, which will be deleted and readded + // Gtk stuff + // TODO: Investigate if the two box approach is still needed, since we now actually add/delete items Widget* parentBox; Widget* iconBox; @@ -92,181 +92,198 @@ namespace SNI return false; } - static Item CreateItem(std::string&& name, std::string&& object) + template + inline void GatherItemProperties(Item& item, OnFinishFn&& onFinish) { - Item item{}; - item.name = name; - item.object = object; - auto getProperty = [&](const char* prop) -> GVariant* + // Only gather once. + if (item.gathering) + return; + item.gathering = true; + struct AsyncData { + Item& item; + OnFinishFn onFinish; + }; + auto onAsyncResult = [](GObject*, GAsyncResult* result, void* dataPtr) + { + // Data *must* be manually freed! + AsyncData* data = (AsyncData*)dataPtr; + data->item.gathering = false; + GError* err = nullptr; - GVariant* params[2]; - params[0] = g_variant_new_string("org.kde.StatusNotifierItem"); - params[1] = g_variant_new_string(prop); - GVariant* param = g_variant_new_tuple(params, 2); - GVariant* res = g_dbus_connection_call_sync(dbusConnection, name.c_str(), object.c_str(), "org.freedesktop.DBus.Properties", "Get", param, - G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); + GVariant* allPropertiesWrapped = g_dbus_connection_call_finish(dbusConnection, result, &err); if (err) { + LOG("SNI: g_dbus_connection_call failed with: " << err->message); g_error_free(err); - return nullptr; + delete data; + return; } - // g_dbus_connection_call_sync consumes the parameter and its children, so no need to unref - return res; + + // Unwrap tuple + GVariant* allProperties = g_variant_get_child_value(allPropertiesWrapped, 0); + auto getProperty = [&](const std::string_view& prop) + { + return g_variant_lookup_value(allProperties, prop.data(), nullptr); + }; + + // Query tooltip(Steam e.g. doesn't have one) + GVariant* tooltip = getProperty("ToolTip"); + if (tooltip) + { + const gchar* title = nullptr; + if (g_variant_is_container(tooltip) && g_variant_n_children(tooltip) >= 4) + { + // According to spec, ToolTip is of type (sa(iiab)ss) => 4 children + // Most icons only set the "title" component (e.g. Discord, KeePassXC, ...) + g_variant_get_child(tooltip, 2, "s", &title); + } + else + { + // TeamViewer only exposes a string, which is not according to spec! + title = g_variant_get_string(tooltip, nullptr); + } + + if (title != nullptr) + { + data->item.tooltip = title; + } + else + { + LOG("SNI: Error querying tooltip"); + } + LOG("SNI: Title: " << data->item.tooltip); + g_variant_unref(tooltip); + } + + // Query menu + GVariant* menuPath = getProperty("Menu"); + if (menuPath) + { + const char* objectPath; + g_variant_get(menuPath, "o", &objectPath); + LOG("SNI: Menu object path: " << objectPath); + + data->item.menuObjectPath = objectPath; + + g_variant_unref(menuPath); + } + + bool wasExplicitOverride = false; + for (auto& [filter, disabled] : Config::Get().sniDisabled) + { + if (ItemMatchesFilter(data->item, filter, wasExplicitOverride)) + { + if (disabled) + { + LOG("SNI: Disabling item due to config"); + // We're done here. + delete data; + return; + } + } + } + + // First try icon theme querying + std::string iconName; + wasExplicitOverride = false; + for (auto& [filter, name] : Config::Get().sniIconNames) + { + if (ItemMatchesFilter(data->item, filter, wasExplicitOverride)) + { + iconName = name; + } + } + if (iconName == "") + { + GVariant* iconNameVar = getProperty("IconName"); + if (iconNameVar) + { + iconName = g_variant_get_string(iconNameVar, nullptr); + + g_variant_unref(iconNameVar); + } + } + if (iconName != "") + { + GError* err = nullptr; + GtkIconTheme* defaultTheme = gtk_icon_theme_get_default(); + GdkPixbuf* pixbuf = gtk_icon_theme_load_icon(defaultTheme, iconName.c_str(), 64, GTK_ICON_LOOKUP_FORCE_SVG, &err); + if (err) + { + LOG("SNI: gtk_icon_theme_load_icon failed: " << err->message); + g_error_free(err); + } + else if (pixbuf) + { + LOG("SNI: Creating icon from \"" << iconName << "\""); + data->item.pixbuf = pixbuf; + data->item.w = gdk_pixbuf_get_width(pixbuf); + data->item.h = gdk_pixbuf_get_height(pixbuf); + } + } + + if (data->item.pixbuf == nullptr) + { + GVariant* iconPixmap = getProperty("IconPixmap"); + if (iconPixmap == nullptr) + { + // All icon locations have failed, bail. + LOG("SNI: Cannot create item due to missing icon!"); + delete data; + return; + } + GVariantIter* arrIter = nullptr; + g_variant_get(iconPixmap, "a(iiay)", &arrIter); + + if (g_variant_iter_n_children(arrIter) != 0) + { + int width; + int height; + GVariantIter* dataIter = nullptr; + g_variant_iter_next(arrIter, "(iiay)", &width, &height, &dataIter); + + LOG("SNI: Width: " << width); + LOG("SNI: Height: " << height); + data->item.w = width; + data->item.h = height; + uint8_t* iconData = new uint8_t[width * height * 4]; + + uint8_t px = 0; + int i = 0; + while (g_variant_iter_next(dataIter, "y", &px)) + { + iconData[i] = px; + i++; + } + LOG("SNI: Creating icon from pixmap"); + data->item.pixbuf = ToPixbuf(iconData, width, height); + + g_variant_iter_free(dataIter); + } + g_variant_iter_free(arrIter); + g_variant_unref(iconPixmap); + } + + data->onFinish(data->item); + delete data; + g_variant_unref(allProperties); + g_variant_unref(allPropertiesWrapped); }; - // Query tooltip(Steam e.g. doesn't have one) - GVariant* tooltip = getProperty("ToolTip"); - if (tooltip) + // The tuples will be owned by g_dbus_connection_call, so no cleanup needed + AsyncData* data = new AsyncData{item, onFinish}; + GError* err = nullptr; + GVariant* params[1]; + params[0] = g_variant_new_string("org.kde.StatusNotifierItem"); + GVariant* paramsTuple = g_variant_new_tuple(params, 1); + g_dbus_connection_call(dbusConnection, item.name.c_str(), item.object.c_str(), "org.freedesktop.DBus.Properties", "GetAll", paramsTuple, + G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, +onAsyncResult, data); + if (err) { - GVariant* tooltipVar; - g_variant_get_child(tooltip, 0, "v", &tooltipVar); - const gchar* title = nullptr; - if (g_variant_is_container(tooltipVar) && g_variant_n_children(tooltipVar) >= 4) - { - // According to spec, ToolTip is of type (sa(iiab)ss) => 4 children - // Most icons only set the "title" component (e.g. Discord, KeePassXC, ...) - g_variant_get_child(tooltipVar, 2, "s", &title); - } - else - { - // TeamViewer only exposes a string, which is not according to spec! - title = g_variant_get_string(tooltipVar, nullptr); - } - - if (title != nullptr) - { - item.tooltip = title; - } - else - { - LOG("SNI: Error querying tooltip"); - } - LOG("SNI: Title: " << item.tooltip); - g_variant_unref(tooltip); - g_variant_unref(tooltipVar); + LOG("SNI: g_dbus_connection_call failed with: " << err->message); + g_error_free(err); } - - // Query menu - GVariant* menuPath = getProperty("Menu"); - if (menuPath) - { - GVariant* menuVariant; - g_variant_get_child(menuPath, 0, "v", &menuVariant); - const char* objectPath; - g_variant_get(menuVariant, "o", &objectPath); - LOG("SNI: Menu object path: " << objectPath); - - item.menuObjectPath = objectPath; - - g_variant_unref(menuVariant); - g_variant_unref(menuPath); - } - - bool wasExplicitOverride = false; - for (auto& [filter, disabled] : Config::Get().sniDisabled) - { - if (ItemMatchesFilter(item, filter, wasExplicitOverride)) - { - if (disabled) - { - LOG("SNI: Disabling item due to config"); - // We're done here. - return item; - } - } - } - - // First try icon theme querying - std::string iconName; - wasExplicitOverride = false; - for (auto& [filter, name] : Config::Get().sniIconNames) - { - if (ItemMatchesFilter(item, filter, wasExplicitOverride)) - { - iconName = name; - } - } - if (iconName == "") - { - GVariant* iconNameVariant = getProperty("IconName"); - if (iconNameVariant) - { - GVariant* iconNameStr; - g_variant_get(iconNameVariant, "(v)", &iconNameStr); - - iconName = g_variant_get_string(iconNameStr, nullptr); - - g_variant_unref(iconNameVariant); - g_variant_unref(iconNameStr); - } - } - if (iconName != "") - { - GError* err = nullptr; - GtkIconTheme* defaultTheme = gtk_icon_theme_get_default(); - GdkPixbuf* pixbuf = gtk_icon_theme_load_icon(defaultTheme, iconName.c_str(), 64, GTK_ICON_LOOKUP_FORCE_SVG, &err); - if (err) - { - LOG("SNI: gtk_icon_theme_load_icon failed: " << err->message); - g_error_free(err); - } - else if (pixbuf) - { - LOG("SNI: Creating icon from \"" << iconName << "\""); - item.pixbuf = pixbuf; - item.w = gdk_pixbuf_get_width(pixbuf); - item.h = gdk_pixbuf_get_height(pixbuf); - } - } - - if (item.pixbuf == nullptr) - { - GVariant* iconPixmap = getProperty("IconPixmap"); - if (iconPixmap == nullptr) - { - // All icon locations have failed, bail. - LOG("SNI: Cannot create item due to missing icon!"); - return {}; - } - // Only get first item - GVariant* arr = nullptr; - g_variant_get(iconPixmap, "(v)", &arr); - - GVariantIter* arrIter = nullptr; - g_variant_get(arr, "a(iiay)", &arrIter); - - if (g_variant_iter_n_children(arrIter) != 0) - { - int width; - int height; - GVariantIter* data = nullptr; - g_variant_iter_next(arrIter, "(iiay)", &width, &height, &data); - - LOG("SNI: Width: " << width); - LOG("SNI: Height: " << height); - item.w = width; - item.h = height; - uint8_t* iconData = new uint8_t[width * height * 4]; - - uint8_t px = 0; - int i = 0; - while (g_variant_iter_next(data, "y", &px)) - { - iconData[i] = px; - i++; - } - LOG("SNI: Creating icon from pixmap"); - item.pixbuf = ToPixbuf(iconData, width, height); - - g_variant_iter_free(data); - } - g_variant_iter_free(arrIter); - g_variant_unref(arr); - g_variant_unref(iconPixmap); - } - - return item; } static void DestroyItem(Item& item) @@ -274,27 +291,114 @@ namespace SNI g_bus_unwatch_name(item.watcherID); g_dbus_connection_signal_unsubscribe(dbusConnection, item.propertyChangeWatcherID); g_object_unref(item.pixbuf); - if (item.dbusMenu) - { - gtk_menu_detach(item.dbusMenu); - } + // dbus menu will be deleted by automatically when the parent widget is destroyed } - static void InvalidateWidget(); + static void AddSNIItem(Item& item) + { + if (!item.pixbuf) + return; + auto eventBox = Widget::Create(); + item.gtkEvent = eventBox.get(); + + eventBox->SetOnCreate( + [&](Widget& w) + { + // Create the menu if it wasn't already created + if (!item.dbusMenu) + { + item.dbusMenu = (GtkMenu*)dbusmenu_gtkmenu_new(item.name.data(), item.menuObjectPath.data()); + gtk_menu_attach_to_widget(item.dbusMenu, item.gtkEvent->Get(), nullptr); + } + auto clickFn = [](GtkWidget*, GdkEventButton* event, void* data) -> gboolean + { + if (event->button == 1) + { + GtkMenu* menu = (GtkMenu*)data; + + gtk_menu_popup_at_pointer(menu, (GdkEvent*)event); + LOG("SNI: Opened popup!"); + } + return GDK_EVENT_STOP; + }; + g_signal_connect(w.Get(), "button-release-event", G_CALLBACK(+clickFn), item.dbusMenu); + }); + + LOG("SNI: Add " << item.name << " to widget"); + auto texture = Widget::Create(); + bool wasExplicitOverride = false; + int size = 24; + for (auto& [filter, iconSize] : Config::Get().sniIconSizes) + { + if (ItemMatchesFilter(item, filter, wasExplicitOverride)) + { + size = iconSize; + } + } + wasExplicitOverride = false; + for (auto& [filter, padding] : Config::Get().sniPaddingTop) + { + if (ItemMatchesFilter(item, filter, wasExplicitOverride)) + { + texture->AddPaddingTop(padding); + } + } + bool rotatedIcons = (Config::Get().location == 'L' || Config::Get().location == 'R') && Config::Get().iconsAlwaysUp; + Utils::SetTransform(*texture, {size, true, Alignment::Fill}, {size, true, Alignment::Fill, 0, rotatedIcons ? 6 : 0}); + texture->SetBuf(item.pixbuf, item.w, item.h); + texture->SetTooltip(item.tooltip); + texture->SetAngle(Utils::GetAngle() == 270 ? 90 : 0); + + item.textureWidget = texture.get(); + + eventBox->AddChild(std::move(texture)); + iconBox->AddChild(std::move(eventBox)); + } + + static void RemoveSNIItem(Item& item) + { + iconBox->RemoveChild(item.gtkEvent); + } + + static TimerResult UpdateWidgets(Box&); + + void WidgetSNI(Widget& parent) + { + if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false) + { + return; + } + // Add parent box + auto box = Widget::Create(); + box->AddClass("widget"); + box->AddClass("sni"); + Utils::SetTransform(*box, {-1, false, Alignment::Fill}); + + auto container = Widget::Create(); + container->AddTimer(UpdateWidgets, 1000, TimerDispatchBehaviour::LateDispatch); + container->SetSpacing({4, false}); + container->SetOrientation(Utils::GetOrientation()); + Utils::SetTransform(*container, {-1, true, Alignment::Fill, 0, 8}); + + iconBox = container.get(); + parentBox = box.get(); + box->AddChild(std::move(container)); + parent.AddChild(std::move(box)); + } static void DBusNameVanished(GDBusConnection*, const char* name, void*) { auto it = std::find_if(items.begin(), items.end(), - [&](const Item& item) + [&](const std::unique_ptr& item) { - return item.name == name; + return item->name == name; }); if (it != items.end()) { LOG("SNI: " << name << " vanished!"); - DestroyItem(*it); + RemoveSNIItem(*it->get()); + DestroyItem(*it->get()); items.erase(it); - InvalidateWidget(); return; } @@ -308,59 +412,44 @@ namespace SNI return; } - static void ItemPropertyChanged(GDBusConnection*, const char* senderName, const char* object, const char*, const char*, GVariant*, void* name) + static void ItemPropertyChanged(GDBusConnection*, const char* senderName, const char* object, const char*, const char* signalName, GVariant*, + void* name) { - // I think the multiple calling of this callback is a symptom of not unsubscribing the ItemPropertyChanged callback. Since this is now done, I - // think the reloadedNames and object path checking is no longer needed. Since it doesn't do any harm (except being a little bit pointless) - // I'll leave the checks here for now. - // TODO: Investigate whether it is now actually fixed - - if (reloadedNames.insert(senderName).second == false) - { - // senderName has already requested a change, ignore - LOG("SNI: " << senderName << " already signaled property change"); - return; - } - - std::string nameStr = (const char*)name; LOG("SNI: Reloading " << (const char*)name << " " << object << " (Sender: " << senderName << ")"); - // We can't trust the object path given to us, since ItemPropertyChanged is called multiple times with the same name, but with different - // object paths. - auto it = std::find_if(items.begin(), items.end(), - [&](const Item& item) - { - return item.name == nameStr; - }); - std::string itemObjectPath; - if (it != items.end()) - { - itemObjectPath = it->object; - } - else - { - // Item not already registered, fallback to provided name and hope the object path provided is accurate - LOG("SNI: Couldn't find name for actual object path!"); - itemObjectPath = object; - } + // Check if we're interested. + if (strcmp(signalName, "NewIcon") != 0 && strcmp(signalName, "NewToolTip") != 0) + return; - // We don't care about *what* changed, just remove and reload - LOG("SNI: Actual object path: " << itemObjectPath) - if (it != items.end()) + GError* err = nullptr; + g_dbus_connection_flush_sync(dbusConnection, nullptr, &err); + if (err) { - DestroyItem(*it); - items.erase(it); + LOG("SNI: g_dbus_connection_call_sync failed: " << err->message); + g_error_free(err); } - else + auto itemIt = std::find_if(items.begin(), items.end(), + [&](const std::unique_ptr& item) + { + return item->name == (const char*)name; + }); + if (itemIt == items.end()) { - LOG("SNI: Coudn't remove item " << nameStr << " when reloading"); + LOG("SNI: Couldn't update " << name << "!"); + return; } - clientsToQuery[nameStr] = {nameStr, itemObjectPath}; + GatherItemProperties(**itemIt, + [](Item& item) + { + item.textureWidget->SetBuf(item.pixbuf, item.w, item.h); + item.textureWidget->SetTooltip(item.tooltip); + }); } static TimerResult UpdateWidgets(Box&) { // Flush connection, so we hopefully don't deadlock with any client + // TODO: Figure out if this is still needed when using gathering async GError* err = nullptr; g_dbus_connection_flush_sync(dbusConnection, nullptr, &err); if (err) @@ -377,142 +466,41 @@ namespace SNI for (auto& [name, client] : clientsToQuery) { LOG("SNI: Creating Item " << client.name << " " << client.object); - Item item = CreateItem(std::move(client.name), std::move(client.object)); - if (item.pixbuf == nullptr) - { - continue; - } - // Add handler for removing - item.watcherID = g_bus_watch_name_on_connection(dbusConnection, item.name.c_str(), G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, - DBusNameVanished, nullptr, nullptr); - - // Add handler for icon change - char* staticBuf = new char[item.name.size() + 1]{0x0}; - memcpy(staticBuf, item.name.c_str(), item.name.size()); - LOG("SNI: Allocating static name buffer for " << item.name); - item.propertyChangeWatcherID = g_dbus_connection_signal_subscribe( - dbusConnection, item.name.c_str(), "org.kde.StatusNotifierItem", nullptr, nullptr, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, - ItemPropertyChanged, staticBuf, - +[](void* ptr) - { - LOG("SNI: Delete static name buffer for " << (char*)ptr); - delete[] (char*)ptr; - }); - + std::unique_ptr item = std::make_unique(); + item->name = std::move(client.name); + item->object = std::move(client.object); + Item& itemRef = *item; items.push_back(std::move(item)); - } - if (clientsToQuery.size() > 0) - { - InvalidateWidget(); + auto onGatherFinish = [](Item& item) + { + if (item.pixbuf == nullptr) + { + return; + } + // Add handler for removing + item.watcherID = g_bus_watch_name_on_connection(dbusConnection, item.name.c_str(), G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, + DBusNameVanished, nullptr, nullptr); + + // Add handler for icon change + char* staticBuf = new char[item.name.size() + 1]{0x0}; + memcpy(staticBuf, item.name.c_str(), item.name.size()); + LOG("SNI: Allocating static name buffer for " << item.name); + item.propertyChangeWatcherID = g_dbus_connection_signal_subscribe( + dbusConnection, item.name.c_str(), "org.kde.StatusNotifierItem", nullptr, nullptr, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, + ItemPropertyChanged, staticBuf, + +[](void* ptr) + { + LOG("SNI: Delete static name buffer for " << (char*)ptr); + delete[] (char*)ptr; + }); + AddSNIItem(item); + }; + GatherItemProperties(itemRef, onGatherFinish); } clientsToQuery.clear(); return TimerResult::Ok; } - // SNI implements the GTK-Thingies itself internally - static void InvalidateWidget() - { - LOG("SNI: Clearing old children"); - parentBox->RemoveChild(iconBox); - - // Allow further updates from the icon - reloadedNames.clear(); - - auto container = Widget::Create(); - container->SetSpacing({4, false}); - container->SetOrientation(Utils::GetOrientation()); - Utils::SetTransform(*container, {-1, true, Alignment::Fill, 0, 8}); - iconBox = container.get(); - - // Sort items, so they don't jump around randomly - std::sort(items.begin(), items.end(), - [](const Item& a, const Item& b) - { - return a.name < b.name; - }); - - bool rotatedIcons = (Config::Get().location == 'L' || Config::Get().location == 'R') && Config::Get().iconsAlwaysUp; - for (auto& item : items) - { - if (item.pixbuf) - { - auto eventBox = Widget::Create(); - item.gtkEvent = eventBox.get(); - - eventBox->SetOnCreate( - [&](Widget& w) - { - auto clickFn = [](GtkWidget*, GdkEventButton* event, void* data) -> gboolean - { - if (event->button == 1) - { - Item* item = (Item*)data; - - // Create the menu if it wasn't already created - if (!item->dbusMenu) - { - item->dbusMenu = (GtkMenu*)dbusmenu_gtkmenu_new(item->name.data(), item->menuObjectPath.data()); - gtk_menu_attach_to_widget(item->dbusMenu, item->gtkEvent->Get(), nullptr); - } - gtk_menu_popup_at_pointer(item->dbusMenu, (GdkEvent*)event); - LOG(item->menuObjectPath << " click"); - } - return GDK_EVENT_STOP; - }; - g_signal_connect(w.Get(), "button-release-event", G_CALLBACK(+clickFn), &item); - }); - - LOG("SNI: Add " << item.name << " to widget"); - auto texture = Widget::Create(); - bool wasExplicitOverride = false; - int size = 24; - for (auto& [filter, iconSize] : Config::Get().sniIconSizes) - { - if (ItemMatchesFilter(item, filter, wasExplicitOverride)) - { - size = iconSize; - } - } - wasExplicitOverride = false; - for (auto& [filter, padding] : Config::Get().sniPaddingTop) - { - if (ItemMatchesFilter(item, filter, wasExplicitOverride)) - { - texture->AddPaddingTop(padding); - } - } - Utils::SetTransform(*texture, {size, true, Alignment::Fill}, {size, true, Alignment::Fill, 0, rotatedIcons ? 6 : 0}); - texture->SetBuf(item.pixbuf, item.w, item.h); - texture->SetTooltip(item.tooltip); - texture->SetAngle(Utils::GetAngle() == 270 ? 90 : 0); - - eventBox->AddChild(std::move(texture)); - iconBox->AddChild(std::move(eventBox)); - } - } - parentBox->AddChild(std::move(container)); - } - - void WidgetSNI(Widget& parent) - { - if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false) - { - return; - } - // Add parent box - auto box = Widget::Create(); - box->AddClass("widget"); - box->AddClass("sni"); - Utils::SetTransform(*box, {-1, false, Alignment::Fill}); - auto container = Widget::Create(); - container->AddTimer(UpdateWidgets, 1000, TimerDispatchBehaviour::LateDispatch); - iconBox = container.get(); - parentBox = box.get(); - InvalidateWidget(); - box->AddChild(std::move(container)); - parent.AddChild(std::move(box)); - } - // Methods static bool RegisterItem(sniWatcher* watcher, GDBusMethodInvocation* invocation, const char* service) { @@ -531,9 +519,9 @@ namespace SNI object = "/StatusNotifierItem"; } auto it = std::find_if(items.begin(), items.end(), - [&](const Item& item) + [&](const std::unique_ptr& item) { - return item.name == name && item.object == object; + return item->name == name && item->object == object; }); if (it != items.end()) { @@ -591,6 +579,7 @@ namespace SNI // Host is always available sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true); + sni_watcher_emit_status_notifier_host_registered(watcherSkeleton); }; auto emptyCallback = [](GDBusConnection*, const char*, void*) {}; auto lostName = [](GDBusConnection*, const char*, void*) @@ -606,10 +595,6 @@ namespace SNI std::string hostName = "org.kde.StatusNotifierHost-" + std::to_string(getpid()); g_bus_own_name(G_BUS_TYPE_SESSION, hostName.c_str(), (GBusNameOwnerFlags)flags, +emptyCallback, +emptyCallback, +emptyCallback, nullptr, nullptr); - - // Host is always available - sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true); - sni_watcher_emit_status_notifier_host_registered(watcherSkeleton); } void Shutdown() {} diff --git a/src/Widget.cpp b/src/Widget.cpp index e9fb7bb..4bcad07 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -542,11 +542,19 @@ void Texture::SetBuf(GdkPixbuf* pixbuf, size_t width, size_t height) { m_Width = width; m_Height = height; + if (m_Pixbuf) + g_free(m_Pixbuf); + m_Pixbuf = gdk_pixbuf_copy(pixbuf); + + if (m_Widget) + gtk_widget_queue_draw(m_Widget); } void Texture::Draw(cairo_t* cr) { + if (!m_Pixbuf) + return; Quad q = GetQuad(); // TODO: Non-quad sizes