]> Dogcows Code - chaz/openbox/blob - obt/ddfile.c
parse key/value pairs from the .desktop file and save them in a hashtable
[chaz/openbox] / obt / ddfile.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 obt/ddfile.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/ddfile.h"
20 #include <glib.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 void (*ObtDDParseGroupFunc)(const gchar *group,
29 GHashTable *key_hash);
30
31 typedef struct _ObtDDParseGroup {
32 gchar *name;
33 gboolean seen;
34 ObtDDParseGroupFunc func;
35 /* the key is a string (a key in the .desktop).
36 the value is a strings (a value in the .desktop) */
37 GHashTable *key_hash;
38 } ObtDDParseGroup;
39
40 typedef struct _ObtDDParse {
41 gchar *filename;
42 gulong lineno;
43 ObtDDParseGroup *group;
44 /* the key is a group name, the value is a ObtDDParseGroup */
45 GHashTable *group_hash;
46 } ObtDDParse;
47
48 typedef enum {
49 DATA_STRING,
50 DATA_LOCALESTRING,
51 DATA_BOOLEAN,
52 DATA_NUMERIC,
53 NUM_DATA_TYPES
54 } ObtDDDataType;
55
56 struct _ObtDDFile {
57 guint ref;
58
59 ObtDDFileType type;
60 gchar *name; /*!< Specific name for the object (eg Firefox) */
61 gchar *generic; /*!< Generic name for the object (eg Web Browser) */
62 gchar *comment; /*!< Comment/description to display for the object */
63 gchar *icon; /*!< Name/path for an icon for the object */
64
65 union _ObtDDFileData {
66 struct {
67 gchar *exec; /*!< Executable to run for the app */
68 gchar *wdir; /*!< Working dir to run the app in */
69 gboolean term; /*!< Run the app in a terminal or not */
70 ObtDDFileAppOpen open;
71
72 /* XXX gchar**? or something better, a mime struct.. maybe
73 glib has something i can use. */
74 gchar **mime; /*!< Mime types the app can open */
75
76 ObtDDFileAppStartup startup;
77 gchar *startup_wmclass;
78 } app;
79 struct {
80 gchar *url;
81 } link;
82 struct {
83 } dir;
84 } d;
85 };
86
87 static void group_free(ObtDDParseGroup *g)
88 {
89 g_free(g->name);
90 g_hash_table_destroy(g->key_hash);
91 g_slice_free(ObtDDParseGroup, g);
92 }
93
94 /* Displays a warning message including the file name and line number, and
95 sets the boolean @error to true if it points to a non-NULL address.
96 */
97 static void parse_error(const gchar *m, const ObtDDParse *const parse,
98 gboolean *error)
99 {
100 if (!parse->filename)
101 g_warning("%s at line %lu of input", m, parse->lineno);
102 else
103 g_warning("%s at line %lu of file %s",
104 m, parse->lineno, parse->filename);
105 if (error) *error = TRUE;
106 }
107
108 /* reads an input string, strips out invalid stuff, and parses
109 backslash-stuff */
110 static gchar* parse_string(const gchar *in, gboolean locale,
111 const ObtDDParse *const parse,
112 gboolean *error)
113 {
114 const gint bytes = strlen(in);
115 gboolean backslash;
116 gchar *out, *o;
117 const gchar *end, *i;
118
119 g_return_val_if_fail(in != NULL, NULL);
120
121 if (!locale) {
122 end = in + bytes;
123 for (i = in; i < end; ++i) {
124 if ((guchar)*i > 126 || (guchar)*i < 32) {
125 /* non-control character ascii */
126 end = i;
127 parse_error("Invalid bytes in string", parse, error);
128 break;
129 }
130 }
131 }
132 else if (!g_utf8_validate(in, bytes, &end))
133 parse_error("Invalid bytes in localestring", parse, error);
134
135 out = g_new(char, bytes + 1);
136 i = in; o = out;
137 backslash = FALSE;
138 while (i < end) {
139 const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
140 if (backslash) {
141 switch(*i) {
142 case 's': *o++ = ' '; break;
143 case 'n': *o++ = '\n'; break;
144 case 't': *o++ = '\t'; break;
145 case 'r': *o++ = '\r'; break;
146 case '\\': *o++ = '\\'; break;
147 default:
148 parse_error((locale ?
149 "Invalid escape sequence in localestring" :
150 "Invalid escape sequence in string"),
151 parse, error);
152 }
153 backslash = FALSE;
154 }
155 else if (*i == '\\')
156 backslash = TRUE;
157 else if ((guchar)*i >= 127 || (guchar)*i < 32) {
158 /* avoid ascii control characters */
159 parse_error("Found control character in string", parse, error);
160 break;
161 }
162 else {
163 memcpy(o, i, next-i);
164 o += next-i;
165 }
166 i = next;
167 }
168 *o = '\0';
169 return o;
170 }
171
172 static gboolean parse_bool(const gchar *in, const ObtDDParse *const parse,
173 gboolean *error)
174 {
175 if (strcmp(in, "true") == 0)
176 return TRUE;
177 else if (strcmp(in, "false") != 0)
178 parse_error("Invalid boolean value", parse, error);
179 return FALSE;
180 }
181
182 static float parse_numeric(const gchar *in, const ObtDDParse *const parse,
183 gboolean *error)
184 {
185 float out = 0;
186 if (sscanf(in, "%f", &out) == 0)
187 parse_error("Invalid numeric value", parse, error);
188 return out;
189 }
190
191 gboolean parse_file_line(FILE *f, gchar **buf, gulong *size, gulong *read,
192 ObtDDParse *parse, gboolean *error)
193 {
194 const gulong BUFMUL = 80;
195 size_t ret;
196 gulong i, null;
197
198 if (*size == 0) {
199 g_assert(*read == 0);
200 *size = BUFMUL;
201 *buf = g_new(char, *size);
202 }
203
204 /* remove everything up to a null zero already in the buffer and shift
205 the rest to the front */
206 null = *size;
207 for (i = 0; i < *read; ++i) {
208 if (null < *size)
209 (*buf)[i-null-1] = (*buf)[i];
210 else if ((*buf)[i] == '\0')
211 null = i;
212 }
213 if (null < *size)
214 *read -= null + 1;
215
216 /* is there already a newline in the buffer? */
217 for (i = 0; i < *read; ++i)
218 if ((*buf)[i] == '\n') {
219 /* turn it into a null zero and done */
220 (*buf)[i] = '\0';
221 return TRUE;
222 }
223
224 /* we need to read some more to find a newline */
225 while (TRUE) {
226 gulong eol;
227 gchar *newread;
228
229 newread = *buf + *read;
230 ret = fread(newread, sizeof(char), *size-*read, f);
231 if (ret < *size - *read && !feof(f)) {
232 parse_error("Error reading", parse, error);
233 return FALSE;
234 }
235 *read += ret;
236
237 /* strip out null zeros in the input and look for an endofline */
238 null = 0;
239 eol = *size;
240 for (i = newread-*buf; i < *read; ++i) {
241 if (null > 0)
242 (*buf)[i] = (*buf)[i+null];
243 if ((*buf)[i] == '\0') {
244 ++null;
245 --(*read);
246 --i; /* try again */
247 }
248 else if ((*buf)[i] == '\n' && eol == *size) {
249 eol = i;
250 /* turn it into a null zero */
251 (*buf)[i] = '\0';
252 }
253 }
254
255 if (eol != *size)
256 /* found an endofline, done */
257 break;
258 else if (feof(f) && *read < *size) {
259 /* found the endoffile, done (if there is space) */
260 if (*read > 0) {
261 /* stick a null zero on if there is test on the last line */
262 (*buf)[(*read)++] = '\0';
263 }
264 break;
265 }
266 else {
267 /* read more */
268 size += BUFMUL;
269 *buf = g_renew(char, *buf, *size);
270 }
271 }
272 return *read > 0;
273 }
274
275 static void parse_group(const gchar *buf, gulong len,
276 ObtDDParse *parse, gboolean *error)
277 {
278 ObtDDParseGroup *g;
279 gchar *group;
280 gulong i;
281
282 /* get the group name */
283 group = g_strndup(buf+1, len-2);
284 for (i = 0; i < len-2; ++i)
285 if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) {
286 /* valid ASCII only */
287 parse_error("Invalid character found", parse, NULL);
288 group[i] = '\0'; /* stopping before this character */
289 break;
290 }
291
292 /* make sure it's a new group */
293 g = g_hash_table_lookup(parse->group_hash, group);
294 if (g && g->seen) {
295 parse_error("Duplicate group found", parse, error);
296 g_free(group);
297 return;
298 }
299 /* if it's the first group, make sure it's named Desktop Entry */
300 else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
301 {
302 parse_error("Incorrect group found, "
303 "expected [Desktop Entry]",
304 parse, error);
305 g_free(group);
306 return;
307 }
308 else {
309 if (!g) {
310 g = g_slice_new(ObtDDParseGroup);
311 g->name = group;
312 g->func = NULL;
313 g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
314 g_free, g_free);
315 g_hash_table_insert(parse->group_hash, group, g);
316 }
317 else
318 g_free(group);
319
320 g->seen = TRUE;
321 parse->group = g;
322 g_print("Found group %s\n", g->name);
323 }
324 }
325
326 static void parse_key_value(const gchar *buf, gulong len,
327 ObtDDParse *parse, gboolean *error)
328 {
329 gulong i, keyend, valstart, eq;
330 char *key, *val;
331
332 /* find the end of the key */
333 for (i = 0; i < len; ++i)
334 if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
335 ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') ||
336 ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') ||
337 ((guchar)buf[i] == '-'))) {
338 /* not part of the key */
339 keyend = i;
340 break;
341 }
342 if (keyend < 1) {
343 parse_error("Empty key", parse, error);
344 return;
345 }
346 /* find the = character */
347 for (i = keyend; i < len; ++i) {
348 if (buf[i] == '=') {
349 eq = i;
350 break;
351 }
352 else if (buf[i] != ' ') {
353 parse_error("Invalid character in key name", parse, error);
354 return ;
355 }
356 }
357 if (i == len) {
358 parse_error("Key without value found", parse, error);
359 return;
360 }
361 /* find the start of the value */
362 for (i = eq+1; i < len; ++i)
363 if (buf[i] != ' ') {
364 valstart = i;
365 break;
366 }
367 if (i == len) {
368 parse_error("Empty value found", parse, error);
369 return;
370 }
371
372 key = g_strndup(buf, keyend);
373 val = g_strndup(buf+valstart, len-valstart);
374 if (g_hash_table_lookup(parse->group->key_hash, key)) {
375 parse_error("Duplicate key found", parse, error);
376 g_free(key);
377 g_free(val);
378 return;
379 }
380 g_hash_table_insert(parse->group->key_hash, key, val);
381 g_print("Found key/value %s=%s.\n", key, val);
382 }
383
384 static gboolean parse_file(ObtDDFile *dd, FILE *f, ObtDDParse *parse)
385 {
386 gchar *buf = NULL;
387 gulong bytes = 0, read = 0;
388 gboolean error = FALSE;
389
390 while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
391 /* XXX use the string in buf */
392 gulong len = strlen(buf);
393 if (buf[0] == '#' || buf[0] == '\0')
394 ; /* ignore comment lines */
395 else if (buf[0] == '[' && buf[len-1] == ']')
396 parse_group(buf, len, parse, &error);
397 else if (!parse->group)
398 /* just ignore keys outside of groups */
399 parse_error("Key found before group", parse, NULL);
400 else
401 /* ignore errors in key-value pairs and continue */
402 parse_key_value(buf, len, parse, NULL);
403 ++parse->lineno;
404 }
405
406 if (buf) g_free(buf);
407 return !error;
408 }
409
410 ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths)
411 {
412 ObtDDFile *dd;
413 ObtDDParse parse;
414 GSList *it;
415 FILE *f;
416 gboolean success;
417
418 dd = g_slice_new(ObtDDFile);
419 dd->ref = 1;
420
421 parse.filename = NULL;
422 parse.lineno = 0;
423 parse.group = NULL;
424 parse.group_hash = g_hash_table_new_full(g_str_hash,
425 g_str_equal,
426 NULL,
427 (GDestroyNotify)group_free);
428
429 success = FALSE;
430 for (it = paths; it && !success; it = g_slist_next(it)) {
431 gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
432 if ((f = fopen(path, "r"))) {
433 parse.filename = path;
434 parse.lineno = 1;
435 success = parse_file(dd, f, &parse);
436 fclose(f);
437 }
438 g_free(path);
439 }
440 if (!success) {
441 obt_ddfile_unref(dd);
442 dd = NULL;
443 }
444
445 g_hash_table_destroy(parse.group_hash);
446
447 return dd;
448 }
449
450 void obt_ddfile_ref(ObtDDFile *dd)
451 {
452 ++dd->ref;
453 }
454
455 void obt_ddfile_unref(ObtDDFile *dd)
456 {
457 if (--dd->ref < 1) {
458 g_slice_free(ObtDDFile, dd);
459 }
460 }
This page took 0.056521 seconds and 4 git commands to generate.