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