Imported Upstream version 2.73.3
[platform/upstream/glib.git] / gio / gdesktopappinfo.c
index f1e2fdd..c46a8f0 100644 (file)
@@ -3,6 +3,8 @@
  * Copyright (C) 2006-2007 Red Hat, Inc.
  * Copyright © 2007 Ryan Lortie
  *
+ * 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
@@ -26,7 +28,9 @@
 
 /* For the #GDesktopAppInfoLookup macros; since macro deprecation is implemented
  * in the preprocessor, we need to define this before including glib.h*/
+#ifndef GLIB_DISABLE_DEPRECATION_WARNINGS
 #define GLIB_DISABLE_DEPRECATION_WARNINGS
+#endif
 
 #include <errno.h>
 #include <string.h>
@@ -47,6 +51,7 @@
 #include "gfileicon.h"
 #include <glib/gstdio.h>
 #include "glibintl.h"
+#include "glib-private.h"
 #include "giomodule-priv.h"
 #include "gappinfo.h"
 #include "gappinfoprivate.h"
@@ -87,6 +92,7 @@ enum {
 static void     g_desktop_app_info_iface_init         (GAppInfoIface    *iface);
 static gboolean g_desktop_app_info_ensure_saved       (GDesktopAppInfo  *info,
                                                        GError          **error);
+static gboolean g_desktop_app_info_load_file (GDesktopAppInfo *self);
 
 /**
  * GDesktopAppInfo:
@@ -304,6 +310,55 @@ desktop_file_dir_app_name_is_masked (DesktopFileDir *dir,
   return FALSE;
 }
 
+/* Not much to go on from https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
+ * so validate it as a non-empty alphanumeric ASCII string with `-` and `_` allowed.
+ *
+ * Validation is important as the desktop IDs are used to construct filenames,
+ * and may be set by an unprivileged caller if running in a setuid program. */
+static gboolean
+validate_xdg_desktop (const gchar *desktop)
+{
+  gsize i;
+
+  for (i = 0; desktop[i] != '\0'; i++)
+    if (desktop[i] != '-' && desktop[i] != '_' &&
+        !g_ascii_isalnum (desktop[i]))
+      return FALSE;
+
+  if (i == 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static char **
+get_valid_current_desktops (const char *value)
+{
+  char **tmp;
+  gsize i;
+  GPtrArray *valid_desktops;
+
+  if (value == NULL)
+    value = g_getenv ("XDG_CURRENT_DESKTOP");
+  if (value == NULL)
+    value = "";
+
+  tmp = g_strsplit (value, G_SEARCHPATH_SEPARATOR_S, 0);
+  valid_desktops = g_ptr_array_new_full (g_strv_length (tmp) + 1, g_free);
+  for (i = 0; tmp[i]; i++)
+    {
+      if (validate_xdg_desktop (tmp[i]))
+        g_ptr_array_add (valid_desktops, tmp[i]);
+      else
+        g_free (tmp[i]);
+    }
+  g_ptr_array_add (valid_desktops, NULL);
+  g_free (tmp);
+  tmp = (char **) g_ptr_array_steal (valid_desktops, NULL);
+  g_ptr_array_unref (valid_desktops);
+  return tmp;
+}
+
 static const gchar * const *
 get_lowercase_current_desktops (void)
 {
@@ -311,23 +366,15 @@ get_lowercase_current_desktops (void)
 
   if (g_once_init_enter (&result))
     {
-      const gchar *envvar;
-      gchar **tmp;
-
-      envvar = g_getenv ("XDG_CURRENT_DESKTOP");
+      char **tmp = get_valid_current_desktops (NULL);
+      gsize i, j;
 
-      if (envvar)
+      for (i = 0; tmp[i]; i++)
         {
-          gint i, j;
-
-          tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0);
-
-          for (i = 0; tmp[i]; i++)
-            for (j = 0; tmp[i][j]; j++)
-              tmp[i][j] = g_ascii_tolower (tmp[i][j]);
+          /* Convert to lowercase. */
+          for (j = 0; tmp[i][j]; j++)
+            tmp[i][j] = g_ascii_tolower (tmp[i][j]);
         }
-      else
-        tmp = g_new0 (gchar *, 0 + 1);
 
       g_once_init_leave (&result, tmp);
     }
@@ -342,15 +389,7 @@ get_current_desktops (const gchar *value)
 
   if (g_once_init_enter (&result))
     {
-      gchar **tmp;
-
-      if (!value)
-        value = g_getenv ("XDG_CURRENT_DESKTOP");
-
-      if (!value)
-        value = "";
-
-      tmp = g_strsplit (value, ":", 0);
+      char **tmp = get_valid_current_desktops (value);
 
       g_once_init_leave (&result, tmp);
     }
@@ -417,7 +456,7 @@ const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
 };
 
 /* Common prefix commands to ignore from Exec= lines */
-const char * const exec_key_match_blacklist[] = {
+const char * const exec_key_match_blocklist[] = {
   "bash",
   "env",
   "flatpak",
@@ -772,7 +811,7 @@ desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir,
 static void
 expand_strv (gchar         ***strv_ptr,
              gchar          **to_add,
-             gchar * const   *blacklist)
+             gchar * const   *blocklist)
 {
   guint strv_len, add_len;
   gchar **strv;
@@ -791,10 +830,10 @@ expand_strv (gchar         ***strv_ptr,
 
   for (i = 0; to_add[i]; i++)
     {
-      /* Don't add blacklisted strings */
-      if (blacklist)
-        for (j = 0; blacklist[j]; j++)
-          if (g_str_equal (to_add[i], blacklist[j]))
+      /* Don't add blocklisted strings */
+      if (blocklist)
+        for (j = 0; blocklist[j]; j++)
+          if (g_str_equal (to_add[i], blocklist[j]))
             goto no_add;
 
       /* Don't add duplicates already in the list */
@@ -916,7 +955,7 @@ desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
 
   dir->mime_tweaks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_mime_tweaks);
 
-  /* We process in order of precedence, using a blacklisting approach to
+  /* We process in order of precedence, using a blocklisting approach to
    * avoid recording later instructions that conflict with ones we found
    * earlier.
    *
@@ -969,6 +1008,19 @@ desktop_file_dir_unindexed_init (DesktopFileDir *dir)
 }
 
 static GDesktopAppInfo *
+g_desktop_app_info_new_from_filename_unlocked (const char *filename)
+{
+  GDesktopAppInfo *info = NULL;
+
+  info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL);
+
+  if (!g_desktop_app_info_load_file (info))
+    g_clear_object (&info);
+
+  return info;
+}
+
+static GDesktopAppInfo *
 desktop_file_dir_unindexed_get_app (DesktopFileDir *dir,
                                     const gchar    *desktop_id)
 {
@@ -979,7 +1031,7 @@ desktop_file_dir_unindexed_get_app (DesktopFileDir *dir,
   if (!filename)
     return NULL;
 
-  return g_desktop_app_info_new_from_filename (filename);
+  return g_desktop_app_info_new_from_filename_unlocked (filename);
 }
 
 static void
@@ -999,7 +1051,7 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
       if (desktop_file_dir_app_name_is_masked (dir, app_name))
         continue;
 
-      add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename (filename));
+      add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename_unlocked (filename));
     }
 }
 
@@ -1108,7 +1160,7 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
         {
           /* Index the interesting keys... */
           gchar **implements;
-          gint i;
+          gsize i;
 
           for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
             {
@@ -1135,8 +1187,8 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
                   if ((slash = strrchr (raw, '/')))
                     value = slash + 1;
 
-                  /* Don't match on blacklisted binaries like interpreters */
-                  if (g_strv_contains (exec_key_match_blacklist, value))
+                  /* Don't match on blocklisted binaries like interpreters */
+                  if (g_strv_contains (exec_key_match_blocklist, value))
                    value = NULL;
                 }
 
@@ -1187,7 +1239,7 @@ static gboolean
 array_contains (GPtrArray *array,
                 const gchar *str)
 {
-  gint i;
+  guint i;
 
   for (i = 0; i < array->len; i++)
     if (g_str_equal (array->pdata[i], str))
@@ -1200,7 +1252,7 @@ static void
 desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir,
                                         const gchar    *mime_type,
                                         GPtrArray      *hits,
-                                        GPtrArray      *blacklist)
+                                        GPtrArray      *blocklist)
 {
   UnindexedMimeTweaks *tweaks;
   gint i;
@@ -1217,7 +1269,7 @@ desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir,
           gchar *app_name = tweaks->additions[i];
 
           if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
-              !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
+              !array_contains (blocklist, app_name) && !array_contains (hits, app_name))
             g_ptr_array_add (hits, app_name);
         }
     }
@@ -1229,8 +1281,8 @@ desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir,
           gchar *app_name = tweaks->removals[i];
 
           if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
-              !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
-            g_ptr_array_add (blacklist, app_name);
+              !array_contains (blocklist, app_name) && !array_contains (hits, app_name))
+            g_ptr_array_add (blocklist, app_name);
         }
     }
 }
