/*
* Copyright (C) 2002 - 2004 Red Hat, Inc.
*
* 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 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "desktop-entries.h"
#include "gmenu-desktopappinfo.h"
#include <string.h>
#include "menu-util.h"
#define DESKTOP_ENTRY_GROUP "Desktop Entry"
struct DesktopEntry
{
guint refcount;
char *path;
const char *basename;
guint type : 2;
guint reserved : 30;
};
typedef struct
{
DesktopEntry base;
GMenuDesktopAppInfo *appinfo;
GQuark *categories;
guint showin : 1;
} DesktopEntryDesktop;
typedef struct
{
DesktopEntry base;
char *name;
char *generic_name;
char *comment;
GIcon *icon;
guint nodisplay : 1;
guint hidden : 1;
guint showin : 1;
} DesktopEntryDirectory;
struct DesktopEntrySet
{
int refcount;
GHashTable *hash;
};
/*
* Desktop entries
*/
/**
* unix_basename_from_path:
* @path: Path string
*
* Returns: A constant pointer into the basename of @path
*/
static const char *
unix_basename_from_path (const char *path)
{
const char *basename = g_strrstr (path, "/");
if (basename)
return basename + 1;
else
return path;
}
static const char *
get_current_desktop (void)
{
static char *current_desktop = NULL;
/* Support XDG_CURRENT_DESKTOP environment variable; this can be used
* to abuse gnome-menus in non-GNOME desktops. */
if (!current_desktop)
{
const char *desktop;
desktop = g_getenv ("XDG_CURRENT_DESKTOP");
/* Note: if XDG_CURRENT_DESKTOP is set but empty, do as if it
* was not set */
if (!desktop || desktop[0] == '\0')
current_desktop = g_strdup ("GNOME");
else
current_desktop = g_strdup (desktop);
}
/* Using "*" means skipping desktop-related checks */
if (g_strcmp0 (current_desktop, "*") == 0)
return NULL;
return current_desktop;
}
static GIcon *
key_file_get_icon (GKeyFile *key_file)
{
GIcon *icon = NULL;
gchar *icon_name;
icon_name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP,
"Icon", NULL, NULL);
if (!icon_name)
return NULL;
if (g_path_is_absolute (icon_name)) {
GFile *file;
file = g_file_new_for_path (icon_name);
icon = g_file_icon_new (file);
g_object_unref (file);
} else {
char *p;
/* Work around a common mistake in desktop files */
if ((p = strrchr (icon_name, '.')) != NULL &&
(strcmp (p, ".png") == 0 ||
strcmp (p, ".xpm") == 0 ||
strcmp (p, ".svg") == 0))
*p = 0;
icon = g_themed_icon_new (icon_name);
}
g_free (icon_name);
return icon;
}
static gboolean
key_file_get_show_in (GKeyFile *key_file)
{
const gchar *current_desktop;
gchar **strv;
gboolean show_in = TRUE;
int i;
gchar *exec;
current_desktop = get_current_desktop ();
if (!current_desktop)
return TRUE;
exec = g_key_file_get_string (key_file,
DESKTOP_ENTRY_GROUP,
"Exec",
NULL);
if (exec) {
if (g_str_has_prefix (exec, "gnome-control-center")) {
g_free (exec);
return FALSE;
}
g_free (exec);
}
strv = g_key_file_get_string_list (key_file,
DESKTOP_ENTRY_GROUP,
"OnlyShowIn",
NULL,
NULL);
if (strv)
{
show_in = FALSE;
for (i = 0; strv[i]; i++)
{
if (!strcmp (strv[i], "GNOME") || !strcmp (strv[i], "X-Cinnamon"))
{
show_in = TRUE;
break;
}
}
}
else
{
strv = g_key_file_get_string_list (key_file,
DESKTOP_ENTRY_GROUP,
"NotShowIn",
NULL,
NULL);
if (strv)
{
show_in = TRUE;
for (i = 0; strv[i]; i++)
{
if (!strcmp (strv[i], current_desktop))
{
show_in = FALSE;
}
}
}
}
g_strfreev (strv);
return show_in;
}
static gboolean
desktop_entry_load_directory (DesktopEntry *entry,
GKeyFile *key_file,
GError **error)
{
DesktopEntryDirectory *entry_directory = (DesktopEntryDirectory*)entry;
char *type_str;
type_str = g_key_file_get_string (key_file, DESKTOP_ENTRY_GROUP, "Type", error);
if (!type_str)
return FALSE;
if (strcmp (type_str, "Directory") != 0)
{
g_set_error (error,
G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
"\"%s\" does not contain the correct \"Type\" value\n", entry->path);
g_free (type_str);
return FALSE;
}
g_free (type_str);
entry_directory->name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "Name", NULL, error);
if (entry_directory->name == NULL)
return FALSE;
entry_directory->generic_name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "GenericName", NULL, NULL);
entry_directory->comment = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY_GROUP, "Comment", NULL, NULL);
entry_directory->icon = key_file_get_icon (key_file);
entry_directory->nodisplay = g_key_file_get_boolean (key_file,
DESKTOP_ENTRY_GROUP,
"NoDisplay",
NULL);
entry_directory->hidden = g_key_file_get_boolean (key_file,
DESKTOP_ENTRY_GROUP,
"Hidden",
NULL);
entry_directory->showin = key_file_get_show_in (key_file);
return TRUE;
}
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 = gmenu_desktopappinfo_new_from_filename (entry->path);
if (!GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo))
{
menu_verbose ("Failed to load \"%s\"\n", entry->path);
return DESKTOP_ENTRY_LOAD_FAIL_APPINFO;
}
categories_str = gmenu_desktopappinfo_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;
}
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;
}
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;
}
DesktopEntry *
desktop_entry_ref (DesktopEntry *entry)
{
g_return_val_if_fail (entry != NULL, NULL);
g_return_val_if_fail (entry->refcount > 0, NULL);
entry->refcount += 1;
return 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;
}
void
desktop_entry_unref (DesktopEntry *entry)
{
g_return_if_fail (entry != NULL);
g_return_if_fail (entry->refcount > 0);
entry->refcount -= 1;
if (entry->refcount != 0)
return;
g_free (entry->path);
entry->path = NULL;
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
DesktopEntryDesktop *desktop_entry = (DesktopEntryDesktop*) entry;
g_free (desktop_entry->categories);
if (desktop_entry->appinfo)
{
g_object_unref (desktop_entry->appinfo);
desktop_entry->appinfo = 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;
if (entry_directory->icon != NULL)
{
g_object_unref (entry_directory->icon);
entry_directory->icon = NULL;
}
}
else
g_assert_not_reached ();
g_free (entry);
}
DesktopEntryType
desktop_entry_get_type (DesktopEntry *entry)
{
return entry->type;
}
const char *
desktop_entry_get_path (DesktopEntry *entry)
{
return entry->path;
}
const char *
desktop_entry_get_basename (DesktopEntry *entry)
{
return entry->basename;
}
const char *
desktop_entry_get_name (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL);
return g_app_info_get_name (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo));
}
return ((DesktopEntryDirectory*)entry)->name;
}
const char *
desktop_entry_get_generic_name (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL);
return gmenu_desktopappinfo_get_generic_name (((DesktopEntryDesktop*)entry)->appinfo);
}
return ((DesktopEntryDirectory*)entry)->generic_name;
}
const char *
desktop_entry_get_comment (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL);
return g_app_info_get_description (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo));
}
return ((DesktopEntryDirectory*)entry)->comment;
}
GIcon *
desktop_entry_get_icon (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL);
return g_app_info_get_icon (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo));
}
return ((DesktopEntryDirectory*)entry)->icon;
}
gboolean
desktop_entry_get_no_display (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), FALSE);
return gmenu_desktopappinfo_get_nodisplay (((DesktopEntryDesktop*)entry)->appinfo);
}
return ((DesktopEntryDirectory*)entry)->nodisplay;
}
gboolean
desktop_entry_get_hidden (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), FALSE);
return gmenu_desktopappinfo_get_is_hidden (((DesktopEntryDesktop*)entry)->appinfo);
}
return ((DesktopEntryDirectory*)entry)->hidden;
}
gboolean
desktop_entry_get_show_in (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
const char *current_desktop = get_current_desktop ();
if (current_desktop == NULL)
return TRUE;
else {
return ((DesktopEntryDesktop *)entry)->showin;
}
}
return ((DesktopEntryDirectory*)entry)->showin;
}
const char *
desktop_entry_get_id (DesktopEntry *entry)
{
if (entry->type == DESKTOP_ENTRY_DESKTOP)
{
g_return_val_if_fail (GMENU_IS_DESKTOPAPPINFO (((DesktopEntryDesktop*)entry)->appinfo), NULL);
return g_app_info_get_id (G_APP_INFO (((DesktopEntryDesktop*)entry)->appinfo));
}
// this only applies to non-desktop entries
return entry->basename;
}
GMenuDesktopAppInfo *
desktop_entry_get_app_info (DesktopEntry *entry)
{
g_return_val_if_fail (entry->type == DESKTOP_ENTRY_DESKTOP, NULL);
return ((DesktopEntryDesktop*)entry)->appinfo;
}
gboolean
desktop_entry_has_categories (DesktopEntry *entry)
{
DesktopEntryDesktop *desktop_entry;
if (entry->type != DESKTOP_ENTRY_DESKTOP)
return FALSE;
desktop_entry = (DesktopEntryDesktop*) entry;
return (desktop_entry->categories != NULL && desktop_entry->categories[0] != 0);
}
gboolean
desktop_entry_has_category (DesktopEntry *entry,
const char *category)
{
GQuark quark;
int i;
DesktopEntryDesktop *desktop_entry;
if (entry->type != DESKTOP_ENTRY_DESKTOP)
return FALSE;
desktop_entry = (DesktopEntryDesktop*) entry;
if (desktop_entry->categories == NULL)
return FALSE;
if (!(quark = g_quark_try_string (category)))
return FALSE;
for (i = 0; desktop_entry->categories[i]; i++)
{
if (quark == desktop_entry->categories[i])
return TRUE;
}
return FALSE;
}
/*
* Entry sets
*/
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;
}
DesktopEntrySet *
desktop_entry_set_ref (DesktopEntrySet *set)
{
g_return_val_if_fail (set != NULL, NULL);
g_return_val_if_fail (set->refcount > 0, NULL);
set->refcount += 1;
return set;
}
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));
}
DesktopEntry *
desktop_entry_set_lookup (DesktopEntrySet *set,
const char *file_id)
{
if (set->hash == NULL)
return NULL;
return g_hash_table_lookup (set->hash, file_id);
}
typedef struct
{
DesktopEntrySetForeachFunc func;
gpointer user_data;
} EntryHashForeachData;
static void
entry_hash_foreach (const char *file_id,
DesktopEntry *entry,
EntryHashForeachData *fd)
{
fd->func (file_id, entry, fd->user_data);
}
void
desktop_entry_set_foreach (DesktopEntrySet *set,
DesktopEntrySetForeachFunc func,
gpointer user_data)
{
g_return_if_fail (set != NULL);
g_return_if_fail (func != NULL);
if (set->hash != NULL)
{
EntryHashForeachData fd;
fd.func = func;
fd.user_data = user_data;
g_hash_table_foreach (set->hash,
(GHFunc) entry_hash_foreach,
&fd);
}
}
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;
}
}
int
desktop_entry_set_get_count (DesktopEntrySet *set)
{
if (set->hash == NULL)
return 0;
return g_hash_table_size (set->hash);
}
static void
union_foreach (const char *file_id,
DesktopEntry *entry,
DesktopEntrySet *set)
{
/* we are iterating over "with" adding anything not
* already in "set". We unconditionally overwrite
* the stuff in "set" because we can assume
* two entries with the same name are equivalent.
*/
desktop_entry_set_add_entry (set, entry, file_id);
}
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);
}
typedef struct
{
DesktopEntrySet *set;
DesktopEntrySet *with;
} IntersectData;
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 */
}
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);
}
typedef struct
{
DesktopEntrySet *set;
DesktopEntrySet *other;
} SubtractData;
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_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);
}
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;
}