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 718a3e74..d2e5553b 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..e56950e1 --- /dev/null +++ b/include/modules/sni/snh.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include +#include "modules/sni/sni.hpp" + +namespace waybar::modules::SNI { + +class Host { + public: + Host(Glib::Dispatcher*); + 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); + + std::tuple getBusNameAndObjectPath(const 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..5cf95138 --- /dev/null +++ b/include/modules/sni/sni.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +namespace waybar::modules::SNI { + +class Item { +public: + Item(std::string, std::string, Glib::Dispatcher *); + + std::string bus_name; + std::string object_path; + Gtk::EventBox event_box; + + int icon_size; + int effective_icon_size; + Gtk::Image *image; + Gtk::Menu *gtk_menu = nullptr; + std::string category; + std::string id; + std::string status; + + std::string title; + int32_t window_id; + std::string icon_name; + Glib::RefPtr icon_pixmap; + 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); + static void handleActivate(GObject *, GAsyncResult *, gpointer); + static void handleSecondaryActivate(GObject *, GAsyncResult *, gpointer); + + void updateImage(); + Glib::RefPtr extractPixBuf(GVariant *variant); + Glib::RefPtr getIconByName(std::string name, int size); + bool handleClick(GdkEventButton *const & /*ev*/); + + Glib::Dispatcher *dp_; + GCancellable *cancellable_ = nullptr; + SnOrgKdeStatusNotifierItem *proxy_ = nullptr; +}; + +} // namespace waybar::modules::SNI diff --git a/include/modules/sni/snw.hpp b/include/modules/sni/snw.hpp new file mode 100644 index 00000000..2ee2b6cc --- /dev/null +++ b/include/modules/sni/snw.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace waybar::modules::SNI { + +class Watcher { +public: + Watcher(); + ~Watcher(); + +private: + typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType; + + typedef struct { + GfWatchType type; + Watcher *watcher; + gchar *service; + gchar *bus_name; + gchar *object_path; + guint watch_id; + } GfWatch; + + 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, const gchar *, const gchar *, + const gchar *, Watcher *); + 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; +}; + +} // namespace waybar::modules::SNI + 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 986b423b..41d4624b 100644 --- a/meson.build +++ b/meson.build @@ -32,6 +32,7 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots']) gtkmm = dependency('gtkmm-3.0') +dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4') jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libnl = dependency('libnl-3.0', required: get_option('libnl')) @@ -46,6 +47,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' @@ -86,9 +91,10 @@ executable( libinput, wayland_cursor, gtkmm, + dbusmenu_gtk, 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 da3012bb..de1a99a8 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 cacdb67e..d15c8947 100644 --- a/resources/style.css +++ b/resources/style.css @@ -5,7 +5,7 @@ font-size: 13px; } -window { +window#waybar { background: rgba(43, 48, 59, 0.5); border-bottom: 3px solid rgba(100, 114, 125, 0.5); color: white; @@ -23,7 +23,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; } @@ -90,3 +90,7 @@ window { background: #66cc99; color: #2a5c45; } + +#tray { + background-color: #2980b9; +} diff --git a/src/bar.cpp b/src/bar.cpp index 8e927c4e..2097a35d 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -21,6 +21,7 @@ waybar::Bar::Bar(const Client& client, zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this); window.set_title("waybar"); window.set_decorated(false); + window.set_name("waybar"); window.set_resizable(false); setupConfig(); setupCss(); @@ -199,14 +200,14 @@ auto waybar::Bar::setupWidgets() -> void getModules(factory, "modules-left"); getModules(factory, "modules-center"); getModules(factory, "modules-right"); - for (auto& module : modules_left_) { + for (auto const& module : modules_left_) { left.pack_start(*module, false, true, 0); } - for (auto& module : modules_center_) { + for (auto const& module : modules_center_) { center.pack_start(*module, true, true, 0); } std::reverse(modules_right_.begin(), modules_right_.end()); - for (auto& module : modules_right_) { + for (auto const& module : modules_right_) { right.pack_end(*module, false, false, 0); } } diff --git a/src/factory.cpp b/src/factory.cpp index fc69b0b1..d3c53f39 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/battery.cpp b/src/modules/battery.cpp index 78470e77..002b8ed5 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -4,7 +4,7 @@ waybar::modules::Battery::Battery(const Json::Value& config) : ALabel(config, "{capacity}%") { try { - for (auto &node : fs::directory_iterator(data_dir_)) { + for (auto const& node : fs::directory_iterator(data_dir_)) { if (fs::is_directory(node) && fs::exists(node / "capacity") && fs::exists(node / "status") && fs::exists(node / "uevent")) { batteries_.push_back(node); @@ -20,7 +20,7 @@ waybar::modules::Battery::Battery(const Json::Value& config) if (fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } - for (auto &bat : batteries_) { + for (auto const& bat : batteries_) { inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS); } label_.set_name("battery"); @@ -51,7 +51,7 @@ auto waybar::modules::Battery::update() -> void try { uint16_t total = 0; std::string status; - for (auto &bat : batteries_) { + for (auto const& bat : batteries_) { uint16_t capacity; std::string _status; std::ifstream(bat / "capacity") >> capacity; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 71f82151..1df835e9 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -142,7 +142,7 @@ int waybar::modules::Network::getExternalInterface() int ifidx = -1; /* Prepare request. */ - uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); + constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); char req[reqlen] = {0}; /* Build the RTM_GETROUTE request. */ diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 4d41fd56..3e59a675 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -118,7 +118,7 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const "hifi", "phone", }; - for (auto port : ports) { + for (auto const& port : ports) { if (port_name_.find(port) != std::string::npos) { return port; } diff --git a/src/modules/sni/snh.cpp b/src/modules/sni/snh.cpp new file mode 100644 index 00000000..74abf2e0 --- /dev/null +++ b/src/modules/sni/snh.cpp @@ -0,0 +1,155 @@ +#include "modules/sni/snh.hpp" + +#include + +using namespace 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); +} + +void 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 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 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 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 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 Host::itemRegistered( + SnOrgKdeStatusNotifierWatcher* watcher, const gchar* service, gpointer data) +{ + std::cout << "Item registered" << std::endl; + auto host = static_cast(data); + host->addRegisteredItem(service); +} + +void Host::itemUnregistered( + SnOrgKdeStatusNotifierWatcher* watcher, const gchar* service, gpointer data) +{ + auto host = static_cast(data); + auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); + for (auto it = host->items.begin(); it != host->items.end(); ++it) { + if (it->bus_name == bus_name && it->object_path == object_path) { + host->items.erase(it); + std::cout << "Item Unregistered" << std::endl; + break; + } + } + host->dp_->emit(); +} + +std::tuple Host::getBusNameAndObjectPath( + const gchar* service) +{ + std::string bus_name; + std::string object_path; + gchar* tmp = g_strstr_len(service, -1, "/"); + if (tmp != nullptr) { + gchar** str = g_strsplit(service, "/", 2); + bus_name = str[0]; + object_path = tmp; + g_strfreev(str); + } else { + bus_name = service; + object_path = "/StatusNotifierItem"; + } + return { bus_name, object_path }; +} + +void Host::addRegisteredItem(const gchar* service) +{ + auto [bus_name, object_path] = getBusNameAndObjectPath(service); + items.emplace_back(bus_name, object_path, dp_); +} diff --git a/src/modules/sni/sni.cpp b/src/modules/sni/sni.cpp new file mode 100644 index 00000000..1882f99c --- /dev/null +++ b/src/modules/sni/sni.cpp @@ -0,0 +1,260 @@ +#include "modules/sni/sni.hpp" + +#include +#include + +waybar::modules::SNI::Item::Item(std::string bn, std::string op, + Glib::Dispatcher *dp) + : bus_name(bn), object_path(op), event_box(), icon_size(16), + effective_icon_size(0), image(Gtk::manage(new Gtk::Image())), dp_(dp) { + event_box.add(*image); + event_box.add_events(Gdk::BUTTON_PRESS_MASK); + event_box.signal_button_press_event().connect( + sigc::mem_fun(*this, &Item::handleClick)); + 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); +} + +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) { + item->icon_pixmap = item->extractPixBuf(value); + } 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 +} + +Glib::RefPtr +waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) { + GVariantIter *it; + g_variant_get(variant, "a(iiay)", &it); + if (it == nullptr) { + return Glib::RefPtr{}; + } + GVariant *val; + gint lwidth = 0; + gint lheight = 0; + gint width; + gint height; + guchar *array = nullptr; + while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) { + if (width > 0 && height > 0 && val != nullptr && + width * height > lwidth * lheight) { + auto size = g_variant_get_size(val); + /* Sanity check */ + if (size == 4U * width * height) { + /* Find the largest image */ + gconstpointer data = g_variant_get_data(val); + if (data != nullptr) { + if (array != nullptr) { + g_free(array); + } + array = static_cast(g_memdup(data, size)); + lwidth = width; + lheight = height; + } + } + } + } + g_variant_iter_free(it); + if (array != nullptr) { + /* argb to rgba */ + for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) { + guchar alpha = array[i]; + array[i] = array[i + 1]; + array[i + 1] = array[i + 2]; + array[i + 2] = array[i + 3]; + array[i + 3] = alpha; + } + return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, + true, 8, lwidth, lheight, 4 * lwidth); + } + return Glib::RefPtr{}; +} + +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 + try { + 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); + } + } catch (Glib::Error &e) { + std::cerr << "Exception: " << e.what() << std::endl; + pixbuf = getIconByName("image-missing", icon_size); + } + } + if (pixbuf->gobj() == nullptr) { + pixbuf = getIconByName("image-missing", icon_size); + } + image->set(pixbuf); + } else if (icon_pixmap) { + image->set(icon_pixmap); + } else { + image->set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); + image->set_pixel_size(icon_size); + } + if (!menu.empty()) { + auto *dbmenu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data()); + if (dbmenu) + gtk_menu = Glib::wrap(GTK_MENU(dbmenu), false); + } +} + +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 const &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); +} + +void waybar::modules::SNI::Item::handleActivate(GObject *src, GAsyncResult *res, + gpointer data) { + auto item = static_cast(data); + sn_org_kde_status_notifier_item_call_activate_finish(item->proxy_, res, + nullptr); +} + +void waybar::modules::SNI::Item::handleSecondaryActivate(GObject *src, + GAsyncResult *res, + gpointer data) { + auto item = static_cast(data); + sn_org_kde_status_notifier_item_call_secondary_activate_finish(item->proxy_, + res, nullptr); +} + +bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) { + if (ev->type == GDK_BUTTON_PRESS) { + if (gtk_menu) { + if (!gtk_menu->get_attach_widget()) { + gtk_menu->attach_to_widget(event_box); + } + gtk_menu->popup(ev->button, ev->time); + } else { + sn_org_kde_status_notifier_item_call_activate( + proxy_, ev->x, ev->y, nullptr, &Item::handleActivate, this); + } + } else if (ev->type == GDK_2BUTTON_PRESS) { + sn_org_kde_status_notifier_item_call_secondary_activate( + proxy_, ev->x, ev->y, nullptr, &Item::handleSecondaryActivate, this); + } else { + return false; + } + return true; +} diff --git a/src/modules/sni/snw.cpp b/src/modules/sni/snw.cpp new file mode 100644 index 00000000..208cc1e1 --- /dev/null +++ b/src/modules/sni/snw.cpp @@ -0,0 +1,183 @@ +#include "modules/sni/snw.hpp" + +#include + +using namespace 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(); +} + +Watcher::~Watcher() +{ +} + +void 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 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); + 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 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); + 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; +} + +Watcher::GfWatch* 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; +} + +Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, + const gchar* bus_name, const gchar* object_path, Watcher* watcher) +{ + GfWatch* watch = g_new0(GfWatch, 1); + watch->type = type; + watch->watcher = watcher; + 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 Watcher::nameVanished(GDBusConnection* connection, const char* name, + gpointer data) +{ + auto watch = static_cast(data); + if (watch->type == GF_WATCH_TYPE_HOST) { + watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); + if (watch->watcher->hosts_ == nullptr) { + sn_org_kde_status_notifier_watcher_set_is_status_notifier_host_registered( + watch->watcher->watcher_, FALSE); + sn_org_kde_status_notifier_watcher_emit_status_notifier_host_registered( + watch->watcher->watcher_); + } + } else if (watch->type == GF_WATCH_TYPE_ITEM) { + watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch); + watch->watcher->updateRegisteredItems(watch->watcher->watcher_); + gchar* tmp = g_strdup_printf("%s%s", watch->bus_name, watch->object_path); + sn_org_kde_status_notifier_watcher_emit_status_notifier_item_unregistered( + watch->watcher->watcher_, tmp); + g_free(tmp); + } +} + +void 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..92d2b189 --- /dev/null +++ b/src/modules/sni/tray.cpp @@ -0,0 +1,21 @@ +#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.event_box.set_tooltip_text(item.title); + box_.pack_start(item.event_box); + } + 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_; } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index a2f4469b..8faf7491 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -44,7 +44,7 @@ auto waybar::modules::sway::Window::update() -> void std::tuple waybar::modules::sway::Window::getFocusedNode( Json::Value nodes) { - for (auto &node : nodes) { + for (auto const& node : nodes) { if (node["focused"].asBool() && node["type"] == "con") { return { node["id"].asInt(), node["name"].asString() }; } diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 08bfb1eb..63166378 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -49,7 +49,7 @@ auto waybar::modules::sway::Workspaces::update() -> void ++it; } } - for (auto node : workspaces_) { + for (auto const& node : workspaces_) { if (!config_["all-outputs"].asBool() && bar_.output_name != node["output"].asString()) { continue;