comparison src/desktopitem.c @ 10229:9aa0b6d11bbf

[gaim-migrate @ 11364] This is a heavily warmenhoved patch from Alceste Scalas. Here's what it changes: If the user drags an image file into the buddy list or conversation, it presents three options (when applicable): Set as buddy icon - sets this image to be the buddy icon for this buddy Send image file - Initiates a file transfer to send this image. Insert in message - Inserts in the gtkimhtml for use as an IM image. If the user drags a .desktop web link, it will insert a hyperlink in the conversation. All other types of .desktop files fail with an error dialog. If anyone can think of better ways to handle any of them, let me know. This also happens to implement gaim_request_choice, which had previously been unimplemented. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Mon, 22 Nov 2004 02:57:34 +0000
parents
children 0f7452b1f777
comparison
equal deleted inserted replaced
10228:37c411c8cde3 10229:9aa0b6d11bbf
1 /**
2 * @file gaim-desktop-item.c Functions for managing .desktop files
3 * @ingroup core
4 *
5 * Gaim is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 *
23 */
24
25 /*
26 * The following code has been adapted from gnome-desktop-item.[ch],
27 * as found on gnome-desktop-2.8.1.
28 *
29 * Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>.
30 *
31 * Original copyright notice:
32 *
33 * Copyright (C) 1999, 2000 Red Hat Inc.
34 * Copyright (C) 2001 Sid Vicious
35 * All rights reserved.
36 *
37 * This file is part of the Gnome Library.
38 *
39 * The Gnome Library is free software; you can redistribute it and/or
40 * modify it under the terms of the GNU Library General Public License as
41 * published by the Free Software Foundation; either version 2 of the
42 * License, or (at your option) any later version.
43 *
44 * The Gnome Library is distributed in the hope that it will be useful,
45 * but WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * Library General Public License for more details.
48 *
49 * You should have received a copy of the GNU Library General Public
50 * License along with the Gnome Library; see the file COPYING.LIB. If not,
51 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
52 * Boston, MA 02111-1307, USA.
53 */
54
55 #include <errno.h>
56 #include <stdio.h>
57 #include <string.h>
58 #include <time.h>
59 #include "desktopitem.h"
60 #include "internal.h"
61
62 struct _GaimDesktopItem {
63 int refcount;
64
65 /* all languages used */
66 GList *languages;
67
68 GaimDesktopItemType type;
69
70 /* `modified' means that the ditem has been
71 * modified since the last save. */
72 gboolean modified;
73
74 /* Keys of the main section only */
75 GList *keys;
76
77 GList *sections;
78
79 /* This includes ALL keys, including
80 * other sections, separated by '/' */
81 GHashTable *main_hash;
82
83 char *location;
84
85 time_t mtime;
86 };
87
88 typedef struct {
89 char *name;
90 GList *keys;
91 } Section;
92
93 typedef enum {
94 ENCODING_UNKNOWN,
95 ENCODING_UTF8,
96 ENCODING_LEGACY_MIXED
97 } Encoding;
98
99 /**************************************************************************
100 * Private utility functions
101 **************************************************************************/
102 static GaimDesktopItemType
103 type_from_string (const char *type)
104 {
105 if (!type)
106 return GAIM_DESKTOP_ITEM_TYPE_NULL;
107
108 switch (type [0]) {
109 case 'A':
110 if (!strcmp (type, "Application"))
111 return GAIM_DESKTOP_ITEM_TYPE_APPLICATION;
112 break;
113 case 'L':
114 if (!strcmp (type, "Link"))
115 return GAIM_DESKTOP_ITEM_TYPE_LINK;
116 break;
117 case 'F':
118 if (!strcmp (type, "FSDevice"))
119 return GAIM_DESKTOP_ITEM_TYPE_FSDEVICE;
120 break;
121 case 'M':
122 if (!strcmp (type, "MimeType"))
123 return GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE;
124 break;
125 case 'D':
126 if (!strcmp (type, "Directory"))
127 return GAIM_DESKTOP_ITEM_TYPE_DIRECTORY;
128 break;
129 case 'S':
130 if (!strcmp (type, "Service"))
131 return GAIM_DESKTOP_ITEM_TYPE_SERVICE;
132
133 else if (!strcmp (type, "ServiceType"))
134 return GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE;
135 break;
136 default:
137 break;
138 }
139
140 return GAIM_DESKTOP_ITEM_TYPE_OTHER;
141 }
142
143 static Section *
144 find_section (GaimDesktopItem *item, const char *section)
145 {
146 GList *li;
147 Section *sec;
148
149 if (section == NULL)
150 return NULL;
151 if (strcmp (section, "Desktop Entry") == 0)
152 return NULL;
153
154 for (li = item->sections; li != NULL; li = li->next) {
155 sec = li->data;
156 if (strcmp (sec->name, section) == 0)
157 return sec;
158 }
159
160 sec = g_new0 (Section, 1);
161 sec->name = g_strdup (section);
162 sec->keys = NULL;
163
164 item->sections = g_list_append (item->sections, sec);
165
166 /* Don't mark the item modified, this is just an empty section,
167 * it won't be saved even */
168
169 return sec;
170 }
171
172 static Section *
173 section_from_key (GaimDesktopItem *item, const char *key)
174 {
175 char *p;
176 char *name;
177 Section *sec;
178
179 if (key == NULL)
180 return NULL;
181
182 p = strchr (key, '/');
183 if (p == NULL)
184 return NULL;
185
186 name = g_strndup (key, p - key);
187
188 sec = find_section (item, name);
189
190 g_free (name);
191
192 return sec;
193 }
194
195 static const char *
196 key_basename (const char *key)
197 {
198 char *p = strrchr (key, '/');
199 if (p != NULL)
200 return p+1;
201 else
202 return key;
203 }
204
205 static void
206 set (GaimDesktopItem *item, const char *key, const char *value)
207 {
208 Section *sec = section_from_key (item, key);
209
210 if (sec != NULL) {
211 if (value != NULL) {
212 if (g_hash_table_lookup (item->main_hash, key) == NULL)
213 sec->keys = g_list_append
214 (sec->keys,
215 g_strdup (key_basename (key)));
216
217 g_hash_table_replace (item->main_hash,
218 g_strdup (key),
219 g_strdup (value));
220 } else {
221 GList *list = g_list_find_custom
222 (sec->keys, key_basename (key),
223 (GCompareFunc)strcmp);
224 if (list != NULL) {
225 g_free (list->data);
226 sec->keys =
227 g_list_delete_link (sec->keys, list);
228 }
229 g_hash_table_remove (item->main_hash, key);
230 }
231 } else {
232 if (value != NULL) {
233 if (g_hash_table_lookup (item->main_hash, key) == NULL)
234 item->keys = g_list_append (item->keys,
235 g_strdup (key));
236
237 g_hash_table_replace (item->main_hash,
238 g_strdup (key),
239 g_strdup (value));
240 } else {
241 GList *list = g_list_find_custom
242 (item->keys, key, (GCompareFunc)strcmp);
243 if (list != NULL) {
244 g_free (list->data);
245 item->keys =
246 g_list_delete_link (item->keys, list);
247 }
248 g_hash_table_remove (item->main_hash, key);
249 }
250 }
251 item->modified = TRUE;
252 }
253
254
255 static void
256 _gaim_desktop_item_set_string (GaimDesktopItem *item,
257 const char *attr,
258 const char *value)
259 {
260 g_return_if_fail (item != NULL);
261 g_return_if_fail (item->refcount > 0);
262 g_return_if_fail (attr != NULL);
263
264 set (item, attr, value);
265
266 if (strcmp (attr, GAIM_DESKTOP_ITEM_TYPE) == 0)
267 item->type = type_from_string (value);
268 }
269
270 static GaimDesktopItem *
271 _gaim_desktop_item_new (void)
272 {
273 GaimDesktopItem *retval;
274
275 retval = g_new0 (GaimDesktopItem, 1);
276
277 retval->refcount++;
278
279 retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
280 (GDestroyNotify) g_free,
281 (GDestroyNotify) g_free);
282
283 /* These are guaranteed to be set */
284 _gaim_desktop_item_set_string (retval,
285 GAIM_DESKTOP_ITEM_NAME,
286 _("No name"));
287 _gaim_desktop_item_set_string (retval,
288 GAIM_DESKTOP_ITEM_ENCODING,
289 "UTF-8");
290 _gaim_desktop_item_set_string (retval,
291 GAIM_DESKTOP_ITEM_VERSION,
292 "1.0");
293
294 return retval;
295 }
296
297 static gpointer
298 _gaim_desktop_item_copy (gpointer boxed)
299 {
300 return gaim_desktop_item_copy (boxed);
301 }
302
303 static void
304 _gaim_desktop_item_free (gpointer boxed)
305 {
306 gaim_desktop_item_unref (boxed);
307 }
308
309 /* Note, does not include the trailing \n */
310 static char *
311 my_fgets (char *buf, gsize bufsize, FILE *df)
312 {
313 int c;
314 gsize pos;
315
316 g_return_val_if_fail (buf != NULL, NULL);
317 g_return_val_if_fail (df != NULL, NULL);
318
319 pos = 0;
320 buf[0] = '\0';
321
322 do {
323 c = getc (df);
324 if (c == EOF || c == '\n')
325 break;
326 buf[pos++] = c;
327 } while (pos < bufsize-1);
328
329 if (c == EOF && pos == 0)
330 return NULL;
331
332 buf[pos++] = '\0';
333
334 return buf;
335 }
336
337 static Encoding
338 get_encoding (FILE *df)
339 {
340 gboolean old_kde = FALSE;
341 char buf [BUFSIZ];
342 gboolean all_valid_utf8 = TRUE;
343
344 while (my_fgets (buf, sizeof (buf), df) != NULL) {
345 if (strncmp (GAIM_DESKTOP_ITEM_ENCODING,
346 buf,
347 strlen (GAIM_DESKTOP_ITEM_ENCODING)) == 0) {
348 char *p = &buf[strlen (GAIM_DESKTOP_ITEM_ENCODING)];
349 if (*p == ' ')
350 p++;
351 if (*p != '=')
352 continue;
353 p++;
354 if (*p == ' ')
355 p++;
356 if (strcmp (p, "UTF-8") == 0) {
357 return ENCODING_UTF8;
358 } else if (strcmp (p, "Legacy-Mixed") == 0) {
359 return ENCODING_LEGACY_MIXED;
360 } else {
361 /* According to the spec we're not supposed
362 * to read a file like this */
363 return ENCODING_UNKNOWN;
364 }
365 } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) {
366 old_kde = TRUE;
367 /* don't break yet, we still want to support
368 * Encoding even here */
369 }
370 if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL))
371 all_valid_utf8 = FALSE;
372 }
373
374 if (old_kde)
375 return ENCODING_LEGACY_MIXED;
376
377 /* A dilemma, new KDE files are in UTF-8 but have no Encoding
378 * info, at this time we really can't tell. The best thing to
379 * do right now is to just assume UTF-8 if the whole file
380 * validates as utf8 I suppose */
381
382 if (all_valid_utf8)
383 return ENCODING_UTF8;
384 else
385 return ENCODING_LEGACY_MIXED;
386 }
387
388 static char *
389 snarf_locale_from_key (const char *key)
390 {
391 const char *brace;
392 char *locale, *p;
393
394 brace = strchr (key, '[');
395 if (brace == NULL)
396 return NULL;
397
398 locale = g_strdup (brace + 1);
399 if (*locale == '\0') {
400 g_free (locale);
401 return NULL;
402 }
403 p = strchr (locale, ']');
404 if (p == NULL) {
405 g_free (locale);
406 return NULL;
407 }
408 *p = '\0';
409 return locale;
410 }
411
412 static gboolean
413 check_locale (const char *locale)
414 {
415 GIConv cd = g_iconv_open ("UTF-8", locale);
416 if ((GIConv)-1 == cd)
417 return FALSE;
418 g_iconv_close (cd);
419 return TRUE;
420 }
421
422 static void
423 insert_locales (GHashTable *encodings, char *enc, ...)
424 {
425 va_list args;
426 char *s;
427
428 va_start (args, enc);
429 for (;;) {
430 s = va_arg (args, char *);
431 if (s == NULL)
432 break;
433 g_hash_table_insert (encodings, s, enc);
434 }
435 va_end (args);
436 }
437
438 /* make a standard conversion table from the desktop standard spec */
439 static GHashTable *
440 init_encodings (void)
441 {
442 GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal);
443
444 /* "C" is plain ascii */
445 insert_locales (encodings, "ASCII", "C", NULL);
446
447 insert_locales (encodings, "ARMSCII-8", "by", NULL);
448 insert_locales (encodings, "BIG5", "zh_TW", NULL);
449 insert_locales (encodings, "CP1251", "be", "bg", NULL);
450 if (check_locale ("EUC-CN")) {
451 insert_locales (encodings, "EUC-CN", "zh_CN", NULL);
452 } else {
453 insert_locales (encodings, "GB2312", "zh_CN", NULL);
454 }
455 insert_locales (encodings, "EUC-JP", "ja", NULL);
456 insert_locales (encodings, "EUC-KR", "ko", NULL);
457 /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/
458 insert_locales (encodings, "GEORGIAN-PS", "ka", NULL);
459 insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL);
460 insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL);
461 insert_locales (encodings, "ISO-8859-3", "eo", NULL);
462 insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL);
463 insert_locales (encodings, "ISO-8859-7", "el", NULL);
464 insert_locales (encodings, "ISO-8859-9", "tr", NULL);
465 insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL);
466 insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL);
467 insert_locales (encodings, "ISO-8859-15", "et", NULL);
468 insert_locales (encodings, "KOI8-R", "ru", NULL);
469 insert_locales (encodings, "KOI8-U", "uk", NULL);
470 if (check_locale ("TCVN-5712")) {
471 insert_locales (encodings, "TCVN-5712", "vi", NULL);
472 } else {
473 insert_locales (encodings, "TCVN", "vi", NULL);
474 }
475 insert_locales (encodings, "TIS-620", "th", NULL);
476 /*insert_locales (encodings, "VISCII", NULL);*/
477
478 return encodings;
479 }
480
481 static const char *
482 get_encoding_from_locale (const char *locale)
483 {
484 char lang[3];
485 const char *encoding;
486 static GHashTable *encodings = NULL;
487
488 if (locale == NULL)
489 return NULL;
490
491 /* if locale includes encoding, use it */
492 encoding = strchr (locale, '.');
493 if (encoding != NULL) {
494 return encoding+1;
495 }
496
497 if (encodings == NULL)
498 encodings = init_encodings ();
499
500 /* first try the entire locale (at this point ll_CC) */
501 encoding = g_hash_table_lookup (encodings, locale);
502 if (encoding != NULL)
503 return encoding;
504
505 /* Try just the language */
506 strncpy (lang, locale, 2);
507 lang[2] = '\0';
508 return g_hash_table_lookup (encodings, lang);
509 }
510
511 static char *
512 decode_string_and_dup (const char *s)
513 {
514 char *p = g_malloc (strlen (s) + 1);
515 char *q = p;
516
517 do {
518 if (*s == '\\'){
519 switch (*(++s)){
520 case 's':
521 *p++ = ' ';
522 break;
523 case 't':
524 *p++ = '\t';
525 break;
526 case 'n':
527 *p++ = '\n';
528 break;
529 case '\\':
530 *p++ = '\\';
531 break;
532 case 'r':
533 *p++ = '\r';
534 break;
535 default:
536 *p++ = '\\';
537 *p++ = *s;
538 break;
539 }
540 } else {
541 *p++ = *s;
542 }
543 } while (*s++);
544
545 return q;
546 }
547
548 static char *
549 decode_string (const char *value, Encoding encoding, const char *locale)
550 {
551 char *retval = NULL;
552
553 /* if legacy mixed, then convert */
554 if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) {
555 const char *char_encoding = get_encoding_from_locale (locale);
556 char *utf8_string;
557 if (char_encoding == NULL)
558 return NULL;
559 if (strcmp (char_encoding, "ASCII") == 0) {
560 return decode_string_and_dup (value);
561 }
562 utf8_string = g_convert (value, -1, "UTF-8", char_encoding,
563 NULL, NULL, NULL);
564 if (utf8_string == NULL)
565 return NULL;
566 retval = decode_string_and_dup (utf8_string);
567 g_free (utf8_string);
568 return retval;
569 /* if utf8, then validate */
570 } else if (locale != NULL && encoding == ENCODING_UTF8) {
571 if ( ! g_utf8_validate (value, -1, NULL))
572 /* invalid utf8, ignore this key */
573 return NULL;
574 return decode_string_and_dup (value);
575 } else {
576 /* Meaning this is not a localized string */
577 return decode_string_and_dup (value);
578 }
579 }
580
581 /************************************************************
582 * Parser: *
583 ************************************************************/
584
585 static gboolean G_GNUC_CONST
586 standard_is_boolean (const char * key)
587 {
588 static GHashTable *bools = NULL;
589
590 if (bools == NULL) {
591 bools = g_hash_table_new (g_str_hash, g_str_equal);
592 g_hash_table_insert (bools,
593 GAIM_DESKTOP_ITEM_NO_DISPLAY,
594 GAIM_DESKTOP_ITEM_NO_DISPLAY);
595 g_hash_table_insert (bools,
596 GAIM_DESKTOP_ITEM_HIDDEN,
597 GAIM_DESKTOP_ITEM_HIDDEN);
598 g_hash_table_insert (bools,
599 GAIM_DESKTOP_ITEM_TERMINAL,
600 GAIM_DESKTOP_ITEM_TERMINAL);
601 g_hash_table_insert (bools,
602 GAIM_DESKTOP_ITEM_READ_ONLY,
603 GAIM_DESKTOP_ITEM_READ_ONLY);
604 }
605
606 return g_hash_table_lookup (bools, key) != NULL;
607 }
608
609 static gboolean G_GNUC_CONST
610 standard_is_strings (const char *key)
611 {
612 static GHashTable *strings = NULL;
613
614 if (strings == NULL) {
615 strings = g_hash_table_new (g_str_hash, g_str_equal);
616 g_hash_table_insert (strings,
617 GAIM_DESKTOP_ITEM_FILE_PATTERN,
618 GAIM_DESKTOP_ITEM_FILE_PATTERN);
619 g_hash_table_insert (strings,
620 GAIM_DESKTOP_ITEM_ACTIONS,
621 GAIM_DESKTOP_ITEM_ACTIONS);
622 g_hash_table_insert (strings,
623 GAIM_DESKTOP_ITEM_MIME_TYPE,
624 GAIM_DESKTOP_ITEM_MIME_TYPE);
625 g_hash_table_insert (strings,
626 GAIM_DESKTOP_ITEM_PATTERNS,
627 GAIM_DESKTOP_ITEM_PATTERNS);
628 g_hash_table_insert (strings,
629 GAIM_DESKTOP_ITEM_SORT_ORDER,
630 GAIM_DESKTOP_ITEM_SORT_ORDER);
631 }
632
633 return g_hash_table_lookup (strings, key) != NULL;
634 }
635
636 /* If no need to cannonize, returns NULL */
637 static char *
638 cannonize (const char *key, const char *value)
639 {
640 if (standard_is_boolean (key)) {
641 if (value[0] == 'T' ||
642 value[0] == 't' ||
643 value[0] == 'Y' ||
644 value[0] == 'y' ||
645 atoi (value) != 0) {
646 return g_strdup ("true");
647 } else {
648 return g_strdup ("false");
649 }
650 } else if (standard_is_strings (key)) {
651 int len = strlen (value);
652 if (len == 0 || value[len-1] != ';') {
653 return g_strconcat (value, ";", NULL);
654 }
655 }
656 /* XXX: Perhaps we should canonize numeric values as well, but this
657 * has caused some subtle problems before so it needs to be done
658 * carefully if at all */
659 return NULL;
660 }
661
662 static void
663 insert_key (GaimDesktopItem *item,
664 Section *cur_section,
665 Encoding encoding,
666 const char *key,
667 const char *value,
668 gboolean old_kde,
669 gboolean no_translations)
670 {
671 char *k;
672 char *val;
673 /* we always store everything in UTF-8 */
674 if (cur_section == NULL &&
675 strcmp (key, GAIM_DESKTOP_ITEM_ENCODING) == 0) {
676 k = g_strdup (key);
677 val = g_strdup ("UTF-8");
678 } else {
679 char *locale = snarf_locale_from_key (key);
680 /* If we're ignoring translations */
681 if (no_translations && locale != NULL) {
682 g_free (locale);
683 return;
684 }
685 val = decode_string (value, encoding, locale);
686
687 /* Ignore this key, it's whacked */
688 if (val == NULL) {
689 g_free (locale);
690 return;
691 }
692
693 g_strchomp (val);
694
695 /* For old KDE entries, we can also split by a comma
696 * on sort order, so convert to semicolons */
697 if (old_kde &&
698 cur_section == NULL &&
699 strcmp (key, GAIM_DESKTOP_ITEM_SORT_ORDER) == 0 &&
700 strchr (val, ';') == NULL) {
701 int i;
702 for (i = 0; val[i] != '\0'; i++) {
703 if (val[i] == ',')
704 val[i] = ';';
705 }
706 }
707
708 /* Check some types, not perfect, but catches a lot
709 * of things */
710 if (cur_section == NULL) {
711 char *cannon = cannonize (key, val);
712 if (cannon != NULL) {
713 g_free (val);
714 val = cannon;
715 }
716 }
717
718 k = g_strdup (key);
719
720 /* Take care of the language part */
721 if (locale != NULL &&
722 strcmp (locale, "C") == 0) {
723 char *p;
724 /* Whack C locale */
725 p = strchr (k, '[');
726 *p = '\0';
727 g_free (locale);
728 } else if (locale != NULL) {
729 char *p, *brace;
730
731 /* Whack the encoding part */
732 p = strchr (locale, '.');
733 if (p != NULL)
734 *p = '\0';
735
736 if (g_list_find_custom (item->languages, locale,
737 (GCompareFunc)strcmp) == NULL) {
738 item->languages = g_list_prepend
739 (item->languages, locale);
740 } else {
741 g_free (locale);
742 }
743
744 /* Whack encoding from encoding in the key */
745 brace = strchr (k, '[');
746 p = strchr (brace, '.');
747 if (p != NULL) {
748 *p = ']';
749 *(p+1) = '\0';
750 }
751 }
752 }
753
754
755 if (cur_section == NULL) {
756 /* only add to list if we haven't seen it before */
757 if (g_hash_table_lookup (item->main_hash, k) == NULL) {
758 item->keys = g_list_prepend (item->keys,
759 g_strdup (k));
760 }
761 /* later duplicates override earlier ones */
762 g_hash_table_replace (item->main_hash, k, val);
763 } else {
764 char *full = g_strdup_printf
765 ("%s/%s",
766 cur_section->name, k);
767 /* only add to list if we haven't seen it before */
768 if (g_hash_table_lookup (item->main_hash, full) == NULL) {
769 cur_section->keys =
770 g_list_prepend (cur_section->keys, k);
771 }
772 /* later duplicates override earlier ones */
773 g_hash_table_replace (item->main_hash,
774 full, val);
775 }
776 }
777
778 static const char *
779 lookup (const GaimDesktopItem *item, const char *key)
780 {
781 return g_hash_table_lookup (item->main_hash, key);
782 }
783
784 static void
785 setup_type (GaimDesktopItem *item, const char *uri)
786 {
787 const char *type = g_hash_table_lookup (item->main_hash,
788 GAIM_DESKTOP_ITEM_TYPE);
789 if (type == NULL && uri != NULL) {
790 char *base = g_path_get_basename (uri);
791 if (base != NULL &&
792 strcmp (base, ".directory") == 0) {
793 /* This gotta be a directory */
794 g_hash_table_replace (item->main_hash,
795 g_strdup (GAIM_DESKTOP_ITEM_TYPE),
796 g_strdup ("Directory"));
797 item->keys = g_list_prepend
798 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_TYPE));
799 item->type = GAIM_DESKTOP_ITEM_TYPE_DIRECTORY;
800 } else {
801 item->type = GAIM_DESKTOP_ITEM_TYPE_NULL;
802 }
803 g_free (base);
804 } else {
805 item->type = type_from_string (type);
806 }
807 }
808
809 static const char *
810 lookup_locale (const GaimDesktopItem *item, const char *key, const char *locale)
811 {
812 if (locale == NULL ||
813 strcmp (locale, "C") == 0) {
814 return lookup (item, key);
815 } else {
816 const char *ret;
817 char *full = g_strdup_printf ("%s[%s]", key, locale);
818 ret = lookup (item, full);
819 g_free (full);
820 return ret;
821 }
822 }
823
824 /* fallback to find something suitable for C locale */
825 static char *
826 try_english_key (GaimDesktopItem *item, const char *key)
827 {
828 char *str;
829 char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL };
830 int i;
831
832 str = NULL;
833 for (i = 0; locales[i] != NULL && str == NULL; i++) {
834 str = g_strdup (lookup_locale (item, key, locales[i]));
835 }
836 if (str != NULL) {
837 /* We need a 7-bit ascii string, so whack all
838 * above 127 chars */
839 guchar *p;
840 for (p = (guchar *)str; *p != '\0'; p++) {
841 if (*p > 127)
842 *p = '?';
843 }
844 }
845 return str;
846 }
847
848
849 static void
850 sanitize (GaimDesktopItem *item, const char *uri)
851 {
852 const char *type;
853
854 type = lookup (item, GAIM_DESKTOP_ITEM_TYPE);
855
856 /* understand old gnome style url exec thingies */
857 if (type != NULL && strcmp (type, "URL") == 0) {
858 const char *exec = lookup (item, GAIM_DESKTOP_ITEM_EXEC);
859 set (item, GAIM_DESKTOP_ITEM_TYPE, "Link");
860 if (exec != NULL) {
861 /* Note, this must be in this order */
862 set (item, GAIM_DESKTOP_ITEM_URL, exec);
863 set (item, GAIM_DESKTOP_ITEM_EXEC, NULL);
864 }
865 }
866
867 /* we make sure we have Name, Encoding and Version */
868 if (lookup (item, GAIM_DESKTOP_ITEM_NAME) == NULL) {
869 char *name = try_english_key (item, GAIM_DESKTOP_ITEM_NAME);
870 /* If no name, use the basename */
871 if (name == NULL && uri != NULL)
872 name = g_path_get_basename (uri);
873 /* If no uri either, use same default as gnome_desktop_item_new */
874 if (name == NULL)
875 name = g_strdup (_("No name"));
876 g_hash_table_replace (item->main_hash,
877 g_strdup (GAIM_DESKTOP_ITEM_NAME),
878 name);
879 item->keys = g_list_prepend
880 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_NAME));
881 }
882 if (lookup (item, GAIM_DESKTOP_ITEM_ENCODING) == NULL) {
883 /* We store everything in UTF-8 so write that down */
884 g_hash_table_replace (item->main_hash,
885 g_strdup (GAIM_DESKTOP_ITEM_ENCODING),
886 g_strdup ("UTF-8"));
887 item->keys = g_list_prepend
888 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_ENCODING));
889 }
890 if (lookup (item, GAIM_DESKTOP_ITEM_VERSION) == NULL) {
891 /* this is the version that we follow, so write it down */
892 g_hash_table_replace (item->main_hash,
893 g_strdup (GAIM_DESKTOP_ITEM_VERSION),
894 g_strdup ("1.0"));
895 item->keys = g_list_prepend
896 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_VERSION));
897 }
898 }
899
900 enum {
901 FirstBrace,
902 OnSecHeader,
903 IgnoreToEOL,
904 IgnoreToEOLFirst,
905 KeyDef,
906 KeyDefOnKey,
907 KeyValue
908 };
909
910 static GaimDesktopItem *
911 ditem_load (FILE *df,
912 gboolean no_translations,
913 const char *uri)
914 {
915 int state;
916 char CharBuffer [1024];
917 char *next = CharBuffer;
918 int c;
919 Encoding encoding;
920 GaimDesktopItem *item;
921 Section *cur_section = NULL;
922 char *key = NULL;
923 gboolean old_kde = FALSE;
924
925 encoding = get_encoding (df);
926 if (encoding == ENCODING_UNKNOWN) {
927 fclose(df);
928 /* spec says, don't read this file */
929 printf ("Unknown encoding of .desktop file");
930
931 return NULL;
932 }
933
934 /* Rewind since get_encoding goes through the file */
935 if (fseek(df, 0L, SEEK_SET)) {
936 fclose(df);
937 /* spec says, don't read this file */
938 printf ("fseek() error on .desktop file");
939 return NULL;
940 }
941
942 item = _gaim_desktop_item_new ();
943 item->modified = FALSE;
944
945 /* Note: location and mtime are filled in by the new_from_file
946 * function since it has those values */
947
948 #define GAIM_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1])
949
950 state = FirstBrace;
951 while ((c = getc (df)) != EOF) {
952 if (c == '\r') /* Ignore Carriage Return */
953 continue;
954
955 switch (state) {
956
957 case OnSecHeader:
958 if (c == ']' || GAIM_DESKTOP_ITEM_OVERFLOW) {
959 *next = '\0';
960 next = CharBuffer;
961
962 /* keys were inserted in reverse */
963 if (cur_section != NULL &&
964 cur_section->keys != NULL) {
965 cur_section->keys = g_list_reverse
966 (cur_section->keys);
967 }
968 if (strcmp (CharBuffer,
969 "KDE Desktop Entry") == 0) {
970 /* Main section */
971 cur_section = NULL;
972 old_kde = TRUE;
973 } else if (strcmp (CharBuffer,
974 "Desktop Entry") == 0) {
975 /* Main section */
976 cur_section = NULL;
977 } else {
978 cur_section = g_new0 (Section, 1);
979 cur_section->name =
980 g_strdup (CharBuffer);
981 cur_section->keys = NULL;
982 item->sections = g_list_prepend
983 (item->sections, cur_section);
984 }
985 state = IgnoreToEOL;
986 } else if (c == '[') {
987 /* FIXME: probably error out instead of ignoring this */
988 } else {
989 *next++ = c;
990 }
991 break;
992
993 case IgnoreToEOL:
994 case IgnoreToEOLFirst:
995 if (c == '\n'){
996 if (state == IgnoreToEOLFirst)
997 state = FirstBrace;
998 else
999 state = KeyDef;
1000 next = CharBuffer;
1001 }
1002 break;
1003
1004 case FirstBrace:
1005 case KeyDef:
1006 case KeyDefOnKey:
1007 if (c == '#') {
1008 if (state == FirstBrace)
1009 state = IgnoreToEOLFirst;
1010 else
1011 state = IgnoreToEOL;
1012 break;
1013 }
1014
1015 if (c == '[' && state != KeyDefOnKey){
1016 state = OnSecHeader;
1017 next = CharBuffer;
1018 g_free (key);
1019 key = NULL;
1020 break;
1021 }
1022 /* On first pass, don't allow dangling keys */
1023 if (state == FirstBrace)
1024 break;
1025
1026 if ((c == ' ' && state != KeyDefOnKey) || c == '\t')
1027 break;
1028
1029 if (c == '\n' || GAIM_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */
1030 next = CharBuffer;
1031 state = KeyDef;
1032 break;
1033 }
1034
1035 if (c == '=' || GAIM_DESKTOP_ITEM_OVERFLOW){
1036 *next = '\0';
1037
1038 g_free (key);
1039 key = g_strdup (CharBuffer);
1040 state = KeyValue;
1041 next = CharBuffer;
1042 } else {
1043 *next++ = c;
1044 state = KeyDefOnKey;
1045 }
1046 break;
1047
1048 case KeyValue:
1049 if (GAIM_DESKTOP_ITEM_OVERFLOW || c == '\n'){
1050 *next = '\0';
1051
1052 insert_key (item, cur_section, encoding,
1053 key, CharBuffer, old_kde,
1054 no_translations);
1055
1056 g_free (key);
1057 key = NULL;
1058
1059 state = (c == '\n') ? KeyDef : IgnoreToEOL;
1060 next = CharBuffer;
1061 } else {
1062 *next++ = c;
1063 }
1064 break;
1065
1066 } /* switch */
1067
1068 } /* while ((c = getc_unlocked (f)) != EOF) */
1069 if (c == EOF && state == KeyValue) {
1070 *next = '\0';
1071
1072 insert_key (item, cur_section, encoding,
1073 key, CharBuffer, old_kde,
1074 no_translations);
1075
1076 g_free (key);
1077 key = NULL;
1078 }
1079
1080 #undef GAIM_DESKTOP_ITEM_OVERFLOW
1081
1082 /* keys were inserted in reverse */
1083 if (cur_section != NULL &&
1084 cur_section->keys != NULL) {
1085 cur_section->keys = g_list_reverse (cur_section->keys);
1086 }
1087 /* keys were inserted in reverse */
1088 item->keys = g_list_reverse (item->keys);
1089 /* sections were inserted in reverse */
1090 item->sections = g_list_reverse (item->sections);
1091
1092 /* sanitize some things */
1093 sanitize (item, uri);
1094
1095 /* make sure that we set up the type */
1096 setup_type (item, uri);
1097
1098 fclose (df);
1099
1100 return item;
1101 }
1102
1103 static void
1104 copy_string_hash (gpointer key, gpointer value, gpointer user_data)
1105 {
1106 GHashTable *copy = user_data;
1107 g_hash_table_replace (copy,
1108 g_strdup (key),
1109 g_strdup (value));
1110 }
1111
1112 static void
1113 free_section (gpointer data, gpointer user_data)
1114 {
1115 Section *section = data;
1116
1117 g_free (section->name);
1118 section->name = NULL;
1119
1120 g_list_foreach (section->keys, (GFunc)g_free, NULL);
1121 g_list_free (section->keys);
1122 section->keys = NULL;
1123
1124 g_free (section);
1125 }
1126
1127 static Section *
1128 dup_section (Section *sec)
1129 {
1130 GList *li;
1131 Section *retval = g_new0 (Section, 1);
1132
1133 retval->name = g_strdup (sec->name);
1134
1135 retval->keys = g_list_copy (sec->keys);
1136 for (li = retval->keys; li != NULL; li = li->next)
1137 li->data = g_strdup (li->data);
1138
1139 return retval;
1140 }
1141
1142 /**************************************************************************
1143 * Public functions
1144 **************************************************************************/
1145 GaimDesktopItem *
1146 gaim_desktop_item_new_from_file (const char *filename)
1147 {
1148 GaimDesktopItem *retval;
1149 FILE *dfile;
1150
1151 g_return_val_if_fail (filename != NULL, NULL);
1152
1153 dfile = fopen(filename, "r");
1154 if (!dfile) {
1155 printf ("Can't open %s: %s", filename, strerror(errno));
1156 return NULL;
1157 }
1158
1159 retval = ditem_load(dfile, FALSE, filename);
1160
1161 return retval;
1162 }
1163
1164 GaimDesktopItemType
1165 gaim_desktop_item_get_entry_type (const GaimDesktopItem *item)
1166 {
1167 g_return_val_if_fail (item != NULL, 0);
1168 g_return_val_if_fail (item->refcount > 0, 0);
1169
1170 return item->type;
1171 }
1172
1173 const char *
1174 gaim_desktop_item_get_string (const GaimDesktopItem *item,
1175 const char *attr)
1176 {
1177 g_return_val_if_fail (item != NULL, NULL);
1178 g_return_val_if_fail (item->refcount > 0, NULL);
1179 g_return_val_if_fail (attr != NULL, NULL);
1180
1181 return lookup (item, attr);
1182 }
1183
1184 GaimDesktopItem *
1185 gaim_desktop_item_copy (const GaimDesktopItem *item)
1186 {
1187 GList *li;
1188 GaimDesktopItem *retval;
1189
1190 g_return_val_if_fail (item != NULL, NULL);
1191 g_return_val_if_fail (item->refcount > 0, NULL);
1192
1193 retval = _gaim_desktop_item_new ();
1194
1195 retval->type = item->type;
1196 retval->modified = item->modified;
1197 retval->location = g_strdup (item->location);
1198 retval->mtime = item->mtime;
1199
1200 /* Languages */
1201 retval->languages = g_list_copy (item->languages);
1202 for (li = retval->languages; li != NULL; li = li->next)
1203 li->data = g_strdup (li->data);
1204
1205 /* Keys */
1206 retval->keys = g_list_copy (item->keys);
1207 for (li = retval->keys; li != NULL; li = li->next)
1208 li->data = g_strdup (li->data);
1209
1210 /* Sections */
1211 retval->sections = g_list_copy (item->sections);
1212 for (li = retval->sections; li != NULL; li = li->next)
1213 li->data = dup_section (li->data);
1214
1215 retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1216 (GDestroyNotify) g_free,
1217 (GDestroyNotify) g_free);
1218
1219 g_hash_table_foreach (item->main_hash,
1220 copy_string_hash,
1221 retval->main_hash);
1222
1223 return retval;
1224 }
1225
1226 void
1227 gaim_desktop_item_unref (GaimDesktopItem *item)
1228 {
1229 g_return_if_fail (item != NULL);
1230 g_return_if_fail (item->refcount > 0);
1231
1232 item->refcount--;
1233
1234 if(item->refcount != 0)
1235 return;
1236
1237 g_list_foreach (item->languages, (GFunc)g_free, NULL);
1238 g_list_free (item->languages);
1239 item->languages = NULL;
1240
1241 g_list_foreach (item->keys, (GFunc)g_free, NULL);
1242 g_list_free (item->keys);
1243 item->keys = NULL;
1244
1245 g_list_foreach (item->sections, free_section, NULL);
1246 g_list_free (item->sections);
1247 item->sections = NULL;
1248
1249 g_hash_table_destroy (item->main_hash);
1250 item->main_hash = NULL;
1251
1252 g_free (item->location);
1253 item->location = NULL;
1254
1255 g_free (item);
1256 }
1257
1258 GType
1259 gaim_desktop_item_get_type (void)
1260 {
1261 static GType type = 0;
1262
1263 if (type == 0) {
1264 type = g_boxed_type_register_static ("GaimDesktopItem",
1265 _gaim_desktop_item_copy,
1266 _gaim_desktop_item_free);
1267 }
1268
1269 return type;
1270 }