News: described the new features for future 0.95.6 release
[wmaker-crm.git] / util / wmmenugen_parse_xdg.c
blobf5cdb58e9a27a107e390a117d47be59c3896aac9
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
25 * http://standards.freedesktop.org/menu-spec/menu-spec-1.1.html
27 * We will only deal with Type == "Application" entries in [Desktop Entry]
28 * groups. Since there is no passing of file name arguments or anything of
29 * the sort to applications from the menu, execname is determined as follows:
30 * - If `TryExec' is present, use that;
31 * - else use `Exec' with any switches stripped
33 * Only the (first, though there should not be more than one) `Main Category'
34 * is used to place the entry in a submenu.
36 * Basic validation of the .desktop file is done.
39 #include <sys/types.h>
40 #include <sys/stat.h>
42 #include <ctype.h>
43 #include <ftw.h>
44 #if DEBUG
45 #include <errno.h>
46 #endif
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
51 #include "wmmenugen.h"
53 /* LocaleString match levels */
54 enum {
55 MATCH_DEFAULT,
56 MATCH_LANG,
57 MATCH_LANG_MODIFIER,
58 MATCH_LANG_COUNTRY,
59 MATCH_LANG_COUNTRY_MODIFIER
62 typedef struct {
63 char *Name; /* Name */ /* localestring */
64 int MatchLevel; /* LocaleString match type */ /* int */
65 char *TryExec; /* TryExec */ /* string */
66 char *Exec; /* Exec */ /* string */
67 char *Path; /* Path */ /* string */
68 int Flags; /* Flags */
69 char *Category; /* Categories (first item only) */ /* string */
70 } XDGMenuEntry;
72 static void getKey(char **target, const char *line);
73 static void getStringValue(char **target, const char *line);
74 static void getLocalizedStringValue(char **target, const char *line, int *match_level);
75 static int getBooleanValue(const char *line);
76 static void getMenuHierarchyFor(char **xdgmenuspec);
77 static int compare_matchlevel(int *current_level, const char *found_locale);
78 static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wmentry);
79 static void init_xdg_storage(XDGMenuEntry **xdg);
80 static void init_wm_storage(WMMenuEntry **wm);
82 void parse_xdg(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry))
84 FILE *fp;
85 char buf[1024];
86 char *p, *tmp, *key;
87 WMMenuEntry *wm;
88 XDGMenuEntry *xdg;
89 int InGroup;
91 fp = fopen(file, "r");
92 if (!fp) {
93 #if DEBUG
94 fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno));
95 #endif
96 return;
99 xdg = (XDGMenuEntry *)wmalloc(sizeof(XDGMenuEntry));
100 wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry));
101 InGroup = 0;
102 memset(buf, 0, sizeof(buf));
104 while (fgets(buf, sizeof(buf), fp)) {
106 p = buf;
108 /* skip whitespaces */
109 while (isspace(*p))
110 p++;
111 /* skip comments, empty lines */
112 if (*p == '\r' || *p == '\n' || *p == '#') {
113 memset(buf, 0, sizeof(buf));
114 continue;
116 /* trim crlf */
117 buf[strcspn(buf, "\r\n")] = '\0';
118 if (strlen(buf) == 0)
119 continue;
121 if (strcmp(p, "[Desktop Entry]") == 0) {
122 /* if currently processing a group, we've just hit the
123 * end of its definition, try processing it
125 if (InGroup && xdg_to_wm(&xdg, &wm)) {
126 (*addWMMenuEntryCallback)(wm);
128 init_xdg_storage(&xdg);
129 init_wm_storage(&wm);
130 InGroup = 1;
131 /* start processing group */
132 memset(buf, 0, sizeof(buf));
133 continue;
136 if (!InGroup) {
137 memset(buf, 0, sizeof(buf));
138 continue;
141 getKey(&key, p);
142 if (key == NULL) { /* not `key' = `value' */
143 memset(buf, 0, sizeof(buf));
144 continue;
147 if (strcmp(key, "Type") == 0) {
148 getStringValue(&tmp, p);
149 if (strcmp(tmp, "Application") != 0)
150 InGroup = 0; /* if not application, skip current group */
151 wfree(tmp);
152 tmp = NULL;
153 } else if (strcmp(key, "Name") == 0) {
154 getLocalizedStringValue(&xdg->Name, p, &xdg->MatchLevel);
155 } else if (strcmp(key, "NoDisplay") == 0) {
156 if (getBooleanValue(p)) /* if nodisplay, skip current group */
157 InGroup = 0;
158 } else if (strcmp(key, "Hidden") == 0) {
159 if (getBooleanValue(p))
160 InGroup = 0; /* if hidden, skip current group */
161 } else if (strcmp(key, "TryExec") == 0) {
162 getStringValue(&xdg->TryExec, p);
163 } else if (strcmp(key, "Exec") == 0) {
164 getStringValue(&xdg->Exec, p);
165 } else if (strcmp(key, "Path") == 0) {
166 getStringValue(&xdg->Path, p);
167 } else if (strcmp(key, "Terminal") == 0) {
168 if (getBooleanValue(p))
169 xdg->Flags |= F_TERMINAL;
170 } else if (strcmp(key, "Categories") == 0) {
171 getStringValue(&xdg->Category, p);
172 getMenuHierarchyFor(&xdg->Category);
175 wfree(key);
176 key = NULL;
179 fclose(fp);
181 /* at the end of the file, might as well try to menuize what we have
182 * unless there was no group at all or it was marked as hidden
184 if (InGroup && 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 char *p;
196 /* Exec or TryExec is mandatory */
197 if (!((*xdg)->Exec || (*xdg)->TryExec))
198 return False;
200 /* if there's no Name, use the first word of Exec or TryExec
202 if ((*xdg)->Name) {
203 (*wm)->Name = (*xdg)->Name;
204 } else {
205 if ((*xdg)->Exec)
206 (*wm)->Name = wstrdup((*xdg)->Exec);
207 else /* (*xdg)->TryExec */
208 (*wm)->Name = wstrdup((*xdg)->TryExec);
210 p = strchr((*wm)->Name, ' ');
211 if (p)
212 *p = '\0';
215 if ((*xdg)->Exec)
216 (*wm)->CmdLine = (*xdg)->Exec;
217 else /* (*xdg)->TryExec */
218 (*wm)->CmdLine = (*xdg)->TryExec;
220 (*wm)->SubMenu = (*xdg)->Category;
221 (*wm)->Flags = (*xdg)->Flags;
223 return True;
226 /* (re-)initialize a XDGMenuEntry storage
228 static void init_xdg_storage(XDGMenuEntry **xdg)
231 if ((*xdg)->Name)
232 wfree((*xdg)->Name);
233 if ((*xdg)->TryExec)
234 wfree((*xdg)->TryExec);
235 if ((*xdg)->Exec)
236 wfree((*xdg)->Exec);
237 if ((*xdg)->Category)
238 wfree((*xdg)->Category);
239 if ((*xdg)->Path)
240 wfree((*xdg)->Path);
242 (*xdg)->Name = NULL;
243 (*xdg)->TryExec = NULL;
244 (*xdg)->Exec = NULL;
245 (*xdg)->Category = NULL;
246 (*xdg)->Path = NULL;
247 (*xdg)->Flags = 0;
248 (*xdg)->MatchLevel = -1;
251 /* (re-)initialize a WMMenuEntry storage
253 static void init_wm_storage(WMMenuEntry **wm)
255 (*wm)->Name = NULL;
256 (*wm)->CmdLine = NULL;
257 (*wm)->Flags = 0;
260 /* get a key from line. allocates target, which must be wfreed later */
261 static void getKey(char **target, const char *line)
263 const char *p;
264 int kstart, kend;
266 p = line;
268 if (strchr(p, '=') == NULL) { /* not `key' = `value' */
269 *target = NULL;
270 return;
273 kstart = 0;
275 /* skip whitespace */
276 while (isspace(*(p + kstart)))
277 kstart++;
279 /* skip up until first whitespace or '[' (localestring) or '=' */
280 kend = kstart + 1;
281 while (*(p + kend) && !isspace(*(p + kend)) && *(p + kend) != '=' && *(p + kend) != '[')
282 kend++;
284 *target = wstrndup(p + kstart, kend - kstart);
287 /* get a string value from line. allocates target, which must be wfreed later. */
288 static void getStringValue(char **target, const char *line)
290 const char *p;
291 int kstart;
293 p = line;
294 kstart = 0;
296 /* skip until after '=' */
297 while (*(p + kstart) && *(p + kstart) != '=')
298 kstart++;
299 kstart++;
301 /* skip whitespace */
302 while (*(p + kstart) && isspace(*(p + kstart)))
303 kstart++;
305 *target = wstrdup(p + kstart);
308 /* get a localized string value from line. allocates target, which must be wfreed later.
309 * matching is dependent on the current value of target as well as on the
310 * level the current value is matched on. guts matching algorithm is in
311 * compare_matchlevel().
313 static void getLocalizedStringValue(char **target, const char *line, int *match_level)
315 const char *p;
316 char *locale;
317 int kstart;
318 int sqbstart, sqbend;
320 p = line;
321 kstart = 0;
322 sqbstart = 0;
323 sqbend = 0;
324 locale = NULL;
326 /* skip until after '=', mark if '[' and ']' is found */
327 while (*(p + kstart) && *(p + kstart) != '=') {
328 switch (*(p + kstart)) {
329 case '[': sqbstart = kstart + 1;break;
330 case ']': sqbend = kstart; break;
331 default : break;
333 kstart++;
335 kstart++;
337 /* skip whitespace */
338 while (isspace(*(p + kstart)))
339 kstart++;
341 if (sqbstart > 0 && sqbend > sqbstart)
342 locale = wstrndup(p + sqbstart, sqbend - sqbstart);
344 /* if there is no value yet and this is the default key, return */
345 if (!*target && !locale) {
346 *match_level = MATCH_DEFAULT;
347 *target = wstrdup(p + kstart);
348 return;
351 if (compare_matchlevel(match_level, locale)) {
352 wfree(locale);
353 *target = wstrdup(p + kstart);
354 return;
357 return;
360 /* get a boolean value from line */
361 static Bool getBooleanValue(const char *line)
363 char *p;
364 int ret;
366 ret = 0;
367 getStringValue(&p, line);
368 ret = strcmp(p, "true") == 0 ? True : False;
369 wfree(p);
371 return ret;
374 /* perform locale matching by implementing the algorithm specified in
375 * xdg desktop entry specification, section "localized values for keys".
377 static Bool compare_matchlevel(int *current_level, const char *found_locale)
379 /* current key locale */
380 char *key_lang, *key_ctry, *key_enc, *key_mod;
382 parse_locale(found_locale, &key_lang, &key_ctry, &key_enc, &key_mod);
384 if (env_lang && key_lang && /* Shortcut: if key and env languages don't match, */
385 strcmp(env_lang, key_lang) != 0) /* don't even bother. This takes care of the great */
386 return False; /* majority of the cases without having to go through */
387 /* the more theoretical parts of the spec'd algo. */
389 if (!env_mod && key_mod) /* If LC_MESSAGES does not have a MODIFIER field, */
390 return False; /* then no key with a modifier will be matched. */
392 if (!env_ctry && key_ctry) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */
393 return False; /* then no key with a country specified will be matched. */
395 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
396 if (env_lang && env_ctry && env_mod) { /* lang_COUNTRY@MODIFIER */
397 if (key_lang && key_ctry && key_mod &&
398 strcmp(env_lang, key_lang) == 0 &&
399 strcmp(env_ctry, key_ctry) == 0 &&
400 strcmp(env_mod, key_mod) == 0) {
401 *current_level = MATCH_LANG_COUNTRY_MODIFIER;
402 return True;
403 } else if (key_lang && key_ctry && /* lang_COUNTRY */
404 strcmp(env_lang, key_lang) == 0 &&
405 strcmp(env_ctry, key_ctry) == 0 &&
406 *current_level < MATCH_LANG_COUNTRY) {
407 *current_level = MATCH_LANG_COUNTRY;
408 return True;
409 } else if (key_lang && key_mod && /* lang@MODIFIER */
410 strcmp(env_lang, key_lang) == 0 &&
411 strcmp(env_mod, key_mod) == 0 &&
412 *current_level < MATCH_LANG_MODIFIER) {
413 *current_level = MATCH_LANG_MODIFIER;
414 return True;
415 } else if (key_lang && /* lang */
416 strcmp(env_lang, key_lang) == 0 &&
417 *current_level < MATCH_LANG) {
418 *current_level = MATCH_LANG;
419 return True;
420 } else {
421 return False;
425 /* LC_MESSAGES value: lang_COUNTRY */
426 if (env_lang && env_ctry) { /* lang_COUNTRY */
427 if (key_lang && key_ctry &&
428 strcmp(env_lang, key_lang) == 0 &&
429 strcmp(env_ctry, key_ctry) == 0 &&
430 *current_level < MATCH_LANG_COUNTRY) {
431 *current_level = MATCH_LANG_COUNTRY;
432 return True;
433 } else if (key_lang && /* lang */
434 strcmp(env_lang, key_lang) == 0 &&
435 *current_level < MATCH_LANG) {
436 *current_level = MATCH_LANG;
437 return True;
438 } else {
439 return False;
443 /* LC_MESSAGES value: lang@MODIFIER */
444 if (env_lang && env_mod) { /* lang@MODIFIER */
445 if (key_lang && key_mod &&
446 strcmp(env_lang, key_lang) == 0 &&
447 strcmp(env_mod, key_mod) == 0 &&
448 *current_level < MATCH_LANG_MODIFIER) {
449 *current_level = MATCH_LANG_MODIFIER;
450 return True;
451 } else if (key_lang && /* lang */
452 strcmp(env_lang, key_lang) == 0 &&
453 *current_level < MATCH_LANG) {
454 *current_level = MATCH_LANG;
455 return True;
456 } else {
457 return False;
461 /* LC_MESSAGES value: lang */
462 if (env_lang) { /* lang */
463 if (key_lang &&
464 strcmp(env_lang, key_lang) == 0 &&
465 *current_level < MATCH_LANG) {
466 *current_level = MATCH_LANG;
467 return True;
468 } else {
469 return False;
473 /* MATCH_DEFAULT is handled in getLocalizedStringValue */
475 return False;
478 /* get the (first) xdg main category from a list of categories
480 static void getMenuHierarchyFor(char **xdgmenuspec)
482 char *category, *p;
483 char buf[1024];
485 if (!*xdgmenuspec || !**xdgmenuspec)
486 return;
488 category = wstrdup(*xdgmenuspec);
489 wfree(*xdgmenuspec);
490 memset(buf, 0, sizeof(buf));
492 p = strtok(category, ";");
493 while (p) { /* get a known category */
494 if (strcmp(p, "AudioVideo") == 0) {
495 snprintf(buf, sizeof(buf), "%s", _("Audio & Video"));
496 break;
497 } else if (strcmp(p, "Audio") == 0) {
498 snprintf(buf, sizeof(buf), "%s", _("Audio"));
499 break;
500 } else if (strcmp(p, "Video") == 0) {
501 snprintf(buf, sizeof(buf), "%s", _("Video"));
502 break;
503 } else if (strcmp(p, "Development") == 0) {
504 snprintf(buf, sizeof(buf), "%s", _("Development"));
505 break;
506 } else if (strcmp(p, "Education") == 0) {
507 snprintf(buf, sizeof(buf), "%s", _("Education"));
508 break;
509 } else if (strcmp(p, "Game") == 0) {
510 snprintf(buf, sizeof(buf), "%s", _("Game"));
511 break;
512 } else if (strcmp(p, "Graphics") == 0) {
513 snprintf(buf, sizeof(buf), "%s", _("Graphics"));
514 break;
515 } else if (strcmp(p, "Network") == 0) {
516 snprintf(buf, sizeof(buf), "%s", _("Network"));
517 break;
518 } else if (strcmp(p, "Office") == 0) {
519 snprintf(buf, sizeof(buf), "%s", _("Office"));
520 break;
521 } else if (strcmp(p, "Settings") == 0) {
522 snprintf(buf, sizeof(buf), "%s", _("Settings"));
523 break;
524 } else if (strcmp(p, "System") == 0) {
525 snprintf(buf, sizeof(buf), "%s", _("System"));
526 break;
527 } else if (strcmp(p, "Utility") == 0) {
528 snprintf(buf, sizeof(buf), "%s", _("Utility"));
529 break;
531 p = strtok(NULL, ";");
535 if (!*buf) /* come up with something if nothing found */
536 snprintf(buf, sizeof(buf), "%s", _("Applications"));
538 *xdgmenuspec = wstrdup(buf);