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 #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 +#include +#include +#include +#include +#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); +}