@@ -1448,14 +1500,14 @@ desktop_file_dir_get_all (DesktopFileDir *dir,
  * @dir: a #DesktopFileDir
  * @mime_type: the mime type to look up
  * @hits: the array to store the hits
- * @blacklist: the array to store the blacklist
+ * @blocklist: the array to store the blocklist
  *
  * Does a lookup of a mimetype against one desktop file directory,
- * recording any hits and blacklisting and "Removed" associations (so
+ * recording any hits and blocklisting and "Removed" associations (so
  * later directories don't record them as hits).
  *
- * The items added to @hits are duplicated, but the ones in @blacklist
- * are weak pointers.  This facilitates simply freeing the blacklist
+ * The items added to @hits are duplicated, but the ones in @blocklist
+ * are weak pointers.  This facilitates simply freeing the blocklist
  * (which is only used for internal bookkeeping) but using the pdata of
  * @hits as the result of the operation.
  */
@@ -1463,9 +1515,9 @@ static void
 desktop_file_dir_mime_lookup (DesktopFileDir *dir,
                               const gchar    *mime_type,
                               GPtrArray      *hits,
-                              GPtrArray      *blacklist)
+                              GPtrArray      *blocklist)
 {
-  desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blacklist);
+  desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blocklist);
 }
 
 /*< internal >
@@ -1511,7 +1563,7 @@ desktop_file_dir_get_implementations (DesktopFileDir  *dir,
 static void
 desktop_file_dirs_lock (void)
 {
-  gint i;
+  guint i;
   const gchar *user_config_dir = g_get_user_config_dir ();
 
   g_mutex_lock (&desktop_file_dir_lock);
@@ -1720,6 +1772,56 @@ binary_from_exec (const char *exec)
   return g_strndup (start, p - start);
 }
 
+/*< internal >
+ * g_desktop_app_info_get_desktop_id_for_filename
+ * @self: #GDesktopAppInfo to get desktop id of
+ *
+ * Tries to find the desktop ID for a particular `.desktop` filename, as per the
+ * [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-
+ * entry-spec/desktop-entry-spec-latest.html#desktop-file-id).
+ *
+ * Returns: desktop id or basename if filename is unknown.
+ */
+static char *
+g_desktop_app_info_get_desktop_id_for_filename (GDesktopAppInfo *self)
+{
+  guint i;
+  gchar *desktop_id = NULL;
+
+  g_return_val_if_fail (self->filename != NULL, NULL);
+
+  for (i = 0; i < desktop_file_dirs->len; i++)
+    {
+      DesktopFileDir *dir = g_ptr_array_index (desktop_file_dirs, i);
+      GHashTable *app_names;
+      GHashTableIter iter;
+      gpointer key, value;
+
+      app_names = dir->app_names;
+
+      if (!app_names)
+        continue;
+
+      g_hash_table_iter_init (&iter, app_names);
+      while (g_hash_table_iter_next (&iter, &key, &value))
+        {
+          if (!strcmp (value, self->filename))
+            {
+              desktop_id = g_strdup (key);
+              break;
+            }
+        }
+
+      if (desktop_id)
+        break;
+    }
+
+  if (!desktop_id)
+    desktop_id = g_path_get_basename (self->filename);
+
+  return g_steal_pointer (&desktop_id);
+}
+
 static gboolean
 g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info,
                                       GKeyFile        *key_file)
