Merge branch '1904-action-crashes' into 'main'

gaction: Validate actions activated over D-Bus

Closes #1904

See merge request GNOME/glib!3113
This commit is contained in:
Philip Withnall 2022-12-09 11:04:02 +00:00
commit febe922602
11 changed files with 1260 additions and 62 deletions

View File

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

View File

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

View File

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

View File

@ -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) (transfer full): the platform data, or %NULL

View File

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

View File

@ -131,7 +131,7 @@ 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)
@ -141,15 +141,30 @@ activate_action (GFdoNotificationBackend *backend,
/* Callers should not provide a floating variant here */
g_assert (parameter == NULL || !g_variant_is_floating (parameter));
if (name)
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
@ -165,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)")))
@ -187,30 +203,40 @@ 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;
gchar *name = NULL;
GVariant *target = NULL;
if (!g_action_parse_detailed_name (action, &name, &target, NULL) ||
!activate_action (backend, name, target))
notification_closed = FALSE;
if (g_action_parse_detailed_name (action, &name, &target, NULL))
{
activate_action (backend, name, target);
g_free (name);
if (target)
g_variant_unref (target);
}
g_clear_pointer (&target, g_variant_unref);
}
}
/* Get the notification again in case the action redrew it */
/* Remove the notification, as its either been explicitly closed
* (`NotificationClosed` signal) or has been closed as a result of activating
* an action successfully. GLib doesnt 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)
{
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);
}
}
}
static void

View File

@ -190,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
@ -821,9 +837,185 @@ test_dbus_export (void)
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 actions 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;
@ -1030,22 +1222,6 @@ verify_changed (const gchar *log_entry)
g_clear_pointer (&state_change_log, g_free);
}
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)
{
@ -1106,11 +1282,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);
@ -1118,88 +1294,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*");

View File

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

View File

@ -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 cant 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 doesnt 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 dont 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 ();
}

View File

@ -1194,6 +1194,481 @@ 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 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);
g_application_release (app);
g_clear_object (&app);
}
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);
/* Itll 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);
/* Its 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 +1700,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 ();
}

View File

@ -48,6 +48,7 @@ endif
# Test programs buildable on all platforms
gio_tests = {
'application-command-line': {},
'appmonitor' : {
# FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392
'can_fail' : host_system == 'darwin',
@ -405,6 +406,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},