aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/swaybar/tray/tray.h4
-rw-r--r--include/swaybar/tray/watcher.h18
-rw-r--r--swaybar/meson.build1
-rw-r--r--swaybar/tray/tray.c7
-rw-r--r--swaybar/tray/watcher.c212
5 files changed, 242 insertions, 0 deletions
diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
index 217e2d45..cc3e8133 100644
--- a/include/swaybar/tray/tray.h
+++ b/include/swaybar/tray/tray.h
@@ -12,12 +12,16 @@
struct swaybar;
struct swaybar_output;
+struct swaybar_watcher;
struct swaybar_tray {
struct swaybar *bar;
int fd;
sd_bus *bus;
+
+ struct swaybar_watcher *watcher_xdg;
+ struct swaybar_watcher *watcher_kde;
};
struct swaybar_tray *create_tray(struct swaybar *bar);
diff --git a/include/swaybar/tray/watcher.h b/include/swaybar/tray/watcher.h
new file mode 100644
index 00000000..8f276da8
--- /dev/null
+++ b/include/swaybar/tray/watcher.h
@@ -0,0 +1,18 @@
+#ifndef _SWAYBAR_TRAY_WATCHER_H
+#define _SWAYBAR_TRAY_WATCHER_H
+
+#include "swaybar/tray/tray.h"
+#include "list.h"
+
+struct swaybar_watcher {
+ char *interface;
+ sd_bus *bus;
+ list_t *hosts;
+ list_t *items;
+ int version;
+};
+
+struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus);
+void destroy_watcher(struct swaybar_watcher *watcher);
+
+#endif
diff --git a/swaybar/meson.build b/swaybar/meson.build
index b83f47e5..ef64f33a 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -1,5 +1,6 @@
tray_files = get_option('enable-tray') ? [
'tray/tray.c',
+ 'tray/watcher.c'
] : []
swaybar_deps = [
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
index 0a4f2955..36ee3c30 100644
--- a/swaybar/tray/tray.c
+++ b/swaybar/tray/tray.c
@@ -4,6 +4,7 @@
#include <string.h>
#include "swaybar/bar.h"
#include "swaybar/tray/tray.h"
+#include "swaybar/tray/watcher.h"
#include "log.h"
struct swaybar_tray *create_tray(struct swaybar *bar) {
@@ -23,6 +24,10 @@ struct swaybar_tray *create_tray(struct swaybar *bar) {
tray->bar = bar;
tray->bus = bus;
tray->fd = sd_bus_get_fd(tray->bus);
+
+ tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
+ tray->watcher_kde = create_watcher("kde", tray->bus);
+
return tray;
}
@@ -30,6 +35,8 @@ void destroy_tray(struct swaybar_tray *tray) {
if (!tray) {
return;
}
+ destroy_watcher(tray->watcher_xdg);
+ destroy_watcher(tray->watcher_kde);
sd_bus_flush_close_unref(tray->bus);
free(tray);
}
diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c
new file mode 100644
index 00000000..198c6c85
--- /dev/null
+++ b/swaybar/tray/watcher.c
@@ -0,0 +1,212 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "list.h"
+#include "log.h"
+#include "swaybar/tray/watcher.h"
+
+static const char *obj_path = "/StatusNotifierWatcher";
+
+static bool using_standard_protocol(struct swaybar_watcher *watcher) {
+ return watcher->interface[strlen("org.")] == 'f'; // freedesktop
+}
+
+static int cmp_id(const void *item, const void *cmp_to) {
+ return strcmp(item, cmp_to);
+}
+
+static int cmp_service(const void *item, const void *cmp_to) {
+ return strncmp(item, cmp_to, strlen(cmp_to));
+}
+
+static int handle_lost_service(sd_bus_message *msg,
+ void *data, sd_bus_error *error) {
+ char *service, *old_owner, *new_owner;
+ int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
+ return ret;
+ }
+
+ if (!*new_owner) {
+ struct swaybar_watcher *watcher = data;
+ int idx = list_seq_find(watcher->items,
+ using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
+ if (idx != -1) {
+ char *id = watcher->items->items[idx];
+ wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
+ list_del(watcher->items, idx);
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierItemUnregistered", "s", id);
+ free(id);
+ }
+
+ idx = list_seq_find(watcher->hosts, cmp_id, service);
+ if (idx != -1) {
+ wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
+ free(watcher->hosts->items[idx]);
+ list_del(watcher->hosts, idx);
+ }
+ }
+
+ return 0;
+}
+
+static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ char *service_or_path, *id;
+ int ret = sd_bus_message_read(msg, "s", &service_or_path);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
+ return ret;
+ }
+
+ struct swaybar_watcher *watcher = data;
+ if (using_standard_protocol(watcher)) {
+ id = strdup(service_or_path);
+ } else {
+ const char *service, *path;
+ if (service_or_path[0] == '/') {
+ service = sd_bus_message_get_sender(msg);
+ path = service_or_path;
+ } else {
+ service = service_or_path;
+ path = "/StatusNotifierItem";
+ }
+ size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
+ id = malloc(id_len);
+ snprintf(id, id_len, "%s%s", service, path);
+ }
+
+ if (list_seq_find(watcher->items, cmp_id, id) == -1) {
+ wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
+ list_add(watcher->items, id);
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierItemRegistered", "s", id);
+ } else {
+ wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
+ free(id);
+ }
+
+ return sd_bus_reply_method_return(msg, "");
+}
+
+static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ char *service;
+ int ret = sd_bus_message_read(msg, "s", &service);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
+ return ret;
+ }
+
+ struct swaybar_watcher *watcher = data;
+ if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
+ wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
+ list_add(watcher->hosts, strdup(service));
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierHostRegistered", "s", service);
+ } else {
+ wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
+ }
+
+ return sd_bus_reply_method_return(msg, "");
+}
+
+static int get_registered_snis(sd_bus *bus, const char *obj_path,
+ const char *interface, const char *property, sd_bus_message *reply,
+ void *data, sd_bus_error *error) {
+ struct swaybar_watcher *watcher = data;
+ list_add(watcher->items, NULL); // strv expects NULL-terminated string array
+ int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
+ list_del(watcher->items, watcher->items->length - 1);
+ return ret;
+}
+
+static int is_host_registered(sd_bus *bus, const char *obj_path,
+ const char *interface, const char *property, sd_bus_message *reply,
+ void *data, sd_bus_error *error) {
+ struct swaybar_watcher *watcher = data;
+ int val = watcher->hosts->length > 0; // dbus expects int rather than bool
+ return sd_bus_message_append_basic(reply, 'b', &val);
+}
+
+static const sd_bus_vtable watcher_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
+ offsetof(struct swaybar_watcher, version),
+ SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
+ SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
+ SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
+ SD_BUS_VTABLE_END
+};
+
+struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
+ struct swaybar_watcher *watcher =
+ calloc(1, sizeof(struct swaybar_watcher));
+ if (!watcher) {
+ return NULL;
+ }
+
+ size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
+ watcher->interface = malloc(len);
+ snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
+
+ sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
+ int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
+ watcher->interface, watcher_vtable, watcher);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
+ goto error;
+ }
+
+ ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus",
+ "NameOwnerChanged", handle_lost_service, watcher);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ ret = sd_bus_request_name(bus, watcher->interface, 0);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
+ goto error;
+ }
+
+ sd_bus_slot_set_floating(signal_slot, 0);
+ sd_bus_slot_set_floating(vtable_slot, 0);
+
+ watcher->bus = bus;
+ watcher->hosts = create_list();
+ watcher->items = create_list();
+ watcher->version = 0;
+ wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
+ return watcher;
+error:
+ sd_bus_slot_unref(signal_slot);
+ sd_bus_slot_unref(vtable_slot);
+ destroy_watcher(watcher);
+ return NULL;
+}
+
+void destroy_watcher(struct swaybar_watcher *watcher) {
+ if (!watcher) {
+ return;
+ }
+ list_free_items_and_destroy(watcher->hosts);
+ list_free_items_and_destroy(watcher->items);
+ free(watcher->interface);
+ free(watcher);
+}