1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/ddparse.c for the Openbox window manager
4 Copyright (c) 2009 Dana Jansens
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.
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.
16 See the COPYING file for a copy of the GNU General Public License.
19 #include "obt/ddparse.h"
28 typedef struct _ObtDDParse ObtDDParse
;
30 /* Parses the value and adds it to the group's key_hash, with the given
32 Return TRUE if it is added to the hash table, and FALSE if not.
34 typedef gboolean (*ObtDDParseValueFunc
)(gchar
*key
, const gchar
*val
,
35 ObtDDParse
*parse
, gboolean
*error
);
41 ObtDDParseGroup
*group
;
42 /* the key is a group name, the value is a ObtDDParseGroup */
43 GHashTable
*group_hash
;
46 struct _ObtDDParseGroup
{
49 ObtDDParseValueFunc value_func
;
50 /* the key is a string (a key inside the group in the .desktop).
51 the value is an ObtDDParseValue */
55 /* Displays a warning message including the file name and line number, and
56 sets the boolean @error to true if it points to a non-NULL address.
58 static void parse_error(const gchar
*m
, const ObtDDParse
*const parse
,
62 g_warning("%s at line %lu of input", m
, parse
->lineno
);
64 g_warning("%s at line %lu of file %s",
65 m
, parse
->lineno
, parse
->filename
);
66 if (error
) *error
= TRUE
;
69 static void parse_value_free(ObtDDParseValue
*v
)
72 case OBT_DDPARSE_STRING
:
73 case OBT_DDPARSE_LOCALESTRING
:
74 g_free(v
->value
.string
); break;
75 case OBT_DDPARSE_STRINGS
:
76 case OBT_DDPARSE_LOCALESTRINGS
:
77 g_free(v
->value
.strings
.s
);
78 v
->value
.strings
.n
= 0;
80 case OBT_DDPARSE_BOOLEAN
:
81 case OBT_DDPARSE_NUMERIC
:
82 case OBT_DDPARSE_ENUM_APPLICATION
:
85 g_assert_not_reached();
87 g_slice_free(ObtDDParseValue
, v
);
90 static ObtDDParseGroup
* parse_group_new(gchar
*name
, ObtDDParseValueFunc f
)
92 ObtDDParseGroup
*g
= g_slice_new(ObtDDParseGroup
);
96 g
->key_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
98 (GDestroyNotify
)parse_value_free
);
102 static void parse_group_free(ObtDDParseGroup
*g
)
105 g_hash_table_destroy(g
->key_hash
);
106 g_slice_free(ObtDDParseGroup
, g
);
109 /*! Reads an input string, strips out invalid stuff, and parses
111 If @nstrings is not NULL, then it splits the output string at ';'
112 characters. They are all returned in the same string with null zeros
113 between them, @nstrings is set to the number of such strings.
115 static gchar
* parse_value_string(const gchar
*in
,
118 const ObtDDParse
*const parse
,
121 const gint bytes
= strlen(in
);
124 const gchar
*end
, *i
;
126 g_return_val_if_fail(in
!= NULL
, NULL
);
130 for (i
= in
; i
< end
; ++i
) {
131 if ((guchar
)*i
>= 127 || (guchar
)*i
< 32) {
132 /* non-control character ascii */
134 parse_error("Invalid bytes in string", parse
, error
);
139 else if (!g_utf8_validate(in
, bytes
, &end
))
140 parse_error("Invalid bytes in localestring", parse
, error
);
142 if (nstrings
) *nstrings
= 1;
144 out
= g_new(char, bytes
+ 1);
148 const gchar
*next
= locale
? g_utf8_find_next_char(i
, end
) : i
+1;
151 case 's': *o
++ = ' '; break;
152 case 'n': *o
++ = '\n'; break;
153 case 't': *o
++ = '\t'; break;
154 case 'r': *o
++ = '\r'; break;
155 case ';': *o
++ = ';'; break;
156 case '\\': *o
++ = '\\'; break;
158 parse_error((locale
?
159 "Invalid escape sequence in localestring" :
160 "Invalid escape sequence in string"),
167 else if (*i
== ';' && nstrings
) {
171 else if ((guchar
)*i
== 127 || (guchar
)*i
< 32) {
172 /* avoid ascii control characters */
173 parse_error("Found control character in string", parse
, error
);
177 memcpy(o
, i
, next
-i
);
186 static gboolean
parse_value_boolean(const gchar
*in
,
187 const ObtDDParse
*const parse
,
190 if (strcmp(in
, "true") == 0)
192 else if (strcmp(in
, "false") != 0)
193 parse_error("Invalid boolean value", parse
, error
);
197 static gfloat
parse_value_numeric(const gchar
*in
,
198 const ObtDDParse
*const parse
,
202 if (sscanf(in
, "%f", &out
) == 0)
203 parse_error("Invalid numeric value", parse
, error
);
207 static gboolean
parse_file_line(FILE *f
, gchar
**buf
,
208 gulong
*size
, gulong
*read
,
209 ObtDDParse
*parse
, gboolean
*error
)
211 const gulong BUFMUL
= 80;
216 g_assert(*read
== 0);
218 *buf
= g_new(char, *size
);
221 /* remove everything up to a null zero already in the buffer and shift
222 the rest to the front */
224 for (i
= 0; i
< *read
; ++i
) {
226 (*buf
)[i
-null
-1] = (*buf
)[i
];
227 else if ((*buf
)[i
] == '\0')
233 /* is there already a newline in the buffer? */
234 for (i
= 0; i
< *read
; ++i
)
235 if ((*buf
)[i
] == '\n') {
236 /* turn it into a null zero and done */
241 /* we need to read some more to find a newline */
246 newread
= *buf
+ *read
;
247 ret
= fread(newread
, sizeof(char), *size
-*read
, f
);
248 if (ret
< *size
- *read
&& !feof(f
)) {
249 parse_error("Error reading", parse
, error
);
254 /* strip out null zeros in the input and look for an endofline */
257 for (i
= newread
-*buf
; i
< *read
; ++i
) {
259 (*buf
)[i
] = (*buf
)[i
+null
];
260 if ((*buf
)[i
] == '\0') {
265 else if ((*buf
)[i
] == '\n' && eol
== *size
) {
267 /* turn it into a null zero */
273 /* found an endofline, done */
275 else if (feof(f
) && *read
< *size
) {
276 /* found the endoffile, done (if there is space) */
278 /* stick a null zero on if there is test on the last line */
279 (*buf
)[(*read
)++] = '\0';
286 *buf
= g_renew(char, *buf
, *size
);
292 static void parse_group(const gchar
*buf
, gulong len
,
293 ObtDDParse
*parse
, gboolean
*error
)
299 /* get the group name */
300 group
= g_strndup(buf
+1, len
-2);
301 for (i
= 0; i
< len
-2; ++i
)
302 if ((guchar
)group
[i
] < 32 || (guchar
)group
[i
] >= 127) {
303 /* valid ASCII only */
304 parse_error("Invalid character found", parse
, NULL
);
305 group
[i
] = '\0'; /* stopping before this character */
309 /* make sure it's a new group */
310 g
= g_hash_table_lookup(parse
->group_hash
, group
);
312 parse_error("Duplicate group found", parse
, error
);
316 /* if it's the first group, make sure it's named Desktop Entry */
317 else if (!parse
->group
&& strcmp(group
, "Desktop Entry") != 0)
319 parse_error("Incorrect group found, "
320 "expected [Desktop Entry]",
327 g
= parse_group_new(group
, NULL
);
328 g_hash_table_insert(parse
->group_hash
, g
->name
, g
);
335 g_print("Found group %s\n", g
->name
);
339 static void parse_key_value(const gchar
*buf
, gulong len
,
340 ObtDDParse
*parse
, gboolean
*error
)
342 gulong i
, keyend
, valstart
, eq
;
345 /* find the end of the key */
346 for (i
= 0; i
< len
; ++i
)
347 if (!(((guchar
)buf
[i
] >= 'A' && (guchar
)buf
[i
] <= 'Z') ||
348 ((guchar
)buf
[i
] >= 'a' && (guchar
)buf
[i
] <= 'z') ||
349 ((guchar
)buf
[i
] >= '0' && (guchar
)buf
[i
] <= '9') ||
350 ((guchar
)buf
[i
] == '-'))) {
351 /* not part of the key */
356 parse_error("Empty key", parse
, error
);
359 /* find the = character */
360 for (i
= keyend
; i
< len
; ++i
) {
365 else if (buf
[i
] != ' ') {
366 parse_error("Invalid character in key name", parse
, error
);
371 parse_error("Key without value found", parse
, error
);
374 /* find the start of the value */
375 for (i
= eq
+1; i
< len
; ++i
)
381 parse_error("Empty value found", parse
, error
);
385 key
= g_strndup(buf
, keyend
);
386 if (g_hash_table_lookup(parse
->group
->key_hash
, key
)) {
387 parse_error("Duplicate key found", parse
, error
);
391 g_print("Found key/value %s=%s.\n", key
, buf
+valstart
);
392 if (parse
->group
->value_func
)
393 if (!parse
->group
->value_func(key
, buf
+valstart
, parse
, error
)) {
394 parse_error("Unknown key", parse
, error
);
399 static gboolean
parse_file(FILE *f
, ObtDDParse
*parse
)
402 gulong bytes
= 0, read
= 0;
403 gboolean error
= FALSE
;
405 while (!error
&& parse_file_line(f
, &buf
, &bytes
, &read
, parse
, &error
)) {
406 gulong len
= strlen(buf
);
407 if (buf
[0] == '#' || buf
[0] == '\0')
408 ; /* ignore comment lines */
409 else if (buf
[0] == '[' && buf
[len
-1] == ']')
410 parse_group(buf
, len
, parse
, &error
);
411 else if (!parse
->group
)
412 /* just ignore keys outside of groups */
413 parse_error("Key found before group", parse
, NULL
);
415 /* ignore errors in key-value pairs and continue */
416 parse_key_value(buf
, len
, parse
, NULL
);
420 if (buf
) g_free(buf
);
424 static gboolean
parse_desktop_entry_value(gchar
*key
, const gchar
*val
,
425 ObtDDParse
*parse
, gboolean
*error
)
427 ObtDDParseValue v
, *pv
;
432 case 'a': /* Categories */
433 if (strcmp(key
+2, "tegories")) return FALSE
;
434 v
.type
= OBT_DDPARSE_STRINGS
; break;
435 case 'o': /* Comment */
436 if (strcmp(key
+2, "mment")) return FALSE
;
437 v
.type
= OBT_DDPARSE_LOCALESTRING
; break;
443 if (strcmp(key
+1, "xec")) return FALSE
;
444 v
.type
= OBT_DDPARSE_STRING
; break;
445 case 'G': /* GenericName */
446 if (strcmp(key
+1, "enericName")) return FALSE
;
447 v
.type
= OBT_DDPARSE_LOCALESTRING
; break;
449 if (strcmp(key
+1, "con")) return FALSE
;
450 v
.type
= OBT_DDPARSE_LOCALESTRING
; break;
451 case 'H': /* Hidden */
452 if (strcmp(key
+1, "idden")) return FALSE
;
453 v
.type
= OBT_DDPARSE_BOOLEAN
; break;
454 case 'M': /* MimeType */
455 if (strcmp(key
+1, "imeType")) return FALSE
;
456 v
.type
= OBT_DDPARSE_STRINGS
; break;
460 if (strcmp(key
+2, "me")) return FALSE
;
461 v
.type
= OBT_DDPARSE_LOCALESTRING
; break;
464 case 'D': /* NoDisplay */
465 if (strcmp(key
+3, "isplay")) return FALSE
;
466 v
.type
= OBT_DDPARSE_BOOLEAN
; break;
467 case 't': /* NotShowIn */
468 if (strcmp(key
+3, "ShowIn")) return FALSE
;
469 v
.type
= OBT_DDPARSE_STRINGS
; break;
479 if (strcmp(key
+1, "ath")) return FALSE
;
480 v
.type
= OBT_DDPARSE_STRING
; break;
482 if (key
[1] == 't' && key
[2] == 'a' && key
[3] == 'r' &&
483 key
[4] == 't' && key
[5] == 'u' && key
[6] == 'p')
485 case 'N': /* StartupNotify */
486 if (strcmp(key
+8, "otify")) return FALSE
;
487 v
.type
= OBT_DDPARSE_BOOLEAN
; break;
488 case 'W': /* StartupWMClass */
489 if (strcmp(key
+8, "MClass")) return FALSE
;
490 v
.type
= OBT_DDPARSE_STRING
; break;
499 case 'e': /* Terminal */
500 if (strcmp(key
+2, "rminal")) return FALSE
;
501 v
.type
= OBT_DDPARSE_BOOLEAN
; break;
502 case 'r': /* TryExec */
503 if (strcmp(key
+2, "yExec")) return FALSE
;
504 v
.type
= OBT_DDPARSE_STRING
; break;
506 if (strcmp(key
+2, "pe")) return FALSE
;
507 v
.type
= OBT_DDPARSE_STRING
; break;
513 if (strcmp(key
+1, "RL")) return FALSE
;
514 v
.type
= OBT_DDPARSE_STRING
; break;
515 case 'V': /* MimeType */
516 if (strcmp(key
+1, "ersion")) return FALSE
;
517 v
.type
= OBT_DDPARSE_STRING
; break;
522 /* parse the value */
524 case OBT_DDPARSE_STRING
:
525 v
.value
.string
= parse_value_string(val
, FALSE
, NULL
, parse
, error
);
526 g_assert(v
.value
.string
);
528 case OBT_DDPARSE_LOCALESTRING
:
529 v
.value
.string
= parse_value_string(val
, TRUE
, NULL
, parse
, error
);
530 g_assert(v
.value
.string
);
532 case OBT_DDPARSE_STRINGS
:
533 v
.value
.strings
.s
= parse_value_string(val
, FALSE
, &v
.value
.strings
.n
,
535 g_assert(v
.value
.strings
.s
);
536 g_assert(v
.value
.strings
.n
);
538 case OBT_DDPARSE_LOCALESTRINGS
:
539 v
.value
.strings
.s
= parse_value_string(val
, TRUE
, &v
.value
.strings
.n
,
541 g_assert(v
.value
.strings
.s
);
542 g_assert(v
.value
.strings
.n
);
544 case OBT_DDPARSE_BOOLEAN
:
545 v
.value
.boolean
= parse_value_boolean(val
, parse
, error
);
547 case OBT_DDPARSE_NUMERIC
:
548 v
.value
.numeric
= parse_value_numeric(val
, parse
, error
);
550 case OBT_DDPARSE_ENUM_APPLICATION
:
551 if (val
[0] == 'A' && strcmp(val
+1, "pplication") == 0)
552 v
.value
.enumerable
= OBT_LINK_TYPE_APPLICATION
;
553 else if (val
[0] == 'L' && strcmp(val
+1, "ink") == 0)
554 v
.value
.enumerable
= OBT_LINK_TYPE_URL
;
555 else if (val
[0] == 'D' && strcmp(val
+1, "irectory") == 0)
556 v
.value
.enumerable
= OBT_LINK_TYPE_DIRECTORY
;
558 parse_error("Unknown Type", parse
, error
);
563 g_assert_not_reached();
566 pv
= g_slice_new(ObtDDParseValue
);
568 g_hash_table_insert(parse
->group
->key_hash
, key
, pv
);
572 GHashTable
* obt_ddparse_file(const gchar
*name
, GSList
*paths
)
575 ObtDDParseGroup
*desktop_entry
;
580 parse
.filename
= NULL
;
583 parse
.group_hash
= g_hash_table_new_full(g_str_hash
,
586 (GDestroyNotify
)parse_group_free
);
588 /* set up the groups (there's only one right now) */
589 desktop_entry
= parse_group_new(g_strdup("Desktop Entry"),
590 parse_desktop_entry_value
);
591 g_hash_table_insert(parse
.group_hash
, desktop_entry
->name
, desktop_entry
);
594 for (it
= paths
; it
&& !success
; it
= g_slist_next(it
)) {
595 gchar
*path
= g_strdup_printf("%s/%s", (char*)it
->data
, name
);
596 if ((f
= fopen(path
, "r"))) {
597 parse
.filename
= path
;
599 success
= parse_file(f
, &parse
);
605 g_hash_table_destroy(parse
.group_hash
);
609 return parse
.group_hash
;
612 GHashTable
* obt_ddparse_group_keys(ObtDDParseGroup
*g
)