DesktopEntry *
desktop_entry_new (const char *path)
{
  DesktopEntryType  type;
  DesktopEntry     *retval;

  menu_verbose ("Loading desktop entry \"%s\"\n", path);

  if (g_str_has_suffix (path, ".desktop"))
    {
      type = DESKTOP_ENTRY_DESKTOP;
    }
  else if (g_str_has_suffix (path, ".directory"))
    {
      type = DESKTOP_ENTRY_DIRECTORY;
    }
  else
    {
      menu_verbose ("Unknown desktop entry suffix in \"%s\"\n",
                    path);
      return NULL;
    }

  retval = g_new0 (DesktopEntry, 1);

  retval->refcount = 1;
  retval->type     = type;
  retval->basename = g_path_get_basename (path);
  retval->path     = g_strdup (path);

  return desktop_entry_load (retval);
}
EntryDirectory *
entry_directory_new (DesktopEntryType  entry_type,
                     const char       *path)
{
  EntryDirectory *ed;
  char           *canonical;

  menu_verbose ("Loading entry directory \"%s\"\n", path);

  canonical = realpath (path, NULL);
  if (canonical == NULL)
    {
      menu_verbose ("Failed to canonicalize \"%s\": %s\n",
                    path, g_strerror (errno));
      return NULL;
    }

  ed = g_new0 (EntryDirectory, 1);

  ed->dir = cached_dir_lookup (canonical);
  g_assert (ed->dir != NULL);

  cached_dir_add_reference (ed->dir);
  cached_dir_load_entries_recursive (ed->dir, canonical);

  ed->entry_type    = entry_type;
  ed->refcount      = 1;

  g_free (canonical);

  return ed;
}
Example #3
0
static MenuMonitor *
register_monitor (const char *path,
		  gboolean    is_directory)
{
#if !GLIB_CHECK_VERSION(2, 36, 0)
  static gboolean  initted = FALSE;
#endif
  MenuMonitor     *retval;
  GFile           *file;

#if !GLIB_CHECK_VERSION(2, 36, 0)
  if (!initted)
    {
      /* This is the only place where we're using GObject and the app can't
       * know we're using it, so we need to init the type system ourselves. */
      g_type_init ();
      initted = TRUE;
    }
#endif

  retval = g_new0 (MenuMonitor, 1);

  retval->path         = g_strdup (path);
  retval->refcount     = 1;
  retval->is_directory = is_directory != FALSE;

  file = g_file_new_for_path (retval->path);

  if (file == NULL)
    {
      menu_verbose ("Not adding monitor on '%s', failed to create GFile\n",
                    retval->path);
      return retval;
    }

  if (retval->is_directory)
      retval->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE,
                                                  NULL, NULL);
  else
      retval->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
                                             NULL, NULL);

  g_object_unref (G_OBJECT (file));

  if (retval->monitor == NULL)
    {
      menu_verbose ("Not adding monitor on '%s', failed to create monitor\n",
                    retval->path);
      return retval;
    }

  g_signal_connect (retval->monitor, "changed",
                    G_CALLBACK (monitor_callback), retval);

  return retval;
}
DesktopEntrySet *
_entry_directory_list_get_all_desktops (EntryDirectoryList *list)
{
  GList *tmp;
  DesktopEntrySet *set;

  /* The only tricky thing here is that desktop files later
   * in the search list with the same relative path
   * are "hidden" by desktop files earlier in the path,
   * so we have to do the earlier files first causing
   * the later files to replace the earlier files
   * in the DesktopEntrySet
   *
   * We go from the end of the list so we can just
   * g_hash_table_replace and not have to do two
   * hash lookups (check for existing entry, then insert new
   * entry)
   */

  /* This method is -extremely- slow, so we have a simple
     one-entry cache here */
  if (_entry_directory_list_compare (list, entry_directory_last_list))
    {
      menu_verbose (" Hit desktop list (%p) cache\n", list);
      return desktop_entry_set_ref (entry_directory_last_set);
    }

  if (entry_directory_last_set != NULL)
    desktop_entry_set_unref (entry_directory_last_set);
  if (entry_directory_last_list != NULL)
    entry_directory_list_unref (entry_directory_last_list);

  set = desktop_entry_set_new ();
  menu_verbose (" Storing all of list %p in set %p\n",
                list, set);

  tmp = g_list_last (list->dirs);
  while (tmp != NULL)
    {
      entry_directory_foreach (tmp->data, get_all_func, set, NULL);

      tmp = tmp->prev;
    }

  entry_directory_last_list = entry_directory_list_ref (list);
  entry_directory_last_set = desktop_entry_set_ref (set);

  return set;
}
static gboolean
cached_dir_add_entry (CachedDir  *dir,
                      const char *basename,
                      const char *path)
{
  DesktopEntry *entry;
  DesktopEntryResultCode code;

  entry = desktop_entry_new (path, &code);
  if (entry == NULL)
    {
      if (code == DESKTOP_ENTRY_LOAD_FAIL_APPINFO)
        {
          menu_verbose ("Adding %s to the retry list (mimeinfo.cache maybe isn't done getting updated yet\n", path);

          dir->retry_later_desktop_entries = g_slist_prepend (dir->retry_later_desktop_entries, g_strdup (path));
        }

      return FALSE;
    }

  dir->entries = g_slist_prepend (dir->entries, entry);

  return TRUE;
}
void
desktop_entry_add_legacy_category (DesktopEntry *entry)
{
  GQuark *categories;
  int     i;

  menu_verbose ("Adding Legacy category to \"%s\"\n",
                entry->basename);

  i = 0;
  if (entry->categories != NULL)
    {
      for (; entry->categories[i]; i++);
    }

  categories = g_new0 (GQuark, i + 2);

  i = 0;
  if (entry->categories != NULL)
    {
      for (; entry->categories[i]; i++)
        categories[i] = entry->categories[i];
    }

  categories[i] = g_quark_from_string ("Legacy");

  g_free (entry->categories);
  entry->categories = categories;
}
static CachedDir *
cached_dir_add_subdir (CachedDir  *dir,
                       const char *basename,
                       const char *path)
{
  CachedDir *subdir;

  subdir = find_subdir (dir, basename);

  if (subdir != NULL)
    {
      subdir->deleted = FALSE;
      return subdir;
    }

  subdir = cached_dir_new (basename);

  if (path != NULL && !cached_dir_load_entries_recursive (subdir, path))
    {
      cached_dir_free (subdir);
      return NULL;
    }

  menu_verbose ("Caching dir \"%s\"\n", basename);

  subdir->parent = dir;
  dir->subdirs = g_slist_prepend (dir->subdirs, cached_dir_ref (subdir));

  return subdir;
}
DesktopEntry *
desktop_entry_reload (DesktopEntry *entry)
{
  g_return_val_if_fail (entry != NULL, NULL);

  menu_verbose ("Re-loading desktop entry \"%s\"\n", entry->path);

  g_free (entry->categories);
  entry->categories = NULL;

  g_free (entry->name);
  entry->name = NULL;

  g_free (entry->generic_name);
  entry->generic_name = NULL;

  g_free (entry->full_name);
  entry->full_name = NULL;

  g_free (entry->comment);
  entry->comment = NULL;

  g_free (entry->icon);
  entry->icon = NULL;

  g_free (entry->exec);
  entry->exec = NULL;

  entry->terminal = 0;
  entry->flags = 0;

  return desktop_entry_load (entry);
}
DesktopEntry *
desktop_entry_copy (DesktopEntry *entry)
{
  DesktopEntry *retval;

  menu_verbose ("Copying desktop entry \"%s\"\n",
                entry->basename);

  if (entry->type == DESKTOP_ENTRY_DESKTOP)
    retval = (DesktopEntry*)g_new0 (DesktopEntryDesktop, 1);
  else if (entry->type == DESKTOP_ENTRY_DIRECTORY)
    retval = (DesktopEntry*)g_new0 (DesktopEntryDirectory, 1);
  else
    g_assert_not_reached ();

  retval->refcount     = 1;
  retval->type         = entry->type;
  retval->path         = g_strdup (entry->path);
  retval->basename     = unix_basename_from_path (retval->path);

  if (retval->type == DESKTOP_ENTRY_DESKTOP)
    {
      DesktopEntryDesktop *desktop_entry = (DesktopEntryDesktop*) entry;
      DesktopEntryDesktop *retval_desktop_entry = (DesktopEntryDesktop*) retval;
      int i;

      retval_desktop_entry->appinfo = g_object_ref (desktop_entry->appinfo);

      if (desktop_entry->categories != NULL)
        {
          i = 0;
          for (; desktop_entry->categories[i]; i++);

          retval_desktop_entry->categories = g_new0 (GQuark, i + 1);

          i = 0;
          for (; desktop_entry->categories[i]; i++)
            retval_desktop_entry->categories[i] = desktop_entry->categories[i];
        }
      else
        retval_desktop_entry->categories = NULL;
    }
  else if (entry->type == DESKTOP_ENTRY_DIRECTORY)
    {
      DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*)entry;
      DesktopEntryDirectory *retval_directory = (DesktopEntryDirectory*)retval;

      retval_directory->name         = g_strdup (entry_directory->name);
      retval_directory->comment      = g_strdup (entry_directory->comment);
      retval_directory->icon         = g_object_ref (entry_directory->icon);
      retval_directory->nodisplay    = entry_directory->nodisplay;
      retval_directory->hidden       = entry_directory->hidden;
      retval_directory->showin       = entry_directory->showin;
    }

  return retval;
}
DesktopEntry *
desktop_entry_new (const char             *path,
                   DesktopEntryResultCode *res_code)
{
  DesktopEntryType  type;
  DesktopEntry     *retval;
  DesktopEntryResultCode code;

  menu_verbose ("Loading desktop entry \"%s\"\n", path);

  if (g_str_has_suffix (path, ".desktop"))
    {
      type = DESKTOP_ENTRY_DESKTOP;
      retval = (DesktopEntry*)g_new0 (DesktopEntryDesktop, 1);
    }
  else if (g_str_has_suffix (path, ".directory"))
    {
      type = DESKTOP_ENTRY_DIRECTORY;
      retval = (DesktopEntry*)g_new0 (DesktopEntryDirectory, 1);
    }
  else
    {
      menu_verbose ("Unknown desktop entry suffix in \"%s\"\n",
                    path);
      *res_code = DESKTOP_ENTRY_LOAD_FAIL_OTHER;
      return NULL;
    }

  retval->refcount = 1;
  retval->type     = type;
  retval->path     = g_strdup (path);
  retval->basename = unix_basename_from_path (retval->path);

  code = desktop_entry_load (retval);
  *res_code = code;

  if (code < DESKTOP_ENTRY_LOAD_SUCCESS)
    {
      desktop_entry_unref (retval);
      return NULL;
    }

  return retval;
}
static void
desktop_entry_set_clear (DesktopEntrySet *set)
{
  menu_verbose (" Clearing set %p\n", set);

  if (set->hash != NULL)
    {
      g_hash_table_destroy (set->hash);
      set->hash = NULL;
    }
}
void
desktop_entry_set_swap_contents (DesktopEntrySet *a,
                                 DesktopEntrySet *b)
{
  GHashTable *tmp;

  menu_verbose (" Swap contents of %p and %p\n", a, b);

  tmp = a->hash;
  a->hash = b->hash;
  b->hash = tmp;
}
DesktopEntrySet *
desktop_entry_set_new (void)
{
  DesktopEntrySet *set;

  set = g_new0 (DesktopEntrySet, 1);
  set->refcount = 1;

  menu_verbose (" New entry set %p\n", set);

  return set;
}
void
desktop_entry_set_union (DesktopEntrySet *set,
                         DesktopEntrySet *with)
{
  menu_verbose (" Union of %p and %p\n", set, with);

  if (desktop_entry_set_get_count (with) == 0)
    return; /* A fast simple case */

  g_hash_table_foreach (with->hash,
                        (GHFunc) union_foreach,
                        set);
}
static EntryDirectory *
entry_directory_new_full (DesktopEntryType  entry_type,
                          const char       *path,
                          gboolean          is_legacy,
                          const char       *legacy_prefix)
{
  EntryDirectory *ed;
  char           *canonical;

  menu_verbose ("Loading entry directory \"%s\" (legacy %s)\n",
                path,
                is_legacy ? "<yes>" : "<no>");

  canonical = menu_canonicalize_file_name (path, FALSE);
  if (canonical == NULL)
    {
      menu_verbose ("Failed to canonicalize \"%s\": %s\n",
                    path, g_strerror (errno));
      return NULL;
    }

  ed = g_new0 (EntryDirectory, 1);

  ed->dir = cached_dir_lookup (canonical);
  g_assert (ed->dir != NULL);

  cached_dir_add_reference (ed->dir);
  cached_dir_load_entries_recursive (ed->dir, canonical);

  ed->legacy_prefix = g_strdup (legacy_prefix);
  ed->entry_type    = entry_type;
  ed->is_legacy     = is_legacy != FALSE;
  ed->refcount      = 1;

  g_free (canonical);

  return ed;
}
static gboolean
intersect_foreach_remove (const char    *file_id,
                          DesktopEntry  *entry,
                          IntersectData *id)
{
  /* Remove everything in "set" which is not in "with" */

  if (g_hash_table_lookup (id->with->hash, file_id) != NULL)
    return FALSE;

  menu_verbose (" Removing from %p entry %s\n", id->set, file_id);

  return TRUE; /* return TRUE to remove */
}
static gboolean
subtract_foreach_remove (const char   *file_id,
                         DesktopEntry *entry,
                         SubtractData *sd)
{
  /* Remove everything in "set" which is not in "other" */

  if (g_hash_table_lookup (sd->other->hash, file_id) == NULL)
    return FALSE;

  menu_verbose (" Removing from %p entry %s\n", sd->set, file_id);

  return TRUE; /* return TRUE to remove */
}
void
desktop_entry_set_unref (DesktopEntrySet *set)
{
  g_return_if_fail (set != NULL);
  g_return_if_fail (set->refcount > 0);

  set->refcount -= 1;
  if (set->refcount == 0)
    {
      menu_verbose (" Deleting entry set %p\n", set);

      if (set->hash)
        g_hash_table_destroy (set->hash);
      set->hash = NULL;

      g_free (set);
    }
}
void
desktop_entry_set_add_entry (DesktopEntrySet *set,
                             DesktopEntry    *entry,
                             const char      *file_id)
{
  menu_verbose (" Adding to set %p entry %s\n", set, file_id);

  if (set->hash == NULL)
    {
      set->hash = g_hash_table_new_full (g_str_hash,
                                         g_str_equal,
                                         g_free,
                                         (GDestroyNotify) desktop_entry_unref);
    }

  g_hash_table_replace (set->hash,
                        g_strdup (file_id),
                        desktop_entry_ref (entry));
}
void
desktop_entry_set_subtract (DesktopEntrySet *set,
                            DesktopEntrySet *other)
{
  SubtractData sd;

  menu_verbose (" Subtract from %p set %p\n", set, other);

  if (desktop_entry_set_get_count (set) == 0 ||
      desktop_entry_set_get_count (other) == 0)
    return; /* A fast simple case */

  sd.set   = set;
  sd.other = other;

  g_hash_table_foreach_remove (set->hash,
                               (GHRFunc) subtract_foreach_remove,
                               &sd);
}
DesktopEntry *
desktop_entry_copy (DesktopEntry *entry)
{
  DesktopEntry *retval;
  int           i;

  menu_verbose ("Copying desktop entry \"%s\"\n",
                entry->basename);

  retval = g_new0 (DesktopEntry, 1);

  retval->refcount     = 1;
  retval->type         = entry->type;
  retval->basename     = g_strdup (entry->basename);
  retval->path         = g_strdup (entry->path);
  retval->name         = g_strdup (entry->name);
  retval->generic_name = g_strdup (entry->generic_name);
  retval->full_name    = g_strdup (entry->full_name);
  retval->comment      = g_strdup (entry->comment);
  retval->icon         = g_strdup (entry->icon);
  retval->exec         = g_strdup (entry->exec);
  retval->terminal     = entry->terminal;
  retval->flags        = entry->flags;

  i = 0;
  if (entry->categories != NULL)
    {
      for (; entry->categories[i]; i++);
    }

  retval->categories = g_new0 (GQuark, i + 1);

  i = 0;
  if (entry->categories != NULL)
    {
      for (; entry->categories[i]; i++)
        retval->categories[i] = entry->categories[i];
    }

  return retval;
}
DesktopEntry *
desktop_entry_reload (DesktopEntry *entry)
{
  g_return_val_if_fail (entry != NULL, NULL);

  menu_verbose ("Re-loading desktop entry \"%s\"\n", entry->path);

  if (entry->type == DESKTOP_ENTRY_DESKTOP)
    {
      DesktopEntryDesktop *entry_desktop = (DesktopEntryDesktop *) entry;

      g_object_unref (entry_desktop->appinfo);
      entry_desktop->appinfo = NULL;

      g_free (entry_desktop->categories);
      entry_desktop->categories = NULL;
    }
  else if (entry->type == DESKTOP_ENTRY_DIRECTORY)
    {
      DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*) entry;

      g_free (entry_directory->name);
      entry_directory->name = NULL;

      g_free (entry_directory->comment);
      entry_directory->comment = NULL;

      g_object_unref (entry_directory->icon);
      entry_directory->icon = NULL;
    }
  else
    g_assert_not_reached ();

  if (desktop_entry_load (entry) < DESKTOP_ENTRY_LOAD_SUCCESS)
    {
      desktop_entry_unref (entry);
      return NULL;
    }

  return entry;
}
Example #23
0
static CachedDir *
cached_dir_lookup (const char *canonical)
{
  CachedDir  *dir;
  char      **split;
  int         i;

  if (dir_cache == NULL)
    dir_cache = cached_dir_new ("/");
  dir = dir_cache;

  g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR);

  menu_verbose ("Looking up cached dir \"%s\"\n", canonical);

  split = g_strsplit (canonical + 1, "/", -1);

  i = 0;
  while (split[i] != NULL)
    {
      CachedDir *subdir;

      if ((subdir = find_subdir (dir, split[i])) == NULL)
        {
          subdir = cached_dir_new (split[i]);
          dir->subdirs = g_slist_prepend (dir->subdirs, subdir);
          subdir->parent = dir;
        }

      dir = subdir;

      ++i;
    }

  g_strfreev (split);

  g_assert (dir != NULL);

  return dir;
}
void
desktop_entry_set_intersection (DesktopEntrySet *set,
                                DesktopEntrySet *with)
{
  IntersectData id;

  menu_verbose (" Intersection of %p and %p\n", set, with);

  if (desktop_entry_set_get_count (set) == 0 ||
      desktop_entry_set_get_count (with) == 0)
    {
      /* A fast simple case */
      desktop_entry_set_clear (set);
      return;
    }

  id.set  = set;
  id.with = with;

  g_hash_table_foreach_remove (set->hash,
                               (GHRFunc) intersect_foreach_remove,
                               &id);
}
static CachedDir *
cached_dir_lookup (const char *canonical)
{
  CachedDir  *dir;
  char      **split;
  int         i;

  if (dir_cache == NULL)
    dir_cache = cached_dir_new_full ("/",
                                     (GFunc) clear_cache,
                                     &dir_cache);
  dir = dir_cache;

  g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR);

  menu_verbose ("Looking up cached dir \"%s\"\n", canonical);

  split = g_strsplit (canonical + 1, "/", -1);

  i = 0;
  while (split[i] != NULL)
    {
      CachedDir *subdir;

      subdir = cached_dir_add_subdir (dir, split[i], NULL);

      dir = subdir;

      ++i;
    }

  g_strfreev (split);

  g_assert (dir != NULL);

  return dir;
}
static DesktopEntry *
desktop_entry_load (DesktopEntry *entry)
{
  DesktopEntry *retval = NULL;
  GKeyFile     *key_file;
  GError       *error;
  const char   *desktop_entry_group;
  char         *name_str;
  char         *type_str;

  key_file = g_key_file_new ();

  error = NULL;
  if (!g_key_file_load_from_file (key_file, entry->path, 0, &error))
    {
      menu_verbose ("Failed to load \"%s\": %s\n",
                    entry->path, error->message);
      g_error_free (error);
      goto out;
    }

  if (g_key_file_has_group (key_file, DESKTOP_ENTRY_GROUP))
    {
      desktop_entry_group = DESKTOP_ENTRY_GROUP;
    }
  else
    {
      menu_verbose ("\"%s\" contains no \"" DESKTOP_ENTRY_GROUP "\" group\n",
                    entry->path);

      if (g_key_file_has_group (key_file, KDE_DESKTOP_ENTRY_GROUP))
        {
          desktop_entry_group = KDE_DESKTOP_ENTRY_GROUP;
          menu_verbose ("\"%s\" contains deprecated \"" KDE_DESKTOP_ENTRY_GROUP "\" group\n",
                        entry->path);
        }
      else
        {
          goto out;
        }
    }

  if (!g_key_file_has_key (key_file, desktop_entry_group, "Name", NULL))
    {
      menu_verbose ("\"%s\" contains no \"Name\" key\n", entry->path);
      goto out;
    }

  name_str = g_key_file_get_locale_string (key_file, desktop_entry_group, "Name", NULL, NULL);
  if (!name_str)
    {
      menu_verbose ("\"%s\" contains an invalid \"Name\" key\n", entry->path);
      goto out;
    }

  g_free (name_str);

  type_str = g_key_file_get_string (key_file, desktop_entry_group, "Type", NULL);
  if (!type_str)
    {
      menu_verbose ("\"%s\" contains no \"Type\" key\n", entry->path);
      goto out;
    }

  if ((entry->type == DESKTOP_ENTRY_DESKTOP && strcmp (type_str, "Application") != 0) ||
      (entry->type == DESKTOP_ENTRY_DIRECTORY && strcmp (type_str, "Directory") != 0))
    {
      menu_verbose ("\"%s\" does not contain the correct \"Type\" value\n", entry->path);
      g_free (type_str);
      goto out;
    }

  g_free (type_str);

  if (entry->type == DESKTOP_ENTRY_DESKTOP &&
      !g_key_file_has_key (key_file, desktop_entry_group, "Exec", NULL))
    {
      menu_verbose ("\"%s\" does not contain an \"Exec\" key\n", entry->path);
      goto out;
    }

  retval = entry;

#define GET_LOCALE_STRING(n) g_key_file_get_locale_string (key_file, desktop_entry_group, (n), NULL, NULL)

  retval->name         = GET_LOCALE_STRING ("Name");
  retval->generic_name = GET_LOCALE_STRING ("GenericName");
  retval->full_name    = GET_LOCALE_STRING ("X-MATE-FullName");
  retval->comment      = GET_LOCALE_STRING ("Comment");
  retval->icon         = GET_LOCALE_STRING ("Icon");
  retval->flags        = get_flags_from_key_file (retval, key_file, desktop_entry_group);
  retval->categories   = get_categories_from_key_file (retval, key_file, desktop_entry_group);

  if (entry->type == DESKTOP_ENTRY_DESKTOP)
    {
      retval->exec = g_key_file_get_string (key_file, desktop_entry_group, "Exec", NULL);
      retval->terminal = g_key_file_get_boolean (key_file, desktop_entry_group, "Terminal", NULL);
    }

#undef GET_LOCALE_STRING

  menu_verbose ("Desktop entry \"%s\" (%s, %s, %s, %s, %s) flags: NoDisplay=%s, Hidden=%s, ShowInMATE=%s, TryExecFailed=%s\n",
                retval->basename,
                retval->name,
                retval->generic_name ? retval->generic_name : "(null)",
                retval->full_name ? retval->full_name : "(null)",
                retval->comment ? retval->comment : "(null)",
                retval->icon ? retval->icon : "(null)",
                retval->flags & DESKTOP_ENTRY_NO_DISPLAY     ? "(true)" : "(false)",
                retval->flags & DESKTOP_ENTRY_HIDDEN         ? "(true)" : "(false)",
                retval->flags & DESKTOP_ENTRY_SHOW_IN_MATE  ? "(true)" : "(false)",
                retval->flags & DESKTOP_ENTRY_TRYEXEC_FAILED ? "(true)" : "(false)");

 out:
  g_key_file_free (key_file);

  if (!retval)
    desktop_entry_unref (entry);

  return retval;
}
static gboolean
cached_dir_load_entries_recursive (CachedDir  *dir,
                                   const char *dirname)
{
  DIR           *dp;
  struct dirent *dent;
  GString       *fullpath;
  gsize          fullpath_len;

  g_assert (dir != NULL);

  if (dir->have_read_entries)
    return TRUE;

  menu_verbose ("Attempting to read entries from %s (full path %s)\n",
                dir->name, dirname);

  dp = opendir (dirname);
  if (dp == NULL)
    {
      menu_verbose ("Unable to list directory \"%s\"\n",
                    dirname);
      return FALSE;
    }

  cached_dir_ensure_monitor (dir, dirname);

  fullpath = g_string_new (dirname);
  if (fullpath->str[fullpath->len - 1] != G_DIR_SEPARATOR)
    g_string_append_c (fullpath, G_DIR_SEPARATOR);

  fullpath_len = fullpath->len;

  while ((dent = readdir (dp)) != NULL)
    {
      /* ignore . and .. */
      if (dent->d_name[0] == '.' &&
          (dent->d_name[1] == '\0' ||
           (dent->d_name[1] == '.' &&
            dent->d_name[2] == '\0')))
        continue;

      g_string_append (fullpath, dent->d_name);

      if (g_str_has_suffix (dent->d_name, ".desktop") ||
          g_str_has_suffix (dent->d_name, ".directory"))
        {
          cached_dir_add_entry (dir, dent->d_name, fullpath->str);
        }
      else /* Try recursing */
        {
          cached_dir_add_subdir (dir, dent->d_name, fullpath->str);
        }

      g_string_truncate (fullpath, fullpath_len);
    }

  closedir (dp);

  g_string_free (fullpath, TRUE);

  dir->have_read_entries = TRUE;

  return TRUE;
}
static void
handle_cached_dir_changed (MenuMonitor      *monitor,
			   MenuMonitorEvent  event,
			   const char       *path,
                           CachedDir        *dir)
{
  gboolean  handled = FALSE;
  char     *basename;
  char     *dirname;

  menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
		dir->name,
                path,
                event == MENU_MONITOR_EVENT_CREATED ? ("created") :
                event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));

  dirname  = g_path_get_dirname  (path);
  basename = g_path_get_basename (path);

  dir = cached_dir_lookup (dirname);

  if (g_str_has_suffix (basename, ".desktop") ||
      g_str_has_suffix (basename, ".directory"))
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
        case MENU_MONITOR_EVENT_CHANGED:
          handled = cached_dir_update_entry (dir, basename, path);
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_entry (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }
  else /* Try recursing */
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
          handled = cached_dir_add_subdir (dir, basename, path) != NULL;
          break;

        case MENU_MONITOR_EVENT_CHANGED:
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_subdir (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }

  g_free (basename);
  g_free (dirname);

  if (handled)
    {
      /* CHANGED events don't change the set of desktop entries */
      if (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED)
        {
          _entry_directory_list_empty_desktop_cache ();
        }

      cached_dir_queue_monitor_event (dir);
    }
}
static DesktopEntryResultCode
desktop_entry_load (DesktopEntry *entry)
{
  if (strstr (entry->path, "/menu-xdg/"))
    return DESKTOP_ENTRY_LOAD_FAIL_OTHER;
  if (entry->type == DESKTOP_ENTRY_DESKTOP)
    {
      GKeyFile *key_file = NULL;
      DesktopEntryDesktop *entry_desktop = (DesktopEntryDesktop*)entry;
      const char *categories_str;

      entry_desktop->appinfo = g_desktop_app_info_new_from_filename (entry->path);
      if (!G_IS_DESKTOP_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo))
        {
          menu_verbose ("Failed to load \"%s\"\n", entry->path);
          return DESKTOP_ENTRY_LOAD_FAIL_APPINFO;
        }

      categories_str = g_desktop_app_info_get_categories (entry_desktop->appinfo);
      if (categories_str)
        {
          char **categories;
          int i;

          categories = g_strsplit (categories_str, ";", -1);
          entry_desktop->categories = g_new0 (GQuark, g_strv_length (categories) + 1);

          for (i = 0; categories[i]; i++)
            entry_desktop->categories[i] = g_quark_from_string (categories[i]);

          g_strfreev (categories);
        }

      key_file = g_key_file_new ();

      if (!g_key_file_load_from_file (key_file, entry->path, 0, NULL))
        entry_desktop->showin = TRUE;
      else
        entry_desktop->showin = key_file_get_show_in (key_file);

      g_key_file_free (key_file);

      return DESKTOP_ENTRY_LOAD_SUCCESS;
    }
  else if (entry->type == DESKTOP_ENTRY_DIRECTORY)
    {
      GKeyFile *key_file = NULL;
      GError   *error = NULL;
      DesktopEntryResultCode rescode = DESKTOP_ENTRY_LOAD_SUCCESS;

      key_file = g_key_file_new ();

      if (!g_key_file_load_from_file (key_file, entry->path, 0, &error))
        {
          rescode = DESKTOP_ENTRY_LOAD_FAIL_OTHER;
          goto out;
        }

      if (!desktop_entry_load_directory (entry, key_file, &error))
        {
          rescode = DESKTOP_ENTRY_LOAD_FAIL_OTHER;
          goto out;
        }

      rescode = DESKTOP_ENTRY_LOAD_SUCCESS;

    out:
      g_key_file_free (key_file);

      if (rescode == DESKTOP_ENTRY_LOAD_FAIL_OTHER)
        {
          if (error)
            {
              menu_verbose ("Failed to load \"%s\": %s\n", entry->path, error->message);
              g_error_free (error);
            }
          else
            menu_verbose ("Failed to load \"%s\"\n", entry->path);
        }

      return rescode;
    }
  else
    g_assert_not_reached ();

  return DESKTOP_ENTRY_LOAD_FAIL_OTHER;
}
static void
handle_cached_dir_changed (MenuMonitor      *monitor,
			   MenuMonitorEvent  event,
			   const char       *path,
                           CachedDir        *dir)
{
  gboolean  handled = FALSE;
  gboolean  retry_changes = FALSE;

  char     *basename;
  char     *dirname;

  dirname  = g_path_get_dirname  (path);
  basename = g_path_get_basename (path);

  dir = cached_dir_lookup (dirname);
  cached_dir_add_reference (dir);

  if (g_str_has_suffix (basename, ".desktop") ||
      g_str_has_suffix (basename, ".directory"))
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
        case MENU_MONITOR_EVENT_CHANGED:
          handled = cached_dir_update_entry (dir, basename, path);
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_entry (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }
  else if (g_strcmp0 (basename, "mimeinfo.cache") == 0)
    {
        /* The observed file notifies when a new desktop file is added
         * (but fails to load) go something like:
         *
         * NOTIFY: foo.desktop
         * NOTIFY: mimeinfo.cache.tempfile
         * NOTIFY: mimeinfo.cache.tempfile
         * NOTIFY: mimeinfo.cache
         *
         * Additionally, the failure is not upon trying to read the file,
         * but attempting to get its GAppInfo (g_desktop_app_info_new_from_filename()
         * in desktop-entries.c ln 277).  If you jigger desktop_entry_load() around
         * and read the file as a keyfile *first*, it succeeds.  If you then try
         * to run g_desktop_app_info_new_from_keyfile(), *then* it fails.
         *
         * The theory here is there is a race condition where app info (which includes
         * mimetype stuff) is unavailable because mimeinfo.cache is updated immediately
         * after the app is installed.
         *
         * What we do here is, when a desktop fails to load, we add it to a temporary
         * list.  We wait until mimeinfo.cache changes, then retry that desktop file,
         * which succeeds this second time.
         *
         * Note: An alternative fix (presented more as a proof than a suggestion) is to
         * change line 151 in menu-monitor.c to use g_timeout_add_seconds, and delay
         * for one second.  This also avoids the issue (but it remains a race condition).
         */

        GSList *iter;

        menu_verbose ("mimeinfo changed, checking for failed entries\n");

        for (iter = dir->retry_later_desktop_entries; iter != NULL; iter = iter->next)
          {
            const gchar *retry_path = iter->data;

            menu_verbose ("retrying %s\n", retry_path);

            char *retry_basename = g_path_get_basename (retry_path);

            if (cached_dir_update_entry (dir, retry_basename, retry_path))
              retry_changes = TRUE;

            g_free (retry_basename);
          }

        g_slist_free_full (dir->retry_later_desktop_entries, g_free);
        dir->retry_later_desktop_entries = NULL;

        handled = retry_changes;
    }
  else /* Try recursing */
    {
      switch (event)
        {
        case MENU_MONITOR_EVENT_CREATED:
          handled = cached_dir_add_subdir (dir, basename, path) != NULL;
          break;

        case MENU_MONITOR_EVENT_CHANGED:
          break;

        case MENU_MONITOR_EVENT_DELETED:
          handled = cached_dir_remove_subdir (dir, basename);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }

  g_free (basename);
  g_free (dirname);

  if (handled)
    {
      menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
                    dir->name,
                    path,
                    event == MENU_MONITOR_EVENT_CREATED ? ("created") :
                    event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));

      /* CHANGED events don't change the set of desktop entries, unless it's the mimeinfo.cache file changing */
      if (retry_changes || (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED))
        {
          _entry_directory_list_empty_desktop_cache ();
        }

      cached_dir_queue_monitor_event (dir);
    }

  cached_dir_remove_reference (dir);
}