/* * 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "desktop-entries.h" #include #include #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; GDesktopAppInfo *appinfo; GQuark *categories; } DesktopEntryDesktop; typedef struct { DesktopEntry base; char *name; char *generic_name; char *comment; GIcon *icon; char* full_name; char* exec; guint nodisplay : 1; guint hidden : 1; guint showin : 1; guint terminal: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 mate-menus in non-MATE 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 ("MATE"); 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; current_desktop = get_current_desktop (); if (!current_desktop) return TRUE; 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], current_desktop)) { 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 gboolean desktop_entry_load (DesktopEntry *entry) { if (entry->type == DESKTOP_ENTRY_DESKTOP) { DesktopEntryDesktop *entry_desktop = (DesktopEntryDesktop*)entry; const char *categories_str; entry_desktop->appinfo = g_desktop_app_info_new_from_filename (entry->path); if (!entry_desktop->appinfo || !g_app_info_get_name (G_APP_INFO (entry_desktop->appinfo)) || !g_app_info_get_executable (G_APP_INFO (entry_desktop->appinfo))) { menu_verbose ("Failed to load \"%s\"\n", entry->path); return FALSE; } 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); } return TRUE; } else if (entry->type == DESKTOP_ENTRY_DIRECTORY) { GKeyFile *key_file = NULL; GError *error = NULL; gboolean retval = FALSE; key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, entry->path, 0, &error)) goto out; if (!desktop_entry_load_directory (entry, key_file, &error)) goto out; retval = TRUE; out: g_key_file_free (key_file); if (!retval) { 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 retval; } else g_assert_not_reached (); return FALSE; } 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; 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); return NULL; } retval->refcount = 1; retval->type = type; retval->path = g_strdup (path); retval->basename = unix_basename_from_path (retval->path); if (!desktop_entry_load (retval)) { 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_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); } 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) 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) return g_desktop_app_info_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) 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) 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) return g_desktop_app_info_get_nodisplay (((DesktopEntryDesktop*)entry)->appinfo); return ((DesktopEntryDirectory*)entry)->nodisplay; } gboolean desktop_entry_get_hidden(DesktopEntry* entry) { if (entry->type == DESKTOP_ENTRY_DESKTOP) return g_desktop_app_info_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 g_desktop_app_info_get_show_in (((DesktopEntryDesktop*)entry)->appinfo, current_desktop); } return ((DesktopEntryDirectory*)entry)->showin; } GDesktopAppInfo * 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; } void desktop_entry_add_legacy_category(DesktopEntry* entry) { GQuark *categories; int i; DesktopEntryDesktop *desktop_entry; g_return_if_fail (entry->type == DESKTOP_ENTRY_DESKTOP); desktop_entry = (DesktopEntryDesktop*) entry; menu_verbose ("Adding Legacy category to \"%s\"\n", entry->basename); if (desktop_entry->categories != NULL) { i = 0; for (; desktop_entry->categories[i]; i++); categories = g_new0 (GQuark, i + 2); i = 0; for (; desktop_entry->categories[i]; i++) categories[i] = desktop_entry->categories[i]; } else { categories = g_new0 (GQuark, 2); i = 0; } categories[i] = g_quark_from_string ("Legacy"); g_free (desktop_entry->categories); desktop_entry->categories = categories; } /* * 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; }