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 void (*ObtDDParseGroupFunc)(const gchar *group,
29 GHashTable *key_hash);
31 typedef struct _ObtDDParseGroup {
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) */
40 typedef struct _ObtDDParse {
43 ObtDDParseGroup *group;
44 /* the key is a group name, the value is a ObtDDParseGroup */
45 GHashTable *group_hash;
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 */
65 union _ObtDDFileData {
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;
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 */
76 ObtDDFileAppStartup startup;
77 gchar *startup_wmclass;
87 static ObtDDParseGroup* group_new(gchar *name, ObtDDParseGroupFunc f)
89 ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup);
93 g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
98 static void group_free(ObtDDParseGroup *g)
101 g_hash_table_destroy(g->key_hash);
102 g_slice_free(ObtDDParseGroup, g);
105 /* Displays a warning message including the file name and line number, and
106 sets the boolean @error to true if it points to a non-NULL address.
108 static void parse_error(const gchar *m, const ObtDDParse *const parse,
111 if (!parse->filename)
112 g_warning("%s at line %lu of input", m, parse->lineno);
114 g_warning("%s at line %lu of file %s",
115 m, parse->lineno, parse->filename);
116 if (error) *error = TRUE;
119 /* reads an input string, strips out invalid stuff, and parses
121 static gchar* parse_string(const gchar *in, gboolean locale,
122 const ObtDDParse *const parse,
125 const gint bytes = strlen(in);
128 const gchar *end, *i;
130 g_return_val_if_fail(in != NULL, NULL);
134 for (i = in; i < end; ++i) {
135 if ((guchar)*i > 126 || (guchar)*i < 32) {
136 /* non-control character ascii */
138 parse_error("Invalid bytes in string", parse, error);
143 else if (!g_utf8_validate(in, bytes, &end))
144 parse_error("Invalid bytes in localestring", parse, error);
146 out = g_new(char, bytes + 1);
150 const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
153 case 's': *o++ = ' '; break;
154 case 'n': *o++ = '\n'; break;
155 case 't': *o++ = '\t'; break;
156 case 'r': *o++ = '\r'; break;
157 case '\\': *o++ = '\\'; break;
159 parse_error((locale ?
160 "Invalid escape sequence in localestring" :
161 "Invalid escape sequence in string"),
168 else if ((guchar)*i >= 127 || (guchar)*i < 32) {
169 /* avoid ascii control characters */
170 parse_error("Found control character in string", parse, error);
174 memcpy(o, i, next-i);
183 static gboolean parse_bool(const gchar *in, const ObtDDParse *const parse,
186 if (strcmp(in, "true") == 0)
188 else if (strcmp(in, "false") != 0)
189 parse_error("Invalid boolean value", parse, error);
193 static float parse_numeric(const gchar *in, const ObtDDParse *const parse,
197 if (sscanf(in, "%f", &out) == 0)
198 parse_error("Invalid numeric value", parse, error);
202 gboolean parse_file_line(FILE *f, gchar **buf, gulong *size, gulong *read,
203 ObtDDParse *parse, gboolean *error)
205 const gulong BUFMUL = 80;
210 g_assert(*read == 0);
212 *buf = g_new(char, *size);
215 /* remove everything up to a null zero already in the buffer and shift
216 the rest to the front */
218 for (i = 0; i < *read; ++i) {
220 (*buf)[i-null-1] = (*buf)[i];
221 else if ((*buf)[i] == '\0')
227 /* is there already a newline in the buffer? */
228 for (i = 0; i < *read; ++i)
229 if ((*buf)[i] == '\n') {
230 /* turn it into a null zero and done */
235 /* we need to read some more to find a newline */
240 newread = *buf + *read;
241 ret = fread(newread, sizeof(char), *size-*read, f);
242 if (ret < *size - *read && !feof(f)) {
243 parse_error("Error reading", parse, error);
248 /* strip out null zeros in the input and look for an endofline */
251 for (i = newread-*buf; i < *read; ++i) {
253 (*buf)[i] = (*buf)[i+null];
254 if ((*buf)[i] == '\0') {
259 else if ((*buf)[i] == '\n' && eol == *size) {
261 /* turn it into a null zero */
267 /* found an endofline, done */
269 else if (feof(f) && *read < *size) {
270 /* found the endoffile, done (if there is space) */
272 /* stick a null zero on if there is test on the last line */
273 (*buf)[(*read)++] = '\0';
280 *buf = g_renew(char, *buf, *size);
286 static void parse_group(const gchar *buf, gulong len,
287 ObtDDParse *parse, gboolean *error)
293 /* get the group name */
294 group = g_strndup(buf+1, len-2);
295 for (i = 0; i < len-2; ++i)
296 if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) {
297 /* valid ASCII only */
298 parse_error("Invalid character found", parse, NULL);
299 group[i] = '\0'; /* stopping before this character */
303 /* make sure it's a new group */
304 g = g_hash_table_lookup(parse->group_hash, group);
306 parse_error("Duplicate group found", parse, error);
310 /* if it's the first group, make sure it's named Desktop Entry */
311 else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
313 parse_error("Incorrect group found, "
314 "expected [Desktop Entry]",
321 g = group_new(group, NULL);
322 g_hash_table_insert(parse->group_hash, g->name, g);
329 g_print("Found group %s\n", g->name);
333 static void parse_key_value(const gchar *buf, gulong len,
334 ObtDDParse *parse, gboolean *error)
336 gulong i, keyend, valstart, eq;
339 /* find the end of the key */
340 for (i = 0; i < len; ++i)
341 if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
342 ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') ||
343 ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') ||
344 ((guchar)buf[i] == '-'))) {
345 /* not part of the key */
350 parse_error("Empty key", parse, error);
353 /* find the = character */
354 for (i = keyend; i < len; ++i) {
359 else if (buf[i] != ' ') {
360 parse_error("Invalid character in key name", parse, error);
365 parse_error("Key without value found", parse, error);
368 /* find the start of the value */
369 for (i = eq+1; i < len; ++i)
375 parse_error("Empty value found", parse, error);
379 key = g_strndup(buf, keyend);
380 val = g_strndup(buf+valstart, len-valstart);
381 if (g_hash_table_lookup(parse->group->key_hash, key)) {
382 parse_error("Duplicate key found", parse, error);
387 g_hash_table_insert(parse->group->key_hash, key, val);
388 g_print("Found key/value %s=%s.\n", key, val);
391 void foreach_group(gpointer pkey, gpointer pvalue, gpointer user_data)
394 ObtDDParseGroup *g = pvalue;
395 if (g->func) g->func(name, g->key_hash);
398 static gboolean parse_file(ObtDDFile *dd, FILE *f, ObtDDParse *parse)
401 gulong bytes = 0, read = 0;
402 gboolean error = FALSE;
404 while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
405 /* XXX use the string in buf */
406 gulong len = strlen(buf);
407 if (buf[0] == '#' || buf[0] == '\0')
408 ; /* ignore comment lines */
409 else if (buf[0] == '[' && buf[len-1] == ']')
410 parse_group(buf, len, parse, &error);
411 else if (!parse->group)
412 /* just ignore keys outside of groups */
413 parse_error("Key found before group", parse, NULL);
415 /* ignore errors in key-value pairs and continue */
416 parse_key_value(buf, len, parse, NULL);
420 g_hash_table_foreach(parse->group_hash, foreach_group, NULL);
422 if (buf) g_free(buf);
426 static void parse_group_desktop_entry(const gchar *group,
429 g_print("Parsing group %s\n", group);
432 ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths)
436 ObtDDParseGroup *desktop_entry;
441 dd = g_slice_new(ObtDDFile);
444 parse.filename = NULL;
447 parse.group_hash = g_hash_table_new_full(g_str_hash,
450 (GDestroyNotify)group_free);
452 /* set up the groups (there's only one right now) */
453 desktop_entry = group_new(g_strdup("Desktop Entry"),
454 parse_group_desktop_entry);
455 g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry);
458 for (it = paths; it && !success; it = g_slist_next(it)) {
459 gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
460 if ((f = fopen(path, "r"))) {
461 parse.filename = path;
463 success = parse_file(dd, f, &parse);
469 obt_ddfile_unref(dd);
473 g_hash_table_destroy(parse.group_hash);
478 void obt_ddfile_ref(ObtDDFile *dd)
483 void obt_ddfile_unref(ObtDDFile *dd)
486 g_slice_free(ObtDDFile, dd);