diff --git a/.circleci/config.yml b/.circleci/config.yml index d21b045..9609b9e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,12 +56,6 @@ workflows: requires: - misc-doxygen - Alpine - - compileandtest: - name: Ubuntu 14.04 - distro: ubuntu-trusty - requires: - - misc-doxygen - - Alpine - compileandtest: name: Ubuntu 16.04 distro: ubuntu-xenial diff --git a/.travis.yml b/.travis.yml index de3d0ed..6a2d89d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ addons: - libnotify-dev - libgtk-3-dev - valgrind -dist: trusty +dist: xenial sudo: false language: c @@ -35,5 +35,3 @@ matrix: after_success: - coveralls - compiler: clang - after_success: - - coveralls --gcov llvm-cov --gcov-options gcov diff --git a/config.mk b/config.mk index 05380d9..c242dd9 100644 --- a/config.mk +++ b/config.mk @@ -23,7 +23,7 @@ LDFLAGS_DEBUG := pkg_config_packs := gio-2.0 \ gdk-pixbuf-2.0 \ - "glib-2.0 >= 2.36" \ + "glib-2.0 >= 2.44" \ pangocairo \ x11 \ xinerama \ diff --git a/src/dbus.c b/src/dbus.c index bc07096..3994c4f 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -72,24 +72,42 @@ static const char *stack_tag_hints[] = { "x-dunst-stack-tag" }; -static void on_get_capabilities(GDBusConnection *connection, - const gchar *sender, - const GVariant *parameters, - GDBusMethodInvocation *invocation); -static void on_notify(GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation); -static void on_close_notification(GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation); -static void on_get_server_information(GDBusConnection *connection, - const gchar *sender, - const GVariant *parameters, - GDBusMethodInvocation *invocation); +struct dbus_method { + const char *method_name; + void (*method) (GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation); +}; + +#define DBUS_METHOD(name) static void dbus_cb_##name( \ + GDBusConnection *connection, \ + const gchar *sender, \ + GVariant *parameters, \ + GDBusMethodInvocation *invocation) + static struct raw_image *get_raw_image_from_data_hint(GVariant *icon_data); +int cmp_methods(const void *vkey, const void *velem) +{ + const char *key = (const char*)vkey; + const struct dbus_method *m = (const struct dbus_method*)velem; + + return strcmp(key, m->method_name); +} + +DBUS_METHOD(Notify); +DBUS_METHOD(CloseNotification); +DBUS_METHOD(GetCapabilities); +DBUS_METHOD(GetServerInformation); + +static struct dbus_method methods_fdn[] = { + {"CloseNotification", dbus_cb_CloseNotification}, + {"GetCapabilities", dbus_cb_GetCapabilities}, + {"GetServerInformation", dbus_cb_GetServerInformation}, + {"Notify", dbus_cb_Notify}, +}; + void handle_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, @@ -99,14 +117,15 @@ void handle_method_call(GDBusConnection *connection, GDBusMethodInvocation *invocation, gpointer user_data) { - if (STR_EQ(method_name, "GetCapabilities")) { - on_get_capabilities(connection, sender, parameters, invocation); - } else if (STR_EQ(method_name, "Notify")) { - on_notify(connection, sender, parameters, invocation); - } else if (STR_EQ(method_name, "CloseNotification")) { - on_close_notification(connection, sender, parameters, invocation); - } else if (STR_EQ(method_name, "GetServerInformation")) { - on_get_server_information(connection, sender, parameters, invocation); + struct dbus_method *m = bsearch( + method_name, + &methods_fdn, + G_N_ELEMENTS(methods_fdn), + sizeof(struct dbus_method), + cmp_methods); + + if (m) { + m->method(connection, sender, parameters, invocation); } else { LOG_M("Unknown method name: '%s' (sender: '%s').", method_name, @@ -114,10 +133,11 @@ void handle_method_call(GDBusConnection *connection, } } -static void on_get_capabilities(GDBusConnection *connection, - const gchar *sender, - const GVariant *parameters, - GDBusMethodInvocation *invocation) +static void dbus_cb_GetCapabilities( + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { GVariantBuilder *builder; GVariant *value; @@ -142,160 +162,151 @@ static void on_get_capabilities(GDBusConnection *connection, static struct notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters) { + /* Assert that the parameters' type is actually correct. Albeit usually DBus + * already rejects ill typed parameters, it may not be always the case. */ + GVariantType *required_type = g_variant_type_new("(susssasa{sv}i)"); + if (!g_variant_is_of_type(parameters, required_type)) { + g_variant_type_free(required_type); + return NULL; + } struct notification *n = notification_create(); - - n->actions = g_malloc0(sizeof(struct actions)); n->dbus_client = g_strdup(sender); n->dbus_valid = true; - { - GVariantIter *iter = g_variant_iter_new(parameters); - GVariant *content; - GVariant *dict_value; - int idx = 0; - while ((content = g_variant_iter_next_value(iter))) { + GVariant *hints; + gchar **actions; + int timeout; - switch (idx) { - case 0: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING)) - n->appname = g_variant_dup_string(content, NULL); - break; - case 1: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_UINT32)) - n->id = g_variant_get_uint32(content); - break; - case 2: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING)) - n->icon = g_variant_dup_string(content, NULL); - break; - case 3: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING)) - n->summary = g_variant_dup_string(content, NULL); - break; - case 4: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING)) - n->body = g_variant_dup_string(content, NULL); - break; - case 5: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_STRING_ARRAY)) - n->actions->actions = g_variant_dup_strv(content, &(n->actions->count)); - break; - case 6: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_DICTIONARY)) { + GVariantIter i; + g_variant_iter_init(&i, parameters); - dict_value = g_variant_lookup_value(content, "urgency", G_VARIANT_TYPE_BYTE); - if (dict_value) { - n->urgency = g_variant_get_byte(dict_value); - g_variant_unref(dict_value); - } + g_variant_iter_next(&i, "s", &n->appname); + g_variant_iter_next(&i, "u", &n->id); + g_variant_iter_next(&i, "s", &n->icon); + g_variant_iter_next(&i, "s", &n->summary); + g_variant_iter_next(&i, "s", &n->body); + g_variant_iter_next(&i, "^a&s", &actions); + g_variant_iter_next(&i, "@a{?*}", &hints); + g_variant_iter_next(&i, "i", &timeout); - dict_value = g_variant_lookup_value(content, "fgcolor", G_VARIANT_TYPE_STRING); - if (dict_value) { - n->colors.fg = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - } - - dict_value = g_variant_lookup_value(content, "bgcolor", G_VARIANT_TYPE_STRING); - if (dict_value) { - n->colors.bg = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - } - - dict_value = g_variant_lookup_value(content, "frcolor", G_VARIANT_TYPE_STRING); - if (dict_value) { - n->colors.frame = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - } - - dict_value = g_variant_lookup_value(content, "category", G_VARIANT_TYPE_STRING); - if (dict_value) { - n->category = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - } - - dict_value = g_variant_lookup_value(content, "image-path", G_VARIANT_TYPE_STRING); - if (dict_value) { - g_free(n->icon); - n->icon = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - } - - dict_value = g_variant_lookup_value(content, "image-data", G_VARIANT_TYPE("(iiibiiay)")); - if (!dict_value) - dict_value = g_variant_lookup_value(content, "image_data", G_VARIANT_TYPE("(iiibiiay)")); - if (!dict_value) - dict_value = g_variant_lookup_value(content, "icon_data", G_VARIANT_TYPE("(iiibiiay)")); - if (dict_value) { - n->raw_icon = get_raw_image_from_data_hint(dict_value); - g_variant_unref(dict_value); - } - - /* Check for transient hints - * - * According to the spec, the transient hint should be boolean. - * But notify-send does not support hints of type 'boolean'. - * So let's check for int and boolean until notify-send is fixed. - */ - if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_BOOLEAN))) { - n->transient = g_variant_get_boolean(dict_value); - g_variant_unref(dict_value); - } else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_UINT32))) { - n->transient = g_variant_get_uint32(dict_value) > 0; - g_variant_unref(dict_value); - } else if((dict_value = g_variant_lookup_value(content, "transient", G_VARIANT_TYPE_INT32))) { - n->transient = g_variant_get_int32(dict_value) > 0; - g_variant_unref(dict_value); - } - - if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_INT32))) { - n->progress = g_variant_get_int32(dict_value); - g_variant_unref(dict_value); - } else if((dict_value = g_variant_lookup_value(content, "value", G_VARIANT_TYPE_UINT32))) { - n->progress = g_variant_get_uint32(dict_value); - g_variant_unref(dict_value); - } - - /* Check for hints that define the stack_tag - * - * Only accept to first one we find. - */ - for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { - dict_value = g_variant_lookup_value(content, stack_tag_hints[i], G_VARIANT_TYPE_STRING); - if (dict_value) { - n->stack_tag = g_variant_dup_string(dict_value, NULL); - g_variant_unref(dict_value); - break; - } - } - - } - break; - case 7: - if (g_variant_is_of_type(content, G_VARIANT_TYPE_INT32)) - n->timeout = g_variant_get_int32(content) * 1000; - break; - } - g_variant_unref(content); - idx++; + gsize num = 0; + while (actions[num]) { + if (actions[num+1]) { + g_hash_table_insert(n->actions, + g_strdup(actions[num]), + g_strdup(actions[num+1])); + num+=2; + } else { + LOG_W("Odd length in actions array. Ignoring element: %s", actions[num]); + break; } - - g_variant_iter_free(iter); } - if (n->actions->count < 1) - g_clear_pointer(&n->actions, actions_free); + GVariant *dict_value; + if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) { + n->urgency = g_variant_get_byte(dict_value); + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) { + n->colors.fg = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) { + n->colors.bg = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) { + n->colors.frame = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) { + n->category = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "image-path", G_VARIANT_TYPE_STRING))) { + g_free(n->icon); + n->icon = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + } + + dict_value = g_variant_lookup_value(hints, "image-data", G_VARIANT_TYPE("(iiibiiay)")); + if (!dict_value) + dict_value = g_variant_lookup_value(hints, "image_data", G_VARIANT_TYPE("(iiibiiay)")); + if (!dict_value) + dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)")); + if (dict_value) { + n->raw_icon = get_raw_image_from_data_hint(dict_value); + g_variant_unref(dict_value); + } + + /* Check for transient hints + * + * According to the spec, the transient hint should be boolean. + * But notify-send does not support hints of type 'boolean'. + * So let's check for int and boolean until notify-send is fixed. + */ + if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) { + n->transient = g_variant_get_boolean(dict_value); + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) { + n->transient = g_variant_get_uint32(dict_value) > 0; + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) { + n->transient = g_variant_get_int32(dict_value) > 0; + g_variant_unref(dict_value); + } + + if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) { + n->progress = g_variant_get_int32(dict_value); + g_variant_unref(dict_value); + } else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) { + n->progress = g_variant_get_uint32(dict_value); + g_variant_unref(dict_value); + } + + /* Check for hints that define the stack_tag + * + * Only accept to first one we find. + */ + for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) { + if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) { + n->stack_tag = g_variant_dup_string(dict_value, NULL); + g_variant_unref(dict_value); + break; + } + } + + if (timeout >= 0) + n->timeout = timeout * 1000; + + g_variant_unref(hints); + g_variant_type_free(required_type); + g_free(actions); // the strv is only a shallow copy notification_init(n); return n; } -static void on_notify(GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation) +static void dbus_cb_Notify( + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { struct notification *n = dbus_message_to_notification(sender, parameters); + if (!n) { + g_dbus_method_invocation_return_dbus_error( + invocation, + "Cannot decode notification!", + ""); + } + int id = queues_notification_insert(n); GVariant *reply = g_variant_new("(u)", id); @@ -311,10 +322,11 @@ static void on_notify(GDBusConnection *connection, wake_up(); } -static void on_close_notification(GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation) +static void dbus_cb_CloseNotification( + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { guint32 id; g_variant_get(parameters, "(u)", &id); @@ -324,10 +336,11 @@ static void on_close_notification(GDBusConnection *connection, g_dbus_connection_flush(connection, NULL, NULL, NULL); } -static void on_get_server_information(GDBusConnection *connection, - const gchar *sender, - const GVariant *parameters, - GDBusMethodInvocation *invocation) +static void dbus_cb_GetServerInformation( + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { GVariant *value; @@ -404,9 +417,9 @@ static const GDBusInterfaceVTable interface_vtable = { handle_method_call }; -static void on_bus_acquired(GDBusConnection *connection, - const gchar *name, - gpointer user_data) +static void dbus_cb_bus_acquired(GDBusConnection *connection, + const gchar *name, + gpointer user_data) { guint registration_id; @@ -425,9 +438,9 @@ static void on_bus_acquired(GDBusConnection *connection, } } -static void on_name_acquired(GDBusConnection *connection, - const gchar *name, - gpointer user_data) +static void dbus_cb_name_acquired(GDBusConnection *connection, + const gchar *name, + gpointer user_data) { dbus_conn = connection; } @@ -438,7 +451,7 @@ static void on_name_acquired(GDBusConnection *connection, * If name or vendor specified, the name and vendor * will get additionally get via the FDN GetServerInformation method * - * @param connection The dbus connection + * @param connection The DBus connection * @param pid The place to report the PID to * @param name The place to report the name to, if not required set to NULL * @param vendor The place to report the vendor to, if not required set to NULL @@ -446,9 +459,9 @@ static void on_name_acquired(GDBusConnection *connection, * @returns `true` on success, otherwise `false` */ static bool dbus_get_fdn_daemon_info(GDBusConnection *connection, - int *pid, - char **name, - char **vendor) + guint *pid, + char **name, + char **vendor) { g_return_val_if_fail(pid, false); g_return_val_if_fail(connection, false); @@ -475,7 +488,7 @@ static bool dbus_get_fdn_daemon_info(GDBusConnection *connection, return false; } - GVariant *daemoninfo; + GVariant *daemoninfo = NULL; if (name || vendor) { daemoninfo = g_dbus_proxy_call_sync( proxy_fdn, @@ -529,27 +542,29 @@ static bool dbus_get_fdn_daemon_info(GDBusConnection *connection, return false; } - g_variant_get(pidinfo, "(u)", &pid); - g_object_unref(proxy_fdn); g_object_unref(proxy_dbus); g_free(owner); if (daemoninfo) g_variant_unref(daemoninfo); - if (pidinfo) - g_variant_unref(pidinfo); - return true; + if (pidinfo) { + g_variant_get(pidinfo, "(u)", pid); + g_variant_unref(pidinfo); + return true; + } else { + return false; + } } -static void on_name_lost(GDBusConnection *connection, - const gchar *name, - gpointer user_data) +static void dbus_cb_name_lost(GDBusConnection *connection, + const gchar *name, + gpointer user_data) { if (connection) { char *name; - int pid; + unsigned int pid; if (dbus_get_fdn_daemon_info(connection, &pid, &name, NULL)) { DIE("Cannot acquire '"FDN_NAME"': " "Name is acquired by '%s' with PID '%d'.", name, pid); @@ -608,9 +623,9 @@ int dbus_init(void) owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, FDN_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, - on_bus_acquired, - on_name_acquired, - on_name_lost, + dbus_cb_bus_acquired, + dbus_cb_name_acquired, + dbus_cb_name_lost, NULL, NULL); diff --git a/src/menu.c b/src/menu.c index 6c97919..1c52a5a 100644 --- a/src/menu.c +++ b/src/menu.c @@ -141,6 +141,27 @@ void open_browser(const char *in) g_free(url); } +char *notification_dmenu_string(struct notification *n) +{ + char *dmenu_str = NULL; + + gpointer p_key; + gpointer p_value; + GHashTableIter iter; + g_hash_table_iter_init(&iter, n->actions); + while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { + + char *key = (char*) p_key; + char *value = (char*) p_value; + + char *act_str = g_strdup_printf("#%s (%s) [%d,%s]", value, n->summary, n->id, key); + dmenu_str = string_append(dmenu_str, act_str, "\n"); + + g_free(act_str); + } + return dmenu_str; +} + /* * Notify the corresponding client * that an action has been invoked @@ -148,39 +169,49 @@ void open_browser(const char *in) void invoke_action(const char *action) { struct notification *invoked = NULL; - char *action_identifier = NULL; + uint id; - char *appname_begin = strchr(action, '['); - if (!appname_begin) { + char *data_start, *data_comma, *data_end; + + /* format: # ()[,] */ + data_start = strrchr(action, '['); + if (!data_start) { LOG_W("Invalid action: '%s'", action); return; } - appname_begin++; - int appname_len = strlen(appname_begin) - 1; // remove ] - int action_len = strlen(action) - appname_len - 3; // remove space, [, ] - for (const GList *iter = queues_get_displayed(); iter; - iter = iter->next) { + id = strtol(++data_start, &data_comma, 10); + if (*data_comma != ',') { + LOG_W("Invalid action: '%s'", action); + return; + } + + data_end = strchr(data_comma+1, ']'); + if (!data_end) { + LOG_W("Invalid action: '%s'", action); + return; + } + + char *action_key = g_strndup(data_comma+1, data_end-data_comma-1); + + for (const GList *iter = queues_get_displayed(); + iter; + iter = iter->next) { struct notification *n = iter->data; - if (g_str_has_prefix(appname_begin, n->appname) && strlen(n->appname) == appname_len) { - if (!n->actions) - continue; + if (n->id != id) + continue; - for (int i = 0; i < n->actions->count; i += 2) { - char *a_identifier = n->actions->actions[i]; - char *name = n->actions->actions[i + 1]; - if (g_str_has_prefix(action, name) && strlen(name) == action_len) { - invoked = n; - action_identifier = a_identifier; - break; - } - } + if (g_hash_table_contains(n->actions, action_key)) { + invoked = n; + break; } } - if (invoked && action_identifier) { - signal_action_invoked(invoked, action_identifier); + if (invoked && action_key) { + signal_action_invoked(invoked, action_key); } + + g_free(action_key); } /** @@ -289,7 +320,7 @@ static gpointer context_menu_thread(gpointer data) // Reference and lock the notification if we need it - if (n->urls || n->actions) { + if (n->urls || g_hash_table_size(n->actions)) { notification_ref(n); struct notification_lock *nl = @@ -302,13 +333,12 @@ static gpointer context_menu_thread(gpointer data) locked_notifications = g_list_prepend(locked_notifications, nl); } + char *dmenu_str = notification_dmenu_string(n); + dmenu_input = string_append(dmenu_input, dmenu_str, "\n"); + g_free(dmenu_str); + if (n->urls) dmenu_input = string_append(dmenu_input, n->urls, "\n"); - - if (n->actions) - dmenu_input = - string_append(dmenu_input, n->actions->dmenu_str, - "\n"); } dmenu_output = invoke_dmenu(dmenu_input); diff --git a/src/notification.c b/src/notification.c index 9657cc1..f9bde8a 100644 --- a/src/notification.c +++ b/src/notification.c @@ -25,7 +25,6 @@ static void notification_extract_urls(struct notification *n); static void notification_format_message(struct notification *n); -static void notification_dmenu_string(struct notification *n); /* see notification.h */ const char *enum_to_string_fullscreen(enum behavior_fullscreen in) @@ -75,16 +74,16 @@ void notification_print(const struct notification *n) printf("\t}\n"); g_free(urls); } - - if (n->actions) { - printf("\tactions:\n"); - printf("\t{\n"); - for (int i = 0; i < n->actions->count; i += 2) { - printf("\t\t[%s,%s]\n", n->actions->actions[i], - n->actions->actions[i + 1]); - } + if (g_hash_table_size(n->actions) == 0) { + printf("\tactions: {}\n"); + } else { + gpointer p_key, p_value; + GHashTableIter iter; + g_hash_table_iter_init(&iter, n->actions); + printf("\tactions: {\n"); + while (g_hash_table_iter_next(&iter, &p_key, &p_value)) + printf("\t\t\"%s\": \"%s\"\n", (char*)p_key, (char*)p_value); printf("\t}\n"); - printf("\tactions_dmenu: %s\n", n->actions->dmenu_str); } printf("\tscript: %s\n", n->script); printf("}\n"); @@ -189,17 +188,6 @@ int notification_is_duplicate(const struct notification *a, const struct notific && a->urgency == b->urgency; } -/* see notification.h */ -void actions_free(struct actions *a) -{ - if (!a) - return; - - g_strfreev(a->actions); - g_free(a->dmenu_str); - g_free(a); -} - /* see notification.h */ void rawimage_free(struct raw_image *i) { @@ -253,7 +241,7 @@ void notification_unref(struct notification *n) g_free(n->colors.frame); g_free(n->stack_tag); - actions_free(n->actions); + g_hash_table_unref(n->actions); rawimage_free(n->raw_icon); notification_private_free(n->priv); @@ -318,6 +306,8 @@ struct notification *notification_create(void) n->fullscreen = FS_SHOW; + n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + return n; } @@ -377,7 +367,6 @@ void notification_init(struct notification *n) /* UPDATE derived fields */ notification_extract_urls(n); - notification_dmenu_string(n); notification_format_message(n); } @@ -508,23 +497,6 @@ static void notification_extract_urls(struct notification *n) g_free(urls_text); } -static void notification_dmenu_string(struct notification *n) -{ - if (n->actions) { - g_clear_pointer(&n->actions->dmenu_str, g_free); - for (int i = 0; i < n->actions->count; i += 2) { - char *human_readable = n->actions->actions[i + 1]; - string_replace_char('[', '(', human_readable); // kill square brackets - string_replace_char(']', ')', human_readable); - - char *act_str = g_strdup_printf("#%s [%s]", human_readable, n->appname); - if (act_str) { - n->actions->dmenu_str = string_append(n->actions->dmenu_str, act_str, "\n"); - g_free(act_str); - } - } - } -} void notification_update_text_to_render(struct notification *n) { @@ -536,14 +508,14 @@ void notification_update_text_to_render(struct notification *n) /* print dup_count and msg */ if ((n->dup_count > 0 && !settings.hide_duplicate_count) - && (n->actions || n->urls) && settings.show_indicators) { + && (g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%d%s%s) %s", n->dup_count, - n->actions ? "A" : "", + g_hash_table_size(n->actions) ? "A" : "", n->urls ? "U" : "", msg); - } else if ((n->actions || n->urls) && settings.show_indicators) { + } else if ((g_hash_table_size(n->actions) || n->urls) && settings.show_indicators) { buf = g_strdup_printf("(%s%s) %s", - n->actions ? "A" : "", + g_hash_table_size(n->actions) ? "A" : "", n->urls ? "U" : "", msg); } else if (n->dup_count > 0 && !settings.hide_duplicate_count) { buf = g_strdup_printf("(%d) %s", n->dup_count, msg); @@ -584,16 +556,16 @@ void notification_update_text_to_render(struct notification *n) /* see notification.h */ void notification_do_action(const struct notification *n) { - if (n->actions) { - if (n->actions->count == 2) { - signal_action_invoked(n, n->actions->actions[0]); + if (g_hash_table_size(n->actions)) { + if (g_hash_table_contains(n->actions, "default")) { + signal_action_invoked(n, "default"); return; } - for (int i = 0; i < n->actions->count; i += 2) { - if (STR_EQ(n->actions->actions[i], "default")) { - signal_action_invoked(n, n->actions->actions[i]); - return; - } + if (g_hash_table_size(n->actions) == 1) { + GList *keys = g_hash_table_get_keys(n->actions); + signal_action_invoked(n, keys->data); + g_list_free(keys); + return; } context_menu(); diff --git a/src/notification.h b/src/notification.h index cc1e26e..f8f23e8 100644 --- a/src/notification.h +++ b/src/notification.h @@ -36,12 +36,6 @@ struct raw_image { unsigned char *data; }; -struct actions { - char **actions; - char *dmenu_str; - gsize count; -}; - typedef struct _notification_private NotificationPrivate; struct notification_colors { @@ -69,7 +63,7 @@ struct notification { gint64 timestamp; /**< arrival time */ gint64 timeout; /**< time to display */ - struct actions *actions; + GHashTable *actions; enum markup_mode markup; const char *format; @@ -127,13 +121,6 @@ void notification_ref(struct notification *n); */ void notification_init(struct notification *n); -/** - * Free the actions structure - * - * @param a (nullable): Pointer to #actions - */ -void actions_free(struct actions *a); - /** * Free a #raw_image * diff --git a/test/dbus.c b/test/dbus.c new file mode 100644 index 0000000..1dcb0fa --- /dev/null +++ b/test/dbus.c @@ -0,0 +1,753 @@ +#define wake_up wake_up_void +#include "../src/dbus.c" +#include "greatest.h" + +#include +#include + +#include "queues.h" + +void wake_up_void(void) { } + +struct signal_actioninvoked { + guint id; + gchar *key; + guint subscription_id; + GDBusConnection *conn; +}; + +struct signal_closed { + guint32 id; + guint32 reason; + guint subscription_id; + GDBusConnection *conn; +}; + +void dbus_signal_cb_actioninvoked(GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + g_return_if_fail(user_data); + + guint32 id; + gchar *key; + + struct signal_actioninvoked *sig = (struct signal_actioninvoked*) user_data; + + g_variant_get(parameters, "(us)", &id, &key); + + if (id == sig->id) { + sig->id = id; + sig->key = key; + } +} + +void dbus_signal_subscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) +{ + assert(actioninvoked); + + actioninvoked->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + actioninvoked->subscription_id = + g_dbus_connection_signal_subscribe( + actioninvoked->conn, + FDN_NAME, + FDN_IFAC, + "ActionInvoked", + FDN_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + dbus_signal_cb_actioninvoked, + actioninvoked, + NULL); +} + +void dbus_signal_unsubscribe_actioninvoked(struct signal_actioninvoked *actioninvoked) +{ + assert(actioninvoked); + + g_dbus_connection_signal_unsubscribe(actioninvoked->conn, actioninvoked->subscription_id); + g_object_unref(actioninvoked->conn); + + actioninvoked->conn = NULL; + actioninvoked->subscription_id = -1; +} + +void dbus_signal_cb_closed(GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + g_return_if_fail(user_data); + + guint32 id; + guint32 reason; + + struct signal_closed *sig = (struct signal_closed*) user_data; + g_variant_get(parameters, "(uu)", &id, &reason); + + if (id == sig->id) { + sig->id = id; + sig->reason = reason; + } +} + +void dbus_signal_subscribe_closed(struct signal_closed *closed) +{ + assert(closed); + + closed->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + closed->subscription_id = + g_dbus_connection_signal_subscribe( + closed->conn, + FDN_NAME, + FDN_IFAC, + "NotificationClosed", + FDN_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + dbus_signal_cb_closed, + closed, + NULL); +} + +void dbus_signal_unsubscribe_closed(struct signal_closed *closed) +{ + assert(closed); + + g_dbus_connection_signal_unsubscribe(closed->conn, closed->subscription_id); + g_object_unref(closed->conn); + + closed->conn = NULL; + closed->subscription_id = -1; +} + +GVariant *dbus_invoke(const char *method, GVariant *params) +{ + GDBusConnection *connection_client; + GVariant *retdata; + GError *error = NULL; + + connection_client = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + retdata = g_dbus_connection_call_sync( + connection_client, + FDN_NAME, + FDN_PATH, + FDN_IFAC, + method, + params, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) { + printf("Error while calling GTestDBus instance: %s\n", error->message); + g_error_free(error); + } + + g_object_unref(connection_client); + + return retdata; +} + +struct dbus_notification { + const char* app_name; + guint replaces_id; + const char* app_icon; + const char* summary; + const char* body; + GHashTable *actions; + GHashTable *hints; + int expire_timeout; +}; + +void g_free_variant_value(gpointer tofree) +{ + g_variant_unref((GVariant*) tofree); +} + +struct dbus_notification *dbus_notification_new(void) +{ + struct dbus_notification *n = g_malloc0(sizeof(struct dbus_notification)); + n->expire_timeout = -1; + n->replaces_id = 0; + n->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + n->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free_variant_value); + return n; +} + +void dbus_notification_free(struct dbus_notification *n) +{ + g_hash_table_unref(n->hints); + g_hash_table_unref(n->actions); + g_free(n); +} + +bool dbus_notification_fire(struct dbus_notification *n, uint *id) +{ + assert(n); + assert(id); + GVariantBuilder b; + GVariantType *t; + + gpointer p_key; + gpointer p_value; + GHashTableIter iter; + + t = g_variant_type_new("(susssasa{sv}i)"); + g_variant_builder_init(&b, t); + g_variant_type_free(t); + + g_variant_builder_add(&b, "s", n->app_name); + g_variant_builder_add(&b, "u", n->replaces_id); + g_variant_builder_add(&b, "s", n->app_icon); + g_variant_builder_add(&b, "s", n->summary); + g_variant_builder_add(&b, "s", n->body); + + // Add the actions + t = g_variant_type_new("as"); + g_variant_builder_open(&b, t); + g_hash_table_iter_init(&iter, n->actions); + while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { + g_variant_builder_add(&b, "s", (char*)p_key); + g_variant_builder_add(&b, "s", (char*)p_value); + } + // Add an invalid appendix to cover odd numbered action arrays + // Shouldn't interfere with normal testing + g_variant_builder_add(&b, "s", "invalid appendix"); + g_variant_builder_close(&b); + g_variant_type_free(t); + + // Add the hints + t = g_variant_type_new("a{sv}"); + g_variant_builder_open(&b, t); + g_hash_table_iter_init(&iter, n->hints); + while (g_hash_table_iter_next(&iter, &p_key, &p_value)) { + g_variant_builder_add(&b, "{sv}", (char*)p_key, (GVariant*)p_value); + } + g_variant_builder_close(&b); + g_variant_type_free(t); + + g_variant_builder_add(&b, "i", n->expire_timeout); + + GVariant *reply = dbus_invoke("Notify", g_variant_builder_end(&b)); + if (reply) { + g_variant_get(reply, "(u)", id); + g_variant_unref(reply); + return true; + } else { + return false; + } +} + +/////// TESTS +gint owner_id; + +TEST test_dbus_init(void) +{ + owner_id = dbus_init(); + uint waiting = 0; + while (!dbus_conn && waiting < 2000) { + usleep(500); + waiting++; + } + ASSERTm("After 1s, there is still no dbus connection available.", + dbus_conn); + PASS(); +} + +TEST test_dbus_teardown(void) +{ + dbus_teardown(owner_id); + PASS(); +} + +TEST test_invalid_notification(void) +{ + GVariant *faulty = g_variant_new_boolean(true); + + ASSERT(NULL == dbus_message_to_notification(":123", faulty)); + ASSERT(NULL == dbus_invoke("Notify", faulty)); + + g_variant_unref(faulty); + PASS(); +} + +TEST test_empty_notification(void) +{ + struct dbus_notification *n = dbus_notification_new(); + gsize len = queues_length_waiting(); + + guint id; + ASSERT(dbus_notification_fire(n, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + dbus_notification_free(n); + PASS(); +} + +TEST test_basic_notification(void) +{ + struct dbus_notification *n = dbus_notification_new(); + gsize len = queues_length_waiting(); + n->app_name = "dunstteststack"; + n->app_icon = "NONE"; + n->summary = "Headline"; + n->body = "Text"; + g_hash_table_insert(n->actions, g_strdup("actionid"), g_strdup("Print this text")); + g_hash_table_insert(n->hints, + g_strdup("x-dunst-stack-tag"), + g_variant_ref_sink(g_variant_new_string("volume"))); + + n->replaces_id = 10; + + guint id; + ASSERT(dbus_notification_fire(n, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + dbus_notification_free(n); + PASS(); +} + +TEST test_dbus_notify_colors(void) +{ + const char *color_frame = "I allow all string values for frame!"; + const char *color_bg = "I allow all string values for background!"; + const char *color_fg = "I allow all string values for foreground!"; + struct notification *n; + struct dbus_notification *n_dbus; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_dbus_notify_colors"; + n_dbus->body = "Summary of it"; + g_hash_table_insert(n_dbus->hints, + g_strdup("frcolor"), + g_variant_ref_sink(g_variant_new_string(color_frame))); + g_hash_table_insert(n_dbus->hints, + g_strdup("bgcolor"), + g_variant_ref_sink(g_variant_new_string(color_bg))); + g_hash_table_insert(n_dbus->hints, + g_strdup("fgcolor"), + g_variant_ref_sink(g_variant_new_string(color_fg))); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + ASSERT_STR_EQ(n->colors.frame, color_frame); + ASSERT_STR_EQ(n->colors.fg, color_fg); + ASSERT_STR_EQ(n->colors.bg, color_bg); + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_hint_transient(void) +{ + static char msg[50]; + struct notification *n; + struct dbus_notification *n_dbus; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_hint_transient"; + n_dbus->body = "Summary of it"; + + bool values[] = { true, true, true, false, false, false, false }; + GVariant *variants[] = { + g_variant_new_boolean(true), + g_variant_new_int32(1), + g_variant_new_uint32(1), + g_variant_new_boolean(false), + g_variant_new_int32(0), + g_variant_new_uint32(0), + g_variant_new_int32(-1), + }; + for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { + g_hash_table_insert(n_dbus->hints, + g_strdup("transient"), + g_variant_ref_sink(variants[i])); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + snprintf(msg, sizeof(msg), "In round %ld", i); + ASSERT_EQm(msg, values[i], n->transient); + } + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_hint_progress(void) +{ + static char msg[50]; + struct notification *n; + struct dbus_notification *n_dbus; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_hint_progress"; + n_dbus->body = "Summary of it"; + + int values[] = { 99, 12, 123, 123, -1, -1 }; + GVariant *variants[] = { + g_variant_new_int32(99), + g_variant_new_uint32(12), + g_variant_new_int32(123), // allow higher than 100 + g_variant_new_uint32(123), + g_variant_new_int32(-192), + g_variant_new_uint32(-192), + }; + for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { + g_hash_table_insert(n_dbus->hints, + g_strdup("value"), + g_variant_ref_sink(variants[i])); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + snprintf(msg, sizeof(msg), "In round %ld", i); + ASSERT_EQm(msg, values[i], n->progress); + } + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_hint_icons(void) +{ + struct notification *n; + struct dbus_notification *n_dbus; + const char *iconname = "NEWICON"; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_hint_icons"; + n_dbus->body = "Summary of it"; + + g_hash_table_insert(n_dbus->hints, + g_strdup("image-path"), + g_variant_ref_sink(g_variant_new_string(iconname))); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + ASSERT_STR_EQ(iconname, n->icon); + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_hint_category(void) +{ + struct notification *n; + struct dbus_notification *n_dbus; + const char *category = "VOLUME"; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_hint_category"; + n_dbus->body = "Summary of it"; + + g_hash_table_insert(n_dbus->hints, + g_strdup("category"), + g_variant_ref_sink(g_variant_new_string(category))); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + ASSERT_STR_EQ(category, n->category); + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_hint_urgency(void) +{ + static char msg[50]; + struct notification *n; + struct dbus_notification *n_dbus; + + gsize len = queues_length_waiting(); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE"; + n_dbus->summary = "test_hint_urgency"; + n_dbus->body = "Summary of it"; + + enum urgency values[] = { URG_MAX, URG_LOW, URG_NORM, URG_CRIT }; + GVariant *variants[] = { + g_variant_new_byte(10), + g_variant_new_byte(0), + g_variant_new_byte(1), + g_variant_new_byte(2), + }; + for (size_t i = 0; i < G_N_ELEMENTS(variants); i++) { + g_hash_table_insert(n_dbus->hints, + g_strdup("urgency"), + g_variant_ref_sink(variants[i])); + + guint id; + ASSERT(dbus_notification_fire(n_dbus, &id)); + ASSERT(id != 0); + + ASSERT_EQ(queues_length_waiting(), len+1); + + n = queues_debug_find_notification_by_id(id); + + snprintf(msg, sizeof(msg), "In round %ld", i); + ASSERT_EQm(msg, values[i], n->urgency); + + queues_notification_close_id(id, REASON_UNDEF); + } + + dbus_notification_free(n_dbus); + + PASS(); +} + +TEST test_server_caps(enum markup_mode markup) +{ + GVariant *reply; + GVariant *caps = NULL; + const char **capsarray; + + settings.markup = markup; + + reply = dbus_invoke("GetCapabilities", NULL); + + caps = g_variant_get_child_value(reply, 0); + capsarray = g_variant_get_strv(caps, NULL); + + ASSERT(capsarray); + ASSERT(g_strv_contains(capsarray, "actions")); + ASSERT(g_strv_contains(capsarray, "body")); + ASSERT(g_strv_contains(capsarray, "body-hyperlinks")); + ASSERT(g_strv_contains(capsarray, "x-dunst-stack-tag")); + + if (settings.markup != MARKUP_NO) + ASSERT(g_strv_contains(capsarray, "body-markup")); + else + ASSERT_FALSE(g_strv_contains(capsarray, "body-markup")); + + g_free(capsarray); + g_variant_unref(caps); + g_variant_unref(reply); + PASS(); +} + +TEST test_signal_actioninvoked(void) +{ + const struct notification *n; + struct dbus_notification *n_dbus; + struct signal_actioninvoked sig = {0, NULL, -1}; + + dbus_signal_subscribe_actioninvoked(&sig); + + n_dbus = dbus_notification_new(); + n_dbus->app_name = "dunstteststack"; + n_dbus->app_icon = "NONE2"; + n_dbus->summary = "Headline for New"; + n_dbus->body = "Text"; + g_hash_table_insert(n_dbus->actions, g_strdup("actionkey"), g_strdup("Print this text")); + + dbus_notification_fire(n_dbus, &sig.id); + n = queues_debug_find_notification_by_id(sig.id); + + signal_action_invoked(n, "actionkey"); + + uint waiting = 0; + while (!sig.key && waiting < 2000) { + usleep(500); + waiting++; + } + + ASSERT_STR_EQ("actionkey", sig.key); + + g_free(sig.key); + dbus_notification_free(n_dbus); + dbus_signal_unsubscribe_actioninvoked(&sig); + PASS(); +} + +TEST test_close_and_signal(void) +{ + GVariant *data, *ret; + struct dbus_notification *n; + struct signal_closed sig = {0, REASON_MIN-1, -1}; + + dbus_signal_subscribe_closed(&sig); + + n = dbus_notification_new(); + n->app_name = "dunstteststack"; + n->app_icon = "NONE2"; + n->summary = "Headline for New"; + n->body = "Text"; + + dbus_notification_fire(n, &sig.id); + + data = g_variant_new("(u)", sig.id); + ret = dbus_invoke("CloseNotification", data); + + ASSERT(ret); + + uint waiting = 0; + while (sig.reason == REASON_MIN-1 && waiting < 2000) { + usleep(500); + waiting++; + } + + ASSERT(sig.reason != REASON_MIN-1); + + dbus_notification_free(n); + dbus_signal_unsubscribe_closed(&sig); + g_variant_unref(ret); + PASS(); +} + +TEST test_get_fdn_daemon_info(void) +{ + unsigned int pid_is; + pid_t pid_should; + char *name, *vendor; + GDBusConnection *conn; + + pid_should = getpid(); + conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + + ASSERT(dbus_get_fdn_daemon_info(conn, &pid_is, &name, &vendor)); + + ASSERT_EQ_FMT(pid_should, pid_is, "%d"); + ASSERT_STR_EQ("dunst", name); + ASSERT_STR_EQ("knopwob", vendor); + + g_free(name); + g_free(vendor); + + g_object_unref(conn); + PASS(); +} + +TEST assert_methodlists_sorted(void) +{ + for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_fdn); i++) { + ASSERT(0 > strcmp( + methods_fdn[i].method_name, + methods_fdn[i+1].method_name)); + } + + PASS(); +} + + +// TESTS END + +GMainLoop *loop; +GThread *thread_tests; + +gpointer run_threaded_tests(gpointer data) +{ + RUN_TEST(test_dbus_init); + + RUN_TEST(test_get_fdn_daemon_info); + + RUN_TEST(test_empty_notification); + RUN_TEST(test_basic_notification); + RUN_TEST(test_invalid_notification); + RUN_TEST(test_hint_transient); + RUN_TEST(test_hint_progress); + RUN_TEST(test_hint_icons); + RUN_TEST(test_hint_category); + RUN_TEST(test_hint_urgency); + RUN_TEST(test_dbus_notify_colors); + RUN_TESTp(test_server_caps, MARKUP_FULL); + RUN_TESTp(test_server_caps, MARKUP_STRIP); + RUN_TESTp(test_server_caps, MARKUP_NO); + RUN_TEST(test_close_and_signal); + RUN_TEST(test_signal_actioninvoked); + + RUN_TEST(assert_methodlists_sorted); + + RUN_TEST(test_dbus_teardown); + g_main_loop_quit(loop); + return NULL; +} + +SUITE(suite_dbus) +{ + GTestDBus *dbus_bus; + g_test_dbus_unset(); + queues_init(); + + loop = g_main_loop_new(NULL, false); + + dbus_bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(dbus_bus); + + thread_tests = g_thread_new("testexecutor", run_threaded_tests, loop); + g_main_loop_run(loop); + + queues_teardown(); + g_test_dbus_down(dbus_bus); + g_object_unref(dbus_bus); + g_thread_unref(thread_tests); + g_main_loop_unref(loop); +} + +/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/test/queues.c b/test/queues.c index de69578..1ba8216 100644 --- a/test/queues.c +++ b/test/queues.c @@ -6,6 +6,23 @@ #include "greatest.h" #include "queues.h" +struct notification *queues_debug_find_notification_by_id(int id) +{ + assert(id > 0); + + GQueue *allqueues[] = { displayed, waiting, history }; + for (int i = 0; i < sizeof(allqueues)/sizeof(GQueue*); i++) { + for (GList *iter = g_queue_peek_head_link(allqueues[i]); iter; + iter = iter->next) { + struct notification *cur = iter->data; + if (cur->id == id) + return cur; + } + } + + return NULL; +} + TEST test_queue_length(void) { queues_init(); diff --git a/test/queues.h b/test/queues.h index 67b68a7..c9c049f 100644 --- a/test/queues.h +++ b/test/queues.h @@ -50,5 +50,8 @@ static inline struct notification *test_notification(const char *name, gint64 ti return n; } +/* Retrieve a notification by its id. Solely for debugging purposes */ +struct notification *queues_debug_find_notification_by_id(int id); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/test/test.c b/test/test.c index 1863070..d8f907a 100644 --- a/test/test.c +++ b/test/test.c @@ -19,6 +19,7 @@ SUITE_EXTERN(suite_queues); SUITE_EXTERN(suite_dunst); SUITE_EXTERN(suite_log); SUITE_EXTERN(suite_menu); +SUITE_EXTERN(suite_dbus); GREATEST_MAIN_DEFS(); @@ -44,6 +45,7 @@ int main(int argc, char *argv[]) { RUN_SUITE(suite_dunst); RUN_SUITE(suite_log); RUN_SUITE(suite_menu); + RUN_SUITE(suite_dbus); GREATEST_MAIN_END(); base = NULL;