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
);
40 DE_TYPE_APPLICATION
= 1 << 1,
41 DE_TYPE_LINK
= 1 << 2,
51 ObtDDParseGroup
*group
;
52 /* the key is a group name, the value is a ObtDDParseGroup */
53 GHashTable
*group_hash
;
56 struct _ObtDDParseGroup
{
59 ObtDDParseValueFunc value_func
;
60 /* the key is a string (a key inside the group in the .desktop).
61 the value is an ObtDDParseValue */
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.
68 static void parse_error(const gchar
*m
, const ObtDDParse
*const parse
,
72 g_warning("%s at line %lu of input", m
, parse
->lineno
);
74 g_warning("%s at line %lu of file %s",
75 m
, parse
->lineno
, parse
->filename
);
76 if (error
) *error
= TRUE
;
79 static void parse_value_free(ObtDDParseValue
*v
)
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;
91 case OBT_DDPARSE_BOOLEAN
:
92 case OBT_DDPARSE_NUMERIC
:
93 case OBT_DDPARSE_ENUM_TYPE
:
94 case OBT_DDPARSE_ENVIRONMENTS
:
97 g_assert_not_reached();
99 g_slice_free(ObtDDParseValue
, v
);
102 static ObtDDParseGroup
* parse_group_new(gchar
*name
, ObtDDParseValueFunc f
)
104 ObtDDParseGroup
*g
= g_slice_new(ObtDDParseGroup
);
108 g
->key_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
110 (GDestroyNotify
)parse_value_free
);
114 static void parse_group_free(ObtDDParseGroup
*g
)
117 g_hash_table_destroy(g
->key_hash
);
118 g_slice_free(ObtDDParseGroup
, g
);
121 /*! Reads an input string, strips out invalid stuff, and parses
124 static gchar
* parse_value_string(const gchar
*in
,
126 gboolean semicolonterminate
,
128 const ObtDDParse
*const parse
,
134 const gchar
*end
, *i
;
136 /* find the end/size of the string */
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;
147 g_return_val_if_fail(in
!= NULL
, NULL
);
149 if (locale
&& !g_utf8_validate(in
, bytes
, &end
)) {
150 parse_error("Invalid bytes in localestring", parse
, error
);
154 out
= g_new(char, bytes
+ 1);
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
;
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;
174 parse_error((locale
?
175 "Invalid escape sequence in localestring" :
176 "Invalid escape sequence in string"),
183 else if ((guchar
)*i
>= 127 || (guchar
)*i
< 32) {
184 /* avoid ascii control characters */
185 parse_error("Found control character in string", parse
, error
);
189 const gulong s
= next
-i
;
201 /*! Reads a list of input strings, strips out invalid stuff, and parses
204 static gchar
** parse_value_strings(const gchar
*in
,
207 const ObtDDParse
*const parse
,
213 out
= g_new(gchar
*, 1);
222 a
= parse_value_string(i
, locale
, TRUE
, &len
, parse
, error
);
227 out
= g_renew(gchar
*, out
, *nstrings
+1);
228 out
[*nstrings
-1] = a
;
229 out
[*nstrings
] = NULL
;
232 if (!*i
) break; /* no more strings */
238 static guint
parse_value_environments(const gchar
*in
,
239 const ObtDDParse
*const parse
,
249 if (strcmp(s
, "NOME") == 0) {
250 mask
|= OBT_LINK_ENV_GNOME
;
255 if (strcmp(s
, "DE") == 0) {
256 mask
|= OBT_LINK_ENV_KDE
;
261 if (strcmp(s
, "XDE") == 0) {
262 mask
|= OBT_LINK_ENV_LXDE
;
267 if (strcmp(s
, "OX") == 0) {
268 mask
|= OBT_LINK_ENV_ROX
;
273 if (strcmp(s
, "FCE") == 0) {
274 mask
|= OBT_LINK_ENV_XFCE
;
281 if (strcmp(s
, "d") == 0) {
282 mask
|= OBT_LINK_ENV_OLD
;
287 if (strcmp(s
, "ENBOX") == 0) {
288 mask
|= OBT_LINK_ENV_OPENBOX
;
294 /* find the next string, or the end of the sequence */
295 while (*s
&& *s
!= ';') ++s
;
300 static gboolean
parse_value_boolean(const gchar
*in
,
301 const ObtDDParse
*const parse
,
304 if (strcmp(in
, "true") == 0)
306 else if (strcmp(in
, "false") != 0)
307 parse_error("Invalid boolean value", parse
, error
);
311 static gfloat
parse_value_numeric(const gchar
*in
,
312 const ObtDDParse
*const parse
,
316 if (sscanf(in
, "%f", &out
) == 0)
317 parse_error("Invalid numeric value", parse
, error
);
321 static gboolean
parse_file_line(FILE *f
, gchar
**buf
,
322 gulong
*size
, gulong
*read
,
323 ObtDDParse
*parse
, gboolean
*error
)
325 const gulong BUFMUL
= 80;
330 g_assert(*read
== 0);
332 *buf
= g_new(char, *size
);
335 /* remove everything up to a null zero already in the buffer and shift
336 the rest to the front */
338 for (i
= 0; i
< *read
; ++i
) {
340 (*buf
)[i
-null
-1] = (*buf
)[i
];
341 else if ((*buf
)[i
] == '\0')
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 */
355 /* we need to read some more to find a newline */
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
);
368 /* strip out null zeros in the input and look for an endofline */
371 for (i
= newread
-*buf
; i
< *read
; ++i
) {
373 (*buf
)[i
] = (*buf
)[i
+null
];
374 if ((*buf
)[i
] == '\0') {
379 else if ((*buf
)[i
] == '\n' && eol
== *size
) {
381 /* turn it into a null zero */
387 /* found an endofline, done */
389 else if (feof(f
) && *read
< *size
) {
390 /* found the endoffile, done (if there is space) */
392 /* stick a null zero on if there is test on the last line */
393 (*buf
)[(*read
)++] = '\0';
400 *buf
= g_renew(char, *buf
, *size
);
406 static void parse_group(const gchar
*buf
, gulong len
,
407 ObtDDParse
*parse
, gboolean
*error
)
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 */
423 /* make sure it's a new group */
424 g
= g_hash_table_lookup(parse
->group_hash
, group
);
426 parse_error("Duplicate group found", parse
, error
);
430 /* if it's the first group, make sure it's named Desktop Entry */
431 else if (!parse
->group
&& strcmp(group
, "Desktop Entry") != 0)
433 parse_error("Incorrect group found, "
434 "expected [Desktop Entry]",
441 g
= parse_group_new(group
, NULL
);
442 g_hash_table_insert(parse
->group_hash
, g
->name
, g
);
449 g_print("Found group %s\n", g
->name
);
453 static void parse_key_value(const gchar
*buf
, gulong len
,
454 ObtDDParse
*parse
, gboolean
*error
)
456 gulong i
, keyend
, valstart
, eq
;
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 */
471 parse_error("Empty key", parse
, error
);
474 /* find the = character */
475 for (i
= keyend
; i
< len
; ++i
) {
480 else if (buf
[i
] != ' ') {
481 parse_error("Invalid character in key name", parse
, error
);
486 parse_error("Key without value found", parse
, error
);
489 /* find the start of the value */
490 for (i
= eq
+1; i
< len
; ++i
)
496 parse_error("Empty value found", parse
, error
);
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
);
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
);
514 static gboolean
parse_file(FILE *f
, ObtDDParse
*parse
)
517 gulong bytes
= 0, read
= 0;
518 gboolean error
= FALSE
;
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
);
530 /* ignore errors in key-value pairs and continue */
531 parse_key_value(buf
, len
, parse
, NULL
);
535 if (buf
) g_free(buf
);
539 static gboolean
parse_desktop_entry_value(gchar
*key
, const gchar
*val
,
540 ObtDDParse
*parse
, gboolean
*error
)
542 ObtDDParseValue v
, *pv
;
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;
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;
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;
575 if (strcmp(key
+2, "me")) return FALSE
;
576 v
.type
= OBT_DDPARSE_LOCALESTRING
; parse
->flags
|= DE_NAME
; break;
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;
594 if (strcmp(key
+1, "ath")) return FALSE
;
595 v
.type
= OBT_DDPARSE_STRING
; break;
597 if (key
[1] == 't' && key
[2] == 'a' && key
[3] == 'r' &&
598 key
[4] == 't' && key
[5] == 'u' && key
[6] == 'p')
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;
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;
621 if (strcmp(key
+2, "pe")) return FALSE
;
622 v
.type
= OBT_DDPARSE_ENUM_TYPE
; parse
->flags
|= DE_TYPE
; break;
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;
637 /* parse the value */
639 case OBT_DDPARSE_EXEC
: {
644 v
.value
.string
= parse_value_string(val
, FALSE
, FALSE
, NULL
,
646 g_assert(v
.value
.string
);
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
) {
658 m
= g_strdup_printf("Malformed Exec key, "
659 "extraneous %%%c", *c
);
660 parse_error(m
, parse
, error
);
671 m
= g_strdup_printf("Malformed Exec key, "
672 "uses deprecated %%%c", *c
);
673 parse_error(m
, parse
, NULL
); /* just a warning */
682 m
= g_strdup_printf("Malformed Exec key, "
683 "uses unknown %%%c", *c
);
684 parse_error(m
, parse
, NULL
); /* just a warning */
689 else if (*c
== '%') percent
= TRUE
;
693 case OBT_DDPARSE_STRING
:
694 v
.value
.string
= parse_value_string(val
, FALSE
, FALSE
, NULL
,
696 g_assert(v
.value
.string
);
698 case OBT_DDPARSE_LOCALESTRING
:
699 v
.value
.string
= parse_value_string(val
, TRUE
, FALSE
, NULL
,
701 g_assert(v
.value
.string
);
703 case OBT_DDPARSE_STRINGS
:
704 v
.value
.strings
.a
= parse_value_strings(val
, FALSE
, &v
.value
.strings
.n
,
706 g_assert(v
.value
.strings
.a
);
707 g_assert(v
.value
.strings
.n
);
709 case OBT_DDPARSE_LOCALESTRINGS
:
710 v
.value
.strings
.a
= parse_value_strings(val
, TRUE
, &v
.value
.strings
.n
,
712 g_assert(v
.value
.strings
.a
);
713 g_assert(v
.value
.strings
.n
);
715 case OBT_DDPARSE_BOOLEAN
:
716 v
.value
.boolean
= parse_value_boolean(val
, parse
, error
);
718 case OBT_DDPARSE_NUMERIC
:
719 v
.value
.numeric
= parse_value_numeric(val
, parse
, error
);
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
;
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
;
730 else if (val
[0] == 'D' && strcmp(val
+1, "irectory") == 0)
731 v
.value
.enumerable
= OBT_LINK_TYPE_DIRECTORY
;
733 parse_error("Unknown Type", parse
, error
);
737 case OBT_DDPARSE_ENVIRONMENTS
:
738 v
.value
.environments
= parse_value_environments(val
, parse
, error
);
741 g_assert_not_reached();
744 pv
= g_slice_new(ObtDDParseValue
);
746 g_hash_table_insert(parse
->group
->key_hash
, key
, pv
);
750 GHashTable
* obt_ddparse_file(const gchar
*name
, GSList
*paths
)
753 ObtDDParseGroup
*desktop_entry
;
758 parse
.filename
= NULL
;
761 parse
.group_hash
= g_hash_table_new_full(g_str_hash
,
764 (GDestroyNotify
)parse_group_free
);
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
);
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
;
778 if ((success
= parse_file(f
, &parse
))) {
779 /* check that required keys exist */
781 if (!(parse
.flags
& DE_TYPE
)) {
782 g_warning("Missing Type key in %s", path
);
785 if (!(parse
.flags
& DE_NAME
)) {
786 g_warning("Missing Name key in %s", path
);
789 if (parse
.flags
& DE_TYPE_APPLICATION
&&
790 !(parse
.flags
& DE_EXEC
))
792 g_warning("Missing Exec key for Application in %s",
796 else if (parse
.flags
& DE_TYPE_LINK
&& !(parse
.flags
& DE_URL
))
798 g_warning("Missing URL key for Link in %s", path
);
807 g_hash_table_destroy(parse
.group_hash
);
808 parse
.group_hash
= NULL
;
810 return parse
.group_hash
;
813 GHashTable
* obt_ddparse_group_keys(ObtDDParseGroup
*g
)