@@ -1782,6 +1884,10 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info,
       else
         {
           char *t;
+
+          /* Since @exec is not an empty string, there must be at least one
+           * argument, so dereferencing argv[0] should return non-NULL. */
+          g_assert (argc > 0);
           t = g_find_program_in_path (argv[0]);
           g_strfreev (argv);
 
@@ -1878,6 +1984,9 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info,
       g_free (basename);
     }
 
+  if (info->filename)
+    info->desktop_id = g_desktop_app_info_get_desktop_id_for_filename (info);
+
   info->keyfile = g_key_file_ref (key_file);
 
   return TRUE;
@@ -1891,8 +2000,6 @@ g_desktop_app_info_load_file (GDesktopAppInfo *self)
 
   g_return_val_if_fail (self->filename != NULL, FALSE);
 
-  self->desktop_id = g_path_get_basename (self->filename);
-
   key_file = g_key_file_new ();
 
   if (g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, NULL))
@@ -1919,11 +2026,14 @@ g_desktop_app_info_new_from_keyfile (GKeyFile *key_file)
 
   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
   info->filename = NULL;
+
+  desktop_file_dirs_lock ();
+
   if (!g_desktop_app_info_load_from_keyfile (info, key_file))
-    {
-      g_object_unref (info);
-      return NULL;
-    }
+    g_clear_object (&info);
+
+  desktop_file_dirs_unlock ();
+
   return info;
 }
 
