/** * @file purple-desktop-item.c Functions for managing .desktop files * @ingroup core * * 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 * source distribution. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 . * * Original copyright notice: * * Copyright (C) 1999, 2000 Red Hat Inc. * Copyright (C) 2001 Sid Vicious * All rights reserved. * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include "desktopitem.h" #include "internal.h" struct _PurpleDesktopItem { int refcount; /* all languages used */ GList *languages; PurpleDesktopItemType type; /* `modified' means that the ditem has been * modified since the last save. */ gboolean modified; /* Keys of the main section only */ GList *keys; GList *sections; /* This includes ALL keys, including * other sections, separated by '/' */ GHashTable *main_hash; char *location; time_t mtime; }; typedef struct { char *name; GList *keys; } Section; typedef enum { ENCODING_UNKNOWN, ENCODING_UTF8, ENCODING_LEGACY_MIXED } Encoding; /************************************************************************** * Private utility functions **************************************************************************/ static PurpleDesktopItemType type_from_string (const char *type) { if (!type) return PURPLE_DESKTOP_ITEM_TYPE_NULL; switch (type [0]) { case 'A': if (!strcmp (type, "Application")) return PURPLE_DESKTOP_ITEM_TYPE_APPLICATION; break; case 'L': if (!strcmp (type, "Link")) return PURPLE_DESKTOP_ITEM_TYPE_LINK; break; case 'F': if (!strcmp (type, "FSDevice")) return PURPLE_DESKTOP_ITEM_TYPE_FSDEVICE; break; case 'M': if (!strcmp (type, "MimeType")) return PURPLE_DESKTOP_ITEM_TYPE_MIME_TYPE; break; case 'D': if (!strcmp (type, "Directory")) return PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY; break; case 'S': if (!strcmp (type, "Service")) return PURPLE_DESKTOP_ITEM_TYPE_SERVICE; else if (!strcmp (type, "ServiceType")) return PURPLE_DESKTOP_ITEM_TYPE_SERVICE_TYPE; break; default: break; } return PURPLE_DESKTOP_ITEM_TYPE_OTHER; } static Section * find_section (PurpleDesktopItem *item, const char *section) { GList *li; Section *sec; if (section == NULL) return NULL; if (strcmp (section, "Desktop Entry") == 0) return NULL; for (li = item->sections; li != NULL; li = li->next) { sec = li->data; if (strcmp (sec->name, section) == 0) return sec; } sec = g_new0 (Section, 1); sec->name = g_strdup (section); sec->keys = NULL; 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 */ return sec; } static Section * section_from_key (PurpleDesktopItem *item, const char *key) { char *p; char *name; Section *sec; if (key == NULL) return NULL; p = strchr (key, '/'); if (p == NULL) return NULL; name = g_strndup (key, p - key); sec = find_section (item, name); g_free (name); return sec; } static const char * key_basename (const char *key) { char *p = strrchr (key, '/'); if (p != NULL) return p+1; else return key; } static void set (PurpleDesktopItem *item, const char *key, const char *value) { Section *sec = section_from_key (item, key); if (sec != NULL) { if (value != NULL) { if (g_hash_table_lookup (item->main_hash, key) == NULL) sec->keys = g_list_append (sec->keys, g_strdup (key_basename (key))); g_hash_table_replace (item->main_hash, g_strdup (key), g_strdup (value)); } else { GList *list = g_list_find_custom (sec->keys, key_basename (key), (GCompareFunc)strcmp); if (list != NULL) { g_free (list->data); sec->keys = g_list_delete_link (sec->keys, list); } g_hash_table_remove (item->main_hash, key); } } else { if (value != NULL) { if (g_hash_table_lookup (item->main_hash, key) == NULL) item->keys = g_list_append (item->keys, g_strdup (key)); g_hash_table_replace (item->main_hash, g_strdup (key), g_strdup (value)); } else { GList *list = g_list_find_custom (item->keys, key, (GCompareFunc)strcmp); if (list != NULL) { g_free (list->data); item->keys = g_list_delete_link (item->keys, list); } g_hash_table_remove (item->main_hash, key); } } item->modified = TRUE; } static void _purple_desktop_item_set_string (PurpleDesktopItem *item, const char *attr, const char *value) { g_return_if_fail (item != NULL); g_return_if_fail (item->refcount > 0); g_return_if_fail (attr != NULL); set (item, attr, value); if (strcmp (attr, PURPLE_DESKTOP_ITEM_TYPE) == 0) item->type = type_from_string (value); } static PurpleDesktopItem * _purple_desktop_item_new (void) { PurpleDesktopItem *retval; retval = g_new0 (PurpleDesktopItem, 1); retval->refcount++; retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_free); /* These are guaranteed to be set */ _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_NAME, _("No name")); _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_ENCODING, "UTF-8"); _purple_desktop_item_set_string (retval, PURPLE_DESKTOP_ITEM_VERSION, "1.0"); return retval; } static gpointer _purple_desktop_item_copy (gpointer boxed) { return purple_desktop_item_copy (boxed); } static void _purple_desktop_item_free (gpointer boxed) { purple_desktop_item_unref (boxed); } /* Note, does not include the trailing \n */ static char * my_fgets (char *buf, gsize bufsize, FILE *df) { int c; gsize pos; g_return_val_if_fail (buf != NULL, NULL); g_return_val_if_fail (df != NULL, NULL); pos = 0; buf[0] = '\0'; do { c = getc (df); if (c == EOF || c == '\n') break; buf[pos++] = c; } while (pos < bufsize-1); if (c == EOF && pos == 0) return NULL; buf[pos++] = '\0'; return buf; } static Encoding get_encoding (FILE *df) { gboolean old_kde = FALSE; char buf [BUFSIZ]; gboolean all_valid_utf8 = TRUE; while (my_fgets (buf, sizeof (buf), df) != NULL) { if (strncmp (PURPLE_DESKTOP_ITEM_ENCODING, buf, strlen (PURPLE_DESKTOP_ITEM_ENCODING)) == 0) { char *p = &buf[strlen (PURPLE_DESKTOP_ITEM_ENCODING)]; if (*p == ' ') p++; if (*p != '=') continue; p++; if (*p == ' ') p++; if (strcmp (p, "UTF-8") == 0) { return ENCODING_UTF8; } else if (strcmp (p, "Legacy-Mixed") == 0) { return ENCODING_LEGACY_MIXED; } else { /* According to the spec we're not supposed * to read a file like this */ return ENCODING_UNKNOWN; } } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) { old_kde = TRUE; /* don't break yet, we still want to support * Encoding even here */ } if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) all_valid_utf8 = FALSE; } if (old_kde) 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 */ if (all_valid_utf8) return ENCODING_UTF8; else return ENCODING_LEGACY_MIXED; } static char * snarf_locale_from_key (const char *key) { const char *brace; char *locale, *p; brace = strchr (key, '['); if (brace == NULL) return NULL; locale = g_strdup (brace + 1); if (*locale == '\0') { g_free (locale); return NULL; } p = strchr (locale, ']'); if (p == NULL) { g_free (locale); return NULL; } *p = '\0'; return locale; } static gboolean check_locale (const char *locale) { GIConv cd = g_iconv_open ("UTF-8", locale); if ((GIConv)-1 == cd) return FALSE; g_iconv_close (cd); return TRUE; } static void insert_locales (GHashTable *encodings, char *enc, ...) { va_list args; char *s; va_start (args, enc); for (;;) { s = va_arg (args, char *); if (s == NULL) break; g_hash_table_insert (encodings, s, enc); } va_end (args); } /* make a standard conversion table from the desktop standard spec */ static GHashTable * init_encodings (void) { GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); /* "C" is plain ascii */ 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); } else { 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); } else { insert_locales (encodings, "TCVN", "vi", NULL); } insert_locales (encodings, "TIS-620", "th", NULL); /*insert_locales (encodings, "VISCII", NULL);*/ return encodings; } static const char * get_encoding_from_locale (const char *locale) { char lang[3]; const char *encoding; static GHashTable *encodings = NULL; if (locale == NULL) return NULL; /* if locale includes encoding, use it */ encoding = strchr (locale, '.'); if (encoding != NULL) { return encoding+1; } if (encodings == NULL) encodings = init_encodings (); /* first try the entire locale (at this point ll_CC) */ encoding = g_hash_table_lookup (encodings, locale); if (encoding != NULL) return encoding; /* Try just the language */ strncpy (lang, locale, 2); lang[2] = '\0'; return g_hash_table_lookup (encodings, lang); } static char * decode_string_and_dup (const char *s) { char *p = g_malloc (strlen (s) + 1); char *q = p; do { if (*s == '\\'){ switch (*(++s)){ case 's': *p++ = ' '; break; case 't': *p++ = '\t'; break; case 'n': *p++ = '\n'; break; case '\\': *p++ = '\\'; break; case 'r': *p++ = '\r'; break; default: *p++ = '\\'; *p++ = *s; break; } } else { *p++ = *s; } } while (*s++); return q; } static char * decode_string (const char *value, Encoding encoding, const char *locale) { char *retval = NULL; /* if legacy mixed, then convert */ if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { const char *char_encoding = get_encoding_from_locale (locale); char *utf8_string; if (char_encoding == NULL) return NULL; if (strcmp (char_encoding, "ASCII") == 0) { return decode_string_and_dup (value); } utf8_string = g_convert (value, -1, "UTF-8", char_encoding, NULL, NULL, NULL); if (utf8_string == NULL) return NULL; retval = decode_string_and_dup (utf8_string); g_free (utf8_string); return retval; /* if utf8, then validate */ } else if (locale != NULL && encoding == ENCODING_UTF8) { if ( ! g_utf8_validate (value, -1, NULL)) /* invalid utf8, ignore this key */ return NULL; return decode_string_and_dup (value); } else { /* Meaning this is not a localized string */ return decode_string_and_dup (value); } } /************************************************************ * Parser: * ************************************************************/ static gboolean G_GNUC_CONST standard_is_boolean (const char * key) { static GHashTable *bools = NULL; if (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; if (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 */ static char * cannonize (const char *key, const char *value) { if (standard_is_boolean (key)) { if (value[0] == 'T' || value[0] == 't' || value[0] == 'Y' || value[0] == 'y' || atoi (value) != 0) { return g_strdup ("true"); } else { 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 * carefully if at all */ return NULL; } static void insert_key (PurpleDesktopItem *item, Section *cur_section, Encoding encoding, const char *key, const char *value, gboolean old_kde, gboolean no_translations) { char *k; char *val; /* we always store everything in UTF-8 */ if (cur_section == NULL && strcmp (key, PURPLE_DESKTOP_ITEM_ENCODING) == 0) { k = g_strdup (key); val = g_strdup ("UTF-8"); } else { char *locale = snarf_locale_from_key (key); /* If we're ignoring translations */ if (no_translations && locale != NULL) { g_free (locale); return; } val = decode_string (value, encoding, locale); /* Ignore this key, it's whacked */ if (val == NULL) { g_free (locale); return; } g_strchomp (val); /* For old KDE entries, we can also split by a comma * on sort order, so convert to semicolons */ if (old_kde && cur_section == NULL && strcmp (key, PURPLE_DESKTOP_ITEM_SORT_ORDER) == 0 && strchr (val, ';') == NULL) { int i; for (i = 0; val[i] != '\0'; i++) { if (val[i] == ',') val[i] = ';'; } } /* Check some types, not perfect, but catches a lot * of things */ if (cur_section == NULL) { char *cannon = cannonize (key, val); if (cannon != NULL) { g_free (val); val = cannon; } } k = g_strdup (key); /* Take care of the language part */ if (locale != NULL && strcmp (locale, "C") == 0) { char *p; /* Whack C locale */ p = strchr (k, '['); if(p) *p = '\0'; g_free (locale); } else if (locale != NULL) { char *p, *brace; /* Whack the encoding part */ p = strchr (locale, '.'); if (p != NULL) *p = '\0'; if (g_list_find_custom (item->languages, locale, (GCompareFunc)strcmp) == NULL) { item->languages = g_list_prepend (item->languages, locale); } else { g_free (locale); } /* Whack encoding from encoding in the key */ brace = strchr (k, '['); if(brace != NULL) { p = strchr (brace, '.'); if (p != NULL) { *p = ']'; *(p+1) = '\0'; } } } } 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, g_strdup (k)); } /* later duplicates override earlier ones */ g_hash_table_replace (item->main_hash, k, val); } else { char *full = g_strdup_printf ("%s/%s", cur_section->name, k); /* only add to list if we haven't seen it before */ if (g_hash_table_lookup (item->main_hash, full) == NULL) { cur_section->keys = g_list_prepend (cur_section->keys, k); } /* later duplicates override earlier ones */ g_hash_table_replace (item->main_hash, full, val); } } static const char * lookup (const PurpleDesktopItem *item, const char *key) { return g_hash_table_lookup (item->main_hash, key); } static void 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 (base != NULL && strcmp (base, ".directory") == 0) { /* This gotta be a directory */ g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_TYPE), g_strdup ("Directory")); item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_TYPE)); item->type = PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY; } else { item->type = PURPLE_DESKTOP_ITEM_TYPE_NULL; } g_free (base); } else { item->type = type_from_string (type); } } static const char * lookup_locale (const PurpleDesktopItem *item, const char *key, const char *locale) { if (locale == NULL || strcmp (locale, "C") == 0) { return lookup (item, key); } else { const char *ret; char *full = g_strdup_printf ("%s[%s]", key, locale); ret = lookup (item, full); g_free (full); return ret; } } /* fallback to find something suitable for C locale */ static char * try_english_key (PurpleDesktopItem *item, const char *key) { char *str; char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; int i; str = NULL; for (i = 0; locales[i] != NULL && str == NULL; i++) { str = g_strdup (lookup_locale (item, key, locales[i])); } if (str != NULL) { /* We need a 7-bit ascii string, so whack all * above 127 chars */ guchar *p; for (p = (guchar *)str; *p != '\0'; p++) { if (*p > 127) *p = '?'; } } return str; } static void sanitize (PurpleDesktopItem *item, const char *uri) { const char *type; type = lookup (item, PURPLE_DESKTOP_ITEM_TYPE); /* understand old gnome style url exec thingies */ if (type != NULL && strcmp (type, "URL") == 0) { const char *exec = lookup (item, PURPLE_DESKTOP_ITEM_EXEC); set (item, PURPLE_DESKTOP_ITEM_TYPE, "Link"); if (exec != NULL) { /* 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 */ if (name == NULL) name = g_strdup (_("No name")); g_hash_table_replace (item->main_hash, g_strdup (PURPLE_DESKTOP_ITEM_NAME), 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), g_strdup ("UTF-8")); 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), g_strdup ("1.0")); item->keys = g_list_prepend (item->keys, g_strdup (PURPLE_DESKTOP_ITEM_VERSION)); } } enum { FirstBrace, OnSecHeader, IgnoreToEOL, IgnoreToEOLFirst, KeyDef, KeyDefOnKey, KeyValue }; static PurpleDesktopItem * ditem_load (FILE *df, gboolean no_translations, const char *uri) { int state; char CharBuffer [1024]; char *next = CharBuffer; int c; Encoding encoding; PurpleDesktopItem *item; Section *cur_section = NULL; char *key = NULL; gboolean old_kde = FALSE; encoding = get_encoding (df); if (encoding == ENCODING_UNKNOWN) { fclose(df); /* spec says, don't read this file */ printf ("Unknown encoding of .desktop file"); return NULL; } /* Rewind since get_encoding goes through the file */ if (fseek(df, 0L, SEEK_SET)) { fclose(df); /* spec says, don't read this file */ printf ("fseek() error on .desktop file"); return NULL; } item = _purple_desktop_item_new (); item->modified = FALSE; /* 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]) state = FirstBrace; while ((c = getc (df)) != EOF) { if (c == '\r') /* Ignore Carriage Return */ continue; switch (state) { case OnSecHeader: if (c == ']' || PURPLE_DESKTOP_ITEM_OVERFLOW) { *next = '\0'; next = CharBuffer; /* keys were inserted in reverse */ if (cur_section != NULL && cur_section->keys != NULL) { cur_section->keys = g_list_reverse (cur_section->keys); } if (strcmp (CharBuffer, "KDE Desktop Entry") == 0) { /* Main section */ cur_section = NULL; old_kde = TRUE; } else if (strcmp (CharBuffer, "Desktop Entry") == 0) { /* Main section */ cur_section = NULL; } else { cur_section = g_new0 (Section, 1); cur_section->name = g_strdup (CharBuffer); cur_section->keys = NULL; item->sections = g_list_prepend (item->sections, cur_section); } state = IgnoreToEOL; } else if (c == '[') { /* FIXME: probably error out instead of ignoring this */ } else { *next++ = c; } break; case IgnoreToEOL: case IgnoreToEOLFirst: if (c == '\n'){ if (state == IgnoreToEOLFirst) state = FirstBrace; else state = KeyDef; next = CharBuffer; } break; case FirstBrace: case KeyDef: case KeyDefOnKey: if (c == '#') { if (state == FirstBrace) state = IgnoreToEOLFirst; else state = IgnoreToEOL; break; } if (c == '[' && state != KeyDefOnKey){ state = OnSecHeader; next = CharBuffer; g_free (key); key = NULL; break; } /* On first pass, don't allow dangling keys */ if (state == FirstBrace) break; if ((c == ' ' && state != KeyDefOnKey) || c == '\t') break; if (c == '\n' || PURPLE_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */ next = CharBuffer; state = KeyDef; break; } if (c == '=' || PURPLE_DESKTOP_ITEM_OVERFLOW){ *next = '\0'; g_free (key); key = g_strdup (CharBuffer); state = KeyValue; next = CharBuffer; } else { *next++ = c; state = KeyDefOnKey; } break; case KeyValue: if (PURPLE_DESKTOP_ITEM_OVERFLOW || c == '\n'){ *next = '\0'; insert_key (item, cur_section, encoding, key, CharBuffer, old_kde, no_translations); g_free (key); key = NULL; state = (c == '\n') ? KeyDef : IgnoreToEOL; next = CharBuffer; } else { *next++ = c; } break; } /* switch */ } /* while ((c = getc_unlocked (f)) != EOF) */ if (c == EOF && state == KeyValue) { *next = '\0'; insert_key (item, cur_section, encoding, key, CharBuffer, old_kde, no_translations); g_free (key); key = NULL; } #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 */ sanitize (item, uri); /* make sure that we set up the type */ setup_type (item, uri); fclose (df); return item; } static void copy_string_hash (gpointer key, gpointer value, gpointer user_data) { GHashTable *copy = user_data; g_hash_table_replace (copy, g_strdup (key), g_strdup (value)); } static void free_section (gpointer data, gpointer user_data) { Section *section = data; g_free (section->name); section->name = NULL; g_list_foreach (section->keys, (GFunc)g_free, NULL); g_list_free (section->keys); section->keys = NULL; g_free (section); } static Section * dup_section (Section *sec) { GList *li; 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); return retval; } /************************************************************************** * Public functions **************************************************************************/ PurpleDesktopItem * purple_desktop_item_new_from_file (const char *filename) { PurpleDesktopItem *retval; FILE *dfile; g_return_val_if_fail (filename != NULL, NULL); dfile = g_fopen(filename, "r"); if (!dfile) { printf ("Can't open %s: %s", filename, strerror(errno)); return NULL; } retval = ditem_load(dfile, FALSE, filename); return retval; } PurpleDesktopItemType 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); return item->type; } const char * purple_desktop_item_get_string (const PurpleDesktopItem *item, const char *attr) { 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); } PurpleDesktopItem * purple_desktop_item_copy (const PurpleDesktopItem *item) { GList *li; 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; /* Languages */ retval->languages = g_list_copy (item->languages); for (li = retval->languages; li != NULL; li = li->next) li->data = g_strdup (li->data); /* Keys */ retval->keys = g_list_copy (item->keys); for (li = retval->keys; li != NULL; li = li->next) li->data = g_strdup (li->data); /* Sections */ 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, (GDestroyNotify) g_free); g_hash_table_foreach (item->main_hash, copy_string_hash, retval->main_hash); return retval; } void purple_desktop_item_unref (PurpleDesktopItem *item) { g_return_if_fail (item != NULL); g_return_if_fail (item->refcount > 0); item->refcount--; if(item->refcount != 0) return; g_list_foreach (item->languages, (GFunc)g_free, NULL); g_list_free (item->languages); item->languages = NULL; g_list_foreach (item->keys, (GFunc)g_free, NULL); g_list_free (item->keys); item->keys = NULL; g_list_foreach (item->sections, free_section, NULL); g_list_free (item->sections); item->sections = NULL; g_hash_table_destroy (item->main_hash); item->main_hash = NULL; g_free (item->location); item->location = NULL; g_free (item); } GType purple_desktop_item_get_type (void) { static GType type = 0; if (type == 0) { type = g_boxed_type_register_static ("PurpleDesktopItem", _purple_desktop_item_copy, _purple_desktop_item_free); } return type; }