diff options
Diffstat (limited to 'systray/watcher.c')
-rw-r--r-- | systray/watcher.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/systray/watcher.c b/systray/watcher.c new file mode 100644 index 0000000..072ab86 --- /dev/null +++ b/systray/watcher.c @@ -0,0 +1,549 @@ +#include "watcher.h" + +#include "item.h" +#include "tray.h" + +#include <dbus/dbus.h> +#include <wayland-util.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +// 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 = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" + " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES + "\">\n" + " <method name=\"Get\">\n" + " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" + " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" + " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" + " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" + " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" + " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface_name\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE + "\">\n" + " <method name=\"Introspect\">\n" + " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PEER + "\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" SNW_IFACE + "\">\n" + " <!-- methods -->\n" + " <method name=\"RegisterStatusNotifierItem\">\n" + " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" + " </method>\n" + " <!-- properties -->\n" + " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" + " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" + " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" + " <!-- signals -->\n" + " <signal name=\"StatusNotifierHostRegistered\">\n" + " </signal>\n" + " </interface>\n" + "</node>\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); +} |