@@ -1941,12 +2051,12 @@ g_desktop_app_info_new_from_filename (const char *filename)
 {
   GDesktopAppInfo *info = NULL;
 
-  info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL);
-  if (!g_desktop_app_info_load_file (info))
-    {
-      g_object_unref (info);
-      return NULL;
-    }
+  desktop_file_dirs_lock ();
+
+  info = g_desktop_app_info_new_from_filename_unlocked (filename);
+
+  desktop_file_dirs_unlock ();
+
   return info;
 }
 
@@ -2106,7 +2216,7 @@ g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
  * situations such as the #GDesktopAppInfo returned from
  * g_desktop_app_info_new_from_keyfile(), this function will return %NULL.
  *
- * Returns: (type filename): The full path to the file for @info,
+ * Returns: (nullable) (type filename): The full path to the file for @info,
  *     or %NULL if not known.
  * Since: 2.24
  */
@@ -2154,7 +2264,7 @@ g_desktop_app_info_get_icon (GAppInfo *appinfo)
  *
  * Gets the categories from the desktop file.
  *
- * Returns: The unparsed Categories key from the desktop file;
+ * Returns: (nullable): The unparsed Categories key from the desktop file;
  *     i.e. no attempt is made to split it by ';' or validate it.
  */
 const char *
@@ -2183,9 +2293,9 @@ g_desktop_app_info_get_keywords (GDesktopAppInfo *info)
  * g_desktop_app_info_get_generic_name:
  * @info: a #GDesktopAppInfo
  *
- * Gets the generic name from the destkop file.
+ * Gets the generic name from the desktop file.
  *
- * Returns: The value of the GenericName key
+ * Returns: (nullable): The value of the GenericName key
  */
 const char *
 g_desktop_app_info_get_generic_name (GDesktopAppInfo *info)
@@ -2199,7 +2309,7 @@ g_desktop_app_info_get_generic_name (GDesktopAppInfo *info)
  *
  * Gets the value of the NoDisplay key, which helps determine if the
  * application info should be shown in menus. See
- * #G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show().
+ * %G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show().
  *
  * Returns: The value of the NoDisplay key
  *
