Merge pull request #568 from bebehei/dbus-test

Dbus test
This commit is contained in:
Benedikt Heine 2018-12-31 05:58:14 +01:00 committed by GitHub
commit c1090ad7dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1070 additions and 299 deletions

View File

@ -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

View File

@ -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

View File

@ -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 \

View File

@ -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);

View File

@ -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: #<human readable> (<summary>)[<id>,<action>] */
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);

View File

@ -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();

View File

@ -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
*

753
test/dbus.c Normal file
View File

@ -0,0 +1,753 @@
#define wake_up wake_up_void
#include "../src/dbus.c"
#include "greatest.h"
#include <assert.h>
#include <gio/gio.h>
#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: */

View File

@ -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();

View File

@ -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: */

View File

@ -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;