summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2022-12-21 21:14:12 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2022-12-21 21:14:12 +0000
commit0bbce724ada99626ea258cbf238d9a4909143390 (patch)
tree0e5426aa9db4ada97c997a9b3f018b665e766d75
parent47b7d1d707b2914081366824067730e423647195 (diff)
parentb4c7dc11b6a41b469067cdf830df3b0aa37ec507 (diff)
downloadglib-0bbce724ada99626ea258cbf238d9a4909143390.tar.gz
Merge branch 'backport-3113-action-crashes' into 'glib-2-74'
Backport !3113 “gaction: Validate actions activated over D-Bus” to glib-2-74 See merge request GNOME/glib!3114
-rw-r--r--gio/gactiongroupexporter.c60
-rw-r--r--gio/gapplication.c2
-rw-r--r--gio/gapplication.h3
-rw-r--r--gio/gapplicationcommandline.c12
-rw-r--r--gio/gapplicationimpl-dbus.c26
-rw-r--r--gio/gfdonotificationbackend.c75
-rw-r--r--gio/gnotificationbackend.c14
-rw-r--r--gio/gtestdbus.c2
-rw-r--r--gio/tests/actions.c435
-rw-r--r--gio/tests/application-command-line.c92
-rw-r--r--gio/tests/fdo-notification-backend.c326
-rw-r--r--gio/tests/gapplication.c491
-rw-r--r--gio/tests/meson.build2
13 files changed, 1370 insertions, 170 deletions
diff --git a/gio/gactiongroupexporter.c b/gio/gactiongroupexporter.c
index 575a03ca2..3bc2f0418 100644
--- a/gio/gactiongroupexporter.c
+++ b/gio/gactiongroupexporter.c
@@ -433,11 +433,37 @@ org_gtk_Actions_method_call (GDBusConnection *connection,
GVariant *platform_data;
GVariantIter *iter;
const gchar *name;
+ const GVariantType *parameter_type = NULL;
g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
g_variant_iter_next (iter, "v", &parameter);
g_variant_iter_free (iter);
+ /* Check the action exists and the parameter type matches. */
+ if (!g_action_group_query_action (exporter->action_group,
+ name, NULL, &parameter_type,
+ NULL, NULL, NULL))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Unknown action ‘%s’", name);
+ g_clear_pointer (&parameter, g_variant_unref);
+ g_variant_unref (platform_data);
+ return;
+ }
+
+ if (!((parameter_type == NULL && parameter == NULL) ||
+ (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type))))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid parameter for action ‘%s’: expected type %s but got type %s",
+ name,
+ (parameter_type != NULL) ? (const gchar *) parameter_type : "()",
+ (parameter != NULL) ? g_variant_get_type_string (parameter) : "()");
+ g_clear_pointer (&parameter, g_variant_unref);
+ g_variant_unref (platform_data);
+ return;
+ }
+
if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
name, parameter, platform_data);
@@ -455,9 +481,43 @@ org_gtk_Actions_method_call (GDBusConnection *connection,
GVariant *platform_data;
const gchar *name;
GVariant *state;
+ const GVariantType *state_type = NULL;
g_variant_get (parameters, "(&sv@a{sv})", &name, &state, &platform_data);
+ /* Check the action exists and the state type matches. */
+ if (!g_action_group_query_action (exporter->action_group,
+ name, NULL, NULL,
+ &state_type, NULL, NULL))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Unknown action ‘%s’", name);
+ g_variant_unref (state);
+ g_variant_unref (platform_data);
+ return;
+ }
+
+ if (state_type == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Cannot change state of action ‘%s’ as it is stateless", name);
+ g_variant_unref (state);
+ g_variant_unref (platform_data);
+ return;
+ }
+
+ if (!g_variant_is_of_type (state, state_type))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid state for action ‘%s’: expected type %s but got type %s",
+ name,
+ (const gchar *) state_type,
+ g_variant_get_type_string (state));
+ g_variant_unref (state);
+ g_variant_unref (platform_data);
+ return;
+ }
+
if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
name, state, platform_data);
diff --git a/gio/gapplication.c b/gio/gapplication.c
index 1d602c763..3708e812c 100644
--- a/gio/gapplication.c
+++ b/gio/gapplication.c
@@ -673,6 +673,8 @@ add_packed_option (GApplication *application,
* inspected and modified. If %G_APPLICATION_HANDLES_COMMAND_LINE is
* set, then the resulting dictionary is sent to the primary instance,
* where g_application_command_line_get_options_dict() will return it.
+ * As it has been passed outside the process at this point, the types of all
+ * values in the options dict must be checked before being used.
* This "packing" is done according to the type of the argument --
* booleans for normal flags, strings for strings, bytestrings for
* filenames, etc. The packing only occurs if the flag is given (ie: we
diff --git a/gio/gapplication.h b/gio/gapplication.h
index 345405366..70074349a 100644
--- a/gio/gapplication.h
+++ b/gio/gapplication.h
@@ -95,8 +95,11 @@ struct _GApplicationClass
gchar ***arguments,
int *exit_status);
+ /* @platform_data comes from an external process and is untrusted. All value types
+ * must be validated before being used. */
void (* before_emit) (GApplication *application,
GVariant *platform_data);
+ /* Same as for @before_emit. */
void (* after_emit) (GApplication *application,
GVariant *platform_data);
void (* add_platform_data) (GApplication *application,
diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c
index 9fe52da5d..ef6c2d2a3 100644
--- a/gio/gapplicationcommandline.c
+++ b/gio/gapplicationcommandline.c
@@ -260,20 +260,20 @@ grok_platform_data (GApplicationCommandLine *cmdline)
g_variant_iter_init (&iter, cmdline->priv->platform_data);
while (g_variant_iter_loop (&iter, "{&sv}", &key, &value))
- if (strcmp (key, "cwd") == 0)
+ if (strcmp (key, "cwd") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING))
{
if (!cmdline->priv->cwd)
cmdline->priv->cwd = g_variant_dup_bytestring (value, NULL);
}
- else if (strcmp (key, "environ") == 0)
+ else if (strcmp (key, "environ") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING_ARRAY))
{
if (!cmdline->priv->environ)
cmdline->priv->environ =
g_variant_dup_bytestring_array (value, NULL);
}
- else if (strcmp (key, "options") == 0)
+ else if (strcmp (key, "options") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_VARDICT))
{
if (!cmdline->priv->options)
cmdline->priv->options = g_variant_ref (value);
@@ -507,6 +507,9 @@ g_application_command_line_get_arguments (GApplicationCommandLine *cmdline,
* If no options were sent then an empty dictionary is returned so that
* you don't need to check for %NULL.
*
+ * The data has been passed via an untrusted external process, so the types of
+ * all values must be checked before being used.
+ *
* Returns: (transfer none): a #GVariantDict with the options
*
* Since: 2.40
@@ -793,6 +796,9 @@ g_application_command_line_get_exit_status (GApplicationCommandLine *cmdline)
* information like the current working directory and the startup
* notification ID.
*
+ * It comes from an untrusted external process and hence the types of all
+ * values must be validated before being used.
+ *
* For local invocation, it will be %NULL.
*
* Returns: (nullable): the platform data, or %NULL
diff --git a/gio/gapplicationimpl-dbus.c b/gio/gapplicationimpl-dbus.c
index bcad19d2e..ac6644d88 100644
--- a/gio/gapplicationimpl-dbus.c
+++ b/gio/gapplicationimpl-dbus.c
@@ -286,6 +286,7 @@ g_application_impl_method_call (GDBusConnection *connection,
GVariant *platform_data;
GVariantIter *iter;
const gchar *name;
+ const GVariantType *parameter_type = NULL;
/* Only on the freedesktop interface */
@@ -293,6 +294,31 @@ g_application_impl_method_call (GDBusConnection *connection,
g_variant_iter_next (iter, "v", &parameter);
g_variant_iter_free (iter);
+ /* Check the action exists and the parameter type matches. */
+ if (!g_action_group_query_action (impl->exported_actions,
+ name, NULL, &parameter_type,
+ NULL, NULL, NULL))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Unknown action ‘%s’", name);
+ g_clear_pointer (&parameter, g_variant_unref);
+ g_variant_unref (platform_data);
+ return;
+ }
+
+ if (!((parameter_type == NULL && parameter == NULL) ||
+ (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type))))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid parameter for action ‘%s’: expected type %s but got type %s",
+ name,
+ (parameter_type != NULL) ? (const gchar *) parameter_type : "()",
+ (parameter != NULL) ? g_variant_get_type_string (parameter) : "()");
+ g_clear_pointer (&parameter, g_variant_unref);
+ g_variant_unref (platform_data);
+ return;
+ }
+
class->before_emit (impl->app, platform_data);
g_action_group_activate_action (impl->exported_actions, name, parameter);
class->after_emit (impl->app, platform_data);
diff --git a/gio/gfdonotificationbackend.c b/gio/gfdonotificationbackend.c
index e0bfea535..594ed9966 100644
--- a/gio/gfdonotificationbackend.c
+++ b/gio/gfdonotificationbackend.c
@@ -63,8 +63,8 @@ typedef struct
GFdoNotificationBackend *backend;
gchar *id;
guint32 notify_id;
- gchar *default_action;
- GVariant *default_action_target;
+ gchar *default_action; /* (nullable) (owned) */
+ GVariant *default_action_target; /* (nullable) (owned), not floating */
} FreedesktopNotification;
static void
@@ -131,22 +131,40 @@ g_fdo_notification_backend_find_notification_by_notify_id (GFdoNotificationBacke
return NULL;
}
-static void
+static gboolean
activate_action (GFdoNotificationBackend *backend,
const gchar *name,
GVariant *parameter)
{
GNotificationBackend *g_backend = G_NOTIFICATION_BACKEND (backend);
- if (name)
+ /* Callers should not provide a floating variant here */
+ g_assert (parameter == NULL || !g_variant_is_floating (parameter));
+
+ if (name != NULL &&
+ g_str_has_prefix (name, "app."))
{
- if (g_str_has_prefix (name, "app."))
- g_action_group_activate_action (G_ACTION_GROUP (g_backend->application), name + 4, parameter);
+ const GVariantType *parameter_type = NULL;
+ const gchar *action_name = name + strlen ("app.");
+
+ /* @name and @parameter come as untrusted input over D-Bus, so validate them first */
+ if (g_action_group_query_action (G_ACTION_GROUP (g_backend->application),
+ action_name, NULL, &parameter_type,
+ NULL, NULL, NULL) &&
+ ((parameter_type == NULL && parameter == NULL) ||
+ (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type))))
+ {
+ g_action_group_activate_action (G_ACTION_GROUP (g_backend->application), action_name, parameter);
+ return TRUE;
+ }
}
- else
+ else if (name == NULL)
{
g_application_activate (g_backend->application);
+ return TRUE;
}
+
+ return FALSE;
}
static void
@@ -162,6 +180,7 @@ notify_signal (GDBusConnection *connection,
guint32 id = 0;
const gchar *action = NULL;
FreedesktopNotification *n;
+ gboolean notification_closed = TRUE;
if (g_str_equal (signal_name, "NotificationClosed") &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
@@ -184,29 +203,39 @@ notify_signal (GDBusConnection *connection,
{
if (g_str_equal (action, "default"))
{
- activate_action (backend, n->default_action, n->default_action_target);
+ if (!activate_action (backend, n->default_action, n->default_action_target))
+ notification_closed = FALSE;
}
else
{
- gchar *name;
- GVariant *target;
-
- if (g_action_parse_detailed_name (action, &name, &target, NULL))
- {
- activate_action (backend, name, target);
- g_free (name);
- if (target)
- g_variant_unref (target);
- }
+ gchar *name = NULL;
+ GVariant *target = NULL;
+
+ if (!g_action_parse_detailed_name (action, &name, &target, NULL) ||
+ !activate_action (backend, name, target))
+ notification_closed = FALSE;
+
+ g_free (name);
+ g_clear_pointer (&target, g_variant_unref);
}
}
- /* Get the notification again in case the action redrew it */
- n = g_fdo_notification_backend_find_notification_by_notify_id (backend, id);
- if (n != NULL)
+ /* Remove the notification, as it’s either been explicitly closed
+ * (`NotificationClosed` signal) or has been closed as a result of activating
+ * an action successfully. GLib doesn’t currently support the `resident` hint
+ * on notifications which would allow them to stay around after having an
+ * action invoked on them (see
+ * https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#idm45877717456448)
+ *
+ * First, get the notification again in case the action redrew it */
+ if (notification_closed)
{
- backend->notifications = g_slist_remove (backend->notifications, n);
- freedesktop_notification_free (n);
+ n = g_fdo_notification_backend_find_notification_by_notify_id (backend, id);
+ if (n != NULL)
+ {
+ backend->notifications = g_slist_remove (backend->notifications, n);
+ freedesktop_notification_free (n);
+ }
}
}
diff --git a/gio/gnotificationbackend.c b/gio/gnotificationbackend.c
index e5f404705..acbd10820 100644
--- a/gio/gnotificationbackend.c
+++ b/gio/gnotificationbackend.c
@@ -29,8 +29,22 @@
G_DEFINE_TYPE (GNotificationBackend, g_notification_backend, G_TYPE_OBJECT)
static void
+g_notification_backend_dispose (GObject *obj)
+{
+ GNotificationBackend *backend = G_NOTIFICATION_BACKEND (obj);
+
+ backend->application = NULL; /* no reference held, but clear the pointer anyway to avoid it dangling */
+ g_clear_object (&backend->dbus_connection);
+
+ G_OBJECT_CLASS (g_notification_backend_parent_class)->dispose (obj);
+}
+
+static void
g_notification_backend_class_init (GNotificationBackendClass *class)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->dispose = g_notification_backend_dispose;
}
static void
diff --git a/gio/gtestdbus.c b/gio/gtestdbus.c
index cf7d1a4b8..6aedb3eae 100644
--- a/gio/gtestdbus.c
+++ b/gio/gtestdbus.c
@@ -94,7 +94,7 @@ _g_object_unref_and_wait_weak_notify (gpointer object)
g_idle_add (unref_on_idle, object);
/* Make sure we don't block forever */
- timeout_id = g_timeout_add (30 * 1000, on_weak_notify_timeout, &data);
+ timeout_id = g_timeout_add_seconds (30, on_weak_notify_timeout, &data);
g_main_loop_run (data.loop);
diff --git a/gio/tests/actions.c b/gio/tests/actions.c
index f27841b4b..0bd63634b 100644
--- a/gio/tests/actions.c
+++ b/gio/tests/actions.c
@@ -2,6 +2,7 @@
* Copyright © 2010, 2011, 2013, 2014 Codethink Limited
* Copyright © 2010, 2011, 2012, 2013, 2015 Red Hat, Inc.
* Copyright © 2012 Pavel Vasin
+ * Copyright © 2022 Endless OS Foundation, LLC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
@@ -189,6 +190,22 @@ strv_set_equal (const gchar * const *strv, ...)
return res;
}
+static void
+ensure_state (GActionGroup *group,
+ const gchar *action_name,
+ const gchar *expected)
+{
+ GVariant *value;
+ gchar *printed;
+
+ value = g_action_group_get_action_state (group, action_name);
+ printed = g_variant_print (value, TRUE);
+ g_variant_unref (value);
+
+ g_assert_cmpstr (printed, ==, expected);
+ g_free (printed);
+}
+
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static void
@@ -644,11 +661,13 @@ compare_action_groups (GActionGroup *a, GActionGroup *b)
}
static gboolean
-stop_loop (gpointer data)
+timeout_cb (gpointer user_data)
{
- GMainLoop *loop = data;
+ gboolean *timed_out = user_data;
- g_main_loop_quit (loop);
+ g_assert_false (*timed_out);
+ *timed_out = TRUE;
+ g_main_context_wakeup (NULL);
return G_SOURCE_REMOVE;
}
@@ -664,96 +683,16 @@ static GActionEntry exported_entries[] = {
};
static void
-list_cb (GObject *source,
- GAsyncResult *res,
- gpointer user_data)
-{
- GDBusConnection *bus = G_DBUS_CONNECTION (source);
- GMainLoop *loop = user_data;
- GError *error = NULL;
- GVariant *v;
- gchar **actions;
-
- v = g_dbus_connection_call_finish (bus, res, &error);
- g_assert_nonnull (v);
- g_variant_get (v, "(^a&s)", &actions);
- g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
- g_free (actions);
- g_variant_unref (v);
- g_main_loop_quit (loop);
-}
-
-static gboolean
-call_list (gpointer user_data)
-{
- GDBusConnection *bus;
-
- bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
- g_dbus_connection_call (bus,
- g_dbus_connection_get_unique_name (bus),
- "/",
- "org.gtk.Actions",
- "List",
- NULL,
- NULL,
- 0,
- G_MAXINT,
- NULL,
- list_cb,
- user_data);
- g_object_unref (bus);
-
- return G_SOURCE_REMOVE;
-}
-
-static void
-describe_cb (GObject *source,
- GAsyncResult *res,
- gpointer user_data)
-{
- GDBusConnection *bus = G_DBUS_CONNECTION (source);
- GMainLoop *loop = user_data;
- GError *error = NULL;
- GVariant *v;
- gboolean enabled;
- gchar *param;
- GVariantIter *iter;
-
- v = g_dbus_connection_call_finish (bus, res, &error);
- g_assert_nonnull (v);
- /* FIXME: there's an extra level of tuplelization in here */
- g_variant_get (v, "((bgav))", &enabled, &param, &iter);
- g_assert_true (enabled);
- g_assert_cmpstr (param, ==, "");
- g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
- g_free (param);
- g_variant_iter_free (iter);
- g_variant_unref (v);
-
- g_main_loop_quit (loop);
-}
-
-static gboolean
-call_describe (gpointer user_data)
+async_result_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
{
- GDBusConnection *bus;
+ GAsyncResult **result_out = user_data;
- bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
- g_dbus_connection_call (bus,
- g_dbus_connection_get_unique_name (bus),
- "/",
- "org.gtk.Actions",
- "Describe",
- g_variant_new ("(s)", "copy"),
- NULL,
- 0,
- G_MAXINT,
- NULL,
- describe_cb,
- user_data);
- g_object_unref (bus);
+ g_assert_null (*result_out);
+ *result_out = g_object_ref (res);
- return G_SOURCE_REMOVE;
+ g_main_context_wakeup (NULL);
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
@@ -800,15 +739,16 @@ test_dbus_export (void)
GSimpleActionGroup *group;
GDBusActionGroup *proxy;
GSimpleAction *action;
- GMainLoop *loop;
GError *error = NULL;
GVariant *v;
guint id;
gchar **actions;
guint n_actions_added = 0, n_actions_enabled_changed = 0, n_actions_removed = 0, n_actions_state_changed = 0;
gulong added_signal_id, enabled_changed_signal_id, removed_signal_id, state_changed_signal_id;
-
- loop = g_main_loop_new (NULL, FALSE);
+ gboolean enabled;
+ gchar *param;
+ GVariantIter *iter;
+ GAsyncResult *async_result = NULL;
session_bus_up ();
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
@@ -842,16 +782,244 @@ test_dbus_export (void)
g_strfreev (actions);
/* check that calling "List" works too */
- g_idle_add (call_list, loop);
- g_main_loop_run (loop);
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "List",
+ NULL,
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (v);
+ g_variant_get (v, "(^a&s)", &actions);
+ g_assert_cmpuint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
+ g_free (actions);
+ g_variant_unref (v);
+ g_clear_object (&async_result);
/* check that calling "Describe" works */
- g_idle_add (call_describe, loop);
- g_main_loop_run (loop);
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "Describe",
+ g_variant_new ("(s)", "copy"),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (v);
+ /* FIXME: there's an extra level of tuplelization in here */
+ g_variant_get (v, "((bgav))", &enabled, &param, &iter);
+ g_assert_true (enabled);
+ g_assert_cmpstr (param, ==, "");
+ g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
+ g_free (param);
+ g_variant_iter_free (iter);
+ g_variant_unref (v);
+ g_clear_object (&async_result);
+
+ /* check that activating a parameterless action over D-Bus works */
+ g_assert_cmpint (activation_count ("undo"), ==, 0);
+
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_new ("(sava{sv})", "undo", NULL, NULL),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (v);
+ g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
+ g_variant_unref (v);
+ g_clear_object (&async_result);
+
+ g_assert_cmpint (activation_count ("undo"), ==, 1);
+
+ /* check that activating a parameterful action over D-Bus works */
+ g_assert_cmpint (activation_count ("lang"), ==, 0);
+ ensure_state (G_ACTION_GROUP (group), "lang", "'latin'");
+
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_new ("(s@ava{sv})", "lang", g_variant_new_parsed ("[<'spanish'>]"), NULL),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (v);
+ g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
+ g_variant_unref (v);
+ g_clear_object (&async_result);
+
+ g_assert_cmpint (activation_count ("lang"), ==, 1);
+ ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
+
+ /* check that various error conditions are rejected */
+ {
+ struct
+ {
+ const gchar *action_name;
+ GVariant *parameter; /* (owned floating) (nullable) */
+ }
+ activate_error_conditions[] =
+ {
+ { "nope", NULL }, /* non-existent action */
+ { "lang", g_variant_new_parsed ("[<@u 4>]") }, /* wrong parameter type */
+ { "lang", NULL }, /* parameter missing */
+ { "undo", g_variant_new_parsed ("[<'silly'>]") }, /* extraneous parameter */
+ };
+
+ for (gsize i = 0; i < G_N_ELEMENTS (activate_error_conditions); i++)
+ {
+ GVariant *parameter = g_steal_pointer (&activate_error_conditions[i].parameter);
+ const gchar *type_string = (parameter != NULL) ? "(s@ava{sv})" : "(sava{sv})";
+
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_new (type_string,
+ activate_error_conditions[i].action_name,
+ g_steal_pointer (&parameter),
+ NULL),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_null (v);
+ g_clear_error (&error);
+ g_clear_object (&async_result);
+ }
+ }
+
+ /* check that setting an action’s state over D-Bus works */
+ g_assert_cmpint (activation_count ("lang"), ==, 1);
+ ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
+
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "SetState",
+ g_variant_new ("(sva{sv})", "lang", g_variant_new_string ("portuguese"), NULL),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (v);
+ g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
+ g_variant_unref (v);
+ g_clear_object (&async_result);
+
+ g_assert_cmpint (activation_count ("lang"), ==, 1);
+ ensure_state (G_ACTION_GROUP (group), "lang", "'portuguese'");
+
+ /* check that various error conditions are rejected */
+ {
+ struct
+ {
+ const gchar *action_name;
+ GVariant *state; /* (owned floating) (not nullable) */
+ }
+ set_state_error_conditions[] =
+ {
+ { "nope", g_variant_new_string ("hello") }, /* non-existent action */
+ { "undo", g_variant_new_string ("not stateful") }, /* not a stateful action */
+ { "lang", g_variant_new_uint32 (3) }, /* wrong state type */
+ };
+
+ for (gsize i = 0; i < G_N_ELEMENTS (set_state_error_conditions); i++)
+ {
+ g_dbus_connection_call (bus,
+ g_dbus_connection_get_unique_name (bus),
+ "/",
+ "org.gtk.Actions",
+ "SetState",
+ g_variant_new ("(s@va{sv})",
+ set_state_error_conditions[i].action_name,
+ g_variant_new_variant (g_steal_pointer (&set_state_error_conditions[i].state)),
+ NULL),
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ async_result_cb,
+ &async_result);
+
+ while (async_result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ v = g_dbus_connection_call_finish (bus, async_result, &error);
+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+ g_assert_null (v);
+ g_clear_error (&error);
+ g_clear_object (&async_result);
+ }
+ }
/* test that the initial transfer works */
g_assert_true (G_IS_DBUS_ACTION_GROUP (proxy));
- g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
+ while (!compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)))
+ g_main_context_iteration (NULL, TRUE);
+ n_actions_state_changed = 0;
/* test that various changes get propagated from group to proxy */
n_actions_added = 0;
@@ -931,7 +1099,6 @@ test_dbus_export (void)
g_signal_handler_disconnect (proxy, state_changed_signal_id);
g_object_unref (proxy);
g_object_unref (group);
- g_main_loop_unref (loop);
g_object_unref (bus);
session_bus_down ();
@@ -1016,9 +1183,7 @@ test_bug679509 (void)
{
GDBusConnection *bus;
GDBusActionGroup *proxy;
- GMainLoop *loop;
-
- loop = g_main_loop_new (NULL, FALSE);
+ gboolean timed_out = FALSE;
session_bus_up ();
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
@@ -1027,10 +1192,10 @@ test_bug679509 (void)
g_strfreev (g_action_group_list_actions (G_ACTION_GROUP (proxy)));
g_object_unref (proxy);
- g_timeout_add (100, stop_loop, loop);
- g_main_loop_run (loop);
+ g_timeout_add (100, timeout_cb, &timed_out);
+ while (!timed_out)
+ g_main_context_iteration (NULL, TRUE);
- g_main_loop_unref (loop);
g_object_unref (bus);
session_bus_down ();
@@ -1062,22 +1227,6 @@ verify_changed (const gchar *log_entry)
}
static void
-ensure_state (GSimpleActionGroup *group,
- const gchar *action_name,
- const gchar *expected)
-{
- GVariant *value;
- gchar *printed;
-
- value = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name);
- printed = g_variant_print (value, TRUE);
- g_variant_unref (value);
-
- g_assert_cmpstr (printed, ==, expected);
- g_free (printed);
-}
-
-static void
test_property_actions (void)
{
GSimpleActionGroup *group;
@@ -1137,11 +1286,11 @@ test_property_actions (void)
g_object_unref (client);
g_object_unref (app);
- ensure_state (group, "app-id", "'org.gtk.test'");
- ensure_state (group, "keepalive", "uint32 0");
- ensure_state (group, "tls", "false");
- ensure_state (group, "disable-proxy", "false");
- ensure_state (group, "type", "'stream'");
+ ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
+ ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
+ ensure_state (G_ACTION_GROUP (group), "tls", "false");
+ ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
+ ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
verify_changed (NULL);
@@ -1149,88 +1298,88 @@ test_property_actions (void)
g_action_group_change_action_state (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test2"));
verify_changed ("app-id:'org.gtk.test2'");
g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test2");
- ensure_state (group, "app-id", "'org.gtk.test2'");
+ ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test2'");
g_action_group_activate_action (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test3"));
verify_changed ("app-id:'org.gtk.test3'");
g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test3");
- ensure_state (group, "app-id", "'org.gtk.test3'");
+ ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test3'");
g_application_set_application_id (app, "org.gtk.test");
verify_changed ("app-id:'org.gtk.test'");
- ensure_state (group, "app-id", "'org.gtk.test'");
+ ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
/* uint tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 1234));
verify_changed ("keepalive:uint32 1234");
g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 1234);
- ensure_state (group, "keepalive", "uint32 1234");
+ ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 1234");
g_action_group_activate_action (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 5678));
verify_changed ("keepalive:uint32 5678");
g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 5678);
- ensure_state (group, "keepalive", "uint32 5678");
+ ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 5678");
g_application_set_inactivity_timeout (app, 0);
verify_changed ("keepalive:uint32 0");
- ensure_state (group, "keepalive", "uint32 0");
+ ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
/* bool tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "tls", g_variant_new ("b", TRUE));
verify_changed ("tls:true");
g_assert_true (g_socket_client_get_tls (client));
- ensure_state (group, "tls", "true");
+ ensure_state (G_ACTION_GROUP (group), "tls", "true");
g_action_group_change_action_state (G_ACTION_GROUP (group), "disable-proxy", g_variant_new ("b", TRUE));
verify_changed ("disable-proxy:true");
- ensure_state (group, "disable-proxy", "true");
+ ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
g_assert_false (g_socket_client_get_enable_proxy (client));
/* test toggle true->false */
g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
verify_changed ("tls:false");
g_assert_false (g_socket_client_get_tls (client));
- ensure_state (group, "tls", "false");
+ ensure_state (G_ACTION_GROUP (group), "tls", "false");
/* and now back false->true */
g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
verify_changed ("tls:true");
g_assert_true (g_socket_client_get_tls (client));
- ensure_state (group, "tls", "true");
+ ensure_state (G_ACTION_GROUP (group), "tls", "true");
g_socket_client_set_tls (client, FALSE);
verify_changed ("tls:false");
- ensure_state (group, "tls", "false");
+ ensure_state (G_ACTION_GROUP (group), "tls", "false");
/* now do the same for the inverted action */
g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
verify_changed ("disable-proxy:false");
g_assert_true (g_socket_client_get_enable_proxy (client));
- ensure_state (group, "disable-proxy", "false");
+ ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
verify_changed ("disable-proxy:true");
g_assert_false (g_socket_client_get_enable_proxy (client));
- ensure_state (group, "disable-proxy", "true");
+ ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
g_socket_client_set_enable_proxy (client, TRUE);
verify_changed ("disable-proxy:false");
- ensure_state (group, "disable-proxy", "false");
+ ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
/* enum tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "type", g_variant_new ("s", "datagram"));
verify_changed ("type:'datagram'");
g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_DATAGRAM);
- ensure_state (group, "type", "'datagram'");
+ ensure_state (G_ACTION_GROUP (group), "type", "'datagram'");
g_action_group_activate_action (G_ACTION_GROUP (group), "type", g_variant_new ("s", "stream"));
verify_changed ("type:'stream'");
g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_STREAM);
- ensure_state (group, "type", "'stream'");
+ ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
g_socket_client_set_socket_type (client, G_SOCKET_TYPE_SEQPACKET);
verify_changed ("type:'seqpacket'");
- ensure_state (group, "type", "'seqpacket'");
+ ensure_state (G_ACTION_GROUP (group), "type", "'seqpacket'");
/* Check some error cases... */
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*non-existent*");
diff --git a/gio/tests/application-command-line.c b/gio/tests/application-command-line.c
new file mode 100644
index 000000000..185d49f58
--- /dev/null
+++ b/gio/tests/application-command-line.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Philip Withnall <pwithnall@endlessos.org>
+ */
+
+#include <gio/gio.h>
+#include <locale.h>
+
+
+static void
+test_basic_properties (void)
+{
+ GApplicationCommandLine *cl = NULL;
+ const gchar * const arguments[] = { "arg1", "arg2", "arg3", NULL };
+ GVariantBuilder options_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
+ GVariantBuilder platform_data_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
+ gchar **argv = NULL;
+ int argc = 0;
+ GVariantDict *options_dict;
+ GVariant *platform_data;
+ GVariantDict *platform_data_dict = NULL;
+ gboolean is_remote;
+
+ /* Basic construction. */
+ g_variant_builder_add (&options_builder, "{sv}", "option1", g_variant_new_string ("value1"));
+ g_variant_builder_add (&options_builder, "{sv}", "option2", g_variant_new_string ("value2"));
+
+ g_variant_builder_add (&platform_data_builder, "{sv}", "data1", g_variant_new_string ("data-value1"));
+ g_variant_builder_add (&platform_data_builder, "{sv}", "data2", g_variant_new_string ("data-value2"));
+
+ cl = g_object_new (G_TYPE_APPLICATION_COMMAND_LINE,
+ "arguments", g_variant_new_bytestring_array (arguments, -1),
+ "options", g_variant_builder_end (&options_builder),
+ "platform-data", g_variant_builder_end (&platform_data_builder),
+ NULL);
+ g_assert_nonnull (cl);
+
+ /* Check the getters. */
+ argv = g_application_command_line_get_arguments (cl, &argc);
+ g_assert_cmpint (argc, ==, 3);
+ g_assert_cmpstrv (argv, arguments);
+ g_clear_pointer (&argv, g_strfreev);
+
+ options_dict = g_application_command_line_get_options_dict (cl);
+ g_assert_nonnull (options_dict);
+ g_assert_true (g_variant_dict_contains (options_dict, "option1"));
+ g_assert_true (g_variant_dict_contains (options_dict, "option2"));
+
+ g_assert_false (g_application_command_line_get_is_remote (cl));
+
+ platform_data = g_application_command_line_get_platform_data (cl);
+ g_assert_nonnull (platform_data);
+ platform_data_dict = g_variant_dict_new (platform_data);
+ g_assert_true (g_variant_dict_contains (platform_data_dict, "data1"));
+ g_assert_true (g_variant_dict_contains (platform_data_dict, "data2"));
+ g_variant_dict_unref (platform_data_dict);
+ g_variant_unref (platform_data);
+
+ /* And g_object_get(). */
+ g_object_get (cl, "is-remote", &is_remote, NULL);
+ g_assert_false (is_remote);
+
+ g_clear_object (&cl);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ setlocale (LC_ALL, "");
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/application-command-line/basic-properties", test_basic_properties);
+
+ return g_test_run ();
+}
diff --git a/gio/tests/fdo-notification-backend.c b/gio/tests/fdo-notification-backend.c
new file mode 100644
index 000000000..aed9aab67
--- /dev/null
+++ b/gio/tests/fdo-notification-backend.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright © 2022 Endless OS Foundation, LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Philip Withnall <pwithnall@endlessos.org>
+ */
+
+#include <gio/gio.h>
+#include <locale.h>
+
+#include <gio/giomodule-priv.h>
+#include "gio/gnotificationbackend.h"
+
+
+static GNotificationBackend *
+construct_backend (GApplication **app_out)
+{
+ GApplication *app = NULL;
+ GType fdo_type = G_TYPE_INVALID;
+ GNotificationBackend *backend = NULL;
+ GError *local_error = NULL;
+
+ /* Construct the app first and withdraw a notification, to ensure that IO modules are loaded. */
+ app = g_application_new ("org.gtk.TestApplication", G_APPLICATION_DEFAULT_FLAGS);
+ g_application_register (app, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_application_withdraw_notification (app, "org.gtk.TestApplication.NonexistentNotification");
+
+ fdo_type = g_type_from_name ("GFdoNotificationBackend");
+ g_assert_cmpuint (fdo_type, !=, G_TYPE_INVALID);
+
+ /* Replicate the behaviour from g_notification_backend_new_default(), which is
+ * not exported publicly so can‘t be easily used in the test. */
+ backend = g_object_new (fdo_type, NULL);
+ backend->application = app;
+ backend->dbus_connection = g_application_get_dbus_connection (app);
+ if (backend->dbus_connection)
+ g_object_ref (backend->dbus_connection);
+
+ if (app_out != NULL)
+ *app_out = g_object_ref (app);
+
+ g_clear_object (&app);
+
+ return g_steal_pointer (&backend);
+}
+
+static void
+test_construction (void)
+{
+ GNotificationBackend *backend = NULL;
+ GApplication *app = NULL;
+ GTestDBus *bus = NULL;
+
+ g_test_message ("Test constructing a GFdoNotificationBackend");
+
+ /* Set up a test session bus and connection. */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ backend = construct_backend (&app);
+ g_assert_nonnull (backend);
+
+ g_application_quit (app);
+ g_clear_object (&app);
+ g_clear_object (&backend);
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
+static void
+daemon_method_call_cb (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GDBusMethodInvocation **current_method_invocation_out = user_data;
+
+ g_assert_null (*current_method_invocation_out);
+ *current_method_invocation_out = g_steal_pointer (&invocation);
+
+ g_main_context_wakeup (NULL);
+}
+
+static void
+name_acquired_or_lost_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ gboolean *name_acquired = user_data;
+
+ *name_acquired = !*name_acquired;
+
+ g_main_context_wakeup (NULL);
+}
+
+static void
+dbus_activate_action_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ guint *n_activations = user_data;
+
+ *n_activations = *n_activations + 1;
+ g_main_context_wakeup (NULL);
+}
+
+static void
+assert_send_notification (GNotificationBackend *backend,
+ GDBusMethodInvocation **current_method_invocation,
+ guint32 notify_id)
+{
+ GNotification *notification = NULL;
+
+ notification = g_notification_new ("Some Notification");
+ G_NOTIFICATION_BACKEND_GET_CLASS (backend)->send_notification (backend, "notification1", notification);
+ g_clear_object (&notification);
+
+ while (*current_method_invocation == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_assert_cmpstr (g_dbus_method_invocation_get_interface_name (*current_method_invocation), ==, "org.freedesktop.Notifications");
+ g_assert_cmpstr (g_dbus_method_invocation_get_method_name (*current_method_invocation), ==, "Notify");
+ g_dbus_method_invocation_return_value (g_steal_pointer (current_method_invocation), g_variant_new ("(u)", notify_id));
+}
+
+static void
+assert_emit_action_invoked (GDBusConnection *daemon_connection,
+ GVariant *parameters)
+{
+ GError *local_error = NULL;
+
+ g_dbus_connection_emit_signal (daemon_connection,
+ NULL,
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "ActionInvoked",
+ parameters,
+ &local_error);
+ g_assert_no_error (local_error);
+}
+
+static void
+test_dbus_activate_action (void)
+{
+ /* Very trimmed down version of
+ * https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html */
+ const GDBusArgInfo daemon_notify_in_app_name = { -1, "AppName", "s", NULL };
+ const GDBusArgInfo daemon_notify_in_replaces_id = { -1, "ReplacesId", "u", NULL };
+ const GDBusArgInfo daemon_notify_in_app_icon = { -1, "AppIcon", "s", NULL };
+ const GDBusArgInfo daemon_notify_in_summary = { -1, "Summary", "s", NULL };
+ const GDBusArgInfo daemon_notify_in_body = { -1, "Body", "s", NULL };
+ const GDBusArgInfo daemon_notify_in_actions = { -1, "Actions", "as", NULL };
+ const GDBusArgInfo daemon_notify_in_hints = { -1, "Hints", "a{sv}", NULL };
+ const GDBusArgInfo daemon_notify_in_expire_timeout = { -1, "ExpireTimeout", "i", NULL };
+ const GDBusArgInfo *daemon_notify_in_args[] =
+ {
+ &daemon_notify_in_app_name,
+ &daemon_notify_in_replaces_id,
+ &daemon_notify_in_app_icon,
+ &daemon_notify_in_summary,
+ &daemon_notify_in_body,
+ &daemon_notify_in_actions,
+ &daemon_notify_in_hints,
+ &daemon_notify_in_expire_timeout,
+ NULL
+ };
+ const GDBusArgInfo daemon_notify_out_id = { -1, "Id", "u", NULL };
+ const GDBusArgInfo *daemon_notify_out_args[] = { &daemon_notify_out_id, NULL };
+ const GDBusMethodInfo daemon_notify_info = { -1, "Notify", (GDBusArgInfo **) daemon_notify_in_args, (GDBusArgInfo **) daemon_notify_out_args, NULL };
+ const GDBusMethodInfo *daemon_methods[] = { &daemon_notify_info, NULL };
+ const GDBusInterfaceInfo daemon_interface_info = { -1, "org.freedesktop.Notifications", (GDBusMethodInfo **) daemon_methods, NULL, NULL, NULL };
+
+ GTestDBus *bus = NULL;
+ GDBusConnection *daemon_connection = NULL;
+ guint daemon_object_id = 0, daemon_name_id = 0;
+ const GDBusInterfaceVTable vtable = { daemon_method_call_cb, NULL, NULL, { NULL, } };
+ GDBusMethodInvocation *current_method_invocation = NULL;
+ GApplication *app = NULL;
+ GNotificationBackend *backend = NULL;
+ guint32 notify_id;
+ GError *local_error = NULL;
+ const GActionEntry entries[] =
+ {
+ { "undo", dbus_activate_action_cb, NULL, NULL, NULL, { 0 } },
+ { "lang", dbus_activate_action_cb, "s", "'latin'", NULL, { 0 } },
+ };
+ guint n_activations = 0;
+ gboolean name_acquired = FALSE;
+
+ g_test_summary ("Test how the backend handles valid and invalid ActionInvoked signals from the daemon");
+
+ /* Set up a test session bus and connection. */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ /* Create a mock org.freedesktop.Notifications daemon */
+ daemon_connection = g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (bus),
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ daemon_object_id = g_dbus_connection_register_object (daemon_connection,
+ "/org/freedesktop/Notifications",
+ (GDBusInterfaceInfo *) &daemon_interface_info,
+ &vtable,
+ &current_method_invocation, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ daemon_name_id = g_bus_own_name_on_connection (daemon_connection,
+ "org.freedesktop.Notifications",
+ G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
+ name_acquired_or_lost_cb,
+ name_acquired_or_lost_cb,
+ &name_acquired, NULL);
+
+ while (!name_acquired)
+ g_main_context_iteration (NULL, TRUE);
+
+ /* Construct our FDO backend under test */
+ backend = construct_backend (&app);
+ g_action_map_add_action_entries (G_ACTION_MAP (app), entries, G_N_ELEMENTS (entries), &n_activations);
+
+ /* Send a notification to ensure that the backend is listening for D-Bus action signals. */
+ notify_id = 1233;
+ assert_send_notification (backend, &current_method_invocation, ++notify_id);
+
+ /* Send a valid fake action signal. */
+ n_activations = 0;
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo"));
+
+ while (n_activations == 0)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_assert_cmpuint (n_activations, ==, 1);
+
+ /* Send a valid fake action signal with a target. We have to create a new
+ * notification first, as invoking an action on a notification removes it, and
+ * the GLib implementation of org.freedesktop.Notifications doesn’t currently
+ * support the `resident` hint to avoid that. */
+ assert_send_notification (backend, &current_method_invocation, ++notify_id);
+ n_activations = 0;
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang::spanish"));
+
+ while (n_activations == 0)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_assert_cmpuint (n_activations, ==, 1);
+
+ /* Send a series of invalid action signals, followed by one valid one which
+ * we should be able to detect. */
+ assert_send_notification (backend, &current_method_invocation, ++notify_id);
+ n_activations = 0;
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.nonexistent"));
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang(13)"));
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo::should-have-no-parameter"));
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang"));
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "undo")); /* no `app.` prefix */
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang(")); /* invalid parse format */
+ assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo"));
+
+ while (n_activations == 0)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_assert_cmpuint (n_activations, ==, 1);
+
+ /* Shut down. */
+ g_assert_null (current_method_invocation);
+
+ g_application_quit (app);
+ g_clear_object (&app);
+ g_clear_object (&backend);
+
+ g_dbus_connection_unregister_object (daemon_connection, daemon_object_id);
+ g_bus_unown_name (daemon_name_id);
+
+ g_dbus_connection_flush_sync (daemon_connection, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_dbus_connection_close_sync (daemon_connection, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_clear_object (&daemon_connection);
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ setlocale (LC_ALL, "");
+
+ /* Force use of the FDO backend */
+ g_setenv ("GNOTIFICATION_BACKEND", "freedesktop", TRUE);
+
+ g_test_init (&argc, &argv, NULL);
+
+ /* Make sure we don’t send notifications to the actual D-Bus session. */
+ g_test_dbus_unset ();
+
+ g_test_add_func ("/fdo-notification-backend/construction", test_construction);
+ g_test_add_func ("/fdo-notification-backend/dbus/activate-action", test_dbus_activate_action);
+
+ return g_test_run ();
+}
diff --git a/gio/tests/gapplication.c b/gio/tests/gapplication.c
index bf0395cdb..4320352d1 100644
--- a/gio/tests/gapplication.c
+++ b/gio/tests/gapplication.c
@@ -1194,6 +1194,493 @@ test_replace (gconstpointer data)
}
}
+static void
+dbus_activate_cb (GApplication *app,
+ gpointer user_data)
+{
+ guint *n_activations = user_data;
+
+ *n_activations = *n_activations + 1;
+ g_main_context_wakeup (NULL);
+}
+
+static void dbus_startup_reply_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data);
+static gboolean dbus_startup_reply_idle_cb (gpointer user_data);
+
+static void
+dbus_startup_cb (GApplication *app,
+ gpointer user_data)
+{
+ GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ GDBusMessage *message = G_DBUS_MESSAGE (user_data);
+
+ g_assert_nonnull (connection);
+
+ g_dbus_connection_send_message_with_reply (connection, message,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1,
+ NULL, NULL,
+ dbus_startup_reply_cb, g_object_ref (app));
+
+ g_clear_object (&connection);
+}
+
+static void
+dbus_startup_reply_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
+ GApplication *app = G_APPLICATION (user_data);
+ GDBusMessage *reply = NULL;
+ GError *local_error = NULL;
+
+ reply = g_dbus_connection_send_message_with_reply_finish (connection, result, &local_error);
+ g_assert_no_error (local_error);
+
+ /* Nothing to check on the reply for now. */
+ g_clear_object (&reply);
+
+ /* Release the app in an idle callback, so there’s time to process other
+ * pending sources first. */
+ g_idle_add_full (G_PRIORITY_LOW, dbus_startup_reply_idle_cb, g_steal_pointer (&app), g_object_unref);
+}
+
+static gboolean
+dbus_startup_reply_idle_cb (gpointer user_data)
+{
+ GApplication *app = G_APPLICATION (user_data);
+
+ g_application_release (app);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+test_dbus_activate (void)
+{
+ GTestDBus *bus = NULL;
+ GVariantBuilder builder;
+ GDBusMessage *message = NULL;
+ GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */
+ gsize i;
+
+ g_test_summary ("Test that calling the Activate D-Bus method works");
+
+ /* Try various different messages */
+ messages = g_ptr_array_new_with_free_func (g_object_unref);
+
+ /* Via org.gtk.Application */
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate",
+ "/org/gtk/TestApplication/Activate",
+ "org.gtk.Application",
+ "Activate");
+ g_dbus_message_set_body (message, g_variant_new ("(a{sv})", NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* Via org.freedesktop.Application */
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate",
+ "/org/gtk/TestApplication/Activate",
+ "org.freedesktop.Application",
+ "Activate");
+ g_dbus_message_set_body (message, g_variant_new ("(a{sv})", NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* With some platform data */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder, "{sv}", "cwd", g_variant_new_bytestring ("/home/henry"));
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate",
+ "/org/gtk/TestApplication/Activate",
+ "org.gtk.Application",
+ "Activate");
+ g_dbus_message_set_body (message, g_variant_new ("(a{sv})", &builder));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* Try each message */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ for (i = 0; i < messages->len; i++)
+ {
+ GApplication *app = NULL;
+ gulong activate_id, startup_id;
+ guint n_activations = 0;
+
+ g_test_message ("Message %" G_GSIZE_FORMAT, i);
+
+ app = g_application_new ("org.gtk.TestApplication.Activate", G_APPLICATION_DEFAULT_FLAGS);
+ activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_cb), &n_activations);
+ startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]);
+
+ g_application_hold (app);
+ g_application_run (app, 0, NULL);
+
+ /* It’ll be activated once as normal, and once due to the D-Bus call */
+ g_assert_cmpuint (n_activations, ==, 2);
+
+ g_signal_handler_disconnect (app, startup_id);
+ g_signal_handler_disconnect (app, activate_id);
+ g_clear_object (&app);
+ }
+
+ g_ptr_array_unref (messages);
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
+static void
+dbus_activate_noop_cb (GApplication *app,
+ gpointer user_data)
+{
+ /* noop */
+}
+
+static void
+dbus_open_cb (GApplication *app,
+ gpointer files,
+ int n_files,
+ char *hint,
+ gpointer user_data)
+{
+ guint *n_opens = user_data;
+
+ *n_opens = *n_opens + 1;
+ g_main_context_wakeup (NULL);
+}
+
+static void
+test_dbus_open (void)
+{
+ GTestDBus *bus = NULL;
+ GVariantBuilder builder, builder2;
+ GDBusMessage *message = NULL;
+ GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */
+ gsize i;
+
+ g_test_summary ("Test that calling the Open D-Bus method works");
+
+ /* Try various different messages */
+ messages = g_ptr_array_new_with_free_func (g_object_unref);
+
+ /* Via org.gtk.Application */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&builder, "s", "file:///home/henry/test");
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open",
+ "/org/gtk/TestApplication/Open",
+ "org.gtk.Application",
+ "Open");
+ g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", &builder, "hint", NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* Via org.freedesktop.Application (which has no hint parameter) */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&builder, "s", "file:///home/henry/test");
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open",
+ "/org/gtk/TestApplication/Open",
+ "org.freedesktop.Application",
+ "Open");
+ g_dbus_message_set_body (message, g_variant_new ("(asa{sv})", &builder, NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* With some platform data and more than one file */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (&builder, "s", "file:///home/henry/test");
+ g_variant_builder_add (&builder, "s", "file:///home/henry/test2");
+
+ g_variant_builder_init (&builder2, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder2, "{sv}", "cwd", g_variant_new_bytestring ("/home/henry"));
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open",
+ "/org/gtk/TestApplication/Open",
+ "org.gtk.Application",
+ "Open");
+ g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", &builder, "", &builder2));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* No files */
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open",
+ "/org/gtk/TestApplication/Open",
+ "org.gtk.Application",
+ "Open");
+ g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", NULL, "", NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* Try each message */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ for (i = 0; i < messages->len; i++)
+ {
+ GApplication *app = NULL;
+ gulong activate_id, open_id, startup_id;
+ guint n_opens = 0;
+
+ g_test_message ("Message %" G_GSIZE_FORMAT, i);
+
+ app = g_application_new ("org.gtk.TestApplication.Open", G_APPLICATION_HANDLES_OPEN);
+ activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL);
+ open_id = g_signal_connect (app, "open", G_CALLBACK (dbus_open_cb), &n_opens);
+ startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]);
+
+ g_application_hold (app);
+ g_application_run (app, 0, NULL);
+
+ g_assert_cmpuint (n_opens, ==, 1);
+
+ g_signal_handler_disconnect (app, startup_id);
+ g_signal_handler_disconnect (app, open_id);
+ g_signal_handler_disconnect (app, activate_id);
+ g_clear_object (&app);
+ }
+
+ g_ptr_array_unref (messages);
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
+static void
+dbus_command_line_cb (GApplication *app,
+ GApplicationCommandLine *command_line,
+ gpointer user_data)
+{
+ guint *n_command_lines = user_data;
+
+ *n_command_lines = *n_command_lines + 1;
+ g_main_context_wakeup (NULL);
+}
+
+static void
+test_dbus_command_line (void)
+{
+ GTestDBus *bus = NULL;
+ GVariantBuilder builder, builder2;
+ GDBusMessage *message = NULL;
+ GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */
+ gsize i;
+
+ g_test_summary ("Test that calling the CommandLine D-Bus method works");
+
+ /* Try various different messages */
+ messages = g_ptr_array_new_with_free_func (g_object_unref);
+
+ /* Via org.gtk.Application */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
+ g_variant_builder_add (&builder, "^ay", "test-program");
+ g_variant_builder_add (&builder, "^ay", "--open");
+ g_variant_builder_add (&builder, "^ay", "/path/to/something");
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.CommandLine",
+ "/org/gtk/TestApplication/CommandLine",
+ "org.gtk.Application",
+ "CommandLine");
+ g_dbus_message_set_body (message, g_variant_new ("(oaaya{sv})",
+ "/my/org/gtk/private/CommandLine",
+ &builder, NULL));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* With platform data */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
+ g_variant_builder_add (&builder, "^ay", "test-program");
+ g_variant_builder_add (&builder, "^ay", "--open");
+ g_variant_builder_add (&builder, "^ay", "/path/to/something");
+
+ g_variant_builder_init (&builder2, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder2, "{sv}", "cwd", g_variant_new_bytestring ("/home"));
+ g_variant_builder_add_parsed (&builder2, "{'environ', <@aay [ b'HOME=/home/bloop', b'PATH=/blah']>}");
+ g_variant_builder_add_parsed (&builder2, "{'options', <{'a': <@u 32>, 'b': <'bloop'>}>}");
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.CommandLine",
+ "/org/gtk/TestApplication/CommandLine",
+ "org.gtk.Application",
+ "CommandLine");
+ g_dbus_message_set_body (message, g_variant_new ("(oaaya{sv})",
+ "/my/org/gtk/private/CommandLine",
+ &builder, &builder2));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* With invalid typed platform data */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
+ g_variant_builder_add (&builder, "^ay", "test-program");
+ g_variant_builder_add (&builder, "^ay", "--open");
+ g_variant_builder_add (&builder, "^ay", "/path/to/something");
+
+ g_variant_builder_init (&builder2, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder2, "{sv}", "cwd", g_variant_new_string ("/home should be a bytestring"));
+ g_variant_builder_add_parsed (&builder2, "{'environ', <['HOME=should be a bytestring', 'PATH=this also']>}");
+ g_variant_builder_add_parsed (&builder2, "{'options', <['should be a', 'dict']>}");
+
+ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.CommandLine",
+ "/org/gtk/TestApplication/CommandLine",
+ "org.gtk.Application",
+ "CommandLine");
+ g_dbus_message_set_body (message, g_variant_new ("(oaaya{sv})",
+ "/my/org/gtk/private/CommandLine",
+ &builder, &builder2));
+ g_ptr_array_add (messages, g_steal_pointer (&message));
+
+ /* Try each message */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ for (i = 0; i < messages->len; i++)
+ {
+ GApplication *app = NULL;
+ gulong activate_id, command_line_id, startup_id;
+ guint n_command_lines = 0;
+
+ g_test_message ("Message %" G_GSIZE_FORMAT, i);
+
+ app = g_application_new ("org.gtk.TestApplication.CommandLine", G_APPLICATION_HANDLES_COMMAND_LINE);
+ activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL);
+ command_line_id = g_signal_connect (app, "command-line", G_CALLBACK (dbus_command_line_cb), &n_command_lines);
+ startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]);
+
+ g_application_hold (app);
+ g_application_run (app, 0, NULL);
+
+ /* It’s called once for handling the local command line on startup, and again
+ * for the D-Bus call */
+ g_assert_cmpuint (n_command_lines, ==, 2);
+
+ g_signal_handler_disconnect (app, startup_id);
+ g_signal_handler_disconnect (app, command_line_id);
+ g_signal_handler_disconnect (app, activate_id);
+ g_clear_object (&app);
+ }
+
+ g_ptr_array_unref (messages);
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
+static void
+dbus_activate_action_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ guint *n_activations = user_data;
+
+ *n_activations = *n_activations + 1;
+ g_main_context_wakeup (NULL);
+}
+
+static void
+test_dbus_activate_action (void)
+{
+ GTestDBus *bus = NULL;
+ GVariantBuilder builder;
+ struct
+ {
+ GDBusMessage *message; /* (not nullable) (owned) */
+ guint n_expected_activations;
+ } messages[6];
+ gsize i;
+
+ g_test_summary ("Test that calling the ActivateAction D-Bus method works");
+
+ /* Action without parameter */
+ messages[0].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[0].message, g_variant_new ("(sava{sv})", "undo", NULL, NULL));
+ messages[0].n_expected_activations = 1;
+
+ /* Action with parameter */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+ g_variant_builder_add (&builder, "v", g_variant_new_string ("spanish"));
+
+ messages[1].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[1].message, g_variant_new ("(sava{sv})", "lang", &builder, NULL));
+ messages[1].n_expected_activations = 1;
+
+ /* Action with unexpected parameter */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+ g_variant_builder_add (&builder, "v", g_variant_new_string ("should not be passed"));
+
+ messages[2].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[2].message, g_variant_new ("(sava{sv})", "undo", &builder, NULL));
+ messages[2].n_expected_activations = 0;
+
+ /* Action without required parameter */
+ messages[3].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[3].message, g_variant_new ("(sava{sv})", "lang", NULL, NULL));
+ messages[3].n_expected_activations = 0;
+
+ /* Action with wrong parameter type */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+ g_variant_builder_add (&builder, "v", g_variant_new_uint32 (42));
+
+ messages[4].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[4].message, g_variant_new ("(sava{sv})", "lang", &builder, NULL));
+ messages[4].n_expected_activations = 0;
+
+ /* Nonexistent action */
+ messages[5].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction",
+ "/org/gtk/TestApplication/ActivateAction",
+ "org.freedesktop.Application",
+ "ActivateAction");
+ g_dbus_message_set_body (messages[5].message, g_variant_new ("(sava{sv})", "nonexistent", NULL, NULL));
+ messages[5].n_expected_activations = 0;
+
+ /* Try each message */
+ bus = g_test_dbus_new (G_TEST_DBUS_NONE);
+ g_test_dbus_up (bus);
+
+ for (i = 0; i < G_N_ELEMENTS (messages); i++)
+ {
+ GApplication *app = NULL;
+ gulong activate_id, startup_id;
+ const GActionEntry entries[] =
+ {
+ { "undo", dbus_activate_action_cb, NULL, NULL, NULL, { 0 } },
+ { "lang", dbus_activate_action_cb, "s", "'latin'", NULL, { 0 } },
+ };
+ guint n_activations = 0;
+
+ g_test_message ("Message %" G_GSIZE_FORMAT, i);
+
+ app = g_application_new ("org.gtk.TestApplication.ActivateAction", G_APPLICATION_DEFAULT_FLAGS);
+ activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL);
+ startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages[i].message);
+
+ /* Export some actions. */
+ g_action_map_add_action_entries (G_ACTION_MAP (app), entries, G_N_ELEMENTS (entries), &n_activations);
+
+ g_application_hold (app);
+ g_application_run (app, 0, NULL);
+
+ g_assert_cmpuint (n_activations, ==, messages[i].n_expected_activations);
+
+ g_signal_handler_disconnect (app, startup_id);
+ g_signal_handler_disconnect (app, activate_id);
+ g_clear_object (&app);
+ g_clear_object (&messages[i].message);
+ }
+
+ g_test_dbus_down (bus);
+ g_clear_object (&bus);
+}
+
int
main (int argc, char **argv)
{
@@ -1225,6 +1712,10 @@ main (int argc, char **argv)
g_test_add_func ("/gapplication/api", test_api);
g_test_add_data_func ("/gapplication/replace", GINT_TO_POINTER (TRUE), test_replace);
g_test_add_data_func ("/gapplication/no-replace", GINT_TO_POINTER (FALSE), test_replace);
+ g_test_add_func ("/gapplication/dbus/activate", test_dbus_activate);
+ g_test_add_func ("/gapplication/dbus/open", test_dbus_open);
+ g_test_add_func ("/gapplication/dbus/command-line", test_dbus_command_line);
+ g_test_add_func ("/gapplication/dbus/activate-action", test_dbus_activate_action);
return g_test_run ();
}
diff --git a/gio/tests/meson.build b/gio/tests/meson.build
index e7fa812c4..41d4e3e6c 100644
--- a/gio/tests/meson.build
+++ b/gio/tests/meson.build
@@ -44,6 +44,7 @@ giotypefuncs_inc = custom_target(
# Test programs buildable on all platforms
gio_tests = {
+ 'application-command-line': {},
'appmonitor' : {
# FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392
'should_fail' : host_system == 'darwin',
@@ -350,6 +351,7 @@ if host_machine.system() != 'windows'
'extra_sources' : extra_sources,
'suite' : ['slow'],
},
+ 'fdo-notification-backend': {},
'gdbus-auth' : {'extra_sources' : extra_sources},
'gdbus-bz627724' : {'extra_sources' : extra_sources},
'gdbus-close-pending' : {'extra_sources' : extra_sources},