From 1241d7c87cda15d5856163f0f00a42de8e966836 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sat, 18 Mar 2023 00:02:40 +0100 Subject: [PATCH 01/16] SNI: Initial proof of concept Implements a rough outline of the SNI (StatusNotifierItem) d-bus protocol for tray icons. Note: This is currently *very* WIP Full implementation will close https://github.com/scorpion-26/gBar/issues/5 --- .gitmodules | 3 + meson.build | 31 ++++ protocols/sni-item.xml | 46 ++++++ protocols/sni-watcher.xml | 23 +++ src/Bar.cpp | 3 + src/Log.h | 1 + src/SNI.cpp | 287 ++++++++++++++++++++++++++++++++++++++ src/SNI.h | 8 ++ src/System.cpp | 5 + src/Widget.cpp | 48 ++++++- src/Widget.h | 19 +++ thirdparty/stb | 1 + 12 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 protocols/sni-item.xml create mode 100644 protocols/sni-watcher.xml create mode 100644 src/SNI.cpp create mode 100644 src/SNI.h create mode 160000 thirdparty/stb diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7748e45 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/stb"] + path = thirdparty/stb + url = https://github.com/nothings/stb diff --git a/meson.build b/meson.build index c858f21..a443769 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,28 @@ ext_workspace_header = custom_target('generate-ext-workspace-header', output: ['ext-workspace-unstable-v1.h'], command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@']) +# DBus protocols +dbus_gen = find_program('gdbus-codegen') +sni_item_src = custom_target('generate-sni-item-src', + input: ['protocols/sni-item.xml'], + output: ['sni-item.c'], + command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) + +sni_item_header = custom_target('generate-sni-item-header', + input: ['protocols/sni-item.xml'], + output: ['sni-item.h'], + command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) + +sni_watcher_src = custom_target('generate-sni-watcher-src', + input: ['protocols/sni-watcher.xml'], + output: ['sni-watcher.c'], + command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) + +sni_watcher_header = custom_target('generate-sni-watcher-header', + input: ['protocols/sni-watcher.xml'], + output: ['sni-watcher.h'], + command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) + gtk = dependency('gtk+-3.0') gtk_layer_shell = dependency('gtk-layer-shell-0') @@ -64,9 +86,16 @@ add_global_arguments('-DUSE_LOGFILE', language: 'cpp') pulse = dependency('libpulse') +# stb +stb = include_directories('thirdparty') + libgBar = library('gBar', [ ext_workspace_src, ext_workspace_header, + sni_item_src, + sni_item_header, + sni_watcher_src, + sni_watcher_header, 'src/Window.cpp', 'src/Widget.cpp', 'src/System.cpp', @@ -78,8 +107,10 @@ libgBar = library('gBar', 'src/Config.cpp', 'src/CSS.cpp', 'src/Log.cpp', + 'src/SNI.cpp', ], dependencies: [gtk, gtk_layer_shell, pulse, wayland_client], + include_directories: stb, install: true) pkg = import('pkgconfig') diff --git a/protocols/sni-item.xml b/protocols/sni-item.xml new file mode 100644 index 0000000..74afed2 --- /dev/null +++ b/protocols/sni-item.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/protocols/sni-watcher.xml b/protocols/sni-watcher.xml new file mode 100644 index 0000000..0948962 --- /dev/null +++ b/protocols/sni-watcher.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Bar.cpp b/src/Bar.cpp index 589e8f3..e0f302e 100644 --- a/src/Bar.cpp +++ b/src/Bar.cpp @@ -3,6 +3,7 @@ #include "System.h" #include "Common.h" #include "Config.h" +#include "SNI.h" #include namespace Bar @@ -646,6 +647,8 @@ namespace Bar right->SetSpacing({8, false}); right->SetHorizontalTransform({-1, true, Alignment::Right}); { + SNI::WidgetSNI(*right); + WidgetPackages(*right); WidgetAudio(*right); diff --git a/src/Log.h b/src/Log.h index 79df5a3..33a7ea1 100644 --- a/src/Log.h +++ b/src/Log.h @@ -1,5 +1,6 @@ #pragma once #include +#include #ifdef USE_LOGFILE #define LOG(x) \ diff --git a/src/SNI.cpp b/src/SNI.cpp new file mode 100644 index 0000000..04b8aea --- /dev/null +++ b/src/SNI.cpp @@ -0,0 +1,287 @@ +#include "SNI.h" +#include "Log.h" +#include "Widget.h" + +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include + +namespace SNI +{ + sniWatcher* watcherSkeleton; + guint watcherID; + GDBusConnection* dbusConnection = nullptr; + + guint hostID; + + struct Item + { + std::string name; + std::string object; + size_t w; + size_t h; + uint8_t* iconData = nullptr; + }; + std::vector items; + + // 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 + Widget* parentBox; + Widget* iconBox; + + // SNI implements the GTK-Thingies itself internally + static void InvalidateWidget() + { + parentBox->RemoveChild(iconBox); + + auto container = Widget::Create(); + iconBox = container.get(); + for (auto& item : items) + { + if (item.iconData) + { + auto texture = Widget::Create(); + texture->SetHorizontalTransform({32, true, Alignment::Fill}); + texture->SetBuf(item.w, item.h, item.iconData); + iconBox->AddChild(std::move(texture)); + } + } + parentBox->AddChild(std::move(container)); + } + + void WidgetSNI(Widget& parent) + { + // Add parent box + auto box = Widget::Create(); + auto container = Widget::Create(); + iconBox = container.get(); + parentBox = box.get(); + InvalidateWidget(); + box->AddChild(std::move(container)); + parent.AddChild(std::move(box)); + } + + static Item CreateItem(std::string&& name, std::string&& object) + { + Item item{}; + item.name = name; + item.object = object; + auto getProperty = [&](const char* prop) -> GVariant* + { + 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); + if (err) + { + g_error_free(err); + return nullptr; + } + // There's probably a better method than to use 3 variants + // g_variant_unref(params[0]); + // g_variant_unref(params[1]); + // g_variant_unref(param); + return res; + }; + GVariant* iconPixmap = getProperty("IconPixmap"); + if (iconPixmap) + { + // Only get first item + GVariant* arr = nullptr; + g_variant_get(iconPixmap, "(v)", &arr); + + GVariantIter* arrIter = nullptr; + g_variant_get(arr, "a(iiay)", &arrIter); + + int width; + int height; + GVariantIter* data = nullptr; + g_variant_iter_next(arrIter, "(iiay)", &width, &height, &data); + + LOG(width); + LOG(height); + item.w = width; + item.h = height; + item.iconData = new uint8_t[width * height * 4]; + + uint8_t px = 0; + int i = 0; + while (g_variant_iter_next(data, "y", &px)) + { + item.iconData[i] = px; + i++; + } + for (int i = 0; i < width * height; i++) + { + struct Px + { + // This should be bgra... + // Since source is ARGB32 in network order(=big-endian) + // and x86 Linux is little-endian, we *should* swap b and r... + uint8_t a, r, g, b; + }; + Px& pixel = ((Px*)item.iconData)[i]; + // Swap to create rgba + pixel = {pixel.r, pixel.g, pixel.b, pixel.a}; + } + + g_variant_iter_free(data); + g_variant_iter_free(arrIter); + g_variant_unref(arr); + g_variant_unref(iconPixmap); + } + else + { + // Get icon theme path + GVariant* themePathVariant = getProperty("IconThemePath"); // Not defined by freedesktop, I think ayatana does this... + GVariant* iconNameVariant = getProperty("IconName"); + + std::string iconPath; + if (themePathVariant && iconNameVariant) + { + // Why GLib? + GVariant* themePathStr = nullptr; + g_variant_get(themePathVariant, "(v)", &themePathStr); + GVariant* iconNameStr = nullptr; + g_variant_get(iconNameVariant, "(v)", &iconNameStr); + + const char* themePath = g_variant_get_string(themePathStr, nullptr); + const char* iconName = g_variant_get_string(iconNameStr, nullptr); + iconPath = std::string(themePath) + "/" + iconName + ".png"; // TODO: Find out if this is always png + + g_variant_unref(themePathVariant); + g_variant_unref(themePathStr); + g_variant_unref(iconNameVariant); + g_variant_unref(iconNameStr); + } + else if (iconNameVariant) + { + GVariant* iconNameStr = nullptr; + g_variant_get(iconNameVariant, "(v)", &iconNameStr); + + const char* iconName = g_variant_get_string(iconNameStr, nullptr); + iconPath = std::string(iconName); + + g_variant_unref(iconNameVariant); + g_variant_unref(iconNameStr); + } + else + { + LOG("SNI: Unknown path!"); + return item; + } + + int width, height, channels; + stbi_uc* pixels = stbi_load(iconPath.c_str(), &width, &height, &channels, STBI_rgb_alpha); + if (!pixels) + { + LOG("SNI: Cannot open " << iconPath); + return item; + } + item.w = width; + item.h = height; + item.iconData = new uint8_t[width * height * 4]; + // Already rgba32 + memcpy(item.iconData, pixels, width * height * 4); + stbi_image_free(pixels); + } + return item; + } + + // Methods + static void RegisterItem(sniWatcher*, GDBusMethodInvocation* invocation, const char* service) + { + std::string name; + std::string object; + if (strncmp(service, "/", 1) == 0) + { + // service is object (used by e.g. ayatana -> steam, discord) + object = service; + name = g_dbus_method_invocation_get_sender(invocation); + } + else + { + // service is bus name (used by e.g. Telegram) + name = service; + object = "/StatusNotifierItem"; + } + auto it = std::find_if(items.begin(), items.end(), [&](const Item& item) + { + return item.name == name && item.object == object; + }); + if (it != items.end()) + { + LOG("Rejecting " << name << " " << object); + return; + } + // TODO: Add mechanism to remove items + LOG("SNI: Registered Item " << name << " " << object); + Item item = CreateItem(std::move(name), std::move(object)); + items.push_back(std::move(item)); + InvalidateWidget(); + } + static void RegisterHost(sniWatcher*, GDBusMethodInvocation*, const char*) + { + LOG("TODO: Implement RegisterHost!"); + } + + // Signals + static void ItemRegistered(sniWatcher*, const char*, void*) + { + // Don't care, since watcher and host will always be from gBar (at least for now) + } + static void ItemUnregistered(sniWatcher*, const char*, void*) + { + // Don't care, since watcher and host will always be from gBar (at least for now) + } + + void Init() + { + auto busAcquired = [](GDBusConnection* connection, const char*, void*) + { + GError* err = nullptr; + g_dbus_interface_skeleton_export((GDBusInterfaceSkeleton*)watcherSkeleton, connection, "/StatusNotifierWatcher", &err); + if (err) + { + LOG("Failed to connect to dbus! Error: " << err->message); + g_error_free(err); + return; + } + dbusConnection = connection; + + // Connect methods and signals + g_signal_connect(watcherSkeleton, "handle-register-status-notifier-item", G_CALLBACK(RegisterItem), nullptr); + g_signal_connect(watcherSkeleton, "handle-register-status-notifier-host", G_CALLBACK(RegisterHost), nullptr); + + g_signal_connect(watcherSkeleton, "status-notifier-item-registered", G_CALLBACK(ItemRegistered), nullptr); + g_signal_connect(watcherSkeleton, "status-notifier-item-unregistered", G_CALLBACK(ItemUnregistered), nullptr); + + // Host is always available + sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true); + }; + auto emptyCallback = [](GDBusConnection*, const char*, void*) {}; + auto lostName = [](GDBusConnection*, const char*, void*) + { + LOG("Lost name!"); + }; + auto flags = G_BUS_NAME_OWNER_FLAGS_REPLACE | G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + g_bus_own_name(G_BUS_TYPE_SESSION, "org.kde.StatusNotifierWatcher", (GBusNameOwnerFlags)flags, +busAcquired, +emptyCallback, +lostName, + nullptr, nullptr); + watcherSkeleton = sni_watcher_skeleton_new(); + + 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); + } + + void Shutdown() {} +} diff --git a/src/SNI.h b/src/SNI.h new file mode 100644 index 0000000..0a9128e --- /dev/null +++ b/src/SNI.h @@ -0,0 +1,8 @@ +#pragma once +class Widget; +namespace SNI +{ + void Init(); + void WidgetSNI(Widget& parent); + void Shutdown(); +} diff --git a/src/System.cpp b/src/System.cpp index f39b1c3..f5499b0 100644 --- a/src/System.cpp +++ b/src/System.cpp @@ -5,6 +5,7 @@ #include "PulseAudio.h" #include "Workspaces.h" #include "Config.h" +#include "SNI.h" #include #include @@ -646,6 +647,8 @@ namespace System PulseAudio::Init(); + SNI::Init(); + CheckNetwork(); } void FreeResources() @@ -662,6 +665,8 @@ namespace System #ifdef WITH_BLUEZ StopBTScan(); #endif + SNI::Shutdown(); + Logging::Shutdown(); } } diff --git a/src/Widget.cpp b/src/Widget.cpp index 0e62a27..59b36c3 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -122,10 +122,32 @@ void Widget::RemoveChild(size_t idx) if (m_Widget) { auto& child = *m_Childs[idx]; - gtk_container_remove((GtkContainer*)child.m_Widget, m_Widget); + gtk_container_remove((GtkContainer*)m_Widget, child.m_Widget); + child.m_Widget = nullptr; } m_Childs.erase(m_Childs.begin() + idx); } +void Widget::RemoveChild(Widget* widget) +{ + auto it = std::find_if(m_Childs.begin(), m_Childs.end(), + [&](std::unique_ptr& other) + { + return other.get() == widget; + }); + if (it != m_Childs.end()) + { + if (m_Widget) + { + gtk_container_remove((GtkContainer*)m_Widget, it->get()->m_Widget); + it->get()->m_Widget = nullptr; + } + m_Childs.erase(it); + } + else + { + LOG("Invalid child!"); + } +} void Widget::SetVisible(bool visible) { @@ -475,6 +497,30 @@ void NetworkSensor::Draw(cairo_t* cr) gdk_rgba_free(colDown); } +Texture::~Texture() +{ + if (m_Pixbuf) + g_free(m_Pixbuf); + if (m_Bytes) + g_free(m_Bytes); +} + +void Texture::SetBuf(size_t width, size_t height, uint8_t* buf) +{ + m_Width = width; + m_Height = height; + m_Bytes = g_bytes_new(buf, m_Width * m_Height * 4); + m_Pixbuf = gdk_pixbuf_new_from_bytes((GBytes*)m_Bytes, GDK_COLORSPACE_RGB, true, 8, m_Width, m_Height, m_Width * 4); +} + +void Texture::Draw(cairo_t* cr) +{ + // TODO: W + H + cairo_rectangle(cr, 0.f, 0.f, 32.f, 32.f); + gdk_cairo_set_source_pixbuf(cr, m_Pixbuf, 0, 0); + cairo_fill(cr); +} + void Revealer::SetTransition(Transition transition) { m_Transition = transition; diff --git a/src/Widget.h b/src/Widget.h index 03b1094..150e71f 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -116,6 +116,7 @@ class Widget void AddChild(std::unique_ptr&& widget); void RemoveChild(size_t idx); + void RemoveChild(Widget* widget); std::vector>& GetWidgets() { return m_Childs; } @@ -263,6 +264,24 @@ class NetworkSensor : public CairoArea std::unique_ptr contextDown; }; +class Texture : public CairoArea +{ +public: + Texture() = default; + virtual ~Texture(); + + // Non-Owning, ARGB32 + void SetBuf(size_t width, size_t height, uint8_t* buf); + +private: + void Draw(cairo_t* cr) override; + + size_t m_Width; + size_t m_Height; + GBytes* m_Bytes; + GdkPixbuf* m_Pixbuf; +}; + class Revealer : public Widget { public: diff --git a/thirdparty/stb b/thirdparty/stb new file mode 160000 index 0000000..5736b15 --- /dev/null +++ b/thirdparty/stb @@ -0,0 +1 @@ +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 From 8e953f985b1c6236005777e9d3e5b275224a107f Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sat, 18 Mar 2023 00:16:52 +0100 Subject: [PATCH 02/16] SNI: Fix CI --- .github/workflows/build.yml | 22 ++++++++++++---------- flake.nix | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4abd53f..00c8c84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,14 +20,16 @@ jobs: - name: Download pacman packages run: | pacman -Syu --noconfirm base-devel gcc git ninja meson gtk-layer-shell pulseaudio wayland - + - name: Download gBar - uses: actions/checkout@v3.3.0 - + uses: actions/checkout@v3.3.0 + with: + submodules: recursive + - name: Run meson run: | meson setup build - + - name: Build gBar run: | ninja -C build @@ -35,14 +37,14 @@ jobs: name: Build using Nix runs-on: ubuntu-latest steps: - - name: Download gBar - uses: actions/checkout@v3.3.0 - - name: Install Nix uses: cachix/install-nix-action@v20 - + + - name: Download gBar + uses: actions/checkout@v3.3.0 + with: + submodules: recursive + - name: Build Nix flake run: | nix build --print-build-logs - - diff --git a/flake.nix b/flake.nix index b58f0bd..350e448 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,7 @@ gtk3 gtk-layer-shell libpulseaudio + stb ]; }); in { From d4ffac395b6f47b23584546de5322a9089d8886c Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sat, 18 Mar 2023 15:52:11 +0100 Subject: [PATCH 03/16] SNI: Proper alignment for the icons --- src/SNI.cpp | 3 ++- src/Widget.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index 04b8aea..725b36b 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -41,13 +41,14 @@ namespace SNI parentBox->RemoveChild(iconBox); auto container = Widget::Create(); + container->SetSpacing({4, false}); iconBox = container.get(); for (auto& item : items) { if (item.iconData) { auto texture = Widget::Create(); - texture->SetHorizontalTransform({32, true, Alignment::Fill}); + texture->SetHorizontalTransform({0, true, Alignment::Fill}); texture->SetBuf(item.w, item.h, item.iconData); iconBox->AddChild(std::move(texture)); } diff --git a/src/Widget.cpp b/src/Widget.cpp index 59b36c3..8d7b66c 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -515,8 +515,14 @@ void Texture::SetBuf(size_t width, size_t height, uint8_t* buf) void Texture::Draw(cairo_t* cr) { - // TODO: W + H - cairo_rectangle(cr, 0.f, 0.f, 32.f, 32.f); + GtkAllocation dim; + gtk_widget_get_allocation(m_Widget, &dim); + double ratio = (double)m_Width / (double)m_Height; + gtk_widget_set_size_request(m_Widget, dim.height * ratio, dim.height); + + double scale = (double)dim.height / (double)m_Height; + cairo_scale(cr, scale, scale); + cairo_rectangle(cr, 0.f, 0.f, m_Width, m_Height); gdk_cairo_set_source_pixbuf(cr, m_Pixbuf, 0, 0); cairo_fill(cr); } From a02bce9b915ca366531695063c313c0695c56a9d Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sat, 18 Mar 2023 16:05:51 +0100 Subject: [PATCH 04/16] SNI: Remove items on bus name vanish --- src/SNI.cpp | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index 725b36b..cab7b43 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -198,6 +198,24 @@ namespace SNI return item; } + static void DBusNameVanished(GDBusConnection*, const char* name, void*) + { + auto it = std::find_if(items.begin(), items.end(), + [&](const Item& item) + { + return item.name == name; + }); + if (it != items.end()) + { + items.erase(it); + InvalidateWidget(); + } + else + { + LOG("SNI: Cannot remove unregistered bus name " << name); + } + } + // Methods static void RegisterItem(sniWatcher*, GDBusMethodInvocation* invocation, const char* service) { @@ -215,18 +233,21 @@ namespace SNI name = service; object = "/StatusNotifierItem"; } - auto it = std::find_if(items.begin(), items.end(), [&](const Item& item) - { - return item.name == name && item.object == object; - }); + auto it = std::find_if(items.begin(), items.end(), + [&](const Item& item) + { + return item.name == name && item.object == object; + }); if (it != items.end()) { LOG("Rejecting " << name << " " << object); return; } - // TODO: Add mechanism to remove items LOG("SNI: Registered Item " << name << " " << object); Item item = CreateItem(std::move(name), std::move(object)); + // Add handler for removing + g_bus_watch_name_on_connection(dbusConnection, item.name.c_str(), G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, DBusNameVanished, nullptr, nullptr); + items.push_back(std::move(item)); InvalidateWidget(); } From b50ecb0f6cb11b46a0a97451ab9e7325297cd166 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Fri, 14 Apr 2023 23:44:30 +0200 Subject: [PATCH 05/16] SNI: Fix freeze when connecting to Qt SNI apps Qt waits until the callback to RegisterItem is done. Thus, we can't query the item and need to defer it --- src/SNI.cpp | 118 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 41 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index cab7b43..7215cf5 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -29,45 +29,14 @@ namespace SNI }; std::vector items; + std::vector clientsToQuery; + // 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 Widget* parentBox; Widget* iconBox; - // SNI implements the GTK-Thingies itself internally - static void InvalidateWidget() - { - parentBox->RemoveChild(iconBox); - - auto container = Widget::Create(); - container->SetSpacing({4, false}); - iconBox = container.get(); - for (auto& item : items) - { - if (item.iconData) - { - auto texture = Widget::Create(); - texture->SetHorizontalTransform({0, true, Alignment::Fill}); - texture->SetBuf(item.w, item.h, item.iconData); - iconBox->AddChild(std::move(texture)); - } - } - parentBox->AddChild(std::move(container)); - } - - void WidgetSNI(Widget& parent) - { - // Add parent box - auto box = Widget::Create(); - auto container = Widget::Create(); - iconBox = container.get(); - parentBox = box.get(); - InvalidateWidget(); - box->AddChild(std::move(container)); - parent.AddChild(std::move(box)); - } - static Item CreateItem(std::string&& name, std::string&& object) { Item item{}; @@ -198,6 +167,13 @@ namespace SNI return item; } + static void ItemPropertyChanged(GDBusConnection*, const char* sender, const char* object, const char* interface, const char* signal, + GVariant* params, void*) + { + LOG("ItemPropertyChanged"); + } + + static void InvalidateWidget(); static void DBusNameVanished(GDBusConnection*, const char* name, void*) { auto it = std::find_if(items.begin(), items.end(), @@ -216,8 +192,65 @@ namespace SNI } } + static TimerResult UpdateWidgets(Box&) + { + for (auto& client : clientsToQuery) + { + LOG("SNI: Creating Item " << client.name << " " << client.object); + Item item = CreateItem(std::move(client.name), std::move(client.object)); + // Add handler for removing + 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 + g_dbus_connection_signal_subscribe(dbusConnection, item.name.c_str(), "org.kde.StatusNotifierItem", nullptr, nullptr, nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, ItemPropertyChanged, nullptr, nullptr); + items.push_back(std::move(item)); + } + if (clientsToQuery.size() > 0) + { + InvalidateWidget(); + } + clientsToQuery.clear(); + return TimerResult::Ok; + } + + // SNI implements the GTK-Thingies itself internally + static void InvalidateWidget() + { + parentBox->RemoveChild(iconBox); + + auto container = Widget::Create(); + container->SetSpacing({4, false}); + iconBox = container.get(); + for (auto& item : items) + { + if (item.iconData) + { + auto texture = Widget::Create(); + texture->SetHorizontalTransform({0, true, Alignment::Fill}); + texture->SetBuf(item.w, item.h, item.iconData); + iconBox->AddChild(std::move(texture)); + } + } + parentBox->AddChild(std::move(container)); + } + + void WidgetSNI(Widget& parent) + { + // Add parent box + auto box = Widget::Create(); + 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 void RegisterItem(sniWatcher*, GDBusMethodInvocation* invocation, const char* service) + static bool RegisterItem(sniWatcher* watcher, GDBusMethodInvocation* invocation, const char* service) { std::string name; std::string object; @@ -241,16 +274,15 @@ namespace SNI if (it != items.end()) { LOG("Rejecting " << name << " " << object); - return; + return false; } + sni_watcher_emit_status_notifier_item_registered(watcher, service); + sni_watcher_complete_register_status_notifier_item(watcher, invocation); LOG("SNI: Registered Item " << name << " " << object); - Item item = CreateItem(std::move(name), std::move(object)); - // Add handler for removing - g_bus_watch_name_on_connection(dbusConnection, item.name.c_str(), G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, DBusNameVanished, nullptr, nullptr); - - items.push_back(std::move(item)); - InvalidateWidget(); + clientsToQuery.push_back({std::move(name), std::move(object)}); + return true; } + static void RegisterHost(sniWatcher*, GDBusMethodInvocation*, const char*) { LOG("TODO: Implement RegisterHost!"); @@ -303,6 +335,10 @@ 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() {} From 6bd5a450b10c05d3917d0e0cfa0675b61e9243c8 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sun, 30 Apr 2023 17:46:16 +0200 Subject: [PATCH 06/16] SNI: Implement changing properties --- src/SNI.cpp | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index 7215cf5..2989475 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -167,13 +167,8 @@ namespace SNI return item; } - static void ItemPropertyChanged(GDBusConnection*, const char* sender, const char* object, const char* interface, const char* signal, - GVariant* params, void*) - { - LOG("ItemPropertyChanged"); - } - static void InvalidateWidget(); + static void DBusNameVanished(GDBusConnection*, const char* name, void*) { auto it = std::find_if(items.begin(), items.end(), @@ -192,6 +187,22 @@ namespace SNI } } + static void ItemPropertyChanged(GDBusConnection*, const char*, const char* object, const char*, const char*, GVariant*, void* name) + { + LOG("SNI: Reloading " << (const char*)name << " " << object); + // We don't care about *what* changed, just remove and reload + auto it = std::find_if(items.begin(), items.end(), + [&](const Item& item) + { + return item.name == (const char*)name; + }); + if (it != items.end()) + { + items.erase(it); + } + clientsToQuery.push_back({std::string((const char*)name), std::string(object)}); + } + static TimerResult UpdateWidgets(Box&) { for (auto& client : clientsToQuery) @@ -203,8 +214,17 @@ namespace SNI nullptr); // Add handler for icon change - g_dbus_connection_signal_subscribe(dbusConnection, item.name.c_str(), "org.kde.StatusNotifierItem", nullptr, nullptr, nullptr, - G_DBUS_SIGNAL_FLAGS_NONE, ItemPropertyChanged, nullptr, nullptr); + char* staticBuf = new char[item.name.size() + 1]{0x0}; + memcpy(staticBuf, item.name.c_str(), item.name.size()); + 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; + }); + items.push_back(std::move(item)); } if (clientsToQuery.size() > 0) From 25912c7e353dcd9b393b2f17e66fa2d9347f4259 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:19:40 +0200 Subject: [PATCH 07/16] SNI: Add tooltip --- src/SNI.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/SNI.cpp b/src/SNI.cpp index 2989475..9374e1c 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -26,6 +26,8 @@ namespace SNI size_t w; size_t h; uint8_t* iconData = nullptr; + + std::string tooltip; }; std::vector items; @@ -164,6 +166,22 @@ namespace SNI memcpy(item.iconData, pixels, width * height * 4); stbi_image_free(pixels); } + + // Query tooltip(Steam e.g. doesn't have one) + GVariant* tooltip = getProperty("ToolTip"); + if (tooltip) + { + GVariant* tooltipVar; + g_variant_get_child(tooltip, 0, "v", &tooltipVar); + const gchar* title; + // Both telegram and discord only set the "title" component + g_variant_get_child(tooltipVar, 2, "s", &title); + item.tooltip = title; + LOG("SNI: Title: " << item.tooltip); + g_variant_unref(tooltip); + g_variant_unref(tooltipVar); + } + return item; } @@ -250,6 +268,7 @@ namespace SNI auto texture = Widget::Create(); texture->SetHorizontalTransform({0, true, Alignment::Fill}); texture->SetBuf(item.w, item.h, item.iconData); + texture->SetTooltip(item.tooltip); iconBox->AddChild(std::move(texture)); } } From 40115befddb8e4cee0b23928baaeaae09058afcf Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:21:12 +0200 Subject: [PATCH 08/16] SNI: Improve logging --- src/SNI.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index 9374e1c..b94e751 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -79,8 +79,8 @@ namespace SNI GVariantIter* data = nullptr; g_variant_iter_next(arrIter, "(iiay)", &width, &height, &data); - LOG(width); - LOG(height); + LOG("SNI: Width: " << width); + LOG("SNI: Height: " << height); item.w = width; item.h = height; item.iconData = new uint8_t[width * height * 4]; @@ -196,6 +196,7 @@ namespace SNI }); if (it != items.end()) { + LOG("SNI: " << name << " vanished!"); items.erase(it); InvalidateWidget(); } From b7a92e50d9c26311f66e4b1c385093eab4f7dcbf Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sun, 30 Apr 2023 23:13:41 +0200 Subject: [PATCH 09/16] SNI: Add context menu Since it uses a 13 year old library, it is broken. The popup size is hard-fixed at 200x200, with looks really ugly with non-transparent background. --- css/style.css | 4 ++++ css/style.css.map | 2 +- css/style.scss | 4 ++++ meson.build | 3 ++- src/SNI.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++- src/Widget.cpp | 3 +++ src/Widget.h | 4 ++++ 7 files changed, 65 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index 33266ea..46bc34b 100644 --- a/css/style.css +++ b/css/style.css @@ -3,6 +3,10 @@ font-family: "CaskaydiaCove Nerd Font"; } +.popup { + color: #50fa7b; +} + .bar, tooltip { background-color: #282a36; border-radius: 16px; diff --git a/css/style.css.map b/css/style.css.map index 1189ad7..dc8bbda 100644 --- a/css/style.css.map +++ b/css/style.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAmBA;EACI;EACA;;;AASJ;EACI,kBA7BC;EA8BD;;;AAGJ;EACI;EACA;;;AAGJ;EACI,WAxBO;;;AA2BX;EACI;EAEA;EAEA,OA5CO;;;AA8CX;EACI;EAGA,OAlDO;;;AAoDX;EACI;EAGA,OAxDO;;;AA2DX;EACI;EAGA,OAzDE;;;AA+DN;EACI;;;AAIJ;EACI;EACA;EACA;EACA,kBA/EO;EAgFP;EACA;EACA;;;AAGJ;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA,OArGK;EAsGL;;;AAIA;EACI,kBA/GG;;AAkHP;EACI;;AAGJ;EACI,kBAnHC;;;AAuHT;EACI;EACA,OAvHK;EAwHL;;;AAIA;EACI,kBAnIG;;AAsIP;EACI;;AAGJ;EACI,kBArIC;;;AAyIT;EACI;EACA;EACA;EACA,OA5IE;;;AA+IN;EACI,WA1IO;EA2IP,OA7IK;EA8IL;;;AAEJ;EACI;EACA,OAlJK;EAmJL;;;AAEJ;EACI;EACA,OAvJK;EAwJL;;;AAEJ;EACI;EACA,OA5JK;EA6JL;;;AAGJ;EACI,OAtKK;EAuKL,kBA7KO;EA8KP,WAjKO;;;AAmKX;EACI,OA3KK;EA4KL;EACA,WAtKO;;;AAyKX;EACI,OAnLK;EAoLL,kBAxLO;;;AA0LX;EACI,OAvLK;EAwLL;EACA,WAhLO;;;AAmLX;EACI,OAzLK;EA0LL,kBAlMO;;;AAoMX;EACI,OA7LK;EA8LL;EACA,WA1LO;;;AA6LX;EACI,OAzMG;EA0MH,kBA5MO;;;AA8MX;EACI,OA7MG;EA8MH;EACA,WApMO;;;AAuMX;EACI,OAlNI;EAmNJ,kBAtNO;EAuNP,WA1MO;;;AA4MX;EACI,OAvNI;EAwNJ;EACA,WA/MO;;;AAkNX;EACI,OA3NG;EA4NH,kBAjOO;EAkOP,WArNO;;;AAuNX;EACI,OAhOG;EAiOH;EACA,WA1NO;;;AA6NX;EACI,OAxOI;EAyOJ;EACA,WAhOO;;;AAoOX;EACI,OAlPO;;;AAqPX;EACI,OAnPI;;;AAsPR;EACI,OAlPK;;;AAqPT;EACI,OA1PK;;;AA6PT;EACI,OA5PK;;;AA+PT;EACI,OA/PE;;;AAmQN;EACI,OA3QO;;;AA8QX;EACI,OA5QI;;;AA+QR;EACI,OA3QK;;;AA8QT;EACI,OAnRK;;;AAsRT;EACI,OArRK;;;AAwRT;EACI,OAxRE;;;AA2RN;EACI,OAnSO;EAoSP,WAvRO;;;AAyRX;EACI,OAtSO;EAuSP,WA3RO;;;AA6RX;EACI,OAzSG;EA0SH,WA/RO;;;AAiSX;EACI,OAvSK;EAwSL,WAnSO;;;AAqSX;EACI,OAhTI;EAiTJ,WAvSO;;;AA2SX;EACI;IACI;;EAEJ;IACI;;;AAGR;EACI;IACI;;EAEJ;IACI;;;AAIR;EACI;IACI,OAnUC;;EAqUL;IACI,OA3UA;;;AA8UR;EACI;IACI,OAhVA;;EAkVJ;IACI,OA9UC;;;AAkVT;EACI,kBA7VC;EA8VD;;;AAEJ;EACI;EACA;EACA;EACA;EACA,OAxVK;;;AA0VT;EACI;EACA;;;AAEJ;EAgBI;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAxBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAEJ;EACI,OAhXF;;;AA8XN;EACI,OA/XE;EAgYF,kBAvYO;EAwYP;EACH;EACA;;;AAED;EAaI,OAjZK;EAkZL,kBA1ZO;EA2ZP;EACA;EACH;EACG;;AAjBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA","file":"style.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAmBA;EACI;EACA;;;AASJ;EACI,kBA7BC;EA8BD;;;AAGJ;EACI;EACA;;;AAGJ;EACI,WAxBO;;;AA2BX;EACI;EAEA;EAEA,OA5CO;;;AA8CX;EACI;EAGA,OAlDO;;;AAoDX;EACI;EAGA,OAxDO;;;AA2DX;EACI;EAGA,OAzDE;;;AA+DN;EACI;;;AAIJ;EACI;EACA;EACA;EACA,kBA/EO;EAgFP;EACA;EACA;;;AAGJ;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA,OArGK;EAsGL;;;AAIA;EACI,kBA/GG;;AAkHP;EACI;;AAGJ;EACI,kBAnHC;;;AAuHT;EACI;EACA,OAvHK;EAwHL;;;AAIA;EACI,kBAnIG;;AAsIP;EACI;;AAGJ;EACI,kBArIC;;;AAyIT;EACI;EACA;EACA;EACA,OA5IE;;;AA+IN;EACI,WA1IO;EA2IP,OA7IK;EA8IL;;;AAEJ;EACI;EACA,OAlJK;EAmJL;;;AAEJ;EACI;EACA,OAvJK;EAwJL;;;AAEJ;EACI;EACA,OA5JK;EA6JL;;;AAGJ;EACI,OAtKK;EAuKL,kBA7KO;EA8KP,WAjKO;;;AAmKX;EACI,OA3KK;EA4KL;EACA,WAtKO;;;AAyKX;EACI,OAnLK;EAoLL,kBAxLO;;;AA0LX;EACI,OAvLK;EAwLL;EACA,WAhLO;;;AAmLX;EACI,OAzLK;EA0LL,kBAlMO;;;AAoMX;EACI,OA7LK;EA8LL;EACA,WA1LO;;;AA6LX;EACI,OAzMG;EA0MH,kBA5MO;;;AA8MX;EACI,OA7MG;EA8MH;EACA,WApMO;;;AAuMX;EACI,OAlNI;EAmNJ,kBAtNO;EAuNP,WA1MO;;;AA4MX;EACI,OAvNI;EAwNJ;EACA,WA/MO;;;AAkNX;EACI,OA3NG;EA4NH,kBAjOO;EAkOP,WArNO;;;AAuNX;EACI,OAhOG;EAiOH;EACA,WA1NO;;;AA6NX;EACI,OAxOI;EAyOJ;EACA,WAhOO;;;AAoOX;EACI,OAlPO;;;AAqPX;EACI,OAnPI;;;AAsPR;EACI,OAlPK;;;AAqPT;EACI,OA1PK;;;AA6PT;EACI,OA5PK;;;AA+PT;EACI,OA/PE;;;AAmQN;EACI,OA3QO;;;AA8QX;EACI,OA5QI;;;AA+QR;EACI,OA3QK;;;AA8QT;EACI,OAnRK;;;AAsRT;EACI,OArRK;;;AAwRT;EACI,OAxRE;;;AA2RN;EACI,OAnSO;EAoSP,WAvRO;;;AAyRX;EACI,OAtSO;EAuSP,WA3RO;;;AA6RX;EACI,OAzSG;EA0SH,WA/RO;;;AAiSX;EACI,OAvSK;EAwSL,WAnSO;;;AAqSX;EACI,OAhTI;EAiTJ,WAvSO;;;AA2SX;EACI;IACI;;EAEJ;IACI;;;AAGR;EACI;IACI;;EAEJ;IACI;;;AAIR;EACI;IACI,OAnUC;;EAqUL;IACI,OA3UA;;;AA8UR;EACI;IACI,OAhVA;;EAkVJ;IACI,OA9UC;;;AAkVT;EACI,kBA7VC;EA8VD;;;AAEJ;EACI;EACA;EACA;EACA;EACA,OAxVK;;;AA0VT;EACI;EACA;;;AAEJ;EAgBI;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAxBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAEJ;EACI,OAhXF;;;AA8XN;EACI,OA/XE;EAgYF,kBAvYO;EAwYP;EACH;EACA;;;AAED;EAaI,OAjZK;EAkZL,kBA1ZO;EA2ZP;EACA;EACH;EACG;;AAjBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA","file":"style.css"} diff --git a/css/style.scss b/css/style.scss index 89272e2..d34745a 100644 --- a/css/style.scss +++ b/css/style.scss @@ -27,6 +27,10 @@ $textsize: 16px; //background-color: #00cc00 } +.popup { + color: #50fa7b; +} + .bar,tooltip{ background-color: $bg; diff --git a/meson.build b/meson.build index a443769..bdd1ea0 100644 --- a/meson.build +++ b/meson.build @@ -44,6 +44,7 @@ sni_watcher_header = custom_target('generate-sni-watcher-header', input: ['protocols/sni-watcher.xml'], output: ['sni-watcher.h'], command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) +libdbusmenu = dependency('dbusmenu-gtk3-0.4') gtk = dependency('gtk+-3.0') gtk_layer_shell = dependency('gtk-layer-shell-0') @@ -109,7 +110,7 @@ libgBar = library('gBar', 'src/Log.cpp', 'src/SNI.cpp', ], - dependencies: [gtk, gtk_layer_shell, pulse, wayland_client], + dependencies: [gtk, gtk_layer_shell, pulse, wayland_client, libdbusmenu], include_directories: stb, install: true) diff --git a/src/SNI.cpp b/src/SNI.cpp index b94e751..f7d157e 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #define STB_IMAGE_IMPLEMENTATION #include @@ -28,6 +29,10 @@ namespace SNI uint8_t* iconData = nullptr; std::string tooltip; + + std::string menuObjectPath; + + EventBox* gtkEvent; }; std::vector items; @@ -182,6 +187,22 @@ namespace SNI g_variant_unref(tooltipVar); } + // 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); + } + return item; } @@ -266,11 +287,36 @@ namespace SNI { if (item.iconData) { + 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; + + GtkMenu* menu = (GtkMenu*)dbusmenu_gtkmenu_new(item->name.data(), item->menuObjectPath.data()); + LOG(menu); + gtk_menu_attach_to_widget(menu, item->gtkEvent->Get(), nullptr); + gtk_menu_popup_at_pointer(menu, (GdkEvent*)event); + LOG(item->menuObjectPath << " click"); + } + return GDK_EVENT_STOP; + }; + g_signal_connect(w.Get(), "button-release-event", G_CALLBACK(+clickFn), &item); + }); + auto texture = Widget::Create(); texture->SetHorizontalTransform({0, true, Alignment::Fill}); texture->SetBuf(item.w, item.h, item.iconData); texture->SetTooltip(item.tooltip); - iconBox->AddChild(std::move(texture)); + + eventBox->AddChild(std::move(texture)); + iconBox->AddChild(std::move(eventBox)); } } parentBox->AddChild(std::move(container)); diff --git a/src/Widget.cpp b/src/Widget.cpp index 8d7b66c..955ab14 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -173,6 +173,9 @@ void Widget::ApplyPropertiesToWidget() gtk_widget_set_valign(m_Widget, Utils::ToGtkAlign(m_VerticalTransform.alignment)); gtk_widget_set_hexpand(m_Widget, m_HorizontalTransform.expand); gtk_widget_set_vexpand(m_Widget, m_VerticalTransform.expand); + + if (m_OnCreate) + m_OnCreate(*this); } void Box::SetOrientation(Orientation orientation) diff --git a/src/Widget.h b/src/Widget.h index 150e71f..231748e 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -157,6 +157,8 @@ class Widget void SetVisible(bool visible); + void SetOnCreate(Callback&& onCreate) { m_OnCreate = onCreate; } + protected: void PropagateToParent(GdkEvent* event); void ApplyPropertiesToWidget(); @@ -169,6 +171,8 @@ class Widget std::string m_Tooltip; Transform m_HorizontalTransform; // X Transform m_VerticalTransform; // Y + + Callback m_OnCreate; }; class Box : public Widget From 67f048cd0105a89895c5e63f300eeb3eb4cb2893 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Sun, 30 Apr 2023 23:19:03 +0200 Subject: [PATCH 10/16] SNI: Add libdbusmenu-gtk3 to CI --- .github/workflows/build.yml | 2 +- flake.nix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00c8c84..ade08bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: pacman-key --populate archlinux - name: Download pacman packages run: | - pacman -Syu --noconfirm base-devel gcc git ninja meson gtk-layer-shell pulseaudio wayland + pacman -Syu --noconfirm base-devel gcc git ninja meson gtk-layer-shell pulseaudio wayland libdbusmenu-gtk3 - name: Download gBar uses: actions/checkout@v3.3.0 diff --git a/flake.nix b/flake.nix index 350e448..7273e41 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,7 @@ gtk-layer-shell libpulseaudio stb + libdbusmenu-gtk3 ]; }); in { From 31e070a3da500ceed6a202fdfdc7bde9b48aa8d8 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Wed, 3 May 2023 20:57:24 +0200 Subject: [PATCH 11/16] SNI: Add config --- data/config | 3 ++ meson.build | 109 +++++++++++++++++++++++++++------------------- meson_options.txt | 3 ++ src/Bar.cpp | 2 + src/Config.cpp | 1 + src/Config.h | 7 +++ src/SNI.cpp | 26 +++++++++-- src/SNI.h | 2 + src/System.cpp | 4 ++ 9 files changed, 108 insertions(+), 49 deletions(-) diff --git a/data/config b/data/config index 82b0205..b1a2ae9 100644 --- a/data/config +++ b/data/config @@ -71,6 +71,9 @@ NetworkAdapter: eno1 # Disables the network widget when set to false NetworkWidget: true +# Enables tray icons +EnableSNI: true + # These set the range for the network widget. The widget changes colors at six intervals: # - Below Min...Bytes ("under") # - Between ]0%;25%]. 0% = Min...Bytes; 100% = Max...Bytes ("low") diff --git a/meson.build b/meson.build index bdd1ea0..df83791 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('gBar', ['c', 'cpp'], version: '0.0.1', license: 'MIT', - meson_version: '>=0.49.0', + meson_version: '>=0.53.0', default_options: ['cpp_std=c++17', 'warning_level=3', 'default_library=static', @@ -23,32 +23,11 @@ ext_workspace_header = custom_target('generate-ext-workspace-header', output: ['ext-workspace-unstable-v1.h'], command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@']) -# DBus protocols -dbus_gen = find_program('gdbus-codegen') -sni_item_src = custom_target('generate-sni-item-src', - input: ['protocols/sni-item.xml'], - output: ['sni-item.c'], - command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) - -sni_item_header = custom_target('generate-sni-item-header', - input: ['protocols/sni-item.xml'], - output: ['sni-item.h'], - command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) - -sni_watcher_src = custom_target('generate-sni-watcher-src', - input: ['protocols/sni-watcher.xml'], - output: ['sni-watcher.c'], - command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) - -sni_watcher_header = custom_target('generate-sni-watcher-header', - input: ['protocols/sni-watcher.xml'], - output: ['sni-watcher.h'], - command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) -libdbusmenu = dependency('dbusmenu-gtk3-0.4') - gtk = dependency('gtk+-3.0') gtk_layer_shell = dependency('gtk-layer-shell-0') +pulse = dependency('libpulse') + headers = [ 'src/Common.h', 'src/Log.h', @@ -60,6 +39,25 @@ headers = [ 'src/CSS.h' ] +sources = [ + ext_workspace_src, + ext_workspace_header, + 'src/Window.cpp', + 'src/Widget.cpp', + 'src/System.cpp', + 'src/Bar.cpp', + 'src/Workspaces.cpp', + 'src/AudioFlyin.cpp', + 'src/BluetoothDevices.cpp', + 'src/Plugin.cpp', + 'src/Config.cpp', + 'src/CSS.cpp', + 'src/Log.cpp', + 'src/SNI.cpp', + ] + +dependencies = [gtk, gtk_layer_shell, pulse, wayland_client ] + if get_option('WithHyprland') add_global_arguments('-DWITH_HYPRLAND', language: 'cpp') headers += 'src/Workspaces.h' @@ -82,35 +80,54 @@ endif if get_option('WithSys') add_global_arguments('-DWITH_SYS', language: 'cpp') endif +if get_option('WithSNI') + add_global_arguments('-DWITH_SNI', language: 'cpp') + + # DBus protocols + dbus_gen = find_program('gdbus-codegen') + sni_item_src = custom_target('generate-sni-item-src', + input: ['protocols/sni-item.xml'], + output: ['sni-item.c'], + command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) + + sni_item_header = custom_target('generate-sni-item-header', + input: ['protocols/sni-item.xml'], + output: ['sni-item.h'], + command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) + + sni_watcher_src = custom_target('generate-sni-watcher-src', + input: ['protocols/sni-watcher.xml'], + output: ['sni-watcher.c'], + command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@']) + + sni_watcher_header = custom_target('generate-sni-watcher-header', + input: ['protocols/sni-watcher.xml'], + output: ['sni-watcher.h'], + command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@']) + libdbusmenu = dependency('dbusmenu-gtk3-0.4') + + sources += sni_item_src + sources += sni_item_header + sources += sni_watcher_src + sources += sni_watcher_header + + dependencies += libdbusmenu +endif add_global_arguments('-DUSE_LOGFILE', language: 'cpp') -pulse = dependency('libpulse') - # stb +fs = import('fs') stb = include_directories('thirdparty') +if fs.exists('thirdparty/stb/stb_image.h') + add_global_arguments('-DHAS_STB', language: 'cpp') +endif + + libgBar = library('gBar', - [ ext_workspace_src, - ext_workspace_header, - sni_item_src, - sni_item_header, - sni_watcher_src, - sni_watcher_header, - 'src/Window.cpp', - 'src/Widget.cpp', - 'src/System.cpp', - 'src/Bar.cpp', - 'src/Workspaces.cpp', - 'src/AudioFlyin.cpp', - 'src/BluetoothDevices.cpp', - 'src/Plugin.cpp', - 'src/Config.cpp', - 'src/CSS.cpp', - 'src/Log.cpp', - 'src/SNI.cpp', - ], - dependencies: [gtk, gtk_layer_shell, pulse, wayland_client, libdbusmenu], + sources, + dependencies: dependencies, include_directories: stb, install: true) diff --git a/meson_options.txt b/meson_options.txt index 5b5e663..b0c4500 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,9 @@ option('WithHyprland', type: 'boolean', value : true) # Workspaces general, enables Wayland protocol option('WithWorkspaces', type: 'boolean', value : true) +# Tray icons, requires stb git submodule +option('WithSNI', type: 'boolean', value : true) + option('WithNvidia', type: 'boolean', value : true) option('WithAMD', type: 'boolean', value : true) option('WithBlueZ', type: 'boolean', value : true) diff --git a/src/Bar.cpp b/src/Bar.cpp index e0f302e..326141a 100644 --- a/src/Bar.cpp +++ b/src/Bar.cpp @@ -647,7 +647,9 @@ namespace Bar right->SetSpacing({8, false}); right->SetHorizontalTransform({-1, true, Alignment::Right}); { +#ifdef WITH_SNI SNI::WidgetSNI(*right); +#endif WidgetPackages(*right); diff --git a/src/Config.cpp b/src/Config.cpp index 39dd025..6ed928e 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -147,6 +147,7 @@ void Config::Load() AddConfigVar("WorkspaceScrollOnMonitor", config.workspaceScrollOnMonitor, lineView, foundProperty); AddConfigVar("WorkspaceScrollInvert", config.workspaceScrollInvert, lineView, foundProperty); AddConfigVar("UseHyprlandIPC", config.useHyprlandIPC, lineView, foundProperty); + AddConfigVar("EnableSNI", config.enableSNI, lineView, foundProperty); AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty); AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty); diff --git a/src/Config.h b/src/Config.h index ce48c73..674c5bc 100644 --- a/src/Config.h +++ b/src/Config.h @@ -27,6 +27,7 @@ class Config bool workspaceScrollOnMonitor = true; // Scroll through workspaces on monitor instead of all bool workspaceScrollInvert = false; // Up = +1, instead of Up = -1 bool useHyprlandIPC = false; // Use Hyprland IPC instead of ext_workspaces protocol (Less buggy, but also less performant) + bool enableSNI = true; // Enable tray icon // Controls for color progression of the network widget uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under" @@ -73,6 +74,12 @@ class RuntimeConfig bool hasBlueZ = false; #endif +#if defined WITH_SNI && defined HAS_STB + bool hasSNI = true; +#else + bool hasSNI = false; +#endif + bool hasNet = true; bool hasPackagesScript = true; diff --git a/src/SNI.cpp b/src/SNI.cpp index f7d157e..1be7731 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -1,6 +1,9 @@ #include "SNI.h" #include "Log.h" #include "Widget.h" +#include "Config.h" + +#ifdef WITH_SNI #include #include @@ -245,6 +248,11 @@ namespace SNI static TimerResult UpdateWidgets(Box&) { + if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false) + { + // Don't bother + return TimerResult::Delete; + } for (auto& client : clientsToQuery) { LOG("SNI: Creating Item " << client.name << " " << client.object); @@ -324,6 +332,10 @@ namespace SNI void WidgetSNI(Widget& parent) { + if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false) + { + return; + } // Add parent box auto box = Widget::Create(); auto container = Widget::Create(); @@ -386,13 +398,19 @@ namespace SNI void Init() { + if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false) + { + return; + } + auto busAcquired = [](GDBusConnection* connection, const char*, void*) { GError* err = nullptr; g_dbus_interface_skeleton_export((GDBusInterfaceSkeleton*)watcherSkeleton, connection, "/StatusNotifierWatcher", &err); if (err) { - LOG("Failed to connect to dbus! Error: " << err->message); + LOG("SNI: Failed to connect to dbus! Disabling SNI. Error: " << err->message); + RuntimeConfig::Get().hasSNI = false; g_error_free(err); return; } @@ -409,9 +427,10 @@ namespace SNI sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true); }; auto emptyCallback = [](GDBusConnection*, const char*, void*) {}; - auto lostName = [](GDBusConnection*, const char*, void*) + auto lostName = [](GDBusConnection*, const char* msg, void*) { - LOG("Lost name!"); + LOG("SNI: Lost Name! Disabling SNI!"); + RuntimeConfig::Get().hasSNI = false; }; auto flags = G_BUS_NAME_OWNER_FLAGS_REPLACE | G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; g_bus_own_name(G_BUS_TYPE_SESSION, "org.kde.StatusNotifierWatcher", (GBusNameOwnerFlags)flags, +busAcquired, +emptyCallback, +lostName, @@ -429,3 +448,4 @@ namespace SNI void Shutdown() {} } +#endif diff --git a/src/SNI.h b/src/SNI.h index 0a9128e..8838fe1 100644 --- a/src/SNI.h +++ b/src/SNI.h @@ -1,4 +1,5 @@ #pragma once +#ifdef WITH_SNI class Widget; namespace SNI { @@ -6,3 +7,4 @@ namespace SNI void WidgetSNI(Widget& parent); void Shutdown(); } +#endif diff --git a/src/System.cpp b/src/System.cpp index f5499b0..bc26059 100644 --- a/src/System.cpp +++ b/src/System.cpp @@ -647,7 +647,9 @@ namespace System PulseAudio::Init(); +#ifdef WITH_SNI SNI::Init(); +#endif CheckNetwork(); } @@ -665,7 +667,9 @@ namespace System #ifdef WITH_BLUEZ StopBTScan(); #endif +#ifdef WITH_SNI SNI::Shutdown(); +#endif Logging::Shutdown(); } From 79e78826e0262664b03eb6cba137095c70c58aa8 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Thu, 4 May 2023 15:04:06 +0200 Subject: [PATCH 12/16] SNI: Only add to clientsToQuery once --- src/SNI.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/SNI.cpp b/src/SNI.cpp index 1be7731..8e73c9d 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -39,7 +39,7 @@ namespace SNI }; std::vector items; - std::vector clientsToQuery; + std::unordered_map clientsToQuery; // Gtk stuff, TODO: Allow more than one instance // Simply removing the gtk_drawing_areas doesn't trigger proper redrawing @@ -232,18 +232,23 @@ namespace SNI static void ItemPropertyChanged(GDBusConnection*, const char*, const char* object, const char*, const char*, GVariant*, void* name) { + std::string nameStr = (const char*)name; LOG("SNI: Reloading " << (const char*)name << " " << object); // We don't care about *what* changed, just remove and reload auto it = std::find_if(items.begin(), items.end(), [&](const Item& item) { - return item.name == (const char*)name; + return item.name == nameStr; }); if (it != items.end()) { items.erase(it); } - clientsToQuery.push_back({std::string((const char*)name), std::string(object)}); + else + { + LOG("SNI: Coudn't remove item " << nameStr << " when reloading"); + } + clientsToQuery[nameStr] = {nameStr, std::string(object)}; } static TimerResult UpdateWidgets(Box&) @@ -253,7 +258,7 @@ namespace SNI // Don't bother return TimerResult::Delete; } - for (auto& client : clientsToQuery) + for (auto& [name, client] : clientsToQuery) { LOG("SNI: Creating Item " << client.name << " " << client.object); Item item = CreateItem(std::move(client.name), std::move(client.object)); @@ -286,6 +291,7 @@ namespace SNI // SNI implements the GTK-Thingies itself internally static void InvalidateWidget() { + LOG("SNI: Clearing old children"); parentBox->RemoveChild(iconBox); auto container = Widget::Create(); @@ -318,6 +324,7 @@ namespace SNI g_signal_connect(w.Get(), "button-release-event", G_CALLBACK(+clickFn), &item); }); + LOG("SNI: Add " << item.name << " to widget"); auto texture = Widget::Create(); texture->SetHorizontalTransform({0, true, Alignment::Fill}); texture->SetBuf(item.w, item.h, item.iconData); @@ -377,7 +384,7 @@ namespace SNI sni_watcher_emit_status_notifier_item_registered(watcher, service); sni_watcher_complete_register_status_notifier_item(watcher, invocation); LOG("SNI: Registered Item " << name << " " << object); - clientsToQuery.push_back({std::move(name), std::move(object)}); + clientsToQuery[name] = {name, std::move(object)}; return true; } From e9c9d3abb137808be6f6ff4f85dcbbc8303c7872 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Thu, 4 May 2023 16:14:18 +0200 Subject: [PATCH 13/16] SNI: Add Icon positioning config options --- src/Config.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/Config.h | 5 +++++ src/SNI.cpp | 27 +++++++++++++++++++++++++++ src/Widget.cpp | 12 +++++++----- src/Widget.h | 5 +++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/Config.cpp b/src/Config.cpp index 6ed928e..d8a666f 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -27,6 +27,14 @@ void ApplyProperty(uint32_t& propertyToSet, const std::string_view& va propertyToSet = atoi(valStr.c_str()); } +template<> +void ApplyProperty(int32_t& propertyToSet, const std::string_view& value) +{ + // Why, C++? + std::string valStr = std::string(value); + propertyToSet = atoi(valStr.c_str()); +} + template<> void ApplyProperty(double& propertyToSet, const std::string_view& value) { @@ -53,6 +61,30 @@ void ApplyProperty(bool& propertyToSet, const std::string_view& value) } } +template<> +void ApplyProperty>(std::pair& propertyToSet, const std::string_view& value) +{ + // TODO: Ignore escaped delimiter (e.g. \, is the same as ,) + const char delim = ','; + const char* whitespace = " \t"; + size_t delimPos = value.find(delim); + if (delimPos == std::string::npos) + { + propertyToSet = {std::string(value), 0}; + return; + } + std::string_view before = value.substr(0, delimPos); + std::string_view after = value.substr(delimPos + 1); + + // Strip whitespaces for before + size_t beginBefore = before.find_first_not_of(whitespace); + size_t endBefore = before.find_last_not_of(whitespace); + before = before.substr(beginBefore, endBefore - beginBefore + 1); + + ApplyProperty(propertyToSet.first, before); + ApplyProperty(propertyToSet.second, after); +} + template void AddConfigVar(const std::string& propertyName, T& propertyToSet, std::string_view line, bool& setConfig) { @@ -161,6 +193,21 @@ void Config::Load() AddConfigVar("AudioMinVolume", config.audioMinVolume, lineView, foundProperty); AddConfigVar("AudioMaxVolume", config.audioMaxVolume, lineView, foundProperty); + std::pair buf; + bool hasntFoundProperty = !foundProperty; + AddConfigVar("SNIIconSize", buf, lineView, foundProperty); + if (foundProperty && hasntFoundProperty) + { + // This was found + config.sniIconSizes[buf.first] = buf.second; + } + hasntFoundProperty = !foundProperty; + AddConfigVar("SNIPaddingTop", buf, lineView, foundProperty); + if (foundProperty && hasntFoundProperty) + { + config.sniPaddingTop[buf.first] = buf.second; + } + if (foundProperty == false) { LOG("Warning: unknown config var: " << lineView); diff --git a/src/Config.h b/src/Config.h index 674c5bc..0c268a8 100644 --- a/src/Config.h +++ b/src/Config.h @@ -2,6 +2,7 @@ #include #include #include +#include class Config { @@ -39,6 +40,10 @@ class Config uint32_t checkUpdateInterval = 5 * 60; // Interval to run the "checkPackagesCommand". In seconds + // SNIIconSize: ["Title String"], ["Size"] + std::unordered_map sniIconSizes; + std::unordered_map sniPaddingTop; + // Only affects outputs (i.e.: speakers, not microphones). This remaps the range of the volume; In percent double audioMinVolume = 0.f; // Map the minimum volume to this value double audioMaxVolume = 100.f; // Map the maximum volume to this value diff --git a/src/SNI.cpp b/src/SNI.cpp index 8e73c9d..e105eea 100644 --- a/src/SNI.cpp +++ b/src/SNI.cpp @@ -326,6 +326,33 @@ namespace SNI LOG("SNI: Add " << item.name << " to widget"); auto texture = Widget::Create(); + bool wasExplicitOverride = false; + for (auto& [filter, size] : Config::Get().sniIconSizes) + { + if (item.tooltip.find(filter) != std::string::npos) + { + wasExplicitOverride = true; + texture->ForceHeight(size); + } + else if (filter == "*" && !wasExplicitOverride) + { + texture->ForceHeight(size); + } + } + wasExplicitOverride = false; + for (auto& [filter, padding] : Config::Get().sniPaddingTop) + { + if (item.tooltip.find(filter) != std::string::npos) + { + LOG("Padding " << padding); + wasExplicitOverride = true; + texture->AddPaddingTop(padding); + } + else if (filter == "*" && !wasExplicitOverride) + { + texture->AddPaddingTop(padding); + } + } texture->SetHorizontalTransform({0, true, Alignment::Fill}); texture->SetBuf(item.w, item.h, item.iconData); texture->SetTooltip(item.tooltip); diff --git a/src/Widget.cpp b/src/Widget.cpp index 955ab14..f69e1ac 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -520,13 +520,15 @@ void Texture::Draw(cairo_t* cr) { GtkAllocation dim; gtk_widget_get_allocation(m_Widget, &dim); - double ratio = (double)m_Width / (double)m_Height; - gtk_widget_set_size_request(m_Widget, dim.height * ratio, dim.height); - double scale = (double)dim.height / (double)m_Height; + double height = m_ForcedHeight != 0 ? m_ForcedHeight : dim.height; + double scale = (double)height / (double)m_Height; + double width = (double)m_Width * scale; + + gtk_widget_set_size_request(m_Widget, width + 2, height); cairo_scale(cr, scale, scale); - cairo_rectangle(cr, 0.f, 0.f, m_Width, m_Height); - gdk_cairo_set_source_pixbuf(cr, m_Pixbuf, 0, 0); + cairo_rectangle(cr, (dim.width - width) / 2.0, m_Padding + (dim.height - height) / 2.0, m_Width, m_Height); + gdk_cairo_set_source_pixbuf(cr, m_Pixbuf, (dim.width - width) / 2.0, m_Padding + (dim.height - height) / 2.0); cairo_fill(cr); } diff --git a/src/Widget.h b/src/Widget.h index 231748e..bfb2e95 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -277,11 +277,16 @@ class Texture : public CairoArea // Non-Owning, ARGB32 void SetBuf(size_t width, size_t height, uint8_t* buf); + void ForceHeight(size_t height) { m_ForcedHeight = height; }; + void AddPaddingTop(int32_t topPadding) { m_Padding = topPadding; }; + private: void Draw(cairo_t* cr) override; size_t m_Width; size_t m_Height; + size_t m_ForcedHeight = 0; + int32_t m_Padding = 0; GBytes* m_Bytes; GdkPixbuf* m_Pixbuf; }; From faa3f0041862d9768e30d7ce70f8caa01b4154f5 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Thu, 4 May 2023 16:24:25 +0200 Subject: [PATCH 14/16] SNI: Update Readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 47bc26c..5bee7fe 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Bar: - Disk: Free/Total - Network: Current upload and download speed - Update checking (Non-Arch systems need to be configured manually) +- Tray icons Bluetooth: - Scanning of nearby bluetooth devices @@ -162,3 +163,9 @@ See *Configuration for your system* ### The icons are not showing! Please install a Nerd Font from https://www.nerdfonts.com (I use Caskaydia Cove NF), and change style.css/style.scss accordingly (Refer to 'I want to customize the colors' for that). You _will_ a Nerd Font with version 2.3.0 or newer (For more details see [this comment](https://github.com/scorpion-26/gBar/issues/5#issuecomment-1442037005)) +### The tray doesn't show +Some apps sometimes don't actively query for tray applications. A fix for this is to start gBar before the tray app +If it still doesn't show, please open an issue with your application + +### Clicking on the tray opens a glitchy transparent menu +This is semi-intentional and a known bug (See https://github.com/scorpion-26/gBar/pull/12#issuecomment-1529143790 for an explanation). You can make it opaque by setting the background-color property of .popup in style.css/style.scss From fdda22395996b4949ddff53790aac84fba1b0801 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Thu, 4 May 2023 16:30:44 +0200 Subject: [PATCH 15/16] SNI: Add example config --- data/config | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/config b/data/config index b1a2ae9..bac6f13 100644 --- a/data/config +++ b/data/config @@ -74,6 +74,17 @@ NetworkWidget: true # Enables tray icons EnableSNI: true +# SNIIconSize sets the icon size for a SNI icon. +# SNIPaddingTop Can be used to push the Icon down. Negative values are allowed +# For both: The first parameter is a filter of the tooltip(The text that pops up, when the icon is hovered) of the icon + +# Scale everything down to 25 pixels ('*' as filter means everything) +SNIIconSize: *, 25 +# Explicitly make OBS a bit smaller than default +SNIIconSize: OBS, 23 +# Nudges the Discord icon a bit down +# SNIPaddingTop: Discord, 5 + # These set the range for the network widget. The widget changes colors at six intervals: # - Below Min...Bytes ("under") # - Between ]0%;25%]. 0% = Min...Bytes; 100% = Max...Bytes ("low") From d78afe427968b85622640a1f5f193788ef85cae9 Mon Sep 17 00:00:00 2001 From: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Thu, 4 May 2023 16:31:22 +0200 Subject: [PATCH 16/16] SNI: Mention tested apps --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5bee7fe..a16af2a 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ Please install a Nerd Font from https://www.nerdfonts.com (I use Caskaydia Cove ### The tray doesn't show Some apps sometimes don't actively query for tray applications. A fix for this is to start gBar before the tray app If it still doesn't show, please open an issue with your application +The tray icons are confirmed to work with Discord, Telegram, OBS and KeePassXC ### Clicking on the tray opens a glitchy transparent menu This is semi-intentional and a known bug (See https://github.com/scorpion-26/gBar/pull/12#issuecomment-1529143790 for an explanation). You can make it opaque by setting the background-color property of .popup in style.css/style.scss