aboutsummaryrefslogtreecommitdiff
path: root/systray/watcher.c
diff options
context:
space:
mode:
Diffstat (limited to 'systray/watcher.c')
-rw-r--r--systray/watcher.c549
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, &param,
+ 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);
+}