Add signal handling with the GMainLoop
[dana/openbox.git] / obt / ddparse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/ddparse.c for the Openbox window manager
4    Copyright (c) 2009        Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/ddparse.h"
20 #include "obt/link.h"
21 #ifdef HAVE_STRING_H
22 #include <string.h>
23 #endif
24 #ifdef HAVE_STDIO_H
25 #include <stdio.h>
26 #endif
27
28 typedef struct _ObtDDParse ObtDDParse;
29
30 /* Parses the value and adds it to the group's key_hash, with the given
31    key
32    Return TRUE if it is added to the hash table, and FALSE if not.
33 */
34 typedef gboolean (*ObtDDParseValueFunc)(gchar *key, const gchar *val,
35                                         ObtDDParse *parse, gboolean *error);
36
37
38 enum {
39     DE_TYPE             = 1 << 0,
40     DE_TYPE_APPLICATION = 1 << 1,
41     DE_TYPE_LINK        = 1 << 2,
42     DE_NAME             = 1 << 3,
43     DE_EXEC             = 1 << 4,
44     DE_URL              = 1 << 5
45 };
46
47 struct _ObtDDParse {
48     gchar *filename;
49     gulong lineno;
50     gulong flags;
51     ObtDDParseGroup *group;
52     /* the key is a group name, the value is a ObtDDParseGroup */
53     GHashTable *group_hash;
54 };
55
56 struct _ObtDDParseGroup {
57     gchar *name;
58     gboolean seen;
59     ObtDDParseValueFunc value_func;
60     /* the key is a string (a key inside the group in the .desktop).
61        the value is an ObtDDParseValue */
62     GHashTable *key_hash;
63 };
64
65 /* Displays a warning message including the file name and line number, and
66    sets the boolean @error to true if it points to a non-NULL address.
67 */
68 static void parse_error(const gchar *m, const ObtDDParse *const parse,
69                         gboolean *error)
70 {
71     if (!parse->filename)
72         g_warning("%s at line %lu of input", m, parse->lineno);
73     else
74         g_warning("%s at line %lu of file %s",
75                   m, parse->lineno, parse->filename);
76     if (error) *error = TRUE;
77 }
78
79 static void parse_value_free(ObtDDParseValue *v)
80 {
81     switch (v->type) {
82     case OBT_DDPARSE_EXEC:
83     case OBT_DDPARSE_STRING:
84     case OBT_DDPARSE_LOCALESTRING:
85         g_free(v->value.string); break;
86     case OBT_DDPARSE_STRINGS:
87     case OBT_DDPARSE_LOCALESTRINGS:
88         g_strfreev(v->value.strings.a);
89         v->value.strings.n = 0;
90         break;
91     case OBT_DDPARSE_BOOLEAN:
92     case OBT_DDPARSE_NUMERIC:
93     case OBT_DDPARSE_ENUM_TYPE:
94     case OBT_DDPARSE_ENVIRONMENTS:
95         break;
96     default:
97         g_assert_not_reached();
98     }
99     g_slice_free(ObtDDParseValue, v);
100 }
101
102 static ObtDDParseGroup* parse_group_new(gchar *name, ObtDDParseValueFunc f)
103 {
104     ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup);
105     g->name = name;
106     g->value_func = f;
107     g->seen = FALSE;
108     g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
109                                         g_free,
110                                         (GDestroyNotify)parse_value_free);
111     return g;
112 }
113
114 static void parse_group_free(ObtDDParseGroup *g)
115 {
116     g_free(g->name);
117     g_hash_table_destroy(g->key_hash);
118     g_slice_free(ObtDDParseGroup, g);
119 }
120
121 /*! Reads an input string, strips out invalid stuff, and parses
122     backslash-stuff.
123  */
124 static gchar* parse_value_string(const gchar *in,
125                                  gboolean locale,
126                                  gboolean semicolonterminate,
127                                  gulong *len,
128                                  const ObtDDParse *const parse,
129                                  gboolean *error)
130 {
131     gint bytes;
132     gboolean backslash;
133     gchar *out, *o;
134     const gchar *end, *i;
135
136     /* find the end/size of the string */
137     backslash = FALSE;
138     for (end = in; *end; ++end) {
139         if (semicolonterminate) {
140             if (backslash) backslash = FALSE;
141             else if (*end == '\\') backslash = TRUE;
142             else if (*end == ';') break;
143         }
144     }
145     bytes = end - in;
146
147     g_return_val_if_fail(in != NULL, NULL);
148
149     if (locale && !g_utf8_validate(in, bytes, &end)) {
150         parse_error("Invalid bytes in localestring", parse, error);
151         bytes = end - in;
152     }
153
154     out = g_new(char, bytes + 1);
155     if (len) *len = 0;
156     i = in; o = out;
157     backslash = FALSE;
158     while (i < end) {
159         const gchar *next;
160
161         /* find the next character in the string */
162         if (!locale) next = i+1;
163         else if (!(next = g_utf8_find_next_char(i, end))) next = end;
164
165         if (backslash) {
166             switch(*i) {
167             case 's': *o++ = ' '; break;
168             case 'n': *o++ = '\n'; break;
169             case 't': *o++ = '\t'; break;
170             case 'r': *o++ = '\r'; break;
171             case ';': *o++ = ';'; break;
172             case '\\': *o++ = '\\'; break;
173             default:
174                 parse_error((locale ?
175                              "Invalid escape sequence in localestring" :
176                              "Invalid escape sequence in string"),
177                             parse, error);
178             }
179             backslash = FALSE;
180         }
181         else if (*i == '\\')
182             backslash = TRUE;
183         else if ((guchar)*i >= 127 || (guchar)*i < 32) {
184             /* avoid ascii control characters */
185             parse_error("Found control character in string", parse, error);
186             break;
187         }
188         else {
189             const gulong s = next-i;
190             memcpy(o, i, s);
191             o += s;
192             if (len) *len += s;
193         }
194         i = next;
195     }
196     *o = '\0';
197     return out;
198 }
199
200
201 /*! Reads a list of input strings, strips out invalid stuff, and parses
202     backslash-stuff.
203  */
204 static gchar** parse_value_strings(const gchar *in,
205                                    gboolean locale,
206                                    gulong *nstrings,
207                                    const ObtDDParse *const parse,
208                                    gboolean *error)
209 {
210     gchar **out;
211     const gchar *i;
212
213     out = g_new(gchar*, 1);
214     out[0] = NULL;
215     *nstrings = 0;
216
217     i = in;
218     while (TRUE) {
219         gchar *a;
220         gulong len;
221
222         a = parse_value_string(i, locale, TRUE, &len, parse, error);
223         i += len;
224
225         if (len) {
226             (*nstrings)++;
227             out = g_renew(gchar*, out, *nstrings+1);
228             out[*nstrings-1] = a;
229             out[*nstrings] = NULL;
230         }
231
232         if (!*i) break; /* no more strings */
233         ++i;
234     }
235     return out;
236 }
237
238 static guint parse_value_environments(const gchar *in,
239                                       const ObtDDParse *const parse,
240                                       gboolean *error)
241 {
242     const gchar *s;
243     guint mask = 0;
244
245     s = in;
246     while (*s) {
247         switch (*(s++)) {
248         case 'G':
249             if (strcmp(s, "NOME") == 0) {
250                 mask |= OBT_LINK_ENV_GNOME;
251                 s += 4;
252             }
253             break;
254         case 'K':
255             if (strcmp(s, "DE") == 0) {
256                 mask |= OBT_LINK_ENV_KDE;
257                 s += 2;
258             }
259             break;
260         case 'L':
261             if (strcmp(s, "XDE") == 0) {
262                 mask |= OBT_LINK_ENV_LXDE;
263                 s += 3;
264             }
265             break;
266         case 'R':
267             if (strcmp(s, "OX") == 0) {
268                 mask |= OBT_LINK_ENV_ROX;
269                 s += 2;
270             }
271             break;
272         case 'X':
273             if (strcmp(s, "FCE") == 0) {
274                 mask |= OBT_LINK_ENV_XFCE;
275                 s += 3;
276             }
277             break;
278         case 'O':
279             switch (*(s++)) {
280             case 'l':
281                 if (strcmp(s, "d") == 0) {
282                     mask |= OBT_LINK_ENV_OLD;
283                     s += 1;
284                 }
285                 break;
286             case 'P':
287                 if (strcmp(s, "ENBOX") == 0) {
288                     mask |= OBT_LINK_ENV_OPENBOX;
289                     s += 5;
290                 }
291                 break;
292             }
293         }
294         /* find the next string, or the end of the sequence */
295         while (*s && *s != ';') ++s;
296     }
297     return mask;
298 }
299
300 static gboolean parse_value_boolean(const gchar *in,
301                                     const ObtDDParse *const parse,
302                                     gboolean *error)
303 {
304     if (strcmp(in, "true") == 0)
305         return TRUE;
306     else if (strcmp(in, "false") != 0)
307         parse_error("Invalid boolean value", parse, error);
308     return FALSE;
309 }
310
311 static gfloat parse_value_numeric(const gchar *in,
312                                   const ObtDDParse *const parse,
313                                   gboolean *error)
314 {
315     gfloat out = 0;
316     if (sscanf(in, "%f", &out) == 0)
317         parse_error("Invalid numeric value", parse, error);
318     return out;
319 }
320
321 static gboolean parse_file_line(FILE *f, gchar **buf,
322                                 gulong *size, gulong *read,
323                                 ObtDDParse *parse, gboolean *error)
324 {
325     const gulong BUFMUL = 80;
326     size_t ret;
327     gulong i, null;
328
329     if (*size == 0) {
330         g_assert(*read == 0);
331         *size = BUFMUL;
332         *buf = g_new(char, *size);
333     }
334
335     /* remove everything up to a null zero already in the buffer and shift
336        the rest to the front */
337     null = *size;
338     for (i = 0; i < *read; ++i) {
339         if (null < *size)
340             (*buf)[i-null-1] = (*buf)[i];
341         else if ((*buf)[i] == '\0')
342             null = i;
343     }
344     if (null < *size)
345         *read -= null + 1;
346
347     /* is there already a newline in the buffer? */
348     for (i = 0; i < *read; ++i)
349         if ((*buf)[i] == '\n') {
350             /* turn it into a null zero and done */
351             (*buf)[i] = '\0';
352             return TRUE;
353         }
354
355     /* we need to read some more to find a newline */
356     while (TRUE) {
357         gulong eol;
358         gchar *newread;
359
360         newread = *buf + *read;
361         ret = fread(newread, sizeof(char), *size-*read, f);
362         if (ret < *size - *read && !feof(f)) {
363             parse_error("Error reading", parse, error);
364             return FALSE;
365         }
366         *read += ret;
367
368         /* strip out null zeros in the input and look for an endofline */
369         null = 0;
370         eol = *size;
371         for (i = newread-*buf; i < *read; ++i) {
372             if (null > 0)
373                 (*buf)[i] = (*buf)[i+null];
374             if ((*buf)[i] == '\0') {
375                 ++null;
376                 --(*read);
377                 --i; /* try again */
378             }
379             else if ((*buf)[i] == '\n' && eol == *size) {
380                 eol = i;
381                 /* turn it into a null zero */
382                 (*buf)[i] = '\0';
383             }
384         }
385
386         if (eol != *size)
387             /* found an endofline, done */
388             break;
389         else if (feof(f) && *read < *size) {
390             /* found the endoffile, done (if there is space) */
391             if (*read > 0) {
392                 /* stick a null zero on if there is test on the last line */
393                 (*buf)[(*read)++] = '\0';
394             }
395             break;
396         }
397         else {
398             /* read more */
399             size += BUFMUL;
400             *buf = g_renew(char, *buf, *size);
401         }
402     }
403     return *read > 0;
404 }
405
406 static void parse_group(const gchar *buf, gulong len,
407                         ObtDDParse *parse, gboolean *error)
408 {
409     ObtDDParseGroup *g;
410     gchar *group;
411     gulong i;
412
413     /* get the group name */
414     group = g_strndup(buf+1, len-2);
415     for (i = 0; i < len-2; ++i)
416         if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) {
417             /* valid ASCII only */
418             parse_error("Invalid character found", parse, NULL);
419             group[i] = '\0'; /* stopping before this character */
420             break;
421         }
422
423     /* make sure it's a new group */
424     g = g_hash_table_lookup(parse->group_hash, group);
425     if (g && g->seen) {
426         parse_error("Duplicate group found", parse, error);
427         g_free(group);
428         return;
429     }
430     /* if it's the first group, make sure it's named Desktop Entry */
431     else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
432     {
433         parse_error("Incorrect group found, "
434                     "expected [Desktop Entry]",
435                     parse, error);
436         g_free(group);
437         return;
438     }
439     else {
440         if (!g) {
441             g = parse_group_new(group, NULL);
442             g_hash_table_insert(parse->group_hash, g->name, g);
443         }
444         else
445             g_free(group);
446
447         g->seen = TRUE;
448         parse->group = g;
449         g_print("Found group %s\n", g->name);
450     }
451 }
452
453 static void parse_key_value(const gchar *buf, gulong len,
454                             ObtDDParse *parse, gboolean *error)
455 {
456     gulong i, keyend, valstart, eq;
457     char *key;
458
459     /* find the end of the key */
460     for (i = 0; i < len; ++i)
461         if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
462               ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') ||
463               ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') ||
464               ((guchar)buf[i] == '-'))) {
465             /* not part of the key */
466             break;
467         }
468     keyend = i;
469
470     if (keyend < 1) {
471         parse_error("Empty key", parse, error);
472         return;
473     }
474     /* find the = character */
475     for (i = keyend; i < len; ++i) {
476         if (buf[i] == '=') {
477             eq = i;
478             break;
479         }
480         else if (buf[i] != ' ') {
481             parse_error("Invalid character in key name", parse, error);
482             return ;
483         }
484     }
485     if (i == len) {
486         parse_error("Key without value found", parse, error);
487         return;
488     }
489     /* find the start of the value */
490     for (i = eq+1; i < len; ++i)
491         if (buf[i] != ' ') {
492             valstart = i;
493             break;
494         }
495     if (i == len) {
496         parse_error("Empty value found", parse, error);
497         return;
498     }
499
500     key = g_strndup(buf, keyend);
501     if (g_hash_table_lookup(parse->group->key_hash, key)) {
502         parse_error("Duplicate key found", parse, error);
503         g_free(key);
504         return;
505     }
506     g_print("Found key/value %s=%s.\n", key, buf+valstart);
507     if (parse->group->value_func)
508         if (!parse->group->value_func(key, buf+valstart, parse, error)) {
509             parse_error("Unknown key", parse, error);
510             g_free(key);
511         }
512 }
513
514 static gboolean parse_file(FILE *f, ObtDDParse *parse)
515 {
516     gchar *buf = NULL;
517     gulong bytes = 0, read = 0;
518     gboolean error = FALSE;
519
520     while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
521         gulong len = strlen(buf);
522         if (buf[0] == '#' || buf[0] == '\0')
523             ; /* ignore comment lines */
524         else if (buf[0] == '[' && buf[len-1] == ']')
525             parse_group(buf, len, parse, &error);
526         else if (!parse->group)
527             /* just ignore keys outside of groups */
528             parse_error("Key found before group", parse, NULL);
529         else
530             /* ignore errors in key-value pairs and continue */
531             parse_key_value(buf, len, parse, NULL);
532         ++parse->lineno;
533     }
534
535     if (buf) g_free(buf);
536     return !error;
537 }
538
539 static gboolean parse_desktop_entry_value(gchar *key, const gchar *val,
540                                           ObtDDParse *parse, gboolean *error)
541 {
542     ObtDDParseValue v, *pv;
543
544     switch (key[0]) {
545     case 'C':
546         switch (key[1]) {
547         case 'a': /* Categories */
548             if (strcmp(key+2, "tegories")) return FALSE;
549             v.type = OBT_DDPARSE_STRINGS; break;
550         case 'o': /* Comment */
551             if (strcmp(key+2, "mment")) return FALSE;
552             v.type = OBT_DDPARSE_LOCALESTRING; break;
553         default:
554             return FALSE;
555         }
556         break;
557     case 'E': /* Exec */
558         if (strcmp(key+1, "xec")) return FALSE;
559         v.type = OBT_DDPARSE_EXEC; parse->flags |= DE_EXEC; break;
560     case 'G': /* GenericName */
561         if (strcmp(key+1, "enericName")) return FALSE;
562         v.type = OBT_DDPARSE_LOCALESTRING; break;
563     case 'I': /* Icon */
564         if (strcmp(key+1, "con")) return FALSE;
565         v.type = OBT_DDPARSE_LOCALESTRING; break;
566     case 'H': /* Hidden */
567         if (strcmp(key+1, "idden")) return FALSE;
568         v.type = OBT_DDPARSE_BOOLEAN; break;
569     case 'M': /* MimeType */
570         if (strcmp(key+1, "imeType")) return FALSE;
571         v.type = OBT_DDPARSE_STRINGS; break;
572     case 'N':
573         switch (key[1]) {
574         case 'a': /* Name */
575             if (strcmp(key+2, "me")) return FALSE;
576             v.type = OBT_DDPARSE_LOCALESTRING; parse->flags |= DE_NAME; break;
577         case 'o':
578             switch (key[2]) {
579             case 'D': /* NoDisplay */
580                 if (strcmp(key+3, "isplay")) return FALSE;
581                 v.type = OBT_DDPARSE_BOOLEAN; break;
582             case 't': /* NotShowIn */
583                 if (strcmp(key+3, "ShowIn")) return FALSE;
584                 v.type = OBT_DDPARSE_STRINGS; break;
585             default:
586                 return FALSE;
587             }
588             break;
589         default:
590             return FALSE;
591         }
592         break;
593     case 'P': /* Path */
594         if (strcmp(key+1, "ath")) return FALSE;
595         v.type = OBT_DDPARSE_STRING; break;
596     case 'S': /* Path */
597         if (key[1] == 't' && key[2] == 'a' && key[3] == 'r' &&
598             key[4] == 't' && key[5] == 'u' && key[6] == 'p')
599             switch (key[7]) {
600             case 'N': /* StartupNotify */
601                 if (strcmp(key+8, "otify")) return FALSE;
602                 v.type = OBT_DDPARSE_BOOLEAN; break;
603             case 'W': /* StartupWMClass */
604                 if (strcmp(key+8, "MClass")) return FALSE;
605                 v.type = OBT_DDPARSE_STRING; break;
606             default:
607                 return FALSE;
608             }
609         else
610             return FALSE;
611         break;
612     case 'T':
613         switch (key[1]) {
614         case 'e': /* Terminal */
615             if (strcmp(key+2, "rminal")) return FALSE;
616             v.type = OBT_DDPARSE_BOOLEAN; break;
617         case 'r': /* TryExec */
618             if (strcmp(key+2, "yExec")) return FALSE;
619             v.type = OBT_DDPARSE_STRING; break;
620         case 'y': /* Type */
621             if (strcmp(key+2, "pe")) return FALSE;
622             v.type = OBT_DDPARSE_ENUM_TYPE; parse->flags |= DE_TYPE; break;
623         default:
624             return FALSE;
625         }
626         break;
627     case 'U': /* URL */
628         if (strcmp(key+1, "RL")) return FALSE;
629         v.type = OBT_DDPARSE_STRING; parse->flags |= DE_URL; break;
630     case 'V': /* MimeType */
631         if (strcmp(key+1, "ersion")) return FALSE;
632         v.type = OBT_DDPARSE_STRING; break;
633     default:
634         return FALSE;
635     }
636
637     /* parse the value */
638     switch (v.type) {
639     case OBT_DDPARSE_EXEC: {
640         gchar *c, *m;
641         gboolean percent;
642         gboolean found;
643
644         v.value.string = parse_value_string(val, FALSE, FALSE, NULL,
645                                             parse, error);
646         g_assert(v.value.string);
647
648         /* an exec string can only contain one of the file/url-opening %'s */
649         percent = found = FALSE;
650         for (c = v.value.string; *c; ++c) {
651             if (percent) {
652                 switch (*c) {
653                 case 'f':
654                 case 'F':
655                 case 'u':
656                 case 'U':
657                     if (found) {
658                         m = g_strdup_printf("Malformed Exec key, "
659                                             "extraneous %%%c", *c);
660                         parse_error(m, parse, error);
661                         g_free(m);
662                     }
663                     found = TRUE;
664                     break;
665                 case 'd':
666                 case 'D':
667                 case 'n':
668                 case 'N':
669                 case 'v':
670                 case 'm':
671                     m = g_strdup_printf("Malformed Exec key, "
672                                         "uses deprecated %%%c", *c);
673                     parse_error(m, parse, NULL); /* just a warning */
674                     g_free(m);
675                     break;
676                 case 'i':
677                 case 'c':
678                 case 'k':
679                 case '%':
680                     break;
681                 default:
682                     m = g_strdup_printf("Malformed Exec key, "
683                                         "uses unknown %%%c", *c);
684                     parse_error(m, parse, NULL); /* just a warning */
685                     g_free(m);
686                 }
687                 percent = FALSE;
688             }
689             else if (*c == '%') percent = TRUE;
690         }
691         break;
692     }
693     case OBT_DDPARSE_STRING:
694         v.value.string = parse_value_string(val, FALSE, FALSE, NULL,
695                                             parse, error);
696         g_assert(v.value.string);
697         break;
698     case OBT_DDPARSE_LOCALESTRING:
699         v.value.string = parse_value_string(val, TRUE, FALSE, NULL,
700                                             parse, error);
701         g_assert(v.value.string);
702         break;
703     case OBT_DDPARSE_STRINGS:
704         v.value.strings.a = parse_value_strings(val, FALSE, &v.value.strings.n,
705                                                 parse, error);
706         g_assert(v.value.strings.a);
707         g_assert(v.value.strings.n);
708         break;
709     case OBT_DDPARSE_LOCALESTRINGS:
710         v.value.strings.a = parse_value_strings(val, TRUE, &v.value.strings.n,
711                                                 parse, error);
712         g_assert(v.value.strings.a);
713         g_assert(v.value.strings.n);
714         break;
715     case OBT_DDPARSE_BOOLEAN:
716         v.value.boolean = parse_value_boolean(val, parse, error);
717         break;
718     case OBT_DDPARSE_NUMERIC:
719         v.value.numeric = parse_value_numeric(val, parse, error);
720         break;
721     case OBT_DDPARSE_ENUM_TYPE:
722         if (val[0] == 'A' && strcmp(val+1, "pplication") == 0) {
723             v.value.enumerable = OBT_LINK_TYPE_APPLICATION;
724             parse->flags |= DE_TYPE_APPLICATION;
725         }
726         else if (val[0] == 'L' && strcmp(val+1, "ink") == 0) {
727             v.value.enumerable = OBT_LINK_TYPE_URL;
728             parse->flags |= DE_TYPE_LINK;
729         }
730         else if (val[0] == 'D' && strcmp(val+1, "irectory") == 0)
731             v.value.enumerable = OBT_LINK_TYPE_DIRECTORY;
732         else {
733             parse_error("Unknown Type", parse, error);
734             return FALSE;
735         }
736         break;
737     case OBT_DDPARSE_ENVIRONMENTS:
738         v.value.environments = parse_value_environments(val, parse, error);
739         break;
740     default:
741         g_assert_not_reached();
742     }
743
744     pv = g_slice_new(ObtDDParseValue);
745     *pv = v;
746     g_hash_table_insert(parse->group->key_hash, key, pv);
747     return TRUE;
748 }
749
750 GHashTable* obt_ddparse_file(const gchar *name, GSList *paths)
751 {
752     ObtDDParse parse;
753     ObtDDParseGroup *desktop_entry;
754     GSList *it;
755     FILE *f;
756     gboolean success;
757
758     parse.filename = NULL;
759     parse.lineno = 0;
760     parse.group = NULL;
761     parse.group_hash = g_hash_table_new_full(g_str_hash,
762                                              g_str_equal,
763                                              NULL,
764                                              (GDestroyNotify)parse_group_free);
765
766     /* set up the groups (there's only one right now) */
767     desktop_entry = parse_group_new(g_strdup("Desktop Entry"),
768                                     parse_desktop_entry_value);
769     g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry);
770
771     success = FALSE;
772     for (it = paths; it && !success; it = g_slist_next(it)) {
773         gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
774         if ((f = fopen(path, "r"))) {
775             parse.filename = path;
776             parse.lineno = 1;
777             parse.flags = 0;
778             if ((success = parse_file(f, &parse))) {
779                 /* check that required keys exist */
780
781                 if (!(parse.flags & DE_TYPE)) {
782                     g_warning("Missing Type key in %s", path);
783                     success = FALSE;
784                 }
785                 if (!(parse.flags & DE_NAME)) {
786                     g_warning("Missing Name key in %s", path);
787                     success = FALSE;
788                 }
789                 if (parse.flags & DE_TYPE_APPLICATION &&
790                     !(parse.flags & DE_EXEC))
791                 {
792                     g_warning("Missing Exec key for Application in %s",
793                               path);
794                     success = FALSE;
795                 }
796                 else if (parse.flags & DE_TYPE_LINK && !(parse.flags & DE_URL))
797                 {
798                     g_warning("Missing URL key for Link in %s", path);
799                     success = FALSE;
800                 }
801             }
802             fclose(f);
803         }
804         g_free(path);
805     }
806     if (!success) {
807         g_hash_table_destroy(parse.group_hash);
808         parse.group_hash = NULL;
809     }
810     return parse.group_hash;
811 }
812
813 GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g)
814 {
815     return g->key_hash;
816 }