e0c7f9863aad7e0d06fa7c2b7d7f2528cbe516a5
[dana/openbox.git] / 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 struct _ObtDDParse {
29     gchar *filename;
30     gulong lineno;
31 } ObtDDParse;
32
33 typedef enum {
34     DATA_STRING,
35     DATA_LOCALESTRING,
36     DATA_BOOLEAN,
37     DATA_NUMERIC,
38     NUM_DATA_TYPES
39 } ObtDDDataType;
40
41 struct _ObtDDFile {
42     guint ref;
43
44     ObtDDFileType type;
45     gchar *name; /*!< Specific name for the object (eg Firefox) */
46     gchar *generic; /*!< Generic name for the object (eg Web Browser) */
47     gchar *comment; /*!< Comment/description to display for the object */
48     gchar *icon; /*!< Name/path for an icon for the object */
49
50     union _ObtDDFileData {
51         struct {
52             gchar *exec; /*!< Executable to run for the app */
53             gchar *wdir; /*!< Working dir to run the app in */
54             gboolean term; /*!< Run the app in a terminal or not */
55             ObtDDFileAppOpen open;
56
57             /* XXX gchar**? or something better, a mime struct.. maybe
58                glib has something i can use. */
59             gchar **mime; /*!< Mime types the app can open */
60
61             ObtDDFileAppStartup startup;
62             gchar *startup_wmclass;
63         } app;
64         struct {
65             gchar *url;
66         } link;
67         struct {
68         } dir;
69     } d;
70 };
71
72 static void parse_error(const gchar *m, const ObtDDParse *const parse,
73                         gboolean *error)
74 {
75     if (!parse->filename)
76         g_warning("%s at line %lu of input\n", m, parse->lineno);
77     else
78         g_warning("%s at line %lu of file %s\n",
79                   m, parse->lineno, parse->filename);
80     if (error) *error = TRUE;
81 }
82
83 /* reads an input string, strips out invalid stuff, and parses
84    backslash-stuff */
85 static gchar* parse_string(const gchar *in, gboolean locale,
86                            const ObtDDParse *const parse,
87                            gboolean *error)
88 {
89     const gint bytes = strlen(in);
90     gboolean backslash;
91     gchar *out, *o;
92     const gchar *end, *i;
93
94     g_return_val_if_fail(in != NULL, NULL);
95
96     if (!locale) {
97         end = in + bytes;
98         for (i = in; i < end; ++i) {
99             if (*i > 127) {
100                 end = i;
101                 parse_error("Invalid bytes in string", parse, error);
102                 break;
103             }
104         }
105     }
106     else if (!g_utf8_validate(in, bytes, &end))
107         parse_error("Invalid bytes in localestring", parse, error);
108
109     out = g_new(char, bytes + 1);
110     i = in; o = out;
111     backslash = FALSE;
112     while (i < end) {
113         const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
114         if (backslash) {
115             switch(*i) {
116             case 's': *o++ = ' '; break;
117             case 'n': *o++ = '\n'; break;
118             case 't': *o++ = '\t'; break;
119             case 'r': *o++ = '\r'; break;
120             case '\\': *o++ = '\\'; break;
121             default:
122                 parse_error((locale ?
123                              "Invalid escape sequence in localestring" :
124                              "Invalid escape sequence in string"),
125                             parse, error);
126             }
127             backslash = FALSE;
128         }
129         else if (*i == '\\')
130             backslash = TRUE;
131         else {
132             memcpy(o, i, next-i);
133             o += next-i;
134         }
135         i = next;
136     }
137     *o = '\0';
138     return o;
139 }
140
141 static gboolean parse_bool(const gchar *in, const ObtDDParse *const parse,
142                            gboolean *error)
143 {
144     if (strcmp(in, "true") == 0)
145         return TRUE;
146     else if (strcmp(in, "false") != 0)
147         parse_error("Invalid boolean value", parse, error);
148     return FALSE;
149 }
150
151 static float parse_numeric(const gchar *in, const ObtDDParse *const parse,
152     gboolean *error)
153 {
154     float out = 0;
155     if (sscanf(in, "%f", &out) == 0)
156         parse_error("Invalid numeric value", parse, error);
157     return out;
158 }
159
160 gboolean parse_file_line(FILE *f, gchar **buf, gulong *size, gulong *read,
161                          ObtDDParse *parse, gboolean *error)
162 {
163     const gulong BUFMUL = 80;
164     size_t ret;
165     gulong i, null;
166
167     if (*size == 0) {
168         g_assert(*read == 0);
169         *size = BUFMUL;
170         *buf = g_new(char, *size);
171     }
172
173     /* remove everything up to a null zero already in the buffer and shift
174        the rest to the front */
175     null = *size;
176     for (i = 0; i < *read; ++i) {
177         if (null < *size)
178             (*buf)[i-null-1] = (*buf)[i];
179         else if ((*buf)[i] == '\0')
180             null = i;
181     }
182     if (null < *size)
183         *read -= null + 1;
184
185     /* is there already a newline in the buffer? */
186     for (i = 0; i < *read; ++i)
187         if ((*buf)[i] == '\n') {
188             /* turn it into a null zero and done */
189             (*buf)[i] = '\0';
190             return TRUE;
191         }
192
193     /* we need to read some more to find a newline */
194     while (TRUE) {
195         gulong eol;
196         gchar *newread;
197
198         newread = *buf + *read;
199         ret = fread(newread, sizeof(char), *size-*read, f);
200         if (ret < *size - *read && !feof(f)) {
201             parse_error("Error reading", parse, error);
202             return FALSE;
203         }
204         *read += ret;
205
206         /* strip out null zeros in the input and look for an endofline */
207         null = 0;
208         eol = *size;
209         for (i = newread-*buf; i < *read; ++i) {
210             if (null > 0)
211                 (*buf)[i] = (*buf)[i+null];
212             if ((*buf)[i] == '\0') {
213                 ++null;
214                 --(*read);
215                 --i; /* try again */
216             }
217             else if ((*buf)[i] == '\n' && eol == *size) {
218                 eol = i;
219                 /* turn it into a null zero */
220                 (*buf)[i] = '\0';
221             }
222         }
223
224         if (eol != *size)
225             /* found an endofline, done */
226             break;
227         else if (feof(f) && *read < *size) {
228             /* found the endoffile, done (if there is space) */
229             if (*read > 0) {
230                 /* stick a null zero on if there is test on the last line */
231                 (*buf)[(*read)++] = '\0';
232             }
233             break;
234         }
235         else {
236             /* read more */
237             size += BUFMUL;
238             *buf = g_renew(char, *buf, *size);
239         }
240     }
241     return *read > 0;
242 }
243
244 static gboolean parse_file(ObtDDFile *dd, FILE *f, ObtDDParse *parse)
245 {
246     gchar *buf = NULL;
247     gulong bytes = 0, read = 0;
248     gboolean error = FALSE;
249
250     while (parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
251         /* XXX use the string in buf */
252         ++parse->lineno;
253     }
254
255     if (buf) g_free(buf);
256     return !error;
257 }
258
259 ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths)
260 {
261     ObtDDFile *dd;
262     ObtDDParse parse;
263     GSList *it;
264     FILE *f;
265
266     dd = g_slice_new(ObtDDFile);
267     dd->ref = 1;
268
269     f = NULL;
270     for (it = paths; it && !f; it = g_slist_next(it)) {
271         gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
272         if ((f = fopen(path, "r"))) {
273             parse.filename = path;
274             parse.lineno = 0;
275             if (!parse_file(dd, f, &parse)) f = NULL;
276         }
277     }
278     if (!f) {
279         obt_ddfile_unref(dd);
280         dd = NULL;
281     }
282     return dd;
283 }
284
285 void obt_ddfile_ref(ObtDDFile *dd)
286 {
287     ++dd->ref;
288 }
289
290 void obt_ddfile_unref(ObtDDFile *dd)
291 {
292     if (--dd->ref < 1) {
293         g_slice_free(ObtDDFile, dd);
294     }
295 }