diff --git a/include/client.hpp b/include/client.hpp index d4374dcd..01551050 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -2,14 +2,10 @@ #include #include - #include - #include #include - #include - #include "bar.hpp" namespace waybar { @@ -30,14 +26,14 @@ class Client { struct wl_seat *seat = nullptr; std::vector> bars; -private: - void bindInterfaces(); - auto setupCss(); + private: + void bindInterfaces(); + auto setupCss(); - static void handleGlobal(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version); - static void handleGlobalRemove(void *data, - struct wl_registry *registry, uint32_t name); + static void handleGlobal(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version); + static void handleGlobalRemove(void *data, + struct wl_registry *registry, uint32_t name); }; } diff --git a/include/factory.hpp b/include/factory.hpp index ce5627cc..14b976b0 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -9,6 +9,7 @@ #include "modules/battery.hpp" #include "modules/memory.hpp" #include "modules/cpu.hpp" +#include "modules/sni/tray.hpp" #ifdef HAVE_LIBNL #include "modules/network.hpp" #endif diff --git a/include/modules/sni/snh.hpp b/include/modules/sni/snh.hpp new file mode 100644 index 00000000..585f2888 --- /dev/null +++ b/include/modules/sni/snh.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include "modules/sni/sni.hpp" + +namespace waybar::modules::SNI { + +class Host { + public: + Host(Glib::Dispatcher&); + ~Host(); + std::vector items; + private: + static void busAcquired(GDBusConnection*, const gchar*, gpointer); + static void nameAppeared(GDBusConnection*, const gchar*, const gchar*, + gpointer); + static void nameVanished(GDBusConnection*, const gchar*, gpointer); + static void proxyReady(GObject*, GAsyncResult*, gpointer); + static void registerHost(GObject*, GAsyncResult*, gpointer); + static void itemRegistered(SnOrgKdeStatusNotifierWatcher*, const gchar*, + gpointer); + static void itemUnregistered(SnOrgKdeStatusNotifierWatcher*, const gchar*, + gpointer); + + void getBusNameAndObjectPath(const gchar*, gchar**, gchar**); + void addRegisteredItem(const gchar* service); + + uint32_t bus_name_id_; + uint32_t watcher_id_; + std::string bus_name_; + std::string object_path_; + Glib::Dispatcher& dp_; + GCancellable* cancellable_ = nullptr; + SnOrgKdeStatusNotifierWatcher* watcher_ = nullptr; +}; + +} diff --git a/include/modules/sni/sni.hpp b/include/modules/sni/sni.hpp new file mode 100644 index 00000000..eb57902c --- /dev/null +++ b/include/modules/sni/sni.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +namespace waybar::modules::SNI { + +class Item { + public: + Item(std::string, std::string, Glib::Dispatcher&); + ~Item(); + int icon_size; + int effective_icon_size; + Gtk::Image* image; + std::string category; + std::string id; + std::string status; + + std::string title; + int32_t window_id; + std::string icon_name; + std::string overlay_icon_name; + std::string attention_icon_name; + std::string attention_movie_name; + std::string icon_theme_path; + std::string menu; + bool item_is_menu; + private: + static void proxyReady(GObject* obj, GAsyncResult* res, gpointer data); + static void getAll(GObject* obj, GAsyncResult* res, gpointer data); + + void updateImage(); + Glib::RefPtr getIconByName(std::string name, int size); + std::string bus_name_; + std::string object_path_; + Glib::Dispatcher& dp_; + GCancellable* cancellable_ = nullptr; + SnOrgKdeStatusNotifierItem* proxy_ = nullptr; +}; + +} diff --git a/include/modules/sni/snw.hpp b/include/modules/sni/snw.hpp new file mode 100644 index 00000000..f8965e38 --- /dev/null +++ b/include/modules/sni/snw.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace waybar::modules::SNI { + +typedef enum { + GF_WATCH_TYPE_HOST, + GF_WATCH_TYPE_ITEM +} GfWatchType; + +typedef struct { + GfWatchType type; + gchar* service; + gchar* bus_name; + gchar* object_path; + guint watch_id; +} GfWatch; + +class Watcher { + public: + Watcher(); + ~Watcher(); + private: + static void busAcquired(GDBusConnection*, const gchar*, gpointer); + static gboolean handleRegisterHost(Watcher*, + GDBusMethodInvocation*, const gchar*); + static gboolean handleRegisterItem(Watcher*, + GDBusMethodInvocation*, const gchar*); + static GfWatch* gfWatchFind(GSList* list, const gchar* bus_name, + const gchar* object_path); + static GfWatch* gfWatchNew(GfWatchType type, + const gchar* service, const gchar* bus_name, const gchar* object_path); + static void nameVanished(GDBusConnection* connection, const char* name, + gpointer data); + + void updateRegisteredItems(SnOrgKdeStatusNotifierWatcher* obj); + + uint32_t bus_name_id_; + uint32_t watcher_id_; + GSList* hosts_ = nullptr; + GSList* items_ = nullptr; + SnOrgKdeStatusNotifierWatcher *watcher_ = nullptr; +}; + +} diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp new file mode 100644 index 00000000..5be478f4 --- /dev/null +++ b/include/modules/sni/tray.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "util/json.hpp" +#include "IModule.hpp" +#include "modules/sni/snw.hpp" +#include "modules/sni/snh.hpp" + +namespace waybar::modules::SNI { + +class Tray : public IModule { + public: + Tray(const Json::Value&); + auto update() -> void; + operator Gtk::Widget &(); + private: + std::thread thread_; + const Json::Value& config_; + Gtk::Box box_; + SNI::Watcher watcher_ ; + SNI::Host host_; +}; + +} diff --git a/meson.build b/meson.build index b330dfed..4705aab0 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,10 @@ src_files = files( 'src/modules/clock.cpp', 'src/modules/custom.cpp', 'src/modules/cpu.cpp', + 'src/modules/sni/tray.cpp', + 'src/modules/sni/snw.cpp', + 'src/modules/sni/snh.cpp', + 'src/modules/sni/sni.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp' @@ -88,7 +92,7 @@ executable( gtkmm, libnl, libnlgen, - libpulse, + libpulse ], include_directories: [include_directories('include')], install: true, diff --git a/protocol/dbus-menu.xml b/protocol/dbus-menu.xml new file mode 100644 index 00000000..ae5d7906 --- /dev/null +++ b/protocol/dbus-menu.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocol/dbus-status-notifier-item.xml b/protocol/dbus-status-notifier-item.xml new file mode 100644 index 00000000..bc70ee03 --- /dev/null +++ b/protocol/dbus-status-notifier-item.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocol/dbus-status-notifier-watcher.xml b/protocol/dbus-status-notifier-watcher.xml new file mode 100644 index 00000000..b054f7e6 --- /dev/null +++ b/protocol/dbus-status-notifier-watcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocol/meson.build b/protocol/meson.build index 793aa13e..01cb771b 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -36,10 +36,28 @@ foreach p : client_protocols client_protos_headers += wayland_scanner_client.process(xml) endforeach +gdbus_code = generator( + find_program('gdbus-codegen'), + output: '@BASENAME@.c', + arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@'] +) + +gdbus_header = generator( + find_program('gdbus-codegen'), + output: '@BASENAME@.h', + arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@'] +) + +client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml') +client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml') + +client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml') +client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml') + lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, - dependencies: [wayland_client] + dependencies: [wayland_client, gtkmm] ) # for the include directory client_protos = declare_dependency( diff --git a/resources/config b/resources/config index e2dc8010..42dd753c 100644 --- a/resources/config +++ b/resources/config @@ -6,7 +6,7 @@ // Choose the order of the modules "modules-left": ["sway/workspaces", "custom/spotify"], "modules-center": ["sway/window"], - "modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"], + "modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock", "tray"], // Modules configuration "sway/workspaces": { // "disable-scroll": true, diff --git a/resources/style.css b/resources/style.css index 2ef0bdf4..ee9b6a03 100644 --- a/resources/style.css +++ b/resources/style.css @@ -27,7 +27,7 @@ window { border-bottom: 3px solid white; } -#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify { +#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray { padding: 0 10px; margin: 0 5px; } @@ -94,3 +94,7 @@ window { background: #66cc99; color: #2a5c45; } + +#tray { + background-color: #2980b9; +} \ No newline at end of file diff --git a/src/factory.cpp b/src/factory.cpp index ca35c0e4..0259babe 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -27,6 +27,9 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const if (name == "clock") { return new waybar::modules::Clock(config_[name]); } + if (name == "tray") { + return new waybar::modules::SNI::Tray(config_[name]); + } #ifdef HAVE_LIBNL if (name == "network") { return new waybar::modules::Network(config_[name]); diff --git a/src/modules/sni/snh.cpp b/src/modules/sni/snh.cpp new file mode 100644 index 00000000..49a6fb89 --- /dev/null +++ b/src/modules/sni/snh.cpp @@ -0,0 +1,148 @@ +#include "modules/sni/snh.hpp" + +#include + +waybar::modules::SNI::Host::Host(Glib::Dispatcher& dp) +: dp_(dp) +{ + GBusNameOwnerFlags flags = static_cast( + G_BUS_NAME_OWNER_FLAGS_NONE); + bus_name_ = "org.kde.StatusNotifierHost-" + std::to_string(getpid()); + object_path_ = "/StatusNotifierHost"; + bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION, + bus_name_.c_str(), flags, + &Host::busAcquired, nullptr, nullptr, this, nullptr); +} + +waybar::modules::SNI::Host::~Host() +{ +} + +void waybar::modules::SNI::Host::busAcquired(GDBusConnection* connection, + const gchar* name, gpointer data) +{ + auto host = static_cast(data); + host->watcher_id_ = g_bus_watch_name( + G_BUS_TYPE_SESSION, + "org.kde.StatusNotifierWatcher", + G_BUS_NAME_WATCHER_FLAGS_NONE, + &Host::nameAppeared, &Host::nameVanished, data, nullptr); +} + +void waybar::modules::SNI::Host::nameAppeared(GDBusConnection* connection, + const gchar* name, const gchar* name_owner, gpointer data) +{ + auto host = static_cast(data); + if (host->cancellable_ != nullptr) { + std::cout << "WTF" << std::endl; + } + host->cancellable_ = g_cancellable_new(); + sn_org_kde_status_notifier_watcher_proxy_new( + connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher", + host->cancellable_, &Host::proxyReady, data); +} + +void waybar::modules::SNI::Host::nameVanished(GDBusConnection* connection, + const gchar* name, gpointer data) +{ + auto host = static_cast(data); + g_cancellable_cancel(host->cancellable_); + g_clear_object(&host->cancellable_); + g_clear_object(&host->watcher_); + host->items.clear(); +} + +void waybar::modules::SNI::Host::proxyReady(GObject* src, GAsyncResult* res, + gpointer data) +{ + GError* error = nullptr; + SnOrgKdeStatusNotifierWatcher* watcher = + sn_org_kde_status_notifier_watcher_proxy_new_finish(res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + auto host = static_cast(data); + host->watcher_ = watcher; + if (error != nullptr) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + sn_org_kde_status_notifier_watcher_call_register_status_notifier_host( + host->watcher_, host->object_path_.c_str(), host->cancellable_, + &Host::registerHost, data); +} + +void waybar::modules::SNI::Host::registerHost(GObject* src, GAsyncResult* res, + gpointer data) +{ + GError* error = nullptr; + sn_org_kde_status_notifier_watcher_call_register_status_notifier_host_finish( + SN_ORG_KDE_STATUS_NOTIFIER_WATCHER(src), res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + auto host = static_cast(data); + if (error != nullptr) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + g_signal_connect(host->watcher_, "status-notifier-item-registered", + G_CALLBACK(&Host::itemRegistered), data); + g_signal_connect(host->watcher_, "status-notifier-item-unregistered", + G_CALLBACK(&Host::itemUnregistered), data); + auto items = + sn_org_kde_status_notifier_watcher_dup_registered_status_notifier_items(host->watcher_); + if (items) { + for (uint32_t i = 0; items[i] != nullptr; i += 1) { + host->addRegisteredItem(items[i]); + } + } + g_strfreev(items); +} + +void waybar::modules::SNI::Host::itemRegistered( + SnOrgKdeStatusNotifierWatcher* watcher, const gchar* service, gpointer data) +{ + std::cout << "Item registered" << std::endl; + auto host = static_cast(data); + host->addRegisteredItem(service); +} + +void waybar::modules::SNI::Host::itemUnregistered( + SnOrgKdeStatusNotifierWatcher* watcher, const gchar* service, gpointer data) +{ + std::cout << "Item Unregistered" << std::endl; +} + +void waybar::modules::SNI::Host::getBusNameAndObjectPath(const gchar* service, + gchar** bus_name, gchar** object_path) +{ + gchar* tmp = g_strstr_len (service, -1, "/"); + if (tmp != nullptr) { + gchar** str = g_strsplit(service, "/", 2); + *bus_name = g_strdup(str[0]); + *object_path = g_strdup(tmp); + g_strfreev(str); + } else { + *bus_name = g_strdup(service); + *object_path = g_strdup("/StatusNotifierItem"); + } +} + +void waybar::modules::SNI::Host::addRegisteredItem(const gchar* service) +{ + gchar* bus_name = nullptr; + gchar* object_path = nullptr; + + getBusNameAndObjectPath(service, &bus_name, &object_path); + items.emplace_back(bus_name, object_path, dp_); +} \ No newline at end of file diff --git a/src/modules/sni/sni.cpp b/src/modules/sni/sni.cpp new file mode 100644 index 00000000..0cd06a71 --- /dev/null +++ b/src/modules/sni/sni.cpp @@ -0,0 +1,165 @@ +#include "modules/sni/sni.hpp" + +#include + +waybar::modules::SNI::Item::Item(std::string bus_name, std::string object_path, + Glib::Dispatcher& dp) + : icon_size(16), effective_icon_size(0), + image(Gtk::manage(new Gtk::Image())), + bus_name_(bus_name), object_path_(object_path), dp_(dp) +{ + cancellable_ = g_cancellable_new(); + sn_org_kde_status_notifier_item_proxy_new_for_bus(G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, bus_name_.c_str(), object_path_.c_str(), + cancellable_, &Item::proxyReady, this); +} + +waybar::modules::SNI::Item::~Item() +{ +} + +void waybar::modules::SNI::Item::proxyReady(GObject* obj, GAsyncResult* res, + gpointer data) +{ + GError* error = nullptr; + SnOrgKdeStatusNotifierItem* proxy = + sn_org_kde_status_notifier_item_proxy_new_for_bus_finish(res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + auto item = static_cast(data); + item->proxy_ = proxy; + if (error) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy)); + g_dbus_connection_call(conn, item->bus_name_.c_str(), + item->object_path_.c_str(), "org.freedesktop.DBus.Properties", "GetAll", + g_variant_new("(s)", "org.kde.StatusNotifierItem"), + G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, + item->cancellable_, &Item::getAll, data); +} + +void waybar::modules::SNI::Item::getAll(GObject* obj, GAsyncResult* res, + gpointer data) +{ + GError* error = nullptr; + auto conn = G_DBUS_CONNECTION(obj); + GVariant* properties = g_dbus_connection_call_finish(conn, res, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + auto item = static_cast(data); + if (error) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + GVariantIter* it = nullptr; + g_variant_get(properties, "(a{sv})", &it); + gchar* key; + GVariant* value; + while (g_variant_iter_next(it, "{sv}", &key, &value)) { + if (g_strcmp0(key, "Category") == 0) { + item->category = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "Id") == 0) { + item->id = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "Title") == 0) { + item->title = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "Status") == 0) { + item->status = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "WindowId") == 0) { + item->window_id = g_variant_get_int32 (value); + } else if (g_strcmp0(key, "IconName") == 0) { + item->icon_name = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "IconPixmap") == 0) { + // TODO: icon pixmap + } else if (g_strcmp0(key, "OverlayIconName") == 0) { + item->overlay_icon_name = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "OverlayIconPixmap") == 0) { + // TODO: overlay_icon_pixmap + } else if (g_strcmp0(key, "AttentionIconName") == 0) { + item->attention_icon_name = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "AttentionIconPixmap") == 0) { + // TODO: attention_icon_pixmap + } else if (g_strcmp0(key, "AttentionMovieName") == 0) { + item->attention_movie_name = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "ToolTip") == 0) { + // TODO: tooltip + } else if (g_strcmp0(key, "IconThemePath") == 0) { + item->icon_theme_path = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "Menu") == 0) { + item->menu = g_variant_dup_string(value, nullptr); + } else if (g_strcmp0(key, "ItemIsMenu") == 0) { + item->item_is_menu = g_variant_get_boolean(value); + } + g_variant_unref(value); + g_free(key); + } + g_variant_iter_free(it); + g_variant_unref(properties); + if (item->id.empty() || item->category.empty() || item->status.empty()) { + std::cerr << "Invalid Status Notifier Item: " + item->bus_name_ + "," + + item->object_path_ << std::endl; + return; + } + if (!item->icon_theme_path.empty()) { + GtkIconTheme* icon_theme = gtk_icon_theme_get_default(); + gtk_icon_theme_append_search_path(icon_theme, + item->icon_theme_path.c_str()); + } + item->updateImage(); + item->dp_.emit(); + // TODO: handle change +} + +void waybar::modules::SNI::Item::updateImage() +{ + if (!icon_name.empty()) { + auto pixbuf = getIconByName(icon_name, icon_size); + if (pixbuf->gobj() == nullptr) { + // Try to find icons specified by path and filename + pixbuf = Gdk::Pixbuf::create_from_file(icon_name); + if (pixbuf->gobj() != nullptr) { + // An icon specified by path and filename may be the wrong size for the tray + pixbuf->scale_simple(icon_size - 2, icon_size - 2, + Gdk::InterpType::INTERP_BILINEAR); + } + } + if (pixbuf->gobj() == nullptr) { + pixbuf = getIconByName("image-missing", icon_size); + } + image->set(pixbuf); + } else { + image->set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); + image->set_pixel_size(icon_size); + } +} + +Glib::RefPtr waybar::modules::SNI::Item::getIconByName( + std::string name, int request_size) +{ + int icon_size = 0; + Glib::RefPtr icon_theme = + Gtk::IconTheme::get_default(); + icon_theme->rescan_if_needed(); + auto sizes = icon_theme->get_icon_sizes(name.c_str()); + for (auto size : sizes) { + // -1 == scalable + if (size == request_size || size == -1) { + icon_size = request_size; + break; + } else if (size < request_size || size > icon_size) { + icon_size = size; + } + } + if (icon_size == 0) { + icon_size = request_size; + } + return icon_theme->load_icon(name.c_str(), icon_size, + Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); +} \ No newline at end of file diff --git a/src/modules/sni/snw.cpp b/src/modules/sni/snw.cpp new file mode 100644 index 00000000..fc314f54 --- /dev/null +++ b/src/modules/sni/snw.cpp @@ -0,0 +1,169 @@ +#include "modules/sni/snw.hpp" + +#include + +waybar::modules::SNI::Watcher::Watcher() +{ + GBusNameOwnerFlags flags = static_cast( + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT + | G_BUS_NAME_OWNER_FLAGS_REPLACE); + bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION, + "org.kde.StatusNotifierWatcher", flags, + &Watcher::busAcquired, nullptr, nullptr, this, nullptr); + watcher_ = sn_org_kde_status_notifier_watcher_skeleton_new(); +} + +waybar::modules::SNI::Watcher::~Watcher() +{ +} + +void waybar::modules::SNI::Watcher::busAcquired(GDBusConnection* connection, + const gchar* name, gpointer data) +{ + GError* error = nullptr; + auto host = static_cast(data); + g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(host->watcher_), + connection, "/StatusNotifierWatcher", &error); + if (error != nullptr) { + std::cerr << error->message << std::endl; + g_error_free(error); + return; + } + g_signal_connect_swapped(host->watcher_, + "handle-register-status-notifier-item", + G_CALLBACK(&Watcher::handleRegisterItem), data); + g_signal_connect_swapped(host->watcher_, + "handle-register-status-notifier-host", + G_CALLBACK(&Watcher::handleRegisterHost), data); + sn_org_kde_status_notifier_watcher_set_protocol_version(host->watcher_, 0); + sn_org_kde_status_notifier_watcher_set_is_status_notifier_host_registered( + host->watcher_, TRUE); + std::cout << "Bus aquired" << std::endl; +} + +gboolean waybar::modules::SNI::Watcher::handleRegisterHost( + Watcher* obj, GDBusMethodInvocation* invocation, + const gchar* service) +{ + const gchar* bus_name = service; + const gchar* object_path = "/StatusNotifierHost"; + + if (*service == '/') { + bus_name = g_dbus_method_invocation_get_sender(invocation); + object_path = service; + } + if (g_dbus_is_name(bus_name) == FALSE) { + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); + return TRUE; + } + auto watch = gfWatchFind(obj->hosts_, bus_name, object_path); + if (watch != nullptr) { + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, "Status Notifier Host with bus name '%s' and object path '%s' is already registered", + bus_name, object_path); + return TRUE; + } + watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path); + obj->hosts_ = g_slist_prepend(obj->hosts_, watch); + sn_org_kde_status_notifier_watcher_set_is_status_notifier_host_registered( + obj->watcher_, TRUE); + if (g_slist_length(obj->hosts_)) { + sn_org_kde_status_notifier_watcher_emit_status_notifier_host_registered( + obj->watcher_); + } + sn_org_kde_status_notifier_watcher_complete_register_status_notifier_host( + obj->watcher_, invocation); + std::cout << "Host registered: " << bus_name << std::endl; + return TRUE; +} + +gboolean waybar::modules::SNI::Watcher::handleRegisterItem( + Watcher* obj, GDBusMethodInvocation* invocation, + const gchar* service) +{ + const gchar* bus_name = service; + const gchar* object_path = "/StatusNotifierItem"; + + if (*service == '/') { + bus_name = g_dbus_method_invocation_get_sender(invocation); + object_path = service; + } + if (g_dbus_is_name(bus_name) == FALSE) { + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); + return TRUE; + } + auto watch = gfWatchFind(obj->items_, bus_name, object_path); + if (watch != nullptr) { + g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered", + bus_name, object_path); + sn_org_kde_status_notifier_watcher_complete_register_status_notifier_item( + obj->watcher_, invocation); + return TRUE; + } + watch = gfWatchNew(GF_WATCH_TYPE_ITEM, service, bus_name, object_path); + obj->items_ = g_slist_prepend(obj->items_, watch); + obj->updateRegisteredItems(obj->watcher_); + gchar* tmp = g_strdup_printf("%s%s", bus_name, object_path); + sn_org_kde_status_notifier_watcher_emit_status_notifier_item_registered( + obj->watcher_, tmp); + g_free(tmp); + sn_org_kde_status_notifier_watcher_complete_register_status_notifier_item( + obj->watcher_, invocation); + return TRUE; +} + +waybar::modules::SNI::GfWatch* waybar::modules::SNI::Watcher::gfWatchFind( + GSList* list, const gchar* bus_name, const gchar* object_path) +{ + for (GSList* l = list; l != nullptr; l = g_slist_next (l)) { + GfWatch* watch = static_cast(l->data); + if (g_strcmp0 (watch->bus_name, bus_name) == 0 + && g_strcmp0 (watch->object_path, object_path) == 0) { + return watch; + } + } + return nullptr; +} + +waybar::modules::SNI::GfWatch* waybar::modules::SNI::Watcher::gfWatchNew( + GfWatchType type, const gchar* service, const gchar* bus_name, + const gchar* object_path) +{ + GfWatch* watch = g_new0(GfWatch, 1); + watch->type = type; + watch->service = g_strdup(service); + watch->bus_name = g_strdup(bus_name); + watch->object_path = g_strdup(object_path); + watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, &Watcher::nameVanished, watch, + nullptr); + return watch; +} + +void waybar::modules::SNI::Watcher::nameVanished(GDBusConnection* connection, + const char* name, gpointer data) +{ + //TODO + std::cout << "name vanished" << std::endl; +} + +void waybar::modules::SNI::Watcher::updateRegisteredItems( + SnOrgKdeStatusNotifierWatcher* obj) +{ + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); + for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) { + GfWatch* watch = static_cast(l->data); + gchar* item = g_strdup_printf ("%s%s", watch->bus_name, watch->object_path); + g_variant_builder_add (&builder, "s", item); + g_free (item); + } + GVariant* variant = g_variant_builder_end(&builder); + const gchar** items = g_variant_get_strv (variant, nullptr); + sn_org_kde_status_notifier_watcher_set_registered_status_notifier_items( + obj, items); + g_variant_unref(variant); + g_free(items); +} \ No newline at end of file diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp new file mode 100644 index 00000000..6ddf7447 --- /dev/null +++ b/src/modules/sni/tray.cpp @@ -0,0 +1,26 @@ +#include "modules/sni/tray.hpp" + +#include + +waybar::modules::SNI::Tray::Tray(const Json::Value& config) + : config_(config), watcher_(), host_(dp) +{ +} + +auto waybar::modules::SNI::Tray::update() -> void +{ + for (auto item : host_.items) { + item.image->set_tooltip_text(item.title); + box_.pack_start(*item.image); + } + if (box_.get_children().size() > 0) { + box_.set_name("tray"); + box_.show_all(); + } else { + box_.set_name(""); + } +} + +waybar::modules::SNI::Tray::operator Gtk::Widget &() { + return box_; +}