1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/ddfile.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/ddfile.h"
28 typedef struct _ObtDDParse ObtDDParse
;
29 typedef struct _ObtDDParseGroup ObtDDParseGroup
;
31 typedef void (*ObtDDParseGroupFunc
)(gchar
*key
, const gchar
*val
,
32 ObtDDParse
*parse
, gboolean
*error
);
34 struct _ObtDDParseGroup
{
37 ObtDDParseGroupFunc key_func
;
38 /* the key is a string (a key inside the group in the .desktop).
39 the value is an ObtDDParseValue */
46 ObtDDParseGroup
*group
;
47 /* the key is a group name, the value is a ObtDDParseGroup */
48 GHashTable
*group_hash
;
61 typedef struct _ObtDDParseValue
{
63 union _ObtDDParseValueValue
{
65 struct _ObtDDParseValueStrings
{
78 gchar
*name
; /*!< Specific name for the object (eg Firefox) */
79 gchar
*generic
; /*!< Generic name for the object (eg Web Browser) */
80 gchar
*comment
; /*!< Comment/description to display for the object */
81 gchar
*icon
; /*!< Name/path for an icon for the object */
83 union _ObtDDFileData
{
84 struct _ObtDDFileApp
{
85 gchar
*exec
; /*!< Executable to run for the app */
86 gchar
*wdir
; /*!< Working dir to run the app in */
87 gboolean term
; /*!< Run the app in a terminal or not */
88 ObtDDFileAppOpen open
;
90 /* XXX gchar**? or something better, a mime struct.. maybe
91 glib has something i can use. */
92 gchar
**mime
; /*!< Mime types the app can open */
94 ObtDDFileAppStartup startup
;
95 gchar
*startup_wmclass
;
97 struct _ObtDDFileLink
{
100 struct _ObtDDFileDir
{
105 static void value_free(ObtDDParseValue
*v
)
109 case DATA_LOCALESTRING
:
110 g_free(v
->value
.string
); break;
112 case DATA_LOCALESTRINGS
:
113 g_free(v
->value
.strings
.s
);
114 v
->value
.strings
.n
= 0;
121 g_assert_not_reached();
123 g_slice_free(ObtDDParseValue
, v
);
126 static ObtDDParseGroup
* group_new(gchar
*name
, ObtDDParseGroupFunc f
)
128 ObtDDParseGroup
*g
= g_slice_new(ObtDDParseGroup
);
132 g
->key_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
133 g_free
, (GDestroyNotify
)value_free
);
137 static void group_free(ObtDDParseGroup
*g
)
140 g_hash_table_destroy(g
->key_hash
);
141 g_slice_free(ObtDDParseGroup
, g
);
144 /* Displays a warning message including the file name and line number, and
145 sets the boolean @error to true if it points to a non-NULL address.
147 static void parse_error(const gchar
*m
, const ObtDDParse
*const parse
,
150 if (!parse
->filename
)
151 g_warning("%s at line %lu of input", m
, parse
->lineno
);
153 g_warning("%s at line %lu of file %s",
154 m
, parse
->lineno
, parse
->filename
);
155 if (error
) *error
= TRUE
;
158 /* reads an input string, strips out invalid stuff, and parses
160 if @nstrings is not NULL, then it splits the output string at ';'
161 characters. they are all returned in the same string with null zeros
162 between them, @nstrings is set to the number of such strings.
164 static gchar
* parse_string(const gchar
*in
,
167 const ObtDDParse
*const parse
,
170 const gint bytes
= strlen(in
);
173 const gchar
*end
, *i
;
175 g_return_val_if_fail(in
!= NULL
, NULL
);
179 for (i
= in
; i
< end
; ++i
) {
180 if ((guchar
)*i
> 126 || (guchar
)*i
< 32) {
181 /* non-control character ascii */
183 parse_error("Invalid bytes in string", parse
, error
);
188 else if (!g_utf8_validate(in
, bytes
, &end
))
189 parse_error("Invalid bytes in localestring", parse
, error
);
191 if (nstrings
) *nstrings
= 1;
193 out
= g_new(char, bytes
+ 1);
197 const gchar
*next
= locale
? g_utf8_find_next_char(i
, end
) : i
+1;
200 case 's': *o
++ = ' '; break;
201 case 'n': *o
++ = '\n'; break;
202 case 't': *o
++ = '\t'; break;
203 case 'r': *o
++ = '\r'; break;
204 case ';': *o
++ = ';'; break;
205 case '\\': *o
++ = '\\'; break;
207 parse_error((locale
?
208 "Invalid escape sequence in localestring" :
209 "Invalid escape sequence in string"),
216 else if (*i
== ';' && nstrings
) {
220 else if ((guchar
)*i
>= 127 || (guchar
)*i
< 32) {
221 /* avoid ascii control characters */
222 parse_error("Found control character in string", parse
, error
);
226 memcpy(o
, i
, next
-i
);
235 static gboolean
parse_bool(const gchar
*in
, const ObtDDParse
*const parse
,
238 if (strcmp(in
, "true") == 0)
240 else if (strcmp(in
, "false") != 0)
241 parse_error("Invalid boolean value", parse
, error
);
245 static gfloat
parse_numeric(const gchar
*in
, const ObtDDParse
*const parse
,
249 if (sscanf(in
, "%f", &out
) == 0)
250 parse_error("Invalid numeric value", parse
, error
);
254 static void parse_group_desktop_entry(gchar
*key
, const gchar
*val
,
255 ObtDDParse
*parse
, gboolean
*error
)
257 ObtDDParseValue v
, *pv
;
259 /* figure out value type */
260 v
.type
= NUM_DATA_TYPES
;
262 /* parse the value */
266 v
.value
.string
= parse_string(val
, FALSE
, NULL
, parse
, error
);
267 g_assert(v
.value
.string
);
269 case DATA_LOCALESTRING
:
270 v
.value
.string
= parse_string(val
, TRUE
, NULL
, parse
, error
);
271 g_assert(v
.value
.string
);
274 v
.value
.strings
.s
= parse_string(val
, FALSE
, &v
.value
.strings
.n
,
276 g_assert(v
.value
.strings
.s
);
277 g_assert(v
.value
.strings
.n
);
279 case DATA_LOCALESTRINGS
:
280 v
.value
.strings
.s
= parse_string(val
, TRUE
, &v
.value
.strings
.n
,
282 g_assert(v
.value
.strings
.s
);
283 g_assert(v
.value
.strings
.n
);
286 v
.value
.boolean
= parse_bool(val
, parse
, error
);
289 v
.value
.numeric
= parse_numeric(val
, parse
, error
);
292 g_assert_not_reached();
295 pv
= g_slice_new(ObtDDParseValue
);
297 g_hash_table_insert(parse
->group
->key_hash
, key
, pv
);
300 static gboolean
parse_file_line(FILE *f
, gchar
**buf
,
301 gulong
*size
, gulong
*read
,
302 ObtDDParse
*parse
, gboolean
*error
)
304 const gulong BUFMUL
= 80;
309 g_assert(*read
== 0);
311 *buf
= g_new(char, *size
);
314 /* remove everything up to a null zero already in the buffer and shift
315 the rest to the front */
317 for (i
= 0; i
< *read
; ++i
) {
319 (*buf
)[i
-null
-1] = (*buf
)[i
];
320 else if ((*buf
)[i
] == '\0')
326 /* is there already a newline in the buffer? */
327 for (i
= 0; i
< *read
; ++i
)
328 if ((*buf
)[i
] == '\n') {
329 /* turn it into a null zero and done */
334 /* we need to read some more to find a newline */
339 newread
= *buf
+ *read
;
340 ret
= fread(newread
, sizeof(char), *size
-*read
, f
);
341 if (ret
< *size
- *read
&& !feof(f
)) {
342 parse_error("Error reading", parse
, error
);
347 /* strip out null zeros in the input and look for an endofline */
350 for (i
= newread
-*buf
; i
< *read
; ++i
) {
352 (*buf
)[i
] = (*buf
)[i
+null
];
353 if ((*buf
)[i
] == '\0') {
358 else if ((*buf
)[i
] == '\n' && eol
== *size
) {
360 /* turn it into a null zero */
366 /* found an endofline, done */
368 else if (feof(f
) && *read
< *size
) {
369 /* found the endoffile, done (if there is space) */
371 /* stick a null zero on if there is test on the last line */
372 (*buf
)[(*read
)++] = '\0';
379 *buf
= g_renew(char, *buf
, *size
);
385 static void parse_group(const gchar
*buf
, gulong len
,
386 ObtDDParse
*parse
, gboolean
*error
)
392 /* get the group name */
393 group
= g_strndup(buf
+1, len
-2);
394 for (i
= 0; i
< len
-2; ++i
)
395 if ((guchar
)group
[i
] < 32 || (guchar
)group
[i
] >= 127) {
396 /* valid ASCII only */
397 parse_error("Invalid character found", parse
, NULL
);
398 group
[i
] = '\0'; /* stopping before this character */
402 /* make sure it's a new group */
403 g
= g_hash_table_lookup(parse
->group_hash
, group
);
405 parse_error("Duplicate group found", parse
, error
);
409 /* if it's the first group, make sure it's named Desktop Entry */
410 else if (!parse
->group
&& strcmp(group
, "Desktop Entry") != 0)
412 parse_error("Incorrect group found, "
413 "expected [Desktop Entry]",
420 g
= group_new(group
, NULL
);
421 g_hash_table_insert(parse
->group_hash
, g
->name
, g
);
428 g_print("Found group %s\n", g
->name
);
432 static void parse_key_value(const gchar
*buf
, gulong len
,
433 ObtDDParse
*parse
, gboolean
*error
)
435 gulong i
, keyend
, valstart
, eq
;
438 /* find the end of the key */
439 for (i
= 0; i
< len
; ++i
)
440 if (!(((guchar
)buf
[i
] >= 'A' && (guchar
)buf
[i
] <= 'Z') ||
441 ((guchar
)buf
[i
] >= 'a' && (guchar
)buf
[i
] <= 'z') ||
442 ((guchar
)buf
[i
] >= '0' && (guchar
)buf
[i
] <= '9') ||
443 ((guchar
)buf
[i
] == '-'))) {
444 /* not part of the key */
449 parse_error("Empty key", parse
, error
);
452 /* find the = character */
453 for (i
= keyend
; i
< len
; ++i
) {
458 else if (buf
[i
] != ' ') {
459 parse_error("Invalid character in key name", parse
, error
);
464 parse_error("Key without value found", parse
, error
);
467 /* find the start of the value */
468 for (i
= eq
+1; i
< len
; ++i
)
474 parse_error("Empty value found", parse
, error
);
478 key
= g_strndup(buf
, keyend
);
479 if (g_hash_table_lookup(parse
->group
->key_hash
, key
)) {
480 parse_error("Duplicate key found", parse
, error
);
484 g_print("Found key/value %s=%s.\n", key
, buf
+valstart
);
485 if (parse
->group
->key_func
)
486 parse
->group
->key_func(key
, buf
+valstart
, parse
, error
);
489 static gboolean
parse_file(ObtDDFile
*dd
, FILE *f
, ObtDDParse
*parse
)
492 gulong bytes
= 0, read
= 0;
493 gboolean error
= FALSE
;
495 while (!error
&& parse_file_line(f
, &buf
, &bytes
, &read
, parse
, &error
)) {
496 /* XXX use the string in buf */
497 gulong len
= strlen(buf
);
498 if (buf
[0] == '#' || buf
[0] == '\0')
499 ; /* ignore comment lines */
500 else if (buf
[0] == '[' && buf
[len
-1] == ']')
501 parse_group(buf
, len
, parse
, &error
);
502 else if (!parse
->group
)
503 /* just ignore keys outside of groups */
504 parse_error("Key found before group", parse
, NULL
);
506 /* ignore errors in key-value pairs and continue */
507 parse_key_value(buf
, len
, parse
, NULL
);
511 if (buf
) g_free(buf
);
515 ObtDDFile
* obt_ddfile_new_from_file(const gchar
*name
, GSList
*paths
)
519 ObtDDParseGroup
*desktop_entry
;
524 dd
= g_slice_new(ObtDDFile
);
527 parse
.filename
= NULL
;
530 parse
.group_hash
= g_hash_table_new_full(g_str_hash
,
533 (GDestroyNotify
)group_free
);
535 /* set up the groups (there's only one right now) */
536 desktop_entry
= group_new(g_strdup("Desktop Entry"),
537 parse_group_desktop_entry
);
538 g_hash_table_insert(parse
.group_hash
, desktop_entry
->name
, desktop_entry
);
541 for (it
= paths
; it
&& !success
; it
= g_slist_next(it
)) {
542 gchar
*path
= g_strdup_printf("%s/%s", (char*)it
->data
, name
);
543 if ((f
= fopen(path
, "r"))) {
544 parse
.filename
= path
;
546 success
= parse_file(dd
, f
, &parse
);
552 obt_ddfile_unref(dd
);
556 g_hash_table_destroy(parse
.group_hash
);
561 void obt_ddfile_ref(ObtDDFile
*dd
)
566 void obt_ddfile_unref(ObtDDFile
*dd
)
569 g_slice_free(ObtDDFile
, dd
);