static void
on_transient_window_title_changed (MetaWindow      *window,
                                   GParamSpec      *spec,
                                   ShellWindowTracker *self)
{
  ShellAppSystem *appsys;
  ShellApp *app;
  const char *id;

  /* Check if we now have a mapping using the window title */
  id = get_app_id_from_title (window);
  if (id == NULL)
    return;

  appsys = shell_app_system_get_default ();
  app = shell_app_system_get_app (appsys, id);
  if (app == NULL)
    return;
  g_object_unref (app);

  /* We found an app, don't listen for further title changes */
  g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_transient_window_title_changed),
                                        self);

  /* It's simplest to just treat this as a remove + add. */
  disassociate_window (self, window);
  track_window (self, window);
}
/**
 * get_app_from_window_wmclass:
 *
 * Looks only at the given window, and attempts to determine
 * an application based on WM_CLASS.  If one can't be determined,
 * return %NULL.
 *
 * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
 */
static ShellApp *
get_app_from_window_wmclass (MetaWindow  *window)
{
  ShellApp *app;
  ShellAppSystem *appsys;
  char *wmclass;
  char *with_desktop;

  appsys = shell_app_system_get_default ();
  wmclass = get_appid_from_window (window);

  if (!wmclass)
    return NULL;

  with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL);
  g_free (wmclass);

  app = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
  g_free (with_desktop);

  if (app == NULL)
    {
      const char *id = get_app_id_from_title (window);

      if (id != NULL)
        app = shell_app_system_get_app (appsys, id);
    }

  return app;
}
/**
 * shell_app_system_get_app_for_path:
 * @system: a #ShellAppSystem
 * @desktop_path: (type utf8): UTF-8 encoded absolute file name
 *
 * Find or create a #ShellApp corresponding to a given absolute
 * file name which must be in the standard paths (XDG_DATA_DIRS).
 * For files outside the datadirs, this function returns %NULL.
 *
 * If already cached elsewhere in memory, return that instance.
 * Otherwise, create a new one.
 *
 * Return value: (transfer full): The #ShellApp for id, or %NULL if none
 */
ShellApp *
shell_app_system_get_app_for_path (ShellAppSystem   *system,
                                   const char       *desktop_path)
{
  const char *basename;
  ShellAppInfo *info;

  basename = g_strrstr (desktop_path, "/");
  if (basename)
    basename += 1;
  else
    basename = desktop_path;

  info = g_hash_table_lookup (system->priv->app_id_to_info, basename);
  if (!info)
    return NULL;

  if (info->type == SHELL_APP_INFO_TYPE_ENTRY)
    {
      const char *full_path = gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry*) info->entry);
      if (strcmp (desktop_path, full_path) != 0)
        return NULL;
    }
  else
    return NULL;

  return shell_app_system_get_app (system, basename);
}
/**
 * shell_app_system_lookup_heuristic_basename:
 * @system: a #ShellAppSystem
 * @id: Probable application identifier
 *
 * Find a valid application corresponding to a given
 * heuristically determined application identifier
 * string, or %NULL if none.
 *
 * Returns: (transfer full): A #ShellApp for @name
 */
ShellApp *
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
                                            const char *name)
{
  ShellApp *result;
  GSList *prefix;
  result = shell_app_system_get_app (system, name);
  if (result != NULL)
    return result;
  for (prefix = system->priv->known_vendor_prefixes; prefix; prefix = g_slist_next (prefix))
    {
      char *tmpid = g_strconcat ((char*)prefix->data, name, NULL);
      result = shell_app_system_get_app (system, tmpid);
      g_free (tmpid);
      if (result != NULL)
        return result;
    }

  return NULL;
}
/**
 * shell_app_system_lookup_heuristic_basename:
 * @name: Probable application identifier
 *
 * Find a valid application corresponding to a given
 * heuristically determined application identifier
 * string, or %NULL if none.
 *
 * Returns: (transfer full): A #ShellApp for name
 */
ShellApp *
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
                                            const char *name)
{
  ShellApp *result;
  char **vendor_prefixes;

  result = shell_app_system_get_app (system, name);
  if (result != NULL)
    return result;

  for (vendor_prefixes = (char**)known_vendor_prefixes;
       *vendor_prefixes; vendor_prefixes++)
    {
      char *tmpid = g_strjoin (NULL, *vendor_prefixes, "-", name, NULL);
      result = shell_app_system_get_app (system, tmpid);
      g_free (tmpid);
      if (result != NULL)
        return result;
    }

  return NULL;
}
/**
 * shell_app_info_launch_full:
 * @timestamp: Event timestamp, or 0 for current event timestamp
 * @uris: List of uris to pass to application
 * @workspace: Start on this workspace, or -1 for default
 * @startup_id: (out): Returned startup notification ID, or %NULL if none
 * @error: A #GError
 */
gboolean
shell_app_info_launch_full (ShellAppInfo *info,
                            guint         timestamp,
                            GList        *uris,
                            int           workspace,
                            char        **startup_id,
                            GError      **error)
{
  ShellApp *shell_app;
  GDesktopAppInfo *gapp;
  GdkAppLaunchContext *context;
  gboolean ret;
  ShellGlobal *global;
  MetaScreen *screen;

  if (startup_id)
    *startup_id = NULL;

  if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
    {
      /* We can't pass URIs into a window; shouldn't hit this
       * code path.  If we do, fix the caller to disallow it.
       */
      g_return_val_if_fail (uris == NULL, TRUE);

      meta_window_activate (info->window, timestamp);
      return TRUE;
    }
  else if (info->type == SHELL_APP_INFO_TYPE_ENTRY)
    {
      /* Can't use g_desktop_app_info_new, see bug 614879 */
      const char *filename = gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry *)info->entry);
      gapp = g_desktop_app_info_new_from_filename (filename);
    }
  else
    {
      char *filename = shell_app_info_get_desktop_file_path (info);
      gapp = g_desktop_app_info_new_from_filename (filename);
      g_free (filename);
    }

  if (!gapp)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Not found");
      return FALSE;
    }

  global = shell_global_get ();
  screen = shell_global_get_screen (global);

  if (timestamp == 0)
    timestamp = clutter_get_current_event_time ();

  if (workspace < 0)
    workspace = meta_screen_get_active_workspace_index (screen);

  context = gdk_app_launch_context_new ();
  gdk_app_launch_context_set_timestamp (context, timestamp);
  gdk_app_launch_context_set_desktop (context, workspace);

  shell_app = shell_app_system_get_app (shell_app_system_get_default (),
                                        shell_app_info_get_id (info));

  /* In the case where we know an app, we handle reaping the child internally,
   * in the window tracker.
   */
  if (shell_app != NULL)
    ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
                                                     G_APP_LAUNCH_CONTEXT (context),
                                                     G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
                                                     NULL, NULL,
                                                     _gather_pid_callback, shell_app,
                                                     error);
  else
    ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
                                                     G_APP_LAUNCH_CONTEXT (context),
                                                     G_SPAWN_SEARCH_PATH,
                                                     NULL, NULL,
                                                     NULL, NULL,
                                                     error);

  g_object_unref (G_OBJECT (gapp));

  return ret;
}