]> icculus.org git repositories - mikachu/openbox.git/blob - parser/parse.c
add --debug-focus
[mikachu/openbox.git] / parser / parse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    parse.c for the Openbox window manager
4    Copyright (c) 2003-2007   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 "parse.h"
20 #include <glib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26
27 static gboolean xdg_start;
28 static gchar   *xdg_config_home_path;
29 static gchar   *xdg_data_home_path;
30 static GSList  *xdg_config_dir_paths;
31 static GSList  *xdg_data_dir_paths;
32
33 struct Callback {
34     gchar *tag;
35     ParseCallback func;
36     gpointer data;
37 };
38
39 struct _ObParseInst {
40     GHashTable *callbacks;
41 };
42
43 static void destfunc(struct Callback *c)
44 {
45     g_free(c->tag);
46     g_free(c);
47 }
48
49 ObParseInst* parse_startup()
50 {
51     ObParseInst *i = g_new(ObParseInst, 1);
52     i->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
53                                          (GDestroyNotify)destfunc);
54     return i;
55 }
56
57 void parse_shutdown(ObParseInst *i)
58 {
59     if (i) {
60         g_hash_table_destroy(i->callbacks);
61         g_free(i);
62     }
63 }
64
65 void parse_register(ObParseInst *i, const gchar *tag,
66                     ParseCallback func, gpointer data)
67 {
68     struct Callback *c;
69
70     if ((c = g_hash_table_lookup(i->callbacks, tag))) {
71         g_warning("Tag '%s' already registered", tag);
72         return;
73     }
74
75     c = g_new(struct Callback, 1);
76     c->tag = g_strdup(tag);
77     c->func = func;
78     c->data = data;
79     g_hash_table_insert(i->callbacks, c->tag, c);
80 }
81
82 gboolean parse_load_rc(const gchar *file, xmlDocPtr *doc, xmlNodePtr *root,
83                        gchar **fileused)
84 {
85     GSList *it;
86     gboolean r = FALSE;
87
88     *fileused = NULL;
89
90     for (it = xdg_config_dir_paths; !r && it; it = g_slist_next(it)) {
91         if (file) {
92             if ((r = parse_load(file, "openbox_config", doc, root)))
93                 *fileused = g_strdup(file);
94         } else {
95             gchar *path;
96
97             path = g_build_filename(it->data, "openbox", "rc.xml", NULL);
98             if ((r = parse_load(path, "openbox_config", doc, root)))
99                 *fileused = path;
100             else
101                 g_free(path);
102         }
103     }
104     if (!r)
105         g_warning("Unable to find a valid config file, using defaults");
106     return r;
107 }
108
109 gboolean parse_load_theme(const gchar *name, xmlDocPtr *doc, xmlNodePtr *root,
110                           gchar **retpath)
111 {
112     GSList *it;
113     gchar *path;
114     gboolean r = FALSE;
115
116     /* backward compatibility.. */
117     path = g_build_filename(g_get_home_dir(), ".themes", name,
118                             "openbox-3", "themerc.xml", NULL);
119     if ((r = parse_load(path, "openbox_theme", doc, root)))
120         *retpath = g_path_get_dirname(path);
121     g_free(path);
122
123     if (!r) {
124         for (it = xdg_data_dir_paths; !r && it; it = g_slist_next(it)) {
125             path = g_build_filename(it->data, "themes", name, "openbox-3",
126                                     "themerc.xml", NULL);
127             if ((r = parse_load(path, "openbox_theme", doc, root)))
128                 *retpath = g_path_get_dirname(path);
129             g_free(path);
130         }
131     }
132     if (!r)
133         g_warning("Unable to load the theme %s", name);
134     return r;
135 }
136
137 gboolean parse_load_menu(const gchar *file, xmlDocPtr *doc, xmlNodePtr *root)
138 {
139     GSList *it;
140     gchar *path;
141     gboolean r = FALSE;
142
143     if (file[0] == '/') {
144         r = parse_load(file, "openbox_menu", doc, root);
145     } else {
146         for (it = xdg_config_dir_paths; !r && it; it = g_slist_next(it)) {
147             path = g_build_filename(it->data, "openbox", file, NULL);
148             r = parse_load(path, "openbox_menu", doc, root);
149             g_free(path);
150         }
151     }
152     if (!r)
153         g_warning("Unable to find a valid menu file '%s'", file);
154     return r;
155 }
156
157 gboolean parse_load(const gchar *path, const gchar *rootname,
158                     xmlDocPtr *doc, xmlNodePtr *root)
159 {
160     struct stat s;
161     if (stat(path, &s) < 0)
162         return FALSE;
163
164     /* XML_PARSE_BLANKS is needed apparently. When it loads a theme file,
165        without this option, the tree is weird and has extra nodes in it. */
166     if ((*doc = xmlReadFile(path, NULL,
167                             XML_PARSE_NOBLANKS | XML_PARSE_RECOVER))) {
168         *root = xmlDocGetRootElement(*doc);
169         if (!*root) {
170             xmlFreeDoc(*doc);
171             *doc = NULL;
172             g_warning("%s is an empty document", path);
173         } else {
174             if (xmlStrcmp((*root)->name, (const xmlChar*)rootname)) {
175                 xmlFreeDoc(*doc);
176                 *doc = NULL;
177                 g_warning("Document %s is of wrong type. root node is "
178                           "not '%s'", path, rootname);
179             }
180         }
181     }
182     if (!*doc)
183         return FALSE;
184     return TRUE;
185 }
186
187 gboolean parse_load_mem(gpointer data, guint len, const gchar *rootname,
188                         xmlDocPtr *doc, xmlNodePtr *root)
189 {
190     if ((*doc = xmlParseMemory(data, len))) {
191         *root = xmlDocGetRootElement(*doc);
192         if (!*root) {
193             xmlFreeDoc(*doc);
194             *doc = NULL;
195             g_warning("Given memory is an empty document");
196         } else {
197             if (xmlStrcmp((*root)->name, (const xmlChar*)rootname)) {
198                 xmlFreeDoc(*doc);
199                 *doc = NULL;
200                 g_warning("Document in given memory is of wrong type. root "
201                           "node is not '%s'", rootname);
202             }
203         }
204     }
205     if (!*doc)
206         return FALSE;
207     return TRUE;
208 }
209
210 void parse_close(xmlDocPtr doc)
211 {
212     xmlFreeDoc(doc);
213 }
214
215 void parse_tree(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node)
216 {
217     while (node) {
218         struct Callback *c = g_hash_table_lookup(i->callbacks, node->name);
219
220         if (c)
221             c->func(i, doc, node, c->data);
222
223         node = node->next;
224     }
225 }
226
227 gchar *parse_string(xmlDocPtr doc, xmlNodePtr node)
228 {
229     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
230     gchar *s = g_strdup(c ? (gchar*)c : "");
231     xmlFree(c);
232     return s;
233 }
234
235 gint parse_int(xmlDocPtr doc, xmlNodePtr node)
236 {
237     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
238     gint i = atoi((gchar*)c);
239     xmlFree(c);
240     return i;
241 }
242
243 gboolean parse_bool(xmlDocPtr doc, xmlNodePtr node)
244 {
245     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
246     gboolean b = FALSE;
247     if (!xmlStrcasecmp(c, (const xmlChar*) "true"))
248         b = TRUE;
249     else if (!xmlStrcasecmp(c, (const xmlChar*) "yes"))
250         b = TRUE;
251     else if (!xmlStrcasecmp(c, (const xmlChar*) "on"))
252         b = TRUE;
253     xmlFree(c);
254     return b;
255 }
256
257 gboolean parse_contains(const gchar *val, xmlDocPtr doc, xmlNodePtr node)
258 {
259     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
260     gboolean r;
261     r = !xmlStrcasecmp(c, (const xmlChar*) val);
262     xmlFree(c);
263     return r;
264 }
265
266 xmlNodePtr parse_find_node(const gchar *tag, xmlNodePtr node)
267 {
268     while (node) {
269         if (!xmlStrcmp(node->name, (const xmlChar*) tag))
270             return node;
271         node = node->next;
272     }
273     return NULL;
274 }
275
276 gboolean parse_attr_bool(const gchar *name, xmlNodePtr node, gboolean *value)
277 {
278     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
279     gboolean r = FALSE;
280     if (c) {
281         if (!xmlStrcasecmp(c, (const xmlChar*) "true"))
282             *value = TRUE, r = TRUE;
283         else if (!xmlStrcasecmp(c, (const xmlChar*) "yes"))
284             *value = TRUE, r = TRUE;
285         else if (!xmlStrcasecmp(c, (const xmlChar*) "on"))
286             *value = TRUE, r = TRUE;
287         else if (!xmlStrcasecmp(c, (const xmlChar*) "false"))
288             *value = FALSE, r = TRUE;
289         else if (!xmlStrcasecmp(c, (const xmlChar*) "no"))
290             *value = FALSE, r = TRUE;
291         else if (!xmlStrcasecmp(c, (const xmlChar*) "off"))
292             *value = FALSE, r = TRUE;
293     }
294     xmlFree(c);
295     return r;
296 }
297
298 gboolean parse_attr_int(const gchar *name, xmlNodePtr node, gint *value)
299 {
300     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
301     gboolean r = FALSE;
302     if (c) {
303         *value = atoi((gchar*)c);
304         r = TRUE;
305     }
306     xmlFree(c);
307     return r;
308 }
309
310 gboolean parse_attr_string(const gchar *name, xmlNodePtr node, gchar **value)
311 {
312     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
313     gboolean r = FALSE;
314     if (c) {
315         *value = g_strdup((gchar*)c);
316         r = TRUE;
317     }
318     xmlFree(c);
319     return r;
320 }
321
322 gboolean parse_attr_contains(const gchar *val, xmlNodePtr node,
323                              const gchar *name)
324 {
325     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
326     gboolean r = FALSE;
327     if (c)
328         r = !xmlStrcasecmp(c, (const xmlChar*) val);
329     xmlFree(c);
330     return r;
331 }
332
333 static gint slist_path_cmp(const gchar *a, const gchar *b)
334 {
335     return strcmp(a, b);
336 }
337
338 typedef GSList* (*GSListFunc) (gpointer list, gconstpointer data);
339
340 static GSList* slist_path_add(GSList *list, gpointer data, GSListFunc func)
341 {
342     g_assert(func);
343
344     if (!data)
345         return list;
346
347     if (!g_slist_find_custom(list, data, (GCompareFunc) slist_path_cmp))
348         list = func(list, data);
349     else
350         g_free(data);
351
352     return list;
353 }
354
355 static GSList* split_paths(const gchar *paths)
356 {
357     GSList *list = NULL;
358     gchar **spl, **it;
359
360     if (!paths)
361         return NULL;
362     spl = g_strsplit(paths, ":", -1);
363     for (it = spl; *it; ++it)
364         list = slist_path_add(list, *it, (GSListFunc) g_slist_append);
365     g_free(spl);
366     return list;
367 }
368
369 void parse_paths_startup()
370 {
371     const gchar *path;
372
373     if (xdg_start)
374         return;
375     xdg_start = TRUE;
376
377     path = g_getenv("XDG_CONFIG_HOME");
378     if (path && path[0] != '\0') /* not unset or empty */
379         xdg_config_home_path = g_build_filename(path, NULL);
380     else
381         xdg_config_home_path = g_build_filename(g_get_home_dir(), ".config",
382                                                 NULL);
383
384     path = g_getenv("XDG_DATA_HOME");
385     if (path && path[0] != '\0') /* not unset or empty */
386         xdg_data_home_path = g_build_filename(path, NULL);
387     else
388         xdg_data_home_path = g_build_filename(g_get_home_dir(), ".local",
389                                               "share", NULL);
390
391     path = g_getenv("XDG_CONFIG_DIRS");
392     if (path && path[0] != '\0') /* not unset or empty */
393         xdg_config_dir_paths = split_paths(path);
394     else {
395         xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
396                                               g_build_filename
397                                               (G_DIR_SEPARATOR_S,
398                                                "etc", "xdg", NULL),
399                                               (GSListFunc) g_slist_append);
400         xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
401                                               g_strdup(CONFIGDIR),
402                                               (GSListFunc) g_slist_append);
403     }
404     xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
405                                           g_strdup(xdg_config_home_path),
406                                           (GSListFunc) g_slist_prepend);
407     
408     path = g_getenv("XDG_DATA_DIRS");
409     if (path && path[0] != '\0') /* not unset or empty */
410         xdg_data_dir_paths = split_paths(path);
411     else {
412         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
413                                             g_build_filename
414                                             (G_DIR_SEPARATOR_S,
415                                              "usr", "local", "share", NULL),
416                                             (GSListFunc) g_slist_append);
417         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
418                                             g_build_filename
419                                             (G_DIR_SEPARATOR_S,
420                                              "usr", "share", NULL),
421                                             (GSListFunc) g_slist_append);
422         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
423                                             g_strdup(DATADIR),
424                                             (GSListFunc) g_slist_append);
425     }
426     xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
427                                         g_strdup(xdg_data_home_path),
428                                         (GSListFunc) g_slist_prepend);
429 }
430
431 void parse_paths_shutdown()
432 {
433     GSList *it;
434
435     if (!xdg_start)
436         return;
437     xdg_start = FALSE;
438
439     for (it = xdg_config_dir_paths; it; it = g_slist_next(it))
440         g_free(it->data);
441     g_slist_free(xdg_config_dir_paths);
442     xdg_config_dir_paths = NULL;
443     for (it = xdg_data_dir_paths; it; it = g_slist_next(it))
444         g_free(it->data);
445     g_slist_free(xdg_data_dir_paths);
446     xdg_data_dir_paths = NULL;
447     g_free(xdg_config_home_path);
448     xdg_config_home_path = NULL;
449     g_free(xdg_data_home_path);
450     xdg_data_home_path = NULL;
451 }
452
453 gchar *parse_expand_tilde(const gchar *f)
454 {
455     gchar **spl;
456     gchar *ret;
457
458     if (!f)
459         return NULL;
460     spl = g_strsplit(f, "~", 0);
461     ret = g_strjoinv(g_get_home_dir(), spl);
462     g_strfreev(spl);
463     return ret;
464 }
465
466 gboolean parse_mkdir(const gchar *path, gint mode)
467 {
468     gboolean ret = TRUE;
469
470     g_return_val_if_fail(path != NULL, FALSE);
471     g_return_val_if_fail(path[0] != '\0', FALSE);
472
473     if (!g_file_test(path, G_FILE_TEST_IS_DIR))
474         if (mkdir(path, mode) == -1)
475             ret = FALSE;
476
477     return ret;
478 }
479
480 gboolean parse_mkdir_path(const gchar *path, gint mode)
481 {
482     gboolean ret = TRUE;
483
484     g_return_val_if_fail(path != NULL, FALSE);
485     g_return_val_if_fail(path[0] == '/', FALSE);
486
487     if (!g_file_test(path, G_FILE_TEST_IS_DIR)) {
488         gchar *c, *e;
489
490         c = g_strdup(path);
491         e = c;
492         while ((e = strchr(e + 1, '/'))) {
493             *e = '\0';
494             if (!(ret = parse_mkdir(c, mode)))
495                 goto parse_mkdir_path_end;
496             *e = '/';
497         }
498         ret = parse_mkdir(c, mode);
499
500     parse_mkdir_path_end:
501         g_free(c);
502     }
503
504     return ret;
505 }
506
507 const gchar* parse_xdg_config_home_path()
508 {
509     return xdg_config_home_path;
510 }
511
512 const gchar* parse_xdg_data_home_path()
513 {
514     return xdg_data_home_path;
515 }
516
517 GSList* parse_xdg_config_dir_paths()
518 {
519     return xdg_config_dir_paths;
520 }
521
522 GSList* parse_xdg_data_dir_paths()
523 {
524     return xdg_data_dir_paths;
525 }