Only cache pixmaps for docked icons
[wmaker-crm.git] / util / wmmenugen_parse_xdg.c
blob0ee23f892a971df5d5b9d3f04536192923fcc685
1 /*
2 * wmmenugen - Window Maker PropList menu generator
4 * Desktop Entry Specification 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.
24 * http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html
26 * We will only deal with Type == "Application" entries in [Desktop Entry]
27 * groups. Since there is no passing of file name arguments or anything of
28 * the sort to applications from the menu, execname is determined as follows:
29 * - If `TryExec' is present, use that;
30 * - else use `Exec' with any switches stripped
31 * XXX: Only the first item of `Categories' is taken into consideration,
32 * which will be used as the sole category to put the entry into.
34 * Basic validation of the .desktop file is done.
37 #include <ctype.h>
38 #if DEBUG
39 #include <errno.h>
40 #endif
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
45 #include "wmmenugen.h"
47 /* LocaleString match levels */
48 enum {
49 MATCH_DEFAULT,
50 MATCH_LANG,
51 MATCH_LANG_MODIFIER,
52 MATCH_LANG_COUNTRY,
53 MATCH_LANG_COUNTRY_MODIFIER
56 typedef struct {
57 char *Name; /* Name */ /* localestring */
58 int MatchLevel; /* LocaleString match type */ /* int */
59 char *TryExec; /* TryExec */ /* string */
60 char *Exec; /* Exec */ /* string */
61 char *Path; /* Path */ /* string */
62 int Flags; /* Flags */
63 char *Category; /* Categories (first item only) */ /* string */
64 } XDGMenuEntry;
66 static void getKey(char **target, const char *line);
67 static void getStringValue(char **target, const char *line);
68 static void getLocalizedStringValue(char **target, const char *line, int *match_level);
69 static int getBooleanValue(const char *line);
70 static int compare_matchlevel(int *current_level, const char *found_locale);
71 static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wmentry);
72 static void init_xdg_storage(XDGMenuEntry **xdg);
73 static void init_wm_storage(WMMenuEntry **wm);
75 void parse_xdg(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry))
77 FILE *fp;
78 char buf[1024];
79 char *p, *tmp, *key;
80 WMMenuEntry *wm;
81 XDGMenuEntry *xdg;
82 int InGroup;
84 fp = fopen(file, "r");
85 if (!fp) {
86 #if DEBUG
87 fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno));
88 #endif
89 return;
92 xdg = (XDGMenuEntry *)wmalloc(sizeof(XDGMenuEntry));
93 wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry));
94 InGroup = 0;
95 memset(buf, 0, sizeof(buf));
97 while (fgets(buf, sizeof(buf), fp)) {
99 p = buf;
101 /* skip whitespaces */
102 while (isspace(*p))
103 p++;
104 /* skip comments, empty lines */
105 if (*p == '\r' || *p == '\n' || *p == '#') {
106 memset(buf, 0, sizeof(buf));
107 continue;
109 /* trim crlf */
110 buf[strcspn(buf, "\r\n")] = '\0';
111 if (strlen(buf) == 0)
112 continue;
114 if (strcmp(p, "[Desktop Entry]") == 0) {
115 if (InGroup) {
116 /* if currently processing a group, we've just hit the
117 * end of its definition, try processing it
119 if (xdg_to_wm(&xdg, &wm)) {
120 (*addWMMenuEntryCallback)(wm);
123 init_xdg_storage(&xdg);
124 init_wm_storage(&wm);
125 InGroup = 1;
126 /* start processing group */
127 memset(buf, 0, sizeof(buf));
128 continue;
131 if (!InGroup) {
132 memset(buf, 0, sizeof(buf));
133 continue;
136 getKey(&key, p);
137 if (key == NULL) { /* not `key' = `value' */
138 memset(buf, 0, sizeof(buf));
139 continue;
142 if (strcmp(key, "Type") == 0) {
143 getStringValue(&tmp, p);
144 if (strcmp(tmp, "Application") != 0)
145 InGroup = 0; /* if not application, skip current group */
146 wfree(tmp);
147 tmp = NULL;
148 } else if (strcmp(key, "Name") == 0) {
149 getLocalizedStringValue(&xdg->Name, p, &xdg->MatchLevel);
150 } else if (strcmp(key, "NoDisplay") == 0) {
151 if (getBooleanValue(p)) /* if nodisplay, skip current group */
152 InGroup = 0;
153 } else if (strcmp(key, "Hidden") == 0) {
154 if (getBooleanValue(p))
155 InGroup = 0; /* if hidden, skip current group */
156 } else if (strcmp(key, "TryExec") == 0) {
157 getStringValue(&xdg->TryExec, p);
158 } else if (strcmp(key, "Exec") == 0) {
159 getStringValue(&xdg->Exec, p);
160 } else if (strcmp(key, "Path") == 0) {
161 getStringValue(&xdg->Path, p);
162 } else if (strcmp(key, "Terminal") == 0) {
163 if (getBooleanValue(p))
164 xdg->Flags |= F_TERMINAL;
165 } else if (strcmp(key, "Categories") == 0) {
166 /* use only the first item */
167 getStringValue(&tmp, p);
168 if (tmp != NULL) {
169 tmp[strcspn(tmp, ";")] = '\0';
170 tmp[strcspn(tmp, "\t ")] = '\0';
171 xdg->Category = wstrdup(tmp);
172 wfree(tmp);
173 tmp = NULL;
177 wfree(key);
178 key = NULL;
181 fclose(fp);
183 /* at the end of the file, might as well try to menuize what we have */
184 if (xdg_to_wm(&xdg, &wm))
185 (*addWMMenuEntryCallback)(wm);
190 /* coerce an xdg entry type into a wm entry type
192 static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wm)
194 if (!*xdg)
195 return False;
197 if ((*xdg)->Exec || (*xdg)->TryExec) {
198 (*wm)->Flags = (*xdg)->Flags;
199 if ((*xdg)->Name) {
200 (*wm)->Name = (*xdg)->Name;
202 if ((*xdg)->Exec) {
203 if (!(*wm)->Name)
204 (*wm)->Name = (*xdg)->Exec;
205 (*wm)->CmdLine = (*xdg)->Exec;
207 if ((*xdg)->TryExec) {
208 if (!(*wm)->Name)
209 (*wm)->Name = (*xdg)->TryExec;
210 if (!(*wm)->CmdLine)
211 (*wm)->CmdLine = (*xdg)->TryExec;
214 if ((*wm)->Name && (*wm)->CmdLine)
215 return True;
217 return False;
220 /* (re-)initialize a XDGMenuEntry storage
222 static void init_xdg_storage(XDGMenuEntry **xdg)
225 if ((*xdg)->Name)
226 wfree((*xdg)->Name);
227 if ((*xdg)->TryExec)
228 wfree((*xdg)->TryExec);
229 if ((*xdg)->Exec)
230 wfree((*xdg)->Exec);
231 if ((*xdg)->Category)
232 wfree((*xdg)->Category);
233 if ((*xdg)->Path)
234 wfree((*xdg)->Path);
236 (*xdg)->Name = NULL;
237 (*xdg)->TryExec = NULL;
238 (*xdg)->Exec = NULL;
239 (*xdg)->Category = NULL;
240 (*xdg)->Path = NULL;
241 (*xdg)->Flags = 0;
242 (*xdg)->MatchLevel = -1;
245 /* (re-)initialize a WMMenuEntry storage
247 static void init_wm_storage(WMMenuEntry **wm)
249 (*wm)->Name = NULL;
250 (*wm)->CmdLine = NULL;
251 (*wm)->Flags = 0;
254 /* get a key from line. allocates target, which must be wfreed later */
255 static void getKey(char **target, const char *line)
257 const char *p;
258 int kstart, kend;
260 p = line;
262 if (strchr(p, '=') == NULL) { /* not `key' = `value' */
263 *target = NULL;
264 return;
267 kstart = 0;
269 /* skip whitespace */
270 while (isspace(*(p + kstart)))
271 kstart++;
273 /* skip up until first whitespace or '[' (localestring) or '=' */
274 kend = kstart + 1;
275 while (!isspace(*(p + kend)) && *(p + kend) != '=' && *(p + kend) != '[')
276 kend++;
278 *target = wstrndup(p + kstart, kend - kstart);
281 /* get a string value from line. allocates target, which must be wfreed later. */
282 static void getStringValue(char **target, const char *line)
284 const char *p;
285 int kstart;
287 p = line;
288 kstart = 0;
290 /* skip until after '=' */
291 while (*(p + kstart) != '=')
292 kstart++;
293 kstart++;
295 /* skip whitespace */
296 while (isspace(*(p + kstart)))
297 kstart++;
299 *target = wstrdup(p + kstart);
302 /* get a localized string value from line. allocates target, which must be wfreed later.
303 * matching is dependent on the current value of target as well as on the
304 * level the current value is matched on. guts matching algorithm is in
305 * compare_matchlevel().
307 static void getLocalizedStringValue(char **target, const char *line, int *match_level)
309 const char *p;
310 char *locale;
311 int kstart;
312 int sqbstart, sqbend;
314 p = line;
315 kstart = 0;
316 sqbstart = 0;
317 locale = NULL;
319 /* skip until after '=', mark if '[' and ']' is found */
320 while (*(p + kstart) != '=') {
321 switch (*(p + kstart)) {
322 case '[': sqbstart = kstart + 1;break;
323 case ']': sqbend = kstart; break;
324 default : break;
326 kstart++;
328 kstart++;
330 /* skip whitespace */
331 while (isspace(*(p + kstart)))
332 kstart++;
334 if (sqbstart > 0 && sqbend > sqbstart)
335 locale = wstrndup(p + sqbstart, sqbend - sqbstart);
337 /* if there is no value yet and this is the default key, return */
338 if (!*target && !locale) {
339 *match_level = MATCH_DEFAULT;
340 *target = wstrdup(p + kstart);
341 return;
344 if (compare_matchlevel(match_level, locale)) {
345 wfree(locale);
346 *target = wstrdup(p + kstart);
347 return;
350 return;
353 /* get a boolean value from line */
354 static Bool getBooleanValue(const char *line)
356 char *p;
357 int ret;
359 ret = 0;
360 getStringValue(&p, line);
361 ret = strcmp(p, "true") == 0 ? True : False;
362 wfree(p);
364 return ret;
367 /* perform locale matching by implementing the algorithm specified in
368 * xdg desktop entry specification, section "localized values for keys".
370 static Bool compare_matchlevel(int *current_level, const char *found_locale)
372 /* current key locale */
373 char *key_lang, *key_ctry, *key_enc, *key_mod;
375 parse_locale(found_locale, &key_lang, &key_ctry, &key_enc, &key_mod);
377 if (env_lang && key_lang && /* Shortcut: if key and env languages don't match, */
378 strcmp(env_lang, key_lang) != 0) /* don't even bother. This takes care of the great */
379 return False; /* majority of the cases without having to go through */
380 /* the more theoretical parts of the spec'd algo. */
382 if (!env_mod && key_mod) /* If LC_MESSAGES does not have a MODIFIER field, */
383 return False; /* then no key with a modifier will be matched. */
385 if (!env_ctry && key_ctry) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */
386 return False; /* then no key with a country specified will be matched. */
388 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
389 if (env_lang && env_ctry && env_mod) { /* lang_COUNTRY@MODIFIER */
390 if (key_lang && key_ctry && key_mod &&
391 strcmp(env_lang, key_lang) == 0 &&
392 strcmp(env_ctry, key_ctry) == 0 &&
393 strcmp(env_mod, key_mod) == 0) {
394 *current_level = MATCH_LANG_COUNTRY_MODIFIER;
395 return True;
396 } else if (key_lang && key_ctry && /* lang_COUNTRY */
397 strcmp(env_lang, key_lang) == 0 &&
398 strcmp(env_ctry, key_ctry) == 0 &&
399 *current_level < MATCH_LANG_COUNTRY) {
400 *current_level = MATCH_LANG_COUNTRY;
401 return True;
402 } else if (key_lang && key_mod && /* lang@MODIFIER */
403 strcmp(env_lang, key_lang) == 0 &&
404 strcmp(env_mod, key_mod) == 0 &&
405 *current_level < MATCH_LANG_MODIFIER) {
406 *current_level = MATCH_LANG_MODIFIER;
407 return True;
408 } else if (key_lang && /* lang */
409 strcmp(env_lang, key_lang) == 0 &&
410 *current_level < MATCH_LANG) {
411 *current_level = MATCH_LANG;
412 return True;
413 } else {
414 return False;
418 /* LC_MESSAGES value: lang_COUNTRY */
419 if (env_lang && env_ctry) { /* lang_COUNTRY */
420 if (key_lang && key_ctry &&
421 strcmp(env_lang, key_lang) == 0 &&
422 strcmp(env_ctry, key_ctry) == 0 &&
423 *current_level < MATCH_LANG_COUNTRY) {
424 *current_level = MATCH_LANG_COUNTRY;
425 return True;
426 } else if (key_lang && /* lang */
427 strcmp(env_lang, key_lang) == 0 &&
428 *current_level < MATCH_LANG) {
429 *current_level = MATCH_LANG;
430 return True;
431 } else {
432 return False;
436 /* LC_MESSAGES value: lang@MODIFIER */
437 if (env_lang && env_mod) { /* lang@MODIFIER */
438 if (key_lang && key_mod &&
439 strcmp(env_lang, key_lang) == 0 &&
440 strcmp(env_mod, key_mod) == 0 &&
441 *current_level < MATCH_LANG_MODIFIER) {
442 *current_level = MATCH_LANG_MODIFIER;
443 return True;
444 } else if (key_lang && /* lang */
445 strcmp(env_lang, key_lang) == 0 &&
446 *current_level < MATCH_LANG) {
447 *current_level = MATCH_LANG;
448 return True;
449 } else {
450 return False;
454 /* LC_MESSAGES value: lang */
455 if (env_lang) { /* lang */
456 if (key_lang &&
457 strcmp(env_lang, key_lang) == 0 &&
458 *current_level < MATCH_LANG) {
459 *current_level = MATCH_LANG;
460 return True;
461 } else {
462 return False;
466 /* MATCH_DEFAULT is handled in getLocalizedStringValue */
468 return False;