Allow registering StatusNotifierItems by obj path

This commit impliments a KDE hidden feature where a SNI could be
registered by object path instead of well-known name. This should allow
libappindicator programs to work correctly under sway.

See #1372
This commit is contained in:
Calvin Lee 2017-10-24 11:59:42 -06:00
parent d85ad05fa8
commit ad99d9dff8
4 changed files with 317 additions and 32 deletions

View file

@ -11,6 +11,7 @@
static list_t *items = NULL;
static list_t *hosts = NULL;
static list_t *object_path_items = NULL;
/**
* Describes the function of the StatusNotifierWatcher
@ -18,6 +19,10 @@ static list_t *hosts = NULL;
*
* We also implement KDE's special snowflake protocol, it's like this but with
* all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
*
* We _also_ support registering items by object path (even though this is a
* huge pain in the ass). Hosts that would like to subscribe to these items have
* to go through the `org.swaywm.LessSuckyStatusNotifierWatcher` interface.
*/
static const char *interface_xml =
"<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
@ -64,8 +69,59 @@ static const char *interface_xml =
" <arg type='' name='service' direction='out'/>"
" </signal>"
" </interface>"
" <interface name='org.swaywm.LessSuckyStatusNotifierWatcher'>"
" <property name='RegisteredObjectPathItems' type='a(os)' access='read'/>"
" <signal name='ObjPathItemRegistered'>"
" <arg type='os' name='service' direction='out'/>"
" </signal>"
" </interface>"
"</node>";
struct ObjPathItem {
char *obj_path;
char *unique_name;
};
static void free_obj_path_item(struct ObjPathItem *item) {
if (!item) {
return;
}
free(item->unique_name);
free(item->obj_path);
free(item);
}
static struct ObjPathItem *create_obj_path_item(const char *unique_name, const char *obj_path) {
struct ObjPathItem *item = malloc(sizeof(struct ObjPathItem));
if (!item) {
return NULL;
}
item->unique_name = strdup(unique_name);
item->obj_path = strdup(obj_path);
if (!item->unique_name || !item->obj_path) {
free_obj_path_item(item);
return NULL;
}
return item;
}
/**
* NOTE: This compare function does have ordering, this is because it has to
* comapre two strings.
*/
static int obj_path_item_cmp(const void *_item1, const void *_item2) {
const struct ObjPathItem *item1 = _item1;
const struct ObjPathItem *item2 = _item2;
if (strcmp(item1->unique_name,item2->unique_name) == 0 &&
strcmp(item1->obj_path,item2->obj_path) == 0) {
return 0;
}
return -1;
}
static int obj_path_unique_name_cmp(const void *_item, const void *_unique_name) {
const struct ObjPathItem *item = _item;
const char *unique_name = _unique_name;
return strcmp(item->unique_name, unique_name);
}
static void host_registered_signal(DBusConnection *connection) {
// Send one signal for each protocol
DBusMessage *signal = dbus_message_new_signal(
@ -128,6 +184,19 @@ static void item_unregistered_signal(DBusConnection *connection, const char *nam
dbus_message_unref(signal);
}
static void obj_path_item_registered_signal(DBusConnection *connection, const struct ObjPathItem *item) {
DBusMessage *signal = dbus_message_new_signal(
"/StatusNotifierWatcher",
"org.swaywm.LessSuckyStatusNotifierWatcher",
"ObjPathItemRegistered");
dbus_message_append_args(signal,
DBUS_TYPE_OBJECT_PATH, &item->obj_path,
DBUS_TYPE_STRING, &item->unique_name,
DBUS_TYPE_INVALID);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
DBusMessage *reply;
@ -147,28 +216,44 @@ static void register_item(DBusConnection *connection, DBusMessage *message) {
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
sway_log(L_ERROR, "Error parsing method args: %s", error.message);
}
sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"", name);
// Don't add duplicate or not real item
if (!dbus_validate_bus_name(name, NULL)) {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
return;
if (dbus_validate_path(name, NULL)) {
// Item is registered by object path
struct ObjPathItem *item =
create_obj_path_item(dbus_message_get_sender(message), name);
// Add ObjPathItem
if (list_seq_find(object_path_items, obj_path_item_cmp, item) != -1) {
free_obj_path_item(item);
return;
}
list_add(object_path_items, item);
obj_path_item_registered_signal(connection, item);
return;
} else {
sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
return;
}
} else {
if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
return;
}
if (!dbus_bus_name_has_owner(connection, name, &error)) {
return;
}
list_add(items, strdup(name));
item_registered_signal(connection, name);
}
if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
return;
}
if (!dbus_bus_name_has_owner(connection, name, &error)) {
return;
}
list_add(items, strdup(name));
item_registered_signal(connection, name);
// It's silly, but xembedsniproxy wants a reply for this function
// It's silly, but clients want a reply for this function
DBusMessage *reply = dbus_message_new_method_return(message);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
@ -182,10 +267,10 @@ static void register_host(DBusConnection *connection, DBusMessage *message) {
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
sway_log(L_ERROR, "Error parsing method args: %s", error.message);
}
sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"", name);
// Don't add duplicate or not real host
if (!dbus_validate_bus_name(name, NULL)) {
@ -215,12 +300,12 @@ static void get_property(DBusConnection *connection, DBusMessage *message) {
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) {
sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
sway_log(L_ERROR, "Error parsing prop args: %s", error.message);
return;
}
if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
sway_log(L_INFO, "Replying with items\n");
sway_log(L_INFO, "Replying with items");
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
DBusMessageIter iter;
@ -279,6 +364,41 @@ static void get_property(DBusConnection *connection, DBusMessage *message) {
DBUS_TYPE_INT32, &version);
dbus_message_iter_close_container(&iter, &sub);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
} else if (strcmp(property, "RegisteredObjectPathItems") == 0) {
sway_log(L_INFO, "Replying with ObjPathItems");
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
DBusMessageIter iter;
DBusMessageIter variant;
DBusMessageIter array;
DBusMessageIter dstruct;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
"a(os)", &variant);
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
"(os)", &array);
for (int i = 0; i < object_path_items->length; ++i) {
struct ObjPathItem *item = object_path_items->items[i];
dbus_message_iter_open_container(&array,
DBUS_TYPE_STRUCT, NULL, &dstruct);
dbus_message_iter_append_basic(&dstruct,
DBUS_TYPE_OBJECT_PATH, item->obj_path);
dbus_message_iter_append_basic(&dstruct,
DBUS_TYPE_STRING, item->unique_name);
dbus_message_iter_close_container(&array, &dstruct);
}
dbus_message_iter_close_container(&variant, &array);
dbus_message_iter_close_container(&iter, &variant);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
@ -289,6 +409,8 @@ static void set_property(DBusConnection *connection, DBusMessage *message) {
return;
}
// TODO clean me up please or get rid of me
// also add LessSuckyStatusNotifierWatcher props
static void get_all(DBusConnection *connection, DBusMessage *message) {
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
@ -400,6 +522,8 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
const char *old_owner;
const char *new_owner;
int index;
bool found_obj_path_item = false;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old_owner,
@ -427,6 +551,17 @@ static DBusHandlerResult signal_handler(DBusConnection *connection,
return DBUS_HANDLER_RESULT_HANDLED;
}
while ((index = list_seq_find(object_path_items, obj_path_unique_name_cmp, name)) != -1) {
found_obj_path_item = true;
struct ObjPathItem *item = object_path_items->items[index];
sway_log(L_INFO, "ObjPathItem lost %s", item->obj_path);
list_del(object_path_items, index);
free_obj_path_item(item);
}
if (found_obj_path_item) {
item_unregistered_signal(connection, name);
return DBUS_HANDLER_RESULT_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
@ -446,6 +581,7 @@ int init_sni_watcher() {
items = create_list();
hosts = create_list();
object_path_items = create_list();
int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
DBUS_NAME_FLAG_REPLACE_EXISTING,
@ -456,7 +592,7 @@ int init_sni_watcher() {
sway_log(L_INFO, "Could not get watcher name, it may start later");
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
sway_log(L_ERROR, "dbus err getting watcher name: %s", error.message);
return -1;
}
@ -469,7 +605,7 @@ int init_sni_watcher() {
sway_log(L_INFO, "Could not get kde watcher name, it may start later");
}
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
sway_log(L_ERROR, "dbus err getting kde watcher name: %s", error.message);
return -1;
}
@ -477,7 +613,7 @@ int init_sni_watcher() {
"/StatusNotifierWatcher",
&vtable, NULL, &error);
if (dbus_error_is_set(&error)) {
sway_log(L_ERROR, "dbus_err: %s\n", error.message);
sway_log(L_ERROR, "dbus_err: %s", error.message);
return -1;
}