mirror of
https://github.com/scorpion-26/gBar.git
synced 2024-11-21 18:52:49 +00:00
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'
This commit is contained in:
parent
1cf8f820c2
commit
bc0281ca53
4 changed files with 366 additions and 360 deletions
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
695
src/SNI.cpp
695
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 <gio/gio.h>
|
||||
#include <libdbusmenu-gtk/menu.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
#include <unordered_set>
|
||||
|
||||
|
@ -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<Item> items;
|
||||
std::vector<std::unique_ptr<Item>> items;
|
||||
|
||||
std::unordered_map<std::string, Item> clientsToQuery;
|
||||
std::unordered_set<std::string> 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<typename OnFinishFn>
|
||||
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<EventBox>();
|
||||
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<Texture>();
|
||||
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>();
|
||||
box->AddClass("widget");
|
||||
box->AddClass("sni");
|
||||
Utils::SetTransform(*box, {-1, false, Alignment::Fill});
|
||||
|
||||
auto container = Widget::Create<Box>();
|
||||
container->AddTimer<Box>(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>& 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>& 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> item = std::make_unique<Item>();
|
||||
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<Box>();
|
||||
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<EventBox>();
|
||||
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<Texture>();
|
||||
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>();
|
||||
box->AddClass("widget");
|
||||
box->AddClass("sni");
|
||||
Utils::SetTransform(*box, {-1, false, Alignment::Fill});
|
||||
auto container = Widget::Create<Box>();
|
||||
container->AddTimer<Box>(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>& 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() {}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue