wmmenugen: Finish the XDG parser
[wmaker-crm.git] / util / wmmenugen_parse_wmconfig.c
blobbc73b293a6056f869670c590ac6316fe4d527367
1 /*
2 * wmmenugen - Window Maker PropList menu generator
4 * Wmconfig <http://www.arrishq.net/> parser functions
6 * Copyright (c) 2010. Tamas Tevesz <ice@extreme.hu>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #if __GLIBC__ && \
24 (_XOPEN_SOURCE && _XOPEN_SOURCE < 500) || \
25 !_XOPEN_SOURCE
26 #define _XOPEN_SOURCE 500 /* nftw */
27 #endif
29 #include <sys/types.h>
30 #include <sys/stat.h>
32 #include <ctype.h>
33 #include <ftw.h>
34 #if DEBUG
35 #include <errno.h>
36 #endif
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
42 #include "wmmenugen.h"
44 typedef struct {
45 char *Name;
46 char *Exec;
47 char *Category;
48 char *Restart;
49 int Flags;
50 } WMConfigMenuEntry;
52 static Bool wmc_to_wm(WMConfigMenuEntry **wmc, WMMenuEntry **wm);
53 static void parse_wmconfig_line(char **label, char **key, char **value, char *line);
54 static void init_wmconfig_storage(WMConfigMenuEntry **wmc);
56 void parse_wmconfig(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry))
58 FILE *fp;
59 char buf[1024];
60 char *p, *lastlabel, *label, *key, *value;
61 WMConfigMenuEntry *wmc;
62 WMMenuEntry *wm;
64 lastlabel = label = key = value = NULL;
66 fp = fopen(file, "r");
67 if (!fp) {
68 #if DEBUG
69 fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno));
70 #endif
71 return;
74 wmc = (WMConfigMenuEntry *)wmalloc(sizeof(WMConfigMenuEntry));
75 wmc->Name = NULL;
76 wmc->Exec = NULL;
77 wmc->Category = NULL;
78 wmc->Restart = NULL;
79 wmc->Flags = 0;
81 wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry));
83 memset(buf, 0, sizeof(buf));
85 while (fgets(buf, sizeof(buf), fp)) {
87 p = buf;
89 /* skip whitespaces */
90 while (isspace(*p))
91 p++;
92 /* skip comments, empty lines */
93 if (*p == '\r' || *p == '\n' || *p == '#') {
94 memset(buf, 0, sizeof(buf));
95 continue;
97 /* trim crlf */
98 buf[strcspn(buf, "\r\n")] = '\0';
99 if (strlen(buf) == 0)
100 continue;
102 parse_wmconfig_line(&label, &key, &value, p);
104 if (label && strlen(label) == 0)
105 continue;
106 if (!lastlabel && label)
107 lastlabel = wstrdup(label);
109 if (strcmp(lastlabel, label) != 0) {
110 if (wmc_to_wm(&wmc, &wm)) {
111 (*addWMMenuEntryCallback)(wm);
112 init_wmconfig_storage(&wmc);
115 wfree(lastlabel);
116 lastlabel = wstrdup(label);
119 if (key && value) {
120 if (strcmp(key, "name") == 0)
121 wmc->Name = value;
122 else if (strcmp(key, "exec") == 0)
123 wmc->Exec = value;
124 else if (strcmp(key, "group") == 0)
125 wmc->Category = value;
126 else if (strcmp(key, "restart") == 0)
127 wmc->Restart = value;
128 else if (strcmp(key, "terminal") == 0)
129 wmc->Flags |= F_TERMINAL;
134 fclose(fp);
136 if (wmc_to_wm(&wmc, &wm)) {
137 (*addWMMenuEntryCallback)(wm);
138 init_wmconfig_storage(&wmc);
142 /* an example to illustrate validateFilename.
143 * with wmconfig, no special handling is needed
145 Bool wmconfig_validate_file(const char *filename, const struct stat *st, int tflags, struct FTW *ftw)
147 (void)filename;
148 (void)st;
149 (void)tflags;
150 (void)ftw;
152 return True;
153 #if 0 /* not dead code, example */
155 /* or we could have gone intro extremes */
156 char *base_name;
157 Bool ret;
159 (void)tflags;
161 base_name = wstrdup(filename + ftw->base);
162 ret = True;
164 if (!S_ISREG(st->st_mode) || /* not a regular file */
165 (st->st_uid != 0 && st->st_uid != getuid()) || /* bad guy injected this file */
166 strpbrk(base_name, ".") || /* wmconfig typically has no extension */
167 st->st_size >= 128 * 131072 || /* noone writes wmconfig files > 128K */
168 st->st_size == 0 || /* nor empty ones */
169 ftw->level > 16) /* how did we get this deep? */
170 ret = False;
172 wfree(base_name);
174 return ret;
175 #endif
178 /* get a line allocating label, key and value as necessary */
179 static void parse_wmconfig_line(char **label, char **key, char **value, char *line)
181 char *p;
182 int kstart, kend;
184 p = (char *)line;
185 *label = *key = *value = NULL;
186 kstart = kend = 0;
188 /* skip whitespace */
189 while (isspace(*(p + kstart)))
190 kstart++;
192 kend = kstart;
193 /* find end of label */
194 while (*(p + kend) && !isspace(*(p + kend)))
195 kend++;
197 /* label */
198 *label = wstrndup(p + kstart, kend - kstart);
199 kstart = kend + 1;
201 /* skip whitespace */
202 while (isspace(*(p + kstart)))
203 kstart++;
205 kend = kstart;
206 /* find end of key */
207 while (*(p + kend) && !isspace(*(p + kend)))
208 kend++;
210 /* key */
211 *key = wstrndup(p + kstart, kend - kstart);
212 kstart = kend + 1;
214 /* skip until after " */
215 while (*(p + kstart) && *(p + kstart) != '"')
216 kstart++;
217 kstart++;
219 kend = kstart;
220 /* skip until " */
221 while (*(p + kend) && *(p + kend) != '"')
222 kend++;
224 /* value */
225 *value = wstrndup(p + kstart, kend - kstart);
228 /* normalize and convert one wmconfig-format entry to wm format */
229 static Bool wmc_to_wm(WMConfigMenuEntry **wmc, WMMenuEntry **wm)
231 char *p;
232 size_t slen;
234 /* only Exec is mandatory, and it's better exist in a known place */
235 if (!((*wmc)->Exec &&
236 *(*wmc)->Exec &&
237 fileInPath((*wmc)->Exec)))
238 return False;
240 /* normalize Exec: wmconfig tends to stick an ampersand
241 * at the end of everything, which we don't need */
242 slen = strlen((*wmc)->Exec) - 1;
243 p = (*wmc)->Exec;
244 while (slen > 0 && (isspace(*(p + slen)) || *(p + slen) == '&'))
245 *(p + slen--) = '\0';
247 /* if there's no Name, use the first word of Exec; still better
248 * than nothing. i realize it's highly arguable whether `xterm' from
249 * `xterm -e "ssh dev push-to-prod"' is helpful or not, but since
250 * the alternative is to completely lose the entry, i opt for this.
251 * you could just fix the descriptor file to have a label <G> */
252 if (!(*wmc)->Name) {
253 (*wmc)->Name = wstrdup((*wmc)->Exec);
254 p = strchr((*wmc)->Name, ' ');
255 if (p)
256 *p = '\0';
259 /* if there's no Category, use "Applications"; apparently "no category"
260 * can manifest both as no `group' descriptor at all, or a group
261 * descriptor of "" */
262 if (!(*wmc)->Category || !*(*wmc)->Category)
263 (*wmc)->Category = wstrdup("Applications");
265 /* the `restart' type is used for restart, restart other
266 * wm and quit current wm too. separate these cases. */
267 if ((*wmc)->Restart) {
268 if (strcmp((*wmc)->Restart, "restart") == 0)
269 (*wmc)->Flags |= F_RESTART_SELF;
270 else if (strcmp((*wmc)->Restart, "quit") == 0)
271 (*wmc)->Flags |= F_QUIT;
272 else
273 (*wmc)->Flags |= F_RESTART_OTHER;
276 (*wm)->Name = (*wmc)->Name;
277 (*wm)->CmdLine = (*wmc)->Exec;
278 (*wm)->SubMenu = (*wmc)->Category;
279 (*wm)->Flags = (*wmc)->Flags;
281 return True;
284 static void init_wmconfig_storage(WMConfigMenuEntry **wmc)
286 if ((*wmc)->Category)
287 wfree((*wmc)->Category);
288 (*wmc)->Category = NULL;
289 if ((*wmc)->Name)
290 wfree((*wmc)->Name);
291 (*wmc)->Name = NULL;
292 if ((*wmc)->Restart)
293 wfree((*wmc)->Restart);
294 (*wmc)->Restart = NULL;
295 (*wmc)->Flags = 0;