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