@@ -2567,6 +2677,10 @@ prepend_terminal_to_vector (int    *argc,
       else
         {
           if (check == NULL)
+            check = g_find_program_in_path ("tilix");
+          if (check == NULL)
+            check = g_find_program_in_path ("konsole");
+          if (check == NULL)
             check = g_find_program_in_path ("nxterm");
           if (check == NULL)
             check = g_find_program_in_path ("color-xterm");
@@ -2575,9 +2689,12 @@ prepend_terminal_to_vector (int    *argc,
           if (check == NULL)
             check = g_find_program_in_path ("dtterm");
           if (check == NULL)
+            check = g_find_program_in_path ("xterm");
+          if (check == NULL)
             {
-              check = g_strdup ("xterm");
-              g_debug ("Couldn’t find a terminal: falling back to xterm");
+              g_debug ("Couldn’t find a known terminal");
+              g_free (term_argv);
+              return FALSE;
             }
           term_argv[0] = check;
           term_argv[1] = g_strdup ("-e");
@@ -2690,6 +2807,26 @@ notify_desktop_launch (GDBusConnection  *session_bus,
   g_object_unref (msg);
 }
 
+static void
+emit_launch_started (GAppLaunchContext *context,
+                     GDesktopAppInfo   *info,
+                     const gchar       *startup_id)
+{
+  GVariantBuilder builder;
+  GVariant *platform_data = NULL;
+
+  if (startup_id)
+    {
+      g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+      g_variant_builder_add (&builder, "{sv}",
+                             "startup-notification-id",
+                             g_variant_new_string (startup_id));
+      platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
+    }
+  g_signal_emit_by_name (context, "launch-started", info, platform_data);
+  g_clear_pointer (&platform_data, g_variant_unref);
+}
+
 #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH)
 
 static gboolean
@@ -2728,7 +2865,7 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
    * internally by expand_macro(), so we need to pass a copy of it instead,
    * and also use that copy to control the exit condition of the loop below.
    */
-  dup_uris = g_list_copy (uris);
+  dup_uris = uris;
   do
     {
       GPid pid;
@@ -2776,14 +2913,22 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
             }
 
           g_list_free_full (launched_files, g_object_unref);
+
+          emit_launch_started (launch_context, info, sn_id);
         }
 
       if (g_once_init_enter (&gio_launch_desktop_path))
         {
-          const gchar *tmp;
+          const gchar *tmp = NULL;
+          gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
 
           /* Allow test suite to specify path to gio-launch-desktop */
-          tmp = g_getenv ("GIO_LAUNCH_DESKTOP");
+          if (!is_setuid)
+            tmp = g_getenv ("GIO_LAUNCH_DESKTOP");
+
+          /* Allow build system to specify path to gio-launch-desktop */
+          if (tmp == NULL && g_file_test (GIO_LAUNCH_DESKTOP, G_FILE_TEST_IS_EXECUTABLE))
+            tmp = GIO_LAUNCH_DESKTOP;
 
           /* Fall back on usual searching in $PATH */
           if (tmp == NULL)
@@ -2857,7 +3002,6 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
   completed = TRUE;
 
  out:
-  g_list_free (dup_uris);
   g_strfreev (argv);
   g_strfreev (envp);
 
@@ -2910,6 +3054,64 @@ g_desktop_app_info_make_platform_data (GDesktopAppInfo   *info,
   return g_variant_builder_end (&builder);
 }
 
+typedef struct
+{
+  GDesktopAppInfo     *info; /* (owned) */
+  GAppLaunchContext   *launch_context; /* (owned) (nullable) */
+  GAsyncReadyCallback  callback;
+  gchar               *startup_id; /* (owned) */
+  gpointer             user_data;
+} LaunchUrisWithDBusData;
+
+static void
+launch_uris_with_dbus_data_free (LaunchUrisWithDBusData *data)
+{
+  g_clear_object (&data->info);
+  g_clear_object (&data->launch_context);
+  g_free (data->startup_id);
+
+  g_free (data);
+}
+
+static void
+launch_uris_with_dbus_signal_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  LaunchUrisWithDBusData *data = user_data;
+  GVariantBuilder builder;
+
+  if (data->launch_context)
+    {
+      if (g_task_had_error (G_TASK (result)))
+        g_app_launch_context_launch_failed (data->launch_context, data->startup_id);
+      else
+        {
+          GVariant *platform_data;
+
+          g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+          /* the docs guarantee `pid` will be set, but we can’t
+           * easily know it for a D-Bus process, so set it to zero */
+          g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (0));
+          if (data->startup_id)
+            g_variant_builder_add (&builder, "{sv}",
+                                   "startup-notification-id",
+                                   g_variant_new_string (data->startup_id));
+          platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
+          g_signal_emit_by_name (data->launch_context,
+                                 "launched",
+                                 data->info,
+                                 platform_data);
+          g_variant_unref (platform_data);
+        }
+    }
+
+  if (data->callback)
+    data->callback (object, result, data->user_data);
+
+  launch_uris_with_dbus_data_free (data);
+}
+
 static void
 launch_uris_with_dbus (GDesktopAppInfo    *info,
                        GDBusConnection    *session_bus,
@@ -2919,8 +3121,11 @@ launch_uris_with_dbus (GDesktopAppInfo    *info,
                        GAsyncReadyCallback callback,
                        gpointer            user_data)
 {
+  GVariant *platform_data;
   GVariantBuilder builder;
+  GVariantDict dict;
   gchar *object_path;
+  LaunchUrisWithDBusData *data;
 
   g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
 
@@ -2934,14 +3139,29 @@ launch_uris_with_dbus (GDesktopAppInfo    *info,
       g_variant_builder_close (&builder);
     }
 
-  g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context));
+  platform_data = g_desktop_app_info_make_platform_data (info, uris, launch_context);
 
+  g_variant_builder_add_value (&builder, platform_data);
   object_path = object_path_from_appid (info->app_id);
+
+  data = g_new0 (LaunchUrisWithDBusData, 1);
+  data->info = g_object_ref (info);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->launch_context = launch_context ? g_object_ref (launch_context) : NULL;
+  g_variant_dict_init (&dict, platform_data);
+  g_variant_dict_lookup (&dict, "desktop-startup-id", "s", &data->startup_id);
+
+  if (launch_context)
+    emit_launch_started (launch_context, info, data->startup_id);
+
   g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application",
                           uris ? "Open" : "Activate", g_variant_builder_end (&builder),
                           NULL, G_DBUS_CALL_FLAGS_NONE, -1,
-                          cancellable, callback, user_data);
+                          cancellable, launch_uris_with_dbus_signal_cb, g_steal_pointer (&data));
   g_free (object_path);
