/* * 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 #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_GNOME = 1 << 2, DESKTOP_ENTRY_TRYEXEC_FAILED = 1 << 3 }; struct DesktopEntry { char *path; char *basename; GQuark *categories; char *name; char *generic_name; char *comment; char *icon; char *exec; gboolean terminal : 1; gboolean startup_notify : 1; guint32 show_in_flags; guint type : 2; guint flags : 4; guint refcount : 24; }; struct DesktopEntrySet { int refcount; GHashTable *hash; }; /* defined in menu-cache-gen.c */ guint32 menu_cache_get_de_flag( const char* de_name ); /* * 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_gnome; 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_gnome = TRUE; strv = g_key_file_get_string_list (key_file, desktop_entry_group, "OnlyShowIn", NULL, NULL); if (strv) { /* show_in_gnome = FALSE; */ for (i = 0; strv[i]; i++) { guint32 de_flag = menu_cache_get_de_flag(strv[i]); entry->show_in_flags |= de_flag; /* add the DE */ /* if (!strcmp (strv[i], "GNOME")) { show_in_gnome = TRUE; break; } */ } } else { strv = g_key_file_get_string_list (key_file, desktop_entry_group, "NotShowIn", NULL, NULL); if (strv) { /* show in all DEs by default */ entry->show_in_flags = (guint32)-1; /* show_in_gnome = TRUE; */ for (i = 0; strv[i]; i++) { guint32 de_flag = menu_cache_get_de_flag(strv[i]); entry->show_in_flags &= (~de_flag); /* remove the DE */ /* if (!strcmp (strv[i], "GNOME")) { show_in_gnome = FALSE; } */ } } } g_strfreev (strv); /* printf( "DE flag of %s is %d\n", entry->name, entry->show_in_flags); */ 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_gnome) flags |= DESKTOP_ENTRY_SHOW_IN_GNOME; 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->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); retval->startup_notify = g_key_file_get_boolean (key_file, desktop_entry_group, "StartupNotify", NULL); } #undef GET_LOCALE_STRING menu_verbose ("Desktop entry \"%s\" (%s, %s, %s) flags: NoDisplay=%s, Hidden=%s, ShowInGNOME=%s, TryExecFailed=%s\n", retval->basename, retval->name, 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_GNOME ? "(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->comment); entry->comment = NULL; g_free (entry->icon); entry->icon = NULL; g_free (entry->exec); entry->exec = NULL; entry->terminal = 0; entry->startup_notify = 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->comment = g_strdup (entry->comment); retval->icon = g_strdup (entry->icon); retval->exec = g_strdup (entry->exec); retval->terminal = entry->terminal; retval->startup_notify = entry->startup_notify; 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->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_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; } const GQuark * desktop_entry_get_categories (DesktopEntry *entry) { return entry->categories; } gboolean desktop_entry_get_launch_in_terminal (DesktopEntry *entry) { return entry->terminal; } gboolean desktop_entry_get_use_startup_notify (DesktopEntry *entry) { return entry->startup_notify; } 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_gnome (DesktopEntry *entry) { return (entry->flags & DESKTOP_ENTRY_SHOW_IN_GNOME) != 0; } gboolean desktop_entry_get_show_in_flags (DesktopEntry *entry) { return entry->show_in_flags; } 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; }