* @file purple-desktop-item.c Functions for managing .desktop files /* Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * The following code has been adapted from gnome-desktop-item.[ch], * as found on gnome-desktop-2.8.1. * Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>. * Original copyright notice: * Copyright (C) 1999, 2000 Red Hat Inc. * Copyright (C) 2001 Sid Vicious * This file is part of the Gnome Library. * The Gnome Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * The Gnome 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 * Library General Public License for more details. * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02111-1301, USA. struct _PurpleDesktopItem { PurpleDesktopItemType type; /* `modified' means that the ditem has been * modified since the last save. */ /* Keys of the main section only */ /* This includes ALL keys, including * other sections, separated by '/' */ /************************************************************************** * Private utility functions **************************************************************************/ static PurpleDesktopItemType type_from_string (const char *type) return PURPLE_DESKTOP_ITEM_TYPE_NULL; if (purple_strequal (type, "Application")) return PURPLE_DESKTOP_ITEM_TYPE_APPLICATION; if (purple_strequal (type, "Link")) return PURPLE_DESKTOP_ITEM_TYPE_LINK; if (purple_strequal (type, "FSDevice")) return PURPLE_DESKTOP_ITEM_TYPE_FSDEVICE; if (purple_strequal (type, "MimeType")) return PURPLE_DESKTOP_ITEM_TYPE_MIME_TYPE; if (purple_strequal (type, "Directory")) return PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY; if (purple_strequal (type, "Service")) return PURPLE_DESKTOP_ITEM_TYPE_SERVICE; else if (purple_strequal (type, "ServiceType")) return PURPLE_DESKTOP_ITEM_TYPE_SERVICE_TYPE; return PURPLE_DESKTOP_ITEM_TYPE_OTHER; find_section (PurpleDesktopItem *item, const char *section) if (purple_strequal (section, "Desktop Entry")) for (li = item->sections; li != NULL; li = li->next) { if (purple_strequal (sec->name, section)) sec = g_new0 (Section, 1); sec->name = g_strdup (section); item->sections = g_list_append (item->sections, sec); /* Don't mark the item modified, this is just an empty section, * it won't be saved even */ section_from_key (PurpleDesktopItem *item, const char *key) name = g_strndup (key, p - key); sec = find_section (item, name); key_basename (const char *key) char *p = strrchr (key, '/'); set (PurpleDesktopItem *item, const char *key, const char *value) Section *sec = section_from_key (item, key); if (g_hash_table_lookup (item->main_hash, key) == NULL) sec->keys = g_list_append g_strdup (key_basename (key))); g_hash_table_replace (item->main_hash, GList *list = g_list_find_custom (sec->keys, key_basename (key), g_list_delete_link (sec->keys, list); g_hash_table_remove (item->main_hash, key); if (g_hash_table_lookup (item->main_hash, key) == NULL) item->keys = g_list_append (item->keys, g_hash_table_replace (item->main_hash, GList *list = g_list_find_custom (item->keys, key, (GCompareFunc)strcmp); g_list_delete_link (item->keys, list); g_hash_table_remove (item->main_hash, key); _purple_desktop_item_set_string (PurpleDesktopItem *item, g_return_if_fail (item != NULL); g_return_if_fail (item->refcount > 0); g_return_if_fail (attr != NULL); if (purple_strequal (attr, PURPLE_DESKTOP_ITEM_TYPE)) item->type = type_from_string (value); static PurpleDesktopItem * _purple_desktop_item_new (void) PurpleDesktopItem *retval; retval = g_new0 (PurpleDesktopItem, 1); retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free); /* These are guaranteed to be set */ _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_NAME, _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_ENCODING, _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_VERSION, _purple_desktop_item_copy (gpointer boxed) return purple_desktop_item_copy (boxed); _purple_desktop_item_free (gpointer boxed) purple_desktop_item_unref (boxed); /* Note, does not include the trailing \n */ my_fgets (char *buf, gsize bufsize, FILE *df) g_return_val_if_fail (buf != NULL, NULL); g_return_val_if_fail (df != NULL, NULL); if (c == EOF || c == '\n') } while (pos < bufsize-1); if (c == EOF && pos == 0) gboolean old_kde = FALSE; gboolean all_valid_utf8 = TRUE; while (my_fgets (buf, sizeof (buf), df) != NULL) { if (strncmp (PURPLE_DESKTOP_ITEM_ENCODING, strlen (PURPLE_DESKTOP_ITEM_ENCODING)) == 0) { char *p = &buf[strlen (PURPLE_DESKTOP_ITEM_ENCODING)]; if (purple_strequal (p, "UTF-8")) { } else if (purple_strequal (p, "Legacy-Mixed")) { return ENCODING_LEGACY_MIXED; /* According to the spec we're not supposed * to read a file like this */ } else if (purple_strequal ("[KDE Desktop Entry]", buf)) { /* don't break yet, we still want to support if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) return ENCODING_LEGACY_MIXED; /* A dilemma, new KDE files are in UTF-8 but have no Encoding * info, at this time we really can't tell. The best thing to * do right now is to just assume UTF-8 if the whole file * validates as utf8 I suppose */ return ENCODING_LEGACY_MIXED; snarf_locale_from_key (const char *key) brace = strchr (key, '['); locale = g_strdup (brace + 1); p = strchr (locale, ']'); check_locale (const char *locale) GIConv cd = g_iconv_open ("UTF-8", locale); insert_locales (GHashTable *encodings, char *enc, ...) s = va_arg (args, char *); g_hash_table_insert (encodings, s, enc); /* make a standard conversion table from the desktop standard spec */ GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); insert_locales (encodings, "ASCII", "C", NULL); insert_locales (encodings, "ARMSCII-8", "by", NULL); insert_locales (encodings, "BIG5", "zh_TW", NULL); insert_locales (encodings, "CP1251", "be", "bg", NULL); if (check_locale ("EUC-CN")) { insert_locales (encodings, "EUC-CN", "zh_CN", NULL); insert_locales (encodings, "GB2312", "zh_CN", NULL); insert_locales (encodings, "EUC-JP", "ja", NULL); insert_locales (encodings, "EUC-KR", "ko", NULL); /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/ insert_locales (encodings, "GEORGIAN-PS", "ka", NULL); insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL); insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL); insert_locales (encodings, "ISO-8859-3", "eo", NULL); insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL); insert_locales (encodings, "ISO-8859-7", "el", NULL); insert_locales (encodings, "ISO-8859-9", "tr", NULL); insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL); insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL); insert_locales (encodings, "ISO-8859-15", "et", NULL); insert_locales (encodings, "KOI8-R", "ru", NULL); insert_locales (encodings, "KOI8-U", "uk", NULL); if (check_locale ("TCVN-5712")) { insert_locales (encodings, "TCVN-5712", "vi", NULL); insert_locales (encodings, "TCVN", "vi", NULL); insert_locales (encodings, "TIS-620", "th", NULL); /*insert_locales (encodings, "VISCII", NULL);*/ get_encoding_from_locale (const char *locale) static GHashTable *encodings = NULL; /* if locale includes encoding, use it */ encoding = strchr (locale, '.'); encodings = init_encodings (); /* first try the entire locale (at this point ll_CC) */ encoding = g_hash_table_lookup (encodings, locale); /* Try just the language */ strncpy (lang, locale, 2); return g_hash_table_lookup (encodings, lang); decode_string_and_dup (const char *s) char *p = g_malloc (strlen (s) + 1); decode_string (const char *value, Encoding encoding, const char *locale) /* if legacy mixed, then convert */ if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { const char *char_encoding = get_encoding_from_locale (locale); if (char_encoding == NULL) if (purple_strequal (char_encoding, "ASCII")) { return decode_string_and_dup (value); utf8_string = g_convert (value, -1, "UTF-8", char_encoding, retval = decode_string_and_dup (utf8_string); /* if utf8, then validate */ } else if (locale != NULL && encoding == ENCODING_UTF8) { if ( ! g_utf8_validate (value, -1, NULL)) /* invalid utf8, ignore this key */ return decode_string_and_dup (value); /* Meaning this is not a localized string */ return decode_string_and_dup (value); /************************************************************ ************************************************************/ static gboolean G_GNUC_CONST standard_is_boolean (const char * key) static GHashTable *bools = NULL; bools = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (bools, PURPLE_DESKTOP_ITEM_NO_DISPLAY, PURPLE_DESKTOP_ITEM_NO_DISPLAY); g_hash_table_insert (bools, PURPLE_DESKTOP_ITEM_HIDDEN, PURPLE_DESKTOP_ITEM_HIDDEN); g_hash_table_insert (bools, PURPLE_DESKTOP_ITEM_TERMINAL, PURPLE_DESKTOP_ITEM_TERMINAL); g_hash_table_insert (bools, PURPLE_DESKTOP_ITEM_READ_ONLY, PURPLE_DESKTOP_ITEM_READ_ONLY); return g_hash_table_lookup (bools, key) != NULL; static gboolean G_GNUC_CONST standard_is_strings (const char *key) static GHashTable *strings = NULL; strings = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (strings, PURPLE_DESKTOP_ITEM_FILE_PATTERN, PURPLE_DESKTOP_ITEM_FILE_PATTERN); g_hash_table_insert (strings, PURPLE_DESKTOP_ITEM_ACTIONS, PURPLE_DESKTOP_ITEM_ACTIONS); g_hash_table_insert (strings, PURPLE_DESKTOP_ITEM_MIME_TYPE, PURPLE_DESKTOP_ITEM_MIME_TYPE); g_hash_table_insert (strings, PURPLE_DESKTOP_ITEM_PATTERNS, PURPLE_DESKTOP_ITEM_PATTERNS); g_hash_table_insert (strings, PURPLE_DESKTOP_ITEM_SORT_ORDER, PURPLE_DESKTOP_ITEM_SORT_ORDER); return g_hash_table_lookup (strings, key) != NULL; /* If no need to cannonize, returns NULL */ cannonize (const char *key, const char *value) if (standard_is_boolean (key)) { return g_strdup ("true"); return g_strdup ("false"); } else if (standard_is_strings (key)) { int len = strlen (value); if (len == 0 || value[len-1] != ';') { return g_strconcat (value, ";", NULL); /* XXX: Perhaps we should canonize numeric values as well, but this * has caused some subtle problems before so it needs to be done insert_key (PurpleDesktopItem *item, gboolean no_translations) /* we always store everything in UTF-8 */ if (cur_section == NULL && purple_strequal (key, PURPLE_DESKTOP_ITEM_ENCODING)) { val = g_strdup ("UTF-8"); char *locale = snarf_locale_from_key (key); /* If we're ignoring translations */ if (no_translations && locale != NULL) { val = decode_string (value, encoding, locale); /* Ignore this key, it's whacked */ /* For old KDE entries, we can also split by a comma * on sort order, so convert to semicolons */ purple_strequal (key, PURPLE_DESKTOP_ITEM_SORT_ORDER) && strchr (val, ';') == NULL) { for (i = 0; val[i] != '\0'; i++) { /* Check some types, not perfect, but catches a lot if (cur_section == NULL) { char *cannon = cannonize (key, val); /* Take care of the language part */ purple_strequal (locale, "C")) { } else if (locale != NULL) { /* Whack the encoding part */ p = strchr (locale, '.'); if (g_list_find_custom (item->languages, locale, (GCompareFunc)strcmp) == NULL) { item->languages = g_list_prepend (item->languages, locale); /* Whack encoding from encoding in the key */ if (cur_section == NULL) { /* only add to list if we haven't seen it before */ if (g_hash_table_lookup (item->main_hash, k) == NULL) { item->keys = g_list_prepend (item->keys, /* later duplicates override earlier ones */ g_hash_table_replace (item->main_hash, k, val); char *full = g_strdup_printf /* only add to list if we haven't seen it before */ if (g_hash_table_lookup (item->main_hash, full) == NULL) { g_list_prepend (cur_section->keys, k); /* later duplicates override earlier ones */ g_hash_table_replace (item->main_hash, lookup (const PurpleDesktopItem *item, const char *key) return g_hash_table_lookup (item->main_hash, key); setup_type (PurpleDesktopItem *item, const char *uri) const char *type = g_hash_table_lookup (item->main_hash, PURPLE_DESKTOP_ITEM_TYPE); if (type == NULL && uri != NULL) { char *base = g_path_get_basename (uri); if (purple_strequal(base, ".directory")) { /* This gotta be a directory */ g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_TYPE), item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_TYPE)); item->type = PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY; item->type = PURPLE_DESKTOP_ITEM_TYPE_NULL; item->type = type_from_string (type); lookup_locale (const PurpleDesktopItem *item, const char *key, const char *locale) purple_strequal (locale, "C")) { return lookup (item, key); char *full = g_strdup_printf ("%s[%s]", key, locale); ret = lookup (item, full); * Fallback to find something suitable for C locale. * @return A newly allocated string which should be g_freed by the caller. try_english_key (PurpleDesktopItem *item, const char *key) char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; for (i = 0; locales[i] != NULL && str == NULL; i++) { str = g_strdup (lookup_locale (item, key, locales[i])); /* We need a 7-bit ascii string, so whack all for (p = (guchar *)str; *p != '\0'; p++) { sanitize (PurpleDesktopItem *item, const char *uri) type = lookup (item, PURPLE_DESKTOP_ITEM_TYPE); /* understand old gnome style url exec thingies */ if (purple_strequal(type, "URL")) { const char *exec = lookup (item, PURPLE_DESKTOP_ITEM_EXEC); set (item, PURPLE_DESKTOP_ITEM_TYPE, "Link"); /* Note, this must be in this order */ set (item, PURPLE_DESKTOP_ITEM_URL, exec); set (item, PURPLE_DESKTOP_ITEM_EXEC, NULL); /* we make sure we have Name, Encoding and Version */ if (lookup (item, PURPLE_DESKTOP_ITEM_NAME) == NULL) { char *name = try_english_key (item, PURPLE_DESKTOP_ITEM_NAME); /* If no name, use the basename */ if (name == NULL && uri != NULL) name = g_path_get_basename (uri); /* If no uri either, use same default as gnome_desktop_item_new */ name = g_strdup (_("No name")); g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_NAME), item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_NAME)); if (lookup (item, PURPLE_DESKTOP_ITEM_ENCODING) == NULL) { /* We store everything in UTF-8 so write that down */ g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_ENCODING), item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_ENCODING)); if (lookup (item, PURPLE_DESKTOP_ITEM_VERSION) == NULL) { /* this is the version that we follow, so write it down */ g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_VERSION), item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_VERSION)); static PurpleDesktopItem * gboolean no_translations, Section *cur_section = NULL; gboolean old_kde = FALSE; encoding = get_encoding (df); if (encoding == ENCODING_UNKNOWN) { /* spec says, don't read this file */ printf ("Unknown encoding of .desktop file"); /* Rewind since get_encoding goes through the file */ if (fseek(df, 0L, SEEK_SET)) { /* spec says, don't read this file */ printf ("fseek() error on .desktop file"); item = _purple_desktop_item_new (); /* Note: location and mtime are filled in by the new_from_file * function since it has those values */ #define PURPLE_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1]) while ((c = getc (df)) != EOF) { if (c == '\r') /* Ignore Carriage Return */ if (c == ']' || PURPLE_DESKTOP_ITEM_OVERFLOW) { /* keys were inserted in reverse */ if (cur_section != NULL && cur_section->keys != NULL) { cur_section->keys = g_list_reverse if (purple_strequal (CharBuffer, "KDE Desktop Entry")) { } else if (purple_strequal(CharBuffer, "Desktop Entry")) { cur_section = g_new0 (Section, 1); cur_section->keys = NULL; item->sections = g_list_prepend (item->sections, cur_section); /* FIXME: probably error out instead of ignoring this */ if (state == IgnoreToEOLFirst) state = IgnoreToEOLFirst; if (c == '[' && state != KeyDefOnKey){ /* On first pass, don't allow dangling keys */ if ((c == ' ' && state != KeyDefOnKey) || c == '\t') if (c == '\n' || PURPLE_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */ if (c == '=' || PURPLE_DESKTOP_ITEM_OVERFLOW){ key = g_strdup (CharBuffer); if (PURPLE_DESKTOP_ITEM_OVERFLOW || c == '\n'){ insert_key (item, cur_section, encoding, key, CharBuffer, old_kde, state = (c == '\n') ? KeyDef : IgnoreToEOL; } /* while ((c = getc_unlocked (f)) != EOF) */ if (c == EOF && state == KeyValue) { insert_key (item, cur_section, encoding, key, CharBuffer, old_kde, #undef PURPLE_DESKTOP_ITEM_OVERFLOW /* keys were inserted in reverse */ if (cur_section != NULL && cur_section->keys != NULL) { cur_section->keys = g_list_reverse (cur_section->keys); /* keys were inserted in reverse */ item->keys = g_list_reverse (item->keys); /* sections were inserted in reverse */ item->sections = g_list_reverse (item->sections); /* sanitize some things */ /* make sure that we set up the type */ copy_string_hash (gpointer key, gpointer value, gpointer user_data) GHashTable *copy = user_data; g_hash_table_replace (copy, free_section (gpointer data, gpointer user_data) g_list_foreach (section->keys, (GFunc)g_free, NULL); g_list_free (section->keys); dup_section (Section *sec) Section *retval = g_new0 (Section, 1); retval->name = g_strdup (sec->name); retval->keys = g_list_copy (sec->keys); for (li = retval->keys; li != NULL; li = li->next) li->data = g_strdup (li->data); /************************************************************************** **************************************************************************/ purple_desktop_item_new_from_file (const char *filename) PurpleDesktopItem *retval; g_return_val_if_fail (filename != NULL, NULL); dfile = g_fopen(filename, "r"); printf ("Can't open %s: %s", filename, g_strerror(errno)); retval = ditem_load(dfile, FALSE, filename); purple_desktop_item_get_entry_type (const PurpleDesktopItem *item) g_return_val_if_fail (item != NULL, 0); g_return_val_if_fail (item->refcount > 0, 0); purple_desktop_item_get_string (const PurpleDesktopItem *item, g_return_val_if_fail (item != NULL, NULL); g_return_val_if_fail (item->refcount > 0, NULL); g_return_val_if_fail (attr != NULL, NULL); return lookup (item, attr); purple_desktop_item_copy (const PurpleDesktopItem *item) PurpleDesktopItem *retval; g_return_val_if_fail (item != NULL, NULL); g_return_val_if_fail (item->refcount > 0, NULL); retval = _purple_desktop_item_new (); retval->type = item->type; retval->modified = item->modified; retval->location = g_strdup (item->location); retval->mtime = item->mtime; retval->languages = g_list_copy (item->languages); for (li = retval->languages; li != NULL; li = li->next) li->data = g_strdup (li->data); retval->keys = g_list_copy (item->keys); for (li = retval->keys; li != NULL; li = li->next) li->data = g_strdup (li->data); retval->sections = g_list_copy (item->sections); for (li = retval->sections; li != NULL; li = li->next) li->data = dup_section (li->data); retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free); g_hash_table_foreach (item->main_hash, purple_desktop_item_unref (PurpleDesktopItem *item) g_return_if_fail (item != NULL); g_return_if_fail (item->refcount > 0); g_list_foreach (item->languages, (GFunc)g_free, NULL); g_list_free (item->languages); g_list_foreach (item->keys, (GFunc)g_free, NULL); g_list_free (item->keys); g_list_foreach (item->sections, free_section, NULL); g_list_free (item->sections); g_hash_table_destroy (item->main_hash); purple_desktop_item_get_type (void) type = g_boxed_type_register_static ("PurpleDesktopItem", _purple_desktop_item_copy, _purple_desktop_item_free);