+
+  g_variant_dict_clear (&dict);
 }
 
 static gboolean
@@ -3128,11 +3348,16 @@ launch_uris_bus_get_cb (GObject      *object,
           g_task_return_error (task, g_steal_pointer (&error));
           g_object_unref (task);
         }
-      else
+      else if (session_bus)
         g_dbus_connection_flush (session_bus,
                                  cancellable,
                                  launch_uris_flush_cb,
                                  g_steal_pointer (&task));
+      else
+        {
+          g_task_return_boolean (task, TRUE);
+          g_clear_object (&task);
+        }
     }
 
   g_clear_object (&session_bus);
@@ -3624,7 +3849,9 @@ update_mimeapps_list (const char  *desktop_id,
   data = g_key_file_to_data (key_file, &data_size, error);
   g_key_file_free (key_file);
 
-  res = g_file_set_contents (filename, data, data_size, error);
+  res = g_file_set_contents_full (filename, data, data_size,
+                                  G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
+                                  0600, error);
 
   desktop_file_dirs_invalidate_user_config ();
 
@@ -3686,7 +3913,7 @@ update_program_done (GPid     pid,
                      gpointer data)
 {
   /* Did the application exit correctly */
-  if (g_spawn_check_exit_status (status, NULL))
+  if (g_spawn_check_wait_status (status, NULL))
     {
       /* Here we could clean out any caches in use */
     }
@@ -3768,7 +3995,9 @@ g_desktop_app_info_set_as_default_for_extension (GAppInfo    *appinfo,
                          " </mime-type>\n"
                          "</mime-info>\n", mimetype, extension, extension);
 
-      g_file_set_contents (filename, contents, -1, NULL);
+      g_file_set_contents_full (filename, contents, -1,
+                                G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
+                                0600, NULL);
       g_free (contents);
 
       run_update_command ("update-mime-database", "mime");
@@ -3917,7 +4146,9 @@ g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
   /* FIXME - actually handle error */
   (void) g_close (fd, NULL);
 
-  res = g_file_set_contents (filename, data, data_size, error);
+  res = g_file_set_contents_full (filename, data, data_size,
+                                  G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
+                                  0600, error);
   g_free (data);
   if (!res)
     {
@@ -4095,7 +4326,7 @@ get_list_of_mimetypes (const gchar *content_type,
 
   if (include_fallback)
     {
-      gint i;
+      guint i;
 
       /* Iterate the array as we grow it, until we have nothing more to add */
       for (i = 0; i < array->len; i++)
@@ -4124,12 +4355,12 @@ static gchar **
 g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
                                                      gboolean     include_fallback)
 {
-  GPtrArray *hits, *blacklist;
+  GPtrArray *hits, *blocklist;
   gchar **types;
-  gint i, j;
+  guint i, j;
 
   hits = g_ptr_array_new ();
-  blacklist = g_ptr_array_new ();
+  blocklist = g_ptr_array_new ();
 
   types = get_list_of_mimetypes (content_type, include_fallback);
 
@@ -4137,7 +4368,7 @@ g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
 
   for (i = 0; types[i]; i++)
     for (j = 0; j < desktop_file_dirs->len; j++)
-      desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], hits, blacklist);
+      desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], hits, blocklist);
 
   /* We will keep the hits past unlocking, so we must dup them */
   for (i = 0; i < hits->len; i++)
@@ -4147,7 +4378,7 @@ g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
 
   g_ptr_array_add (hits, NULL);
 
-  g_ptr_array_free (blacklist, TRUE);
+  g_ptr_array_free (blocklist, TRUE);
   g_strfreev (types);
 
   return (gchar **) g_ptr_array_free (hits, FALSE);
@@ -4313,24 +4544,24 @@ g_app_info_reset_type_associations (const char *content_type)
  *
  * Gets the default #GAppInfo for a given content type.
  *
- * Returns: (transfer full): #GAppInfo for given @content_type or
+ * Returns: (transfer full) (nullable): #GAppInfo for given @content_type or
  *     %NULL on error.
  */
 GAppInfo *
 g_app_info_get_default_for_type (const char *content_type,
                                  gboolean    must_support_uris)
 {
-  GPtrArray *blacklist;
+  GPtrArray *blocklist;
   GPtrArray *results;
   GAppInfo *info;
   gchar **types;
-  gint i, j, k;
+  guint i, j, k;
 
   g_return_val_if_fail (content_type != NULL, NULL);
 
   types = get_list_of_mimetypes (content_type, TRUE);
 
-  blacklist = g_ptr_array_new ();
+  blocklist = g_ptr_array_new ();
   results = g_ptr_array_new ();
   info = NULL;
 
@@ -4344,7 +4575,7 @@ g_app_info_get_default_for_type (const char *content_type,
 
       /* Consider the associations as well... */
       for (j = 0; j < desktop_file_dirs->len; j++)
-        desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results, blacklist);
+        desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results, blocklist);
 
       /* (If any), see if one of those apps is installed... */
       for (j = 0; j < results->len; j++)
@@ -4366,7 +4597,7 @@ g_app_info_get_default_for_type (const char *content_type,
         }
 
       /* Reset the list, ready to try again with the next (parent)
-       * mimetype, but keep the blacklist in place.
+       * mimetype, but keep the blocklist in place.
        */
       g_ptr_array_set_size (results, 0);
     }
@@ -4374,7 +4605,7 @@ g_app_info_get_default_for_type (const char *content_type,
 out:
   desktop_file_dirs_unlock ();
 
-  g_ptr_array_unref (blacklist);
+  g_ptr_array_unref (blocklist);
   g_ptr_array_unref (results);
   g_strfreev (types);
 
@@ -4390,7 +4621,8 @@ out:
  * of the URI, up to but not including the ':', e.g. "http",
  * "ftp" or "sip".
  *
- * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
+ * Returns: (transfer full) (nullable): #GAppInfo for given @uri_scheme or
+ *     %NULL on error.
  */
 GAppInfo *
 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
@@ -4398,6 +4630,8 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
   GAppInfo *app_info;
   char *content_type, *scheme_down;
 
+  g_return_val_if_fail (uri_scheme != NULL && *uri_scheme != '\0', NULL);
+
   scheme_down = g_ascii_strdown (uri_scheme, -1);
   content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down);
   g_free (scheme_down);
@@ -4428,7 +4662,7 @@ g_desktop_app_info_get_implementations (const gchar *interface)
 {
   GList *result = NULL;
   GList **ptr;
-  gint i;
+  guint i;
 
   desktop_file_dirs_lock ();
 
@@ -4471,6 +4705,13 @@ g_desktop_app_info_get_implementations (const gchar *interface)
  * The algorithm for determining matches is undefined and may change at
  * any time.
  *
+ * None of the search results are subjected to the normal validation
+ * checks performed by g_desktop_app_info_new() (for example, checking that
+ * the executable referenced by a result exists), and so it is possible for
+ * g_desktop_app_info_new() to return %NULL when passed an app ID returned by
+ * this function. It is expected that calling code will do this when
+ * subsequently creating a #GDesktopAppInfo for each result.
+ *
  * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
  *   list of strvs.  Free each item with g_strfreev() and free the outer
  *   list with g_free().
@@ -4484,6 +4725,7 @@ g_desktop_app_info_search (const gchar *search_string)
   gint n_categories = 0;
   gint start_of_category;
   gint i, j;
+  guint k;
 
   search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
 
@@ -4491,11 +4733,11 @@ g_desktop_app_info_search (const gchar *search_string)
 
   reset_total_search_results ();
 
-  for (i = 0; i < desktop_file_dirs->len; i++)
+  for (k = 0; k < desktop_file_dirs->len; k++)
     {
       for (j = 0; search_tokens[j]; j++)
         {
-          desktop_file_dir_search (g_ptr_array_index (desktop_file_dirs, i), search_tokens[j]);
+          desktop_file_dir_search (g_ptr_array_index (desktop_file_dirs, k), search_tokens[j]);
           merge_token_results (j == 0);
         }
       merge_directory_results ();
@@ -4563,7 +4805,7 @@ g_app_info_get_all (void)
   GHashTable *apps;
   GHashTableIter iter;
   gpointer value;
-  int i;
+  guint i;
   GList *infos;
 
   apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
@@ -4626,7 +4868,8 @@ g_desktop_app_info_lookup_default_init (GDesktopAppInfoLookupInterface *iface)
  * in a GIO module. There is no reason for applications to use it
  * directly. Applications should use g_app_info_get_default_for_uri_scheme().
  *
- * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
+ * Returns: (transfer full) (nullable): #GAppInfo for given @uri_scheme or
+ *    %NULL on error.
  *
  * Deprecated: 2.28: The #GDesktopAppInfoLookup interface is deprecated and
  *    unused by GIO.
@@ -4656,7 +4899,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
  * WM_CLASS property of the main window of the application, if launched
  * through @info.
  *
- * Returns: (transfer none): the startup WM class, or %NULL if none is set
+ * Returns: (nullable) (transfer none): the startup WM class, or %NULL if none is set
  * in the desktop file.
  *
  * Since: 2.34
@@ -4678,7 +4921,7 @@ g_desktop_app_info_get_startup_wm_class (GDesktopAppInfo *info)
  *
  * The @key is looked up in the "Desktop Entry" group.
  *
- * Returns: a newly allocated string, or %NULL if the key
+ * Returns: (nullable): a newly allocated string, or %NULL if the key
  *     is not found
  *
  * Since: 2.36