#include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "tray" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "../stringop.h" #include "../tag.h" static const char *watcher_path = "/StatusNotifierWatcher"; static const char *item_path = "/StatusNotifierItem"; struct sni_slot { struct sni *sni; const char *prop; const char *type; void *dest; sd_bus_slot *slot; }; typedef tll(struct sni_slot *) sni_slots_t; struct sni { struct private *m; struct particle *template; // dbus properties char *watcher_id; char *service; char *path; const char *interface; char *category; char *id; char *title; char *status; char *icon_name; struct icon_pixmaps *icon_pixmap; char *attention_icon_name; struct icon_pixmaps *attention_icon_pixmap; char *overlay_icon_name; struct icon_pixmaps *overlay_icon_pixmap; bool item_is_menu; char *menu; char *icon_theme_path; // non-standard kde property sni_slots_t slots; }; typedef tll(char *) hosts_t; typedef tll(char *) items_t; typedef tll(struct sni *) snis_t; struct watcher { struct private *m; char *interface; // This bus should is the tray's bus. // sd_bus *bus; hosts_t hosts; items_t items; int version; }; struct host { struct private *m; char *service; char *watcher_interface; }; struct private { struct module *mod; struct particle *template; int fd; sd_bus *bus; struct host host_xdg; struct host host_kde; snis_t items; // struct sni struct watcher *watcher_xdg; struct watcher *watcher_kde; }; // sni item ----------- static int read_pixmap(sd_bus_message *msg, struct sni *sni, const char *prop, struct icon_pixmaps **dest) { int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)"); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); return ret; } if (sd_bus_message_at_end(msg, 0)) { LOG_DBG("[SNI] %s %s no. of icons = 0", sni->watcher_id, prop); return ret; } struct icon_pixmaps *pixmaps = new_icon_pixmaps(); while (!sd_bus_message_at_end(msg, 0)) { ret = sd_bus_message_enter_container(msg, 'r', "iiay"); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); goto error; } int width, height; ret = sd_bus_message_read(msg, "ii", &width, &height); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); goto error; } const void *pixels; size_t npixels; ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); goto error; } if (height > 0 && width == height) { LOG_DBG("[SNI] %s %s: found icon w:%d h:%d", sni->watcher_id, prop, width, height); struct icon_pixmap *p = malloc(sizeof(*p) + npixels); p->size = width; // convert from network byte order to host byte order // for (int i = 0; i < width * height; ++i) { ((uint32_t *)p->pixels)[i] = ntohl(((uint32_t *)pixels)[i]); } tll_push_back(pixmaps->list, p); } else { LOG_DBG("[SNI] %s %s: discard invalid icon w:%d h:%d", sni->watcher_id, prop, width, height); } sd_bus_message_exit_container(msg); } if (tll_length(pixmaps->list) < 1) { LOG_DBG("[SNI] %s %s no. of icons = 0", sni->watcher_id, prop); goto error; } if (*dest) { icon_pixmaps_dec(*dest); } *dest = pixmaps; LOG_DBG("[SNI] %s %s no. of icons = %zu", sni->watcher_id, prop, tll_length(pixmaps->list)); return ret; error: icon_pixmaps_dec(pixmaps); return ret; } void destroy_slot(struct sni_slot *slot) { sd_bus_slot_unref(slot->slot); free(slot); } static int get_property_callback(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni_slot *d = data; struct sni *sni = d->sni; const char *prop = d->prop; const char *type = d->type; void *dest = d->dest; int ret; if (sd_bus_message_is_method_error(msg, NULL)) { const sd_bus_error *err = sd_bus_message_get_error(msg); if ((!strcmp(prop, "IconThemePath")) && (!strcmp(err->name, SD_BUS_ERROR_UNKNOWN_PROPERTY))) { LOG_DBG("[SNI]: %s %s: %s", sni->watcher_id, prop, err->message); } else { LOG_WARN("[SNI]: %s %s: %s", sni->watcher_id, prop, err->message); } ret = sd_bus_message_get_errno(msg); goto cleanup; } ret = sd_bus_message_enter_container(msg, 'v', type); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); goto cleanup; } if (!type) { ret = read_pixmap(msg, sni, prop, dest); if (ret < 0) { goto cleanup; } } else { if (*type == 's' || *type == 'o') { free(*(char **)dest); } ret = sd_bus_message_read(msg, type, dest); if (ret < 0) { LOG_ERR("[SNI] %s %s: %s", sni->watcher_id, prop, strerror(-ret)); goto cleanup; } if (*type == 's' || *type == 'o') { char **str = dest; *str = strdup(*str); LOG_DBG("[SNI] %s %s: %s", sni->watcher_id, prop, *str); } else if (*type == 'b') { LOG_DBG("[SNI] %s %s: %s", sni->watcher_id, prop, *(bool *)dest ? "true" : "false"); } } sni->m->mod->bar->refresh(sni->m->mod->bar); cleanup: tll_foreach(sni->slots, it) { if (it->item == d) { tll_remove_and_free(sni->slots, it, destroy_slot); break; } }; return ret; } static void sni_get_property_async(struct sni *sni, const char *prop, const char *type, void *dest) { struct sni_slot *data = calloc(1, sizeof(struct sni_slot)); if (!data) { LOG_ERR("Failed to allocate data slot: %s %s", sni->watcher_id, prop); return; } LOG_DBG("Get property: %s", prop); data->sni = sni; data->prop = prop; data->type = type; data->dest = dest; int ret = sd_bus_call_method_async(sni->m->bus, &data->slot, sni->service, sni->path, "org.freedesktop.DBus.Properties", "Get", get_property_callback, data, "ss", sni->interface, prop); if (ret >= 0) { tll_push_front(sni->slots, data); } else { LOG_ERR("%s %s: %s", sni->watcher_id, prop, strerror(-ret)); free(data); } } /* * There is a quirk in sd-bus that in some systems, it is unable to get the * well-known names on the bus, so it cannot identify if an incoming signal, * which uses the sender's unique name, actually matches the callback's matching * sender if the callback uses a well-known name, in which case it just calls * the callback and hopes for the best, resulting in false positives. In the * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it * means that for NewStatus, if the SNI does not definitely match the sender, * then the safe thing to do is to query the status independently. * This function returns 1 if the SNI definitely matches the signal sender, * which is returned by the calling function to indicate that signal matching * can stop since it has already found the required callback, otherwise, it * returns 0, which allows matching to continue. */ static int sni_check_msg_sender(struct sni *sni, sd_bus_message *msg, const char *signal) { bool has_well_known_names = sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES; if (sni->service[0] == ':' || has_well_known_names) { return 1; } else { return 0; } } static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni *sni = data; LOG_DBG("[Signal Handle] NewIcon: %s", sni->id); sni_get_property_async(sni, "IconName", "s", &sni->icon_name); sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); if (strcmp(sni->interface, "org.kde.StatusNotifierItem") == 0) { sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path); } int result = sni_check_msg_sender(sni, msg, "icon"); return result; } static int handle_new_attention_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni *sni = data; LOG_DBG("[Signal Handle] NewAttentionIcon: %s", sni->id); sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); int result = sni_check_msg_sender(sni, msg, "attention icon"); return result; } static int handle_new_overlay_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni *sni = data; LOG_DBG("[Signal Handle] %s: NewOverlayIcon", sni->watcher_id); sni_get_property_async(sni, "OverlayIconName", "s", &sni->overlay_icon_name); sni_get_property_async(sni, "OverlayIconPixmap", NULL, &sni->overlay_icon_pixmap); int result = sni_check_msg_sender(sni, msg, "overlay icon"); return result; } static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni *sni = data; LOG_DBG("[Signal Handler] %s: NewStatus", sni->watcher_id); int ret = sni_check_msg_sender(sni, msg, "status"); if (ret == 1) { char *status; int r = sd_bus_message_read(msg, "s", &status); if (r < 0) { LOG_ERR("%s new status error: %s", sni->watcher_id, strerror(-ret)); ret = r; } else { free(sni->status); sni->status = strdup(status); LOG_DBG("[Signal Handler] %s: NewStatus = '%s'", sni->watcher_id, status); sni->m->mod->bar->refresh(sni->m->mod->bar); } } else { sni_get_property_async(sni, "Status", "s", &sni->status); } return ret; } static int handle_new_title(sd_bus_message *msg, void *data, sd_bus_error *error) { struct sni *sni = data; LOG_DBG("[Signal Handler] %s: NewTitle", sni->watcher_id); sni_get_property_async(sni, "Title", "s", &sni->status); return 0; } static void sni_match_signal_async(struct sni *sni, char *signal, sd_bus_message_handler_t callback) { struct sni_slot *slot = calloc(1, sizeof(struct sni)); int ret = sd_bus_match_signal_async(sni->m->bus, &slot->slot, sni->service, sni->path, sni->interface, signal, callback, NULL, sni); if (ret >= 0) { tll_push_front(sni->slots, slot); } else { LOG_ERR("%s failed to subscribe to signal %s: %s", sni->service, signal, strerror(-ret)); free(slot); } } struct sni * create_sni(struct private *tray, char *id) { struct sni *sni = calloc(1, sizeof(struct sni)); if (!sni) { return NULL; } sni->m = tray; sni_slots_t tmp = tll_init(); sni->slots = tmp; sni->watcher_id = strdup(id); char *path_ptr = strchr(id, '/'); if (!path_ptr) { sni->service = strdup(id); sni->path = strdup(item_path); sni->interface = "org.freedesktop.StatusNotifierItem"; } else { sni->service = strndup(id, path_ptr - id); sni->path = strdup(path_ptr); sni->interface = "org.kde.StatusNotifierItem"; sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path); } sni->category = NULL; sni->id = NULL; sni->title = NULL; sni->status = NULL; sni->icon_name = NULL; sni->icon_pixmap = NULL; sni->attention_icon_name = NULL; sni->attention_icon_pixmap = NULL; sni->overlay_icon_name = NULL; sni->overlay_icon_pixmap = NULL; // Ignored: WindowId, AttentionMovieName, ToolTip // sni_get_property_async(sni, "Category", "s", &sni->category); sni_get_property_async(sni, "Id", "s", &sni->id); sni_get_property_async(sni, "Title", "s", &sni->title); sni_get_property_async(sni, "Status", "s", &sni->status); sni_get_property_async(sni, "IconName", "s", &sni->icon_name); sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); sni_get_property_async(sni, "OverlayIconName", "s", &sni->overlay_icon_name); sni_get_property_async(sni, "OverlayIconPixmap", NULL, &sni->overlay_icon_pixmap); sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu); sni_get_property_async(sni, "Menu", "o", &sni->menu); sni_match_signal_async(sni, "NewTitle", handle_new_title); sni_match_signal_async(sni, "NewIcon", handle_new_icon); sni_match_signal_async(sni, "NewAttentionIcon", handle_new_attention_icon); sni_match_signal_async(sni, "NewOverlayIcon", handle_new_overlay_icon); sni_match_signal_async(sni, "NewStatus", handle_new_status); return sni; } void destroy_sni(struct sni *sni) { if (!sni) { return; } LOG_DBG("Destroying sni"); free(sni->watcher_id); free(sni->service); free(sni->path); free(sni->category); free(sni->id); free(sni->title); free(sni->status); free(sni->icon_name); if (sni->icon_pixmap) icon_pixmaps_dec(sni->icon_pixmap); free(sni->attention_icon_name); if (sni->attention_icon_pixmap) icon_pixmaps_dec(sni->attention_icon_pixmap); free(sni->overlay_icon_name); if (sni->overlay_icon_pixmap) icon_pixmaps_dec(sni->overlay_icon_pixmap); free(sni->menu); free(sni->icon_theme_path); tll_free_and_free(sni->slots, destroy_slot); free(sni); } // Watcher ------------- static bool using_standard_protocol(struct watcher *watcher) { return watcher->interface[strlen("org.")] == 'f'; // freedesktop } static int handle_lost_service(sd_bus_message *msg, void *data, sd_bus_error *error) { struct watcher *watcher = data; char *service, *old_owner, *new_owner; int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); if (ret < 0) { LOG_ERR("Failed to parse owner change message: %s", strerror(-ret)); return ret; } if (!*new_owner) { tll_foreach(watcher->items, it) { const char *item_name = it->item; int cmp_res = using_standard_protocol(watcher) ? strcmp(item_name, service) : strncmp(item_name, service, strlen(service)); if (cmp_res == 0) { LOG_DBG("[Signal Handler] %s: unregistering Status Notifier Item = '%s'", watcher->interface, item_name); sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, "StatusNotifierItemUnregistered", "s", item_name); tll_remove_and_free(watcher->items, it, free); sd_bus_emit_properties_changed(watcher->bus, watcher_path, watcher->interface, "RegisteredStatusNotifierItems", NULL); if (using_standard_protocol(watcher)) { break; } } } tll_foreach(watcher->hosts, it) { if (strcmp(it->item, service) == 0) { LOG_DBG("[Signal Handler] %s: unregistering Status Notifier Host = '%s'", watcher->interface, service); tll_remove_and_free(watcher->hosts, it, free); if (tll_length(watcher->hosts) == 0) { sd_bus_emit_properties_changed(watcher->bus, watcher_path, watcher->interface, "IsStatusNotifierHostRegistered", NULL); } break; } } } return 0; } static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) { struct watcher *watcher = data; char *service_or_path, *id; int ret = sd_bus_message_read(msg, "s", &service_or_path); if (ret < 0) { LOG_ERR("Failed to parse register SNI message: %s", strerror(-ret)); return ret; } 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 = item_path; } id = format_str("%s%s", service, path); } if (!id) { LOG_ERR("Failed to format id"); return ENOMEM; } bool found = false; tll_foreach(watcher->items, it) { if (strcmp(it->item, id) == 0) { found = true; break; } } if (found) { LOG_DBG("[Callback] %s: Status Notifier Item already registered = '%s'", watcher->interface, id); free(id); } else { LOG_DBG("[Callback] %s: registering Status Notifier Item = '%s'", watcher->interface, id); tll_push_back(watcher->items, id); sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, "StatusNotifierItemRegistered", "s", id); sd_bus_emit_properties_changed(watcher->bus, watcher_path, watcher->interface, "RegisteredStatusNotifierItems", NULL); } return sd_bus_reply_method_return(msg, ""); } static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) { struct watcher *watcher = data; char *service; int ret = sd_bus_message_read(msg, "s", &service); if (ret < 0) { LOG_ERR("Failed to parse register host message: %s", strerror(-ret)); return ret; } bool found = false; tll_foreach(watcher->hosts, it) { if (strcmp(it->item, service) == 0) { found = true; break; } } if (found) { LOG_DBG("[Callback] %s: Status Notifier Host already registered = '%s'", watcher->interface, service); } else { LOG_DBG("[Callback] %s: registering watcher to host = '%s'", watcher->interface, service); tll_push_back(watcher->hosts, strdup(service)); if (tll_length(watcher->hosts) == 1) { sd_bus_emit_properties_changed(watcher->bus, watcher_path, watcher->interface, "IsStatusNotifierHostRegistered", NULL); } sd_bus_emit_signal(watcher->bus, watcher_path, watcher->interface, "StatusNotifierHostRegistered", ""); } 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 watcher *watcher = data; LOG_DBG("[Callback] %s: get registered snis", watcher->interface); int r = sd_bus_message_open_container(reply, 'a', "s"); if (r < 0) return r; tll_foreach(watcher->items, it) { LOG_DBG("[Status Notifier Item] %s has %s", watcher->interface, it->item); r = sd_bus_message_append(reply, "s", it->item); if (r < 0) return r; } return sd_bus_message_close_container(reply); } 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 watcher *watcher = data; LOG_DBG("[Callback] %s: is host registered = '%d'", watcher->interface, tll_length(watcher->hosts) > 0); int val = tll_length(watcher->hosts) > 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 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}; void destroy_watcher(struct watcher *watcher) { LOG_DBG("Destroying watcher"); if (!watcher) { return; } sd_bus_unref(watcher->bus); tll_free_and_free(watcher->hosts, free); tll_free_and_free(watcher->items, free); free(watcher->interface); free(watcher); } struct watcher * create_watcher(char *protocol, sd_bus *bus) { int ret; struct watcher *watcher = calloc(1, sizeof(struct watcher)); if (!watcher) { LOG_ERR("Failed to allocate watcher"); return NULL; } watcher->interface = format_str("org.%s.StatusNotifierWatcher", protocol); if (!watcher->interface) { LOG_ERR("Failed to format interface"); free(watcher); return NULL; } ret = sd_bus_add_object_vtable(bus, NULL, watcher_path, watcher->interface, watcher_vtable, watcher); if (ret < 0) { LOG_ERR("Failed to add object vtable: %s", strerror(-ret)); goto error; } ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", handle_lost_service, watcher); if (ret < 0) { LOG_ERR("Failed to subscribe to unregistering events: %s", strerror(-ret)); goto error; } ret = sd_bus_request_name(bus, watcher->interface, 0); if (ret < 0) { if (-ret == EEXIST) { LOG_ERR("Failed to acquire service name '%s':" "another tray is already running", watcher->interface); } else { LOG_ERR("Failed to acquire service name '%s': %s", watcher->interface, strerror(-ret)); } goto error; } watcher->bus = sd_bus_ref(bus); hosts_t tmp1 = tll_init(); watcher->hosts = tmp1; items_t tmp2 = tll_init(); watcher->items = tmp2; watcher->version = 0; LOG_DBG("Registered %s", watcher->interface); return watcher; error: destroy_watcher(watcher); return NULL; } // Host ---------------- static void add_sni(struct private *m, char *id) { tll_foreach(m->items, it) { if (strcmp(it->item->watcher_id, id) == 0) { return; } } LOG_DBG("[Status Notifier Item] Registering: %s", id); struct sni *sni = create_sni(m, id); if (!sni) { LOG_ERR("[Status Notifier Item] Failed to create: %s", id); return; } tll_push_back(m->items, sni); return; } 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) { LOG_ERRNO("Failed to parse register SNI message: %s", strerror(-ret)); return ret; } LOG_DBG("[Signal Handle] tray: handling registered '%s'", id); struct private *m = data; add_sni(m, id); return 0; } static int handle_sni_unregistered(sd_bus_message *msg, void *data, sd_bus_error *error) { LOG_DBG("[Signal Handle]: StatusNotifierItemUnregistered"); char *id; int ret = sd_bus_message_read(msg, "s", &id); if (ret < 0) { LOG_ERR("Failed to parse unregister SNI message: %s", strerror(-ret)); return ret; } struct private *m = data; bool refresh = false; tll_foreach(m->items, it) { LOG_DBG("%s and %s", it->item->watcher_id, id); if (strcmp(it->item->watcher_id, id) == 0) { LOG_DBG("Unregistering Status Notifier Item '%s'", id); destroy_sni(it->item); tll_remove(m->items, it); refresh = true; } } if (refresh && m->mod->bar) { m->mod->bar->refresh(m->mod->bar); } return 0; } static int get_registered_snis_callback(sd_bus_message *msg, void *data, sd_bus_error *error) { struct private *m = data; LOG_DBG("[Callback] tray: registering status notifier items"); if (sd_bus_message_is_method_error(msg, NULL)) { const sd_bus_error *err = sd_bus_message_get_error(msg); LOG_ERR("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) { LOG_ERR("Failed to read registered SNIs: %s", strerror(-ret)); return ret; } char **ids; ret = sd_bus_message_read_strv(msg, &ids); if (ret < 0) { LOG_ERR("Failed to read registered SNIs: %s", strerror(-ret)); return ret; } if (ids) { for (char **id = ids; *id; ++id) { add_sni(m, *id); free(*id); } } free(ids); return ret; } static bool register_to_watcher(struct host *host) { // this is called asynchronously in case the watcher is owned by this process // int ret = sd_bus_call_method_async(host->m->bus, NULL, host->watcher_interface, watcher_path, host->watcher_interface, "RegisterStatusNotifierHost", NULL, NULL, "s", host->service); if (ret < 0) { LOG_ERR("Failed to send register call: %s", strerror(-ret)); return false; } ret = sd_bus_call_method_async(host->m->bus, NULL, host->watcher_interface, watcher_path, "org.freedesktop.DBus.Properties", "Get", get_registered_snis_callback, host->m, "ss", host->watcher_interface, "RegisteredStatusNotifierItems"); if (ret < 0) { LOG_ERR("Failed to get registered SNIs: %s", strerror(-ret)); return false; } LOG_DBG("Registering watcher (%s) to host (%s)", host->watcher_interface, host->service); return true; } static int handle_new_watcher(sd_bus_message *msg, void *data, sd_bus_error *error) { struct host *host = data; char *service, *old_owner, *new_owner; int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); if (ret < 0) { LOG_ERR("Failed to parse owner change message: %s", strerror(-ret)); return ret; } if (!*old_owner) { if (strcmp(service, host->watcher_interface) == 0) { LOG_DBG("[Signal Handler] %s: registering service %s", host->service, service); register_to_watcher(host); } } return 0; } void finish_host(struct host *host) { sd_bus_release_name(host->m->bus, host->service); free(host->service); free(host->watcher_interface); } bool init_host(struct host *host, char *protocol, struct private *m) { int ret; host->watcher_interface = format_str("org.%s.StatusNotifierWatcher", protocol); if (!host->watcher_interface) { LOG_ERR("Failed to format watcher interface"); return false; }; ret = sd_bus_match_signal(m->bus, NULL, host->watcher_interface, watcher_path, host->watcher_interface, "StatusNotifierItemRegistered", handle_sni_registered, m); if (ret < 0) { LOG_ERR("Failed to subscribe to registering events: %s", strerror(-ret)); goto error; } ret = sd_bus_match_signal(m->bus, NULL, host->watcher_interface, watcher_path, host->watcher_interface, "StatusNotifierItemUnregistered", handle_sni_unregistered, m); if (ret < 0) { LOG_ERR("Failed to subscribe to unregistering events: %s", strerror(-ret)); goto error; } ret = sd_bus_match_signal(m->bus, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", handle_new_watcher, host); if (ret < 0) { LOG_ERR("Failed to subscribe to new watcher events: %s", strerror(-ret)); goto error; } pid_t pid = getpid(); host->service = format_str("org.%s.StatusNotifierHost-%d", protocol, pid); if (!host->service) { LOG_ERR("Failed to format host service"); goto error; } ret = sd_bus_request_name(m->bus, host->service, 0); LOG_DBG("[Init] Initializing Status Notifier Host: %s", host->service); if (ret < 0) { LOG_ERR("Failed to acquire service name: %s", strerror(-ret)); goto error; } host->m = m; if (!register_to_watcher(host)) { goto error; } return true; error: finish_host(host); return false; } // Tray --------------- static int handle_lost_watcher(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) { LOG_ERR("Failed to parse owner change message: %s", strerror(-ret)); return ret; } if (!*new_owner) { struct private *m = data; if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) { destroy_watcher(m->watcher_xdg); m->watcher_xdg = create_watcher("freedesktop", m->bus); } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) { destroy_watcher(m->watcher_kde); m->watcher_kde = create_watcher("kde", m->bus); } } return 0; } bool start_tray(struct private *m) { LOG_DBG("Starting tray"); if (!m) { return false; } sd_bus *bus; int ret = sd_bus_open_user(&bus); if (ret < 0) { LOG_ERR("Failed to connect to user bus: %s", strerror(-ret)); return false; } m->bus = bus; m->fd = sd_bus_get_fd(m->bus); if (ret < 0) { LOG_ERR("Failed to get user bus fd: %s", strerror(-ret)); goto err1; } assert(m->watcher_xdg == NULL); m->watcher_xdg = create_watcher("freedesktop", m->bus); if (m->watcher_xdg == NULL) { LOG_ERR("Failed to start xdg watcher"); goto err1; } assert(m->watcher_kde == NULL); m->watcher_kde = create_watcher("kde", m->bus); if (m->watcher_kde == NULL) { LOG_ERR("Failed to start kde watcher"); goto err2; } ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", handle_lost_watcher, m); if (ret < 0) { LOG_ERR("Failed to subscribe to unregistering events: %s", strerror(-ret)); goto err3; } snis_t tmp = tll_init(); m->items = tmp; if (!init_host(&m->host_xdg, "kde", m)) { LOG_ERR("Failed to start freedesktop host"); goto err3; } if (!init_host(&m->host_kde, "freedesktop", m)) { LOG_ERR("Failed to start kde host"); goto err4; } return true; err4: finish_host(&m->host_xdg); err3: destroy_watcher(m->watcher_xdg); err2: destroy_watcher(m->watcher_kde); err1: sd_bus_close(m->bus); return false; } static void destroy_tray(struct private *m) { if (!m) return; // Destroy everything related to dbus since sd-bus isn't thread-safe. // Make sure not to touch anything that `content` could interactive with. finish_host(&m->host_xdg); finish_host(&m->host_kde); destroy_watcher(m->watcher_xdg); destroy_watcher(m->watcher_kde); sd_bus_flush_close_unref(m->bus); } void tray_in(sd_bus *bus, mtx_t *lock) { int ret; while (true) { // Lock module while proccessing a message // mtx_lock(lock); ret = sd_bus_process(bus, NULL); mtx_unlock(lock); if (ret <= 0) break; } if (ret < 0) { LOG_ERR("Failed to process bus: %s", strerror(-ret)); } } // Handling modules static void destroy(struct module *mod) { struct private *m = mod->private; if (!m) { goto exit; } tll_free_and_free(m->items, destroy_sni); m->template->destroy(m->template); free(m); exit: module_default_destroy(mod); } static const char * description(const struct module *mod) { return "tray"; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; // Lock the tray while we process content // mtx_lock(&mod->lock); // const struct bar *bar = mod->bar; // const char *output_bar_is_on = mod->bar->output_name(mod->bar); size_t num_items = tll_length(m->items); struct exposable *parts[num_items]; size_t idx = 0; tll_foreach(m->items, it) { struct sni *sni = it->item; LOG_DBG("%s status: %s", sni->watcher_id, sni->status); struct tag_set tags = {.tags = (struct tag *[]){ tag_new_string(mod, "watcher-id", sni->watcher_id), tag_new_string(mod, "title", sni->title), tag_new_string(mod, "status", sni->status), tag_new_string(mod, "category", sni->category), tag_new_string(mod, "icon-name", sni->icon_name), tag_new_string(mod, "attention-icon-name", sni->attention_icon_name), tag_new_string(mod, "overlay-icon-name", sni->overlay_icon_name), }, .count = 7, .icon_tags = (struct icon_tag *[]) { icon_tag_new_pixmap(mod, "icon-pixmap", sni->icon_pixmap), icon_tag_new_pixmap(mod, "attention-icon-pixmap", sni->attention_icon_pixmap), icon_tag_new_pixmap(mod, "overlay-icon-pixmap", sni->overlay_icon_pixmap), }, .icon_count = 3, }; parts[idx] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); idx++; } mtx_unlock(&mod->lock); LOG_DBG("Tray exposing content"); return dynlist_exposable_new(parts, num_items, 0, 0); } static int run(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); if (!start_tray(m)) { LOG_ERR("failed to start tray"); return 0; } mtx_unlock(&mod->lock); LOG_DBG("[Init] starting dbus loop"); while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = m->fd, .events = POLLIN | POLLHUP | POLLERR}}; if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); destroy_tray(m); break; } if (fds[0].revents & POLLIN) { destroy_tray(m); break; } if (fds[1].revents & (POLLIN | POLLHUP | POLLERR)) { tray_in(m->bus, &mod->lock); } } return 0; } static struct module * tray_new(struct particle *template) { struct private *m = calloc(1, sizeof(*m)); m->template = template; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; m->mod = mod; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); return tray_new(conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_tray_iface = {.verify_conf = &verify_conf, .from_conf = &from_conf}; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias "module_tray_iface")); #endif