#include "watcher.h" #include "item.h" #include "tray.h" #include #include #include #include #include // IWYU pragma: no_include "dbus/dbus-protocol.h" // IWYU pragma: no_include "dbus/dbus-shared.h" static const char *const match_rule = "type='signal'," "interface='" DBUS_INTERFACE_DBUS "'," "member='NameOwnerChanged'"; static const char *const snw_xml = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"; static void unregister_item(Watcher *watcher, Item *item) { wl_list_remove(&item->link); destroyitem(item); watcher_update_trays(watcher); } static Item * item_name_to_ptr(const Watcher *watcher, const char *busname) { Item *item; wl_list_for_each(item, &watcher->items, link) { if (!item || !item->busname) return NULL; if (strcmp(item->busname, busname) == 0) return item; } return NULL; } static DBusHandlerResult handle_nameowner_changed(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) { char *name, *old_owner, *new_owner; Item *item; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { return DBUS_HANDLER_RESULT_HANDLED; } if (*new_owner != '\0' || *name == '\0') return DBUS_HANDLER_RESULT_HANDLED; item = item_name_to_ptr(watcher, name); if (!item) return DBUS_HANDLER_RESULT_HANDLED; unregister_item(watcher, item); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) { Watcher *watcher = data; if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) return handle_nameowner_changed(watcher, conn, msg); else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult respond_register_item(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) { DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED; DBusMessage *reply = NULL; Item *item; const char *sender, *param, *busobj, *registree_name; if (!(sender = dbus_message_get_sender(msg)) || !dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, ¶m, DBUS_TYPE_INVALID)) { reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Malformed message"); goto send; } switch (*param) { case '/': registree_name = sender; busobj = param; break; case ':': registree_name = param; busobj = SNI_OPATH; break; default: reply = dbus_message_new_error_printf(msg, DBUS_ERROR_INVALID_ARGS, "Bad argument: \"%s\"", param); goto send; } if (*registree_name != ':' || !dbus_validate_bus_name(registree_name, NULL)) { reply = dbus_message_new_error_printf(msg, DBUS_ERROR_INVALID_ARGS, "Invalid busname %s", registree_name); goto send; } if (item_name_to_ptr(watcher, registree_name)) { reply = dbus_message_new_error_printf(msg, DBUS_ERROR_INVALID_ARGS, "%s already tracked", registree_name); goto send; } item = createitem(registree_name, busobj, watcher); wl_list_insert(&watcher->items, &item->link); watcher_update_trays(watcher); reply = dbus_message_new_method_return(msg); send: if (!reply || !dbus_connection_send(conn, reply, NULL)) res = DBUS_HANDLER_RESULT_NEED_MEMORY; if (reply) dbus_message_unref(reply); return res; } static int get_registered_items(const Watcher *watcher, DBusMessageIter *iter) { DBusMessageIter names = DBUS_MESSAGE_ITER_INIT_CLOSED; Item *item; int r; if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &names)) { r = -ENOMEM; goto fail; } wl_list_for_each(item, &watcher->items, link) { if (!dbus_message_iter_append_basic(&names, DBUS_TYPE_STRING, &item->busname)) { r = -ENOMEM; goto fail; } } dbus_message_iter_close_container(iter, &names); return 0; fail: dbus_message_iter_abandon_container_if_open(iter, &names); return r; } static int get_registered_items_variant(const Watcher *watcher, DBusMessageIter *iter) { DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; int r; if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", &variant) || get_registered_items(watcher, &variant) < 0) { r = -ENOMEM; goto fail; } dbus_message_iter_close_container(iter, &variant); return 0; fail: dbus_message_iter_abandon_container_if_open(iter, &variant); return r; } static int get_isregistered(DBusMessageIter *iter) { DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; dbus_bool_t is_registered = TRUE; int r; if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant) || !dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &is_registered)) { r = -ENOMEM; goto fail; } dbus_message_iter_close_container(iter, &variant); return 0; fail: dbus_message_iter_abandon_container_if_open(iter, &variant); return r; } static int get_version(DBusMessageIter *iter) { DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; dbus_int32_t protovers = 0; int r; if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_INT32_AS_STRING, &variant) || !dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32, &protovers)) { r = -ENOMEM; goto fail; } dbus_message_iter_close_container(iter, &variant); return 0; fail: dbus_message_iter_abandon_container_if_open(iter, &variant); return r; } static DBusHandlerResult respond_get_prop(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) { DBusError err = DBUS_ERROR_INIT; DBusMessage *reply = NULL; DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; const char *iface, *prop; if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &prop, DBUS_TYPE_INVALID)) { reply = dbus_message_new_error(msg, err.name, err.message); dbus_error_free(&err); goto send; } if (strcmp(iface, SNW_IFACE) != 0) { reply = dbus_message_new_error_printf( msg, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface \"%s\"", iface); goto send; } reply = dbus_message_new_method_return(msg); if (!reply) goto fail; if (strcmp(prop, "ProtocolVersion") == 0) { dbus_message_iter_init_append(reply, &iter); if (get_version(&iter) < 0) goto fail; } else if (strcmp(prop, "IsStatusNotifierHostRegistered") == 0) { dbus_message_iter_init_append(reply, &iter); if (get_isregistered(&iter) < 0) goto fail; } else if (strcmp(prop, "RegisteredStatusNotifierItems") == 0) { dbus_message_iter_init_append(reply, &iter); if (get_registered_items_variant(watcher, &iter) < 0) goto fail; } else { dbus_message_unref(reply); reply = dbus_message_new_error_printf( reply, DBUS_ERROR_UNKNOWN_PROPERTY, "Property \"%s\" does not exist", prop); } send: if (!reply || !dbus_connection_send(conn, reply, NULL)) goto fail; if (reply) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; fail: if (reply) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_NEED_MEMORY; } static DBusHandlerResult respond_all_props(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) { DBusMessage *reply = NULL; DBusMessageIter array = DBUS_MESSAGE_ITER_INIT_CLOSED; DBusMessageIter dict = DBUS_MESSAGE_ITER_INIT_CLOSED; DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; const char *prop; reply = dbus_message_new_method_return(msg); if (!reply) goto fail; dbus_message_iter_init_append(reply, &iter); if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array)) goto fail; prop = "ProtocolVersion"; if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict) || !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || get_version(&dict) < 0 || !dbus_message_iter_close_container(&array, &dict)) { goto fail; } prop = "IsStatusNotifierHostRegistered"; if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict) || !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || get_isregistered(&dict) < 0 || !dbus_message_iter_close_container(&array, &dict)) { goto fail; } prop = "RegisteredStatusNotifierItems"; if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict) || !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || get_registered_items_variant(watcher, &dict) < 0 || !dbus_message_iter_close_container(&array, &dict)) { goto fail; } if (!dbus_message_iter_close_container(&iter, &array) || !dbus_connection_send(conn, reply, NULL)) { goto fail; } dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; fail: dbus_message_iter_abandon_container_if_open(&array, &dict); dbus_message_iter_abandon_container_if_open(&iter, &array); if (reply) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_NEED_MEMORY; } static DBusHandlerResult respond_introspect(DBusConnection *conn, DBusMessage *msg) { DBusMessage *reply = NULL; reply = dbus_message_new_method_return(msg); if (!reply) goto fail; if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &snw_xml, DBUS_TYPE_INVALID) || !dbus_connection_send(conn, reply, NULL)) { goto fail; } dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; fail: if (reply) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_NEED_MEMORY; } static DBusHandlerResult snw_message_handler(DBusConnection *conn, DBusMessage *msg, void *data) { Watcher *watcher = data; if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) return respond_introspect(conn, msg); else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "GetAll")) return respond_all_props(watcher, conn, msg); else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Get")) return respond_get_prop(watcher, conn, msg); else if (dbus_message_is_method_call(msg, SNW_IFACE, "RegisterStatusNotifierItem")) return respond_register_item(watcher, conn, msg); else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static const DBusObjectPathVTable snw_vtable = { .message_function = snw_message_handler }; int watcher_start(Watcher *watcher, DBusConnection *conn, struct wl_event_loop *loop) { DBusError err = DBUS_ERROR_INIT; int r; wl_list_init(&watcher->items); wl_list_init(&watcher->trays); watcher->conn = conn; watcher->loop = loop; r = dbus_bus_request_name(conn, SNW_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, NULL); if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) goto fail; if (!dbus_connection_add_filter(conn, filter_bus, watcher, NULL)) { dbus_bus_release_name(conn, SNW_NAME, NULL); goto fail; } dbus_bus_add_match(conn, match_rule, &err); if (dbus_error_is_set(&err)) { dbus_connection_remove_filter(conn, filter_bus, watcher); dbus_bus_release_name(conn, SNW_NAME, NULL); goto fail; } if (!dbus_connection_register_object_path(conn, SNW_OPATH, &snw_vtable, watcher)) { dbus_bus_remove_match(conn, match_rule, NULL); dbus_connection_remove_filter(conn, filter_bus, watcher); dbus_bus_release_name(conn, SNW_NAME, NULL); goto fail; } dbus_error_free(&err); return 0; fail: fprintf(stderr, "Couldn't start watcher, systray not available\n"); dbus_error_free(&err); return -1; } void watcher_stop(Watcher *watcher) { dbus_connection_unregister_object_path(watcher->conn, SNW_OPATH); dbus_bus_remove_match(watcher->conn, match_rule, NULL); dbus_connection_remove_filter(watcher->conn, filter_bus, watcher); dbus_bus_release_name(watcher->conn, SNW_NAME, NULL); } int watcher_get_n_items(const Watcher *watcher) { return wl_list_length(&watcher->items); } void watcher_update_trays(Watcher *watcher) { Tray *tray; wl_list_for_each(tray, &watcher->trays, link) tray_update(tray); }