/* * 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 "menu-util.h" #define DESKTOP_ENTRY_GROUP "Desktop Entry" #define KDE_DESKTOP_ENTRY_GROUP "KDE Desktop Entry" enum { DESKTOP_ENTRY_NO_DISPLAY = 1 << 0, DESKTOP_ENTRY_HIDDEN = 1 << 1, DESKTOP_ENTRY_SHOW_IN_MATE = 1 << 2, DESKTOP_ENTRY_TRYEXEC_FAILED = 1 << 3 }; struct DesktopEntry { char* path; char* basename; GQuark* categories; char* name; char* generic_name; char* full_name; char* comment; char* icon; char* exec; gboolean terminal; guint type: 2; guint flags: 4; guint refcount: 24; }; struct DesktopEntrySet { int refcount; GHashTable* hash; }; /* * Desktop entries */ static guint get_flags_from_key_file(DesktopEntry* entry, GKeyFile* key_file, const char* desktop_entry_group) { GError *error; char **strv; gboolean no_display; gboolean hidden; gboolean show_in_mate; gboolean tryexec_failed; char *tryexec; guint flags; int i; error = NULL; no_display = g_key_file_get_boolean (key_file, desktop_entry_group, "NoDisplay", &error); if (error) { no_display = FALSE; g_error_free (error); } error = NULL; hidden = g_key_file_get_boolean (key_file, desktop_entry_group, "Hidden", &error); if (error) { hidden = FALSE; g_error_free (error); } show_in_mate = TRUE; strv = g_key_file_get_string_list (key_file, desktop_entry_group, "OnlyShowIn", NULL, NULL); if (strv) { show_in_mate = FALSE; for (i = 0; strv[i]; i++) { if (!strcmp (strv[i], "MATE")) { show_in_mate = TRUE; break; } } } else { strv = g_key_file_get_string_list (key_file, desktop_entry_group, "NotShowIn", NULL, NULL); if (strv) { show_in_mate = TRUE; for (i = 0; strv[i]; i++) { if (!strcmp (strv[i], "MATE")) { show_in_mate = FALSE; } } } } g_strfreev (strv); tryexec_failed = FALSE; tryexec = g_key_file_get_string (key_file, desktop_entry_group, "TryExec", NULL); if (tryexec) { char *path; path = g_find_program_in_path (g_strstrip (tryexec)); tryexec_failed = (path == NULL); g_free (path); g_free (tryexec); } flags = 0; if (no_display) flags |= DESKTOP_ENTRY_NO_DISPLAY; if (hidden) flags |= DESKTOP_ENTRY_HIDDEN; if (show_in_mate) flags |= DESKTOP_ENTRY_SHOW_IN_MATE; if (tryexec_failed) flags |= DESKTOP_ENTRY_TRYEXEC_FAILED; return flags; } static GQuark* get_categories_from_key_file (DesktopEntry* entry, GKeyFile* key_file, const char* desktop_entry_group) { GQuark *retval; char **strv; gsize len; int i; strv = g_key_file_get_string_list (key_file, desktop_entry_group, "Categories", &len, NULL); if (!strv) return NULL; retval = g_new0 (GQuark, len + 1); for (i = 0; strv[i]; i++) retval[i] = g_quark_from_string (strv[i]); g_strfreev (strv); return retval; } 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; } 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); } 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_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; 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; } 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) { 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; g_free (entry->basename); entry->basename = NULL; g_free (entry->path); entry->path = NULL; 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) { return entry->name; } const char* desktop_entry_get_generic_name(DesktopEntry* entry) { return entry->generic_name; } const char* desktop_entry_get_full_name(DesktopEntry* entry) { return entry->full_name; } const char* desktop_entry_get_comment(DesktopEntry* entry) { return entry->comment; } const char* desktop_entry_get_icon(DesktopEntry* entry) { return entry->icon; } const char* desktop_entry_get_exec(DesktopEntry* entry) { return entry->exec; } gboolean desktop_entry_get_launch_in_terminal(DesktopEntry* entry) { return entry->terminal; } gboolean desktop_entry_get_hidden(DesktopEntry* entry) { return (entry->flags & DESKTOP_ENTRY_HIDDEN) != 0; } gboolean desktop_entry_get_no_display(DesktopEntry* entry) { return (entry->flags & DESKTOP_ENTRY_NO_DISPLAY) != 0; } gboolean desktop_entry_get_show_in_mate(DesktopEntry* entry) { return (entry->flags & DESKTOP_ENTRY_SHOW_IN_MATE) != 0; } gboolean desktop_entry_get_tryexec_failed(DesktopEntry* entry) { return (entry->flags & DESKTOP_ENTRY_TRYEXEC_FAILED) != 0; } gboolean desktop_entry_has_categories(DesktopEntry* entry) { return (entry->categories != NULL && entry->categories[0] != 0); } gboolean desktop_entry_has_category(DesktopEntry* entry, const char* category) { GQuark quark; int i; if (entry->categories == NULL) return FALSE; if (!(quark = g_quark_try_string (category))) return FALSE; for (i = 0; entry->categories[i]; i++) { if (quark == entry->categories[i]) return TRUE; } return FALSE; } 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; } /* * 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; }