char *categories;
char *startup_wm_class;
char **mime_types;
+ char **actions;
guint nodisplay : 1;
guint hidden : 1;
g_free (info->startup_wm_class);
g_strfreev (info->mime_types);
g_free (info->app_id);
+ g_strfreev (info->actions);
G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object);
}
info->startup_wm_class = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, STARTUP_WM_CLASS_KEY, NULL);
info->mime_types = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL, NULL);
bus_activatable = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, NULL);
-
+ info->actions = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ACTIONS, NULL, NULL);
+
+ /* Remove the special-case: no Actions= key just means 0 extra actions */
+ if (info->actions == NULL)
+ info->actions = g_new0 (gchar *, 0 + 1);
+
info->icon = NULL;
if (info->icon_name)
{
static gboolean
expand_application_parameters (GDesktopAppInfo *info,
+ const gchar *exec_line,
GList **uris,
int *argc,
char ***argv,
GError **error)
{
GList *uri_list = *uris;
- const char *p = info->exec;
+ const char *p = exec_line;
GString *expanded_exec;
gboolean res;
- if (info->exec == NULL)
+ if (exec_line == NULL)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Desktop file didn't specify Exec field"));
static gboolean
g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info,
GDBusConnection *session_bus,
+ const gchar *exec_line,
GList *uris,
GAppLaunchContext *launch_context,
GSpawnFlags spawn_flags,
char *display, *sn_id;
old_uris = uris;
- if (!expand_application_parameters (info, &uris,
- &argc, &argv, error))
+ if (!expand_application_parameters (info, exec_line, &uris, &argc, &argv, error))
goto out;
/* Get the subset of URIs we're launching with this process */
return path;
}
+static GVariant *
+g_desktop_app_info_make_platform_data (GDesktopAppInfo *info,
+ GList *uris,
+ GAppLaunchContext *launch_context)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+ if (launch_context)
+ {
+ GList *launched_files = create_files_for_uris (uris);
+
+ if (info->startup_notify)
+ {
+ gchar *sn_id;
+
+ sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files);
+ g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id));
+ }
+
+ g_list_free_full (launched_files, g_object_unref);
+ }
+
+ return g_variant_builder_end (&builder);
+}
+
static gboolean
g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info,
GDBusConnection *session_bus,
g_variant_builder_close (&builder);
}
- g_variant_builder_open (&builder, G_VARIANT_TYPE_VARDICT);
-
- if (launch_context)
- {
- GList *launched_files = create_files_for_uris (uris);
-
- if (info->startup_notify)
- {
- gchar *sn_id;
-
- sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files);
- g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id));
- }
-
- g_list_free_full (launched_files, g_object_unref);
- }
-
- g_variant_builder_close (&builder);
+ g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context));
/* This is non-blocking API. Similar to launching via fork()/exec()
* we don't wait around to see if the program crashed during startup.
if (session_bus && info->app_id)
g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context);
else
- success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, uris, launch_context,
+ success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context,
spawn_flags, user_setup, user_setup_data,
pid_callback, pid_callback_data, error);
return g_key_file_has_key (info->keyfile,
G_KEY_FILE_DESKTOP_GROUP, key, NULL);
}
+
+/**
+ * g_desktop_app_info_list_actions:
+ * @info: a #GDesktopAppInfo
+ *
+ * Returns the list of "additional application actions" supported on the
+ * desktop file, as per the desktop file specification.
+ *
+ * As per the specification, this is the list of actions that are
+ * explicitly listed in the "Actions" key of the [Desktop Entry] group.
+ *
+ * Similar to g_app_info_get_all(), this returns all listed actions and
+ * ignores <literal>OnlyShowIn</literal> or <literal>NotShowIn</literal>
+ * keys. Use g_desktop_app_info_should_show_action() to determine if an
+ * action should actually be shown.
+ *
+ * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL
+ *
+ * Since: 2.38
+ **/
+const gchar * const *
+g_desktop_app_info_list_actions (GDesktopAppInfo *info)
+{
+ g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
+
+ return (const gchar **) info->actions;
+}
+
+static gboolean
+app_info_has_action (GDesktopAppInfo *info,
+ const gchar *action_name)
+{
+ gint i;
+
+ for (i = 0; info->actions[i]; i++)
+ if (g_str_equal (info->actions[i], action_name))
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * g_desktop_app_info_get_action_name:
+ * @info: a #GDesktopAppInfo
+ * @action_name: the name of the action as from
+ * g_desktop_app_info_list_actions()
+ *
+ * Gets the user-visible display name of the "additional application
+ * action" specified by @action_name.
+ *
+ * This corresponds to the "Name" key within the keyfile group for the
+ * action.
+ *
+ * Returns: (transfer full): the locale-specific action name
+ *
+ * Since: 2.38
+ */
+gchar *
+g_desktop_app_info_get_action_name (GDesktopAppInfo *info,
+ const gchar *action_name)
+{
+ gchar *group_name;
+ gchar *result;
+
+ g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+ g_return_if_fail (app_info_has_action (info, action_name));
+
+ group_name = g_strdup_printf ("Desktop Action %s", action_name);
+ result = g_key_file_get_locale_string (info->keyfile, group_name, "Name", NULL, NULL);
+ g_free (group_name);
+
+ /* The spec says that the Name field must be given.
+ *
+ * If it's not, let's follow the behaviour of our get_name()
+ * implementation above and never return %NULL.
+ */
+ if (result == NULL)
+ result = g_strdup (_("Unnamed"));
+
+ return result;
+}
+
+/**
+ * g_desktop_app_info_launch_action:
+ * @info: a #GDesktopAppInfo
+ * @action_name: the name of the action as from
+ * g_desktop_app_info_list_actions()
+ * @launch_context: (allow-none): a #GAppLaunchContext
+ *
+ * Activates the named application action.
+ *
+ * You may only call this function on action names that were
+ * returned from g_desktop_app_info_list_actions().
+ *
+ * Note that if the main entry of the desktop file indicates that the
+ * application supports startup notification, and @launch_context is
+ * non-%NULL, then startup notification will be used when activating the
+ * action (and as such, invocation of the action on the receiving side
+ * must signal the end of startup notification when it is completed).
+ * This is the expected behaviour of applications declaring additional
+ * actions, as per the desktop file specification.
+ *
+ * As with g_app_info_launch() there is no way to detect failures that
+ * occur while using this function.
+ *
+ * Since: 2.38
+ */
+void
+g_desktop_app_info_launch_action (GDesktopAppInfo *info,
+ const gchar *action_name,
+ GAppLaunchContext *launch_context)
+{
+ GDBusConnection *session_bus;
+
+ g_return_if_fail (G_IS_DESKTOP_APP_INFO (info));
+ g_return_if_fail (action_name != NULL);
+ g_return_if_fail (app_info_has_action (info, action_name));
+
+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+ if (session_bus && info->app_id)
+ {
+ gchar *object_path;
+
+ object_path = object_path_from_appid (info->app_id);
+ g_dbus_connection_call (session_bus, info->app_id, object_path,
+ "org.freedesktop.Application", "ActivateAction",
+ g_variant_new ("(sav@a{sv})", action_name, NULL,
+ g_desktop_app_info_make_platform_data (info, NULL, launch_context)),
+ NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ g_free (object_path);
+ }
+ else
+ {
+ gchar *group_name;
+ gchar *exec_line;
+
+ group_name = g_strdup_printf ("Desktop Action %s", action_name);
+ exec_line = g_key_file_get_string (info->keyfile, group_name, "Exec", NULL);
+ g_free (group_name);
+
+ if (exec_line)
+ g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
+ _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, NULL);
+ }
+
+ if (session_bus != NULL)
+ {
+ g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
+ g_object_unref (session_bus);
+ }
+}
#include <gio/gdesktopappinfo.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
static char *basedir;
g_object_unref (appinfo);
}
+static void
+wait_for_file (const gchar *want_this,
+ const gchar *but_not_this,
+ const gchar *or_this)
+{
+ gint retries = 600;
+
+ /* I hate time-based conditions in tests, but this will wait up to one
+ * whole minute for "touch file" to finish running. I think it should
+ * be OK.
+ *
+ * 600 * 100ms = 60 seconds.
+ */
+ while (access (want_this, F_OK) != 0)
+ {
+ g_usleep (100000); /* 100ms */
+ g_assert (retries);
+ retries--;
+ }
+
+ g_assert (access (but_not_this, F_OK) != 0);
+ g_assert (access (or_this, F_OK) != 0);
+
+ unlink (want_this);
+ unlink (but_not_this);
+ unlink (or_this);
+}
+
+static void
+test_actions (void)
+{
+ const gchar * const *actions;
+ GDesktopAppInfo *appinfo;
+ gchar *name;
+
+ appinfo = g_desktop_app_info_new_from_filename (g_test_get_filename (G_TEST_DIST, "appinfo-test-actions.desktop", NULL));
+ g_assert (appinfo != NULL);
+
+ actions = g_desktop_app_info_list_actions (appinfo);
+ g_assert_cmpstr (actions[0], ==, "frob");
+ g_assert_cmpstr (actions[1], ==, "tweak");
+ g_assert_cmpstr (actions[2], ==, "twiddle");
+ g_assert_cmpstr (actions[3], ==, "broken");
+ g_assert_cmpstr (actions[4], ==, NULL);
+
+ name = g_desktop_app_info_get_action_name (appinfo, "frob");
+ g_assert_cmpstr (name, ==, "Frobnicate");
+ g_free (name);
+
+ name = g_desktop_app_info_get_action_name (appinfo, "tweak");
+ g_assert_cmpstr (name, ==, "Tweak");
+ g_free (name);
+
+ name = g_desktop_app_info_get_action_name (appinfo, "twiddle");
+ g_assert_cmpstr (name, ==, "Twiddle");
+ g_free (name);
+
+ name = g_desktop_app_info_get_action_name (appinfo, "broken");
+ g_assert (name != NULL);
+ g_assert (g_utf8_validate (name, -1, NULL));
+ g_free (name);
+
+ unlink ("frob"); unlink ("tweak"); unlink ("twiddle");
+
+ g_desktop_app_info_launch_action (appinfo, "frob", NULL);
+ wait_for_file ("frob", "tweak", "twiddle");
+
+ g_desktop_app_info_launch_action (appinfo, "tweak", NULL);
+ wait_for_file ("tweak", "frob", "twiddle");
+
+ g_desktop_app_info_launch_action (appinfo, "twiddle", NULL);
+ wait_for_file ("twiddle", "frob", "tweak");
+
+ g_object_unref (appinfo);
+}
+
int
main (int argc,
char *argv[])
g_test_add_func ("/desktop-app-info/fallback", test_fallback);
g_test_add_func ("/desktop-app-info/lastused", test_last_used);
g_test_add_func ("/desktop-app-info/extra-getters", test_extra_getters);
+ g_test_add_func ("/desktop-app-info/actions", test_actions);
result = g_test_run ();