Mercurial > pidgin
annotate src/desktopitem.c @ 11261:b53606580f68
[gaim-migrate @ 13439]
Patch #1226486 from Levi Bard
Fixes bug #1224178
If you change the topic in a chat room and that topic change is rejected, the
topic field is wrong -- it shows the new topic even thought it didn't get set.
This patch resets the GUI's topic immediately when you hit enter. Then, if the
topic change is successful, the server will echo the topic change back to us
and we'll update the GUI to the new topic.
The only question is, does the server always echo the topic back to us? From the
core's point of view, I'm ready to assume yes. It's the case for both IRC and
Jabber*. If someone could test changing a topic in SILC or Zephyr, that'd be
great. If servers using those protocols do not echo the topic back, the prpl
will have to fake it as appropriate.
* I didn't actually test on Jabber, but Nathan said the server will echo the
topic change back. If it's broken, let me know.
committer: Tailor Script <tailor@pidgin.im>
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Sun, 14 Aug 2005 06:55:57 +0000 |
parents | 0f7452b1f777 |
children | f3768fab10bd |
rev | line source |
---|---|
10229 | 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 | |
10589
0f7452b1f777
[gaim-migrate @ 11994]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
10229
diff
changeset
|
1153 dfile = g_fopen(filename, "r"); |
10229 | 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 } |