diff --git a/include/swaybar/tray/host.h b/include/swaybar/tray/host.h new file mode 100644 index 00000000..2d4cf82b --- /dev/null +++ b/include/swaybar/tray/host.h @@ -0,0 +1,17 @@ +#ifndef _SWAYBAR_TRAY_HOST_H +#define _SWAYBAR_TRAY_HOST_H + +#include + +struct swaybar_tray; + +struct swaybar_host { + struct swaybar_tray *tray; + char *service; + char *watcher_interface; +}; + +bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray); +void finish_host(struct swaybar_host *host); + +#endif diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h index 8e15fb86..1d976b4a 100644 --- a/include/swaybar/tray/tray.h +++ b/include/swaybar/tray/tray.h @@ -9,6 +9,7 @@ #endif #include #include +#include "swaybar/tray/host.h" #include "list.h" struct swaybar; @@ -21,6 +22,9 @@ struct swaybar_tray { int fd; sd_bus *bus; + struct swaybar_host host_xdg; + struct swaybar_host host_kde; + list_t *items; // char * struct swaybar_watcher *watcher_xdg; struct swaybar_watcher *watcher_kde; diff --git a/swaybar/meson.build b/swaybar/meson.build index b8ed2c23..85783a0f 100644 --- a/swaybar/meson.build +++ b/swaybar/meson.build @@ -1,4 +1,5 @@ tray_files = get_option('enable-tray') ? [ + 'tray/host.c', 'tray/icon.c', 'tray/tray.c', 'tray/watcher.c' diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c new file mode 100644 index 00000000..3cc90254 --- /dev/null +++ b/swaybar/tray/host.c @@ -0,0 +1,177 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include "swaybar/tray/host.h" +#include "swaybar/tray/tray.h" +#include "list.h" +#include "log.h" + +static const char *watcher_path = "/StatusNotifierWatcher"; + +static int cmp_sni_id(const void *item, const void *cmp_to) { + const char *sni = item; + return strcmp(sni, cmp_to); +} + +static void add_sni(struct swaybar_tray *tray, char *id) { + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id); + char *sni = strdup(id); + if (sni) { + list_add(tray->items, sni); + } + } +} + +static int handle_sni_registered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + add_sni(tray, id); + + return ret; +} + +static int handle_sni_unregistered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx != -1) { + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id); + free(tray->items->items[idx]); + list_del(tray->items, idx); + } + return ret; +} + +static int get_registered_snis_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + if (sd_bus_message_is_method_error(msg, NULL)) { + sd_bus_error err = *sd_bus_message_get_error(msg); + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message); + return -sd_bus_error_get_errno(&err); + } + + int ret = sd_bus_message_enter_container(msg, 'v', NULL); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + char **ids; + ret = sd_bus_message_read_strv(msg, &ids); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + if (ids) { + struct swaybar_tray *tray = data; + for (char **id = ids; *id; ++id) { + add_sni(tray, *id); + } + } + + return ret; +} + +static bool register_to_watcher(struct swaybar_host *host) { + // this is called asynchronously in case the watcher is owned by this process + int ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, host->watcher_interface, + "RegisterStatusNotifierHost", NULL, NULL, "s", host->service); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret)); + return false; + } + + ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, + "org.freedesktop.DBus.Properties", "Get", + get_registered_snis_callback, host->tray, "ss", + host->watcher_interface, "RegisteredStatusNotifierItems"); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret)); + } + + return ret >= 0; +} + +bool init_host(struct swaybar_host *host, char *protocol, + struct swaybar_tray *tray) { + size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; + host->watcher_interface = malloc(len); + if (!host->watcher_interface) { + return false; + } + snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol); + + sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL; + int ret = sd_bus_match_signal(tray->bus, ®_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemRegistered", handle_sni_registered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s", + strerror(-ret)); + goto error; + } + ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemUnregistered", handle_sni_unregistered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + pid_t pid = getpid(); + size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", + protocol, pid) + 1; + host->service = malloc(service_len); + if (!host->service) { + goto error; + } + snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid); + ret = sd_bus_request_name(tray->bus, host->service, 0); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); + goto error; + } + + host->tray = tray; + if (!register_to_watcher(host)) { + goto error; + } + + sd_bus_slot_set_floating(reg_slot, 1); + sd_bus_slot_set_floating(unreg_slot, 1); + + wlr_log(WLR_DEBUG, "Registered %s", host->service); + return true; +error: + sd_bus_slot_unref(reg_slot); + sd_bus_slot_unref(unreg_slot); + finish_host(host); + return false; +} + +void finish_host(struct swaybar_host *host) { + sd_bus_release_name(host->tray->bus, host->service); + free(host->service); + free(host->watcher_interface); +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index c1d3b50b..e760812c 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -4,8 +4,10 @@ #include #include "swaybar/bar.h" #include "swaybar/tray/icon.h" +#include "swaybar/tray/host.h" #include "swaybar/tray/tray.h" #include "swaybar/tray/watcher.h" +#include "list.h" #include "log.h" struct swaybar_tray *create_tray(struct swaybar *bar) { @@ -29,6 +31,11 @@ struct swaybar_tray *create_tray(struct swaybar *bar) { tray->watcher_xdg = create_watcher("freedesktop", tray->bus); tray->watcher_kde = create_watcher("kde", tray->bus); + tray->items = create_list(); + + init_host(&tray->host_xdg, "freedesktop", tray); + init_host(&tray->host_kde, "kde", tray); + init_themes(&tray->themes, &tray->basedirs); return tray; @@ -38,6 +45,12 @@ void destroy_tray(struct swaybar_tray *tray) { if (!tray) { return; } + finish_host(&tray->host_xdg); + finish_host(&tray->host_kde); + for (int i = 0; i < tray->items->length; ++i) { + free(tray->items->items[0]); + } + list_free(tray->items); destroy_watcher(tray->watcher_xdg); destroy_watcher(tray->watcher_kde); sd_bus_flush_close_unref(tray->bus);