WPrefs: Hot corner preferences
[wmaker-crm.git] / util / wmmenugen_parse_xdg.c
blob826ce4451dd6102bd84cbdbb37380f9e45366045
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 char *parse_xdg_exec(char *exec);
80 static void init_xdg_storage(XDGMenuEntry *xdg);
81 static void init_wm_storage(WMMenuEntry *wm);
84 void parse_xdg(const char *file, cb_add_menu_entry *addWMMenuEntryCallback)
86 FILE *fp;
87 char buf[1024];
88 char *p, *tmp, *key;
89 WMMenuEntry *wm;
90 XDGMenuEntry *xdg;
91 int InGroup;
93 fp = fopen(file, "r");
94 if (!fp) {
95 #if DEBUG
96 fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno));
97 #endif
98 return;
101 xdg = (XDGMenuEntry *)wmalloc(sizeof(XDGMenuEntry));
102 wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry));
103 InGroup = 0;
104 memset(buf, 0, sizeof(buf));
106 while (fgets(buf, sizeof(buf), fp)) {
108 p = buf;
110 /* skip whitespaces */
111 while (isspace(*p))
112 p++;
113 /* skip comments, empty lines */
114 if (*p == '\r' || *p == '\n' || *p == '#') {
115 memset(buf, 0, sizeof(buf));
116 continue;
118 /* trim crlf */
119 buf[strcspn(buf, "\r\n")] = '\0';
120 if (strlen(buf) == 0)
121 continue;
123 if (strcmp(p, "[Desktop Entry]") == 0) {
124 /* if currently processing a group, we've just hit the
125 * end of its definition, try processing it
127 if (InGroup && xdg_to_wm(xdg, wm)) {
128 (*addWMMenuEntryCallback)(wm);
130 init_xdg_storage(xdg);
131 init_wm_storage(wm);
132 InGroup = 1;
133 /* start processing group */
134 memset(buf, 0, sizeof(buf));
135 continue;
136 } else if (p[0] == '[') {
137 /* If we find a new group and the previous group was the main one,
138 * we stop all further processing
140 if (InGroup) break;
143 if (!InGroup) {
144 memset(buf, 0, sizeof(buf));
145 continue;
148 getKey(&key, p);
149 if (key == NULL) { /* not `key' = `value' */
150 memset(buf, 0, sizeof(buf));
151 continue;
154 if (strcmp(key, "Type") == 0) {
155 getStringValue(&tmp, p);
156 if (strcmp(tmp, "Application") != 0)
157 InGroup = 0; /* if not application, skip current group */
158 wfree(tmp);
159 tmp = NULL;
160 } else if (strcmp(key, "Name") == 0) {
161 getLocalizedStringValue(&xdg->Name, p, &xdg->MatchLevel);
162 } else if (strcmp(key, "NoDisplay") == 0) {
163 if (getBooleanValue(p)) /* if nodisplay, skip current group */
164 InGroup = 0;
165 } else if (strcmp(key, "Hidden") == 0) {
166 if (getBooleanValue(p))
167 InGroup = 0; /* if hidden, skip current group */
168 } else if (strcmp(key, "TryExec") == 0) {
169 getStringValue(&xdg->TryExec, p);
170 } else if (strcmp(key, "Exec") == 0) {
171 getStringValue(&xdg->Exec, p);
172 } else if (strcmp(key, "Path") == 0) {
173 getStringValue(&xdg->Path, p);
174 } else if (strcmp(key, "Terminal") == 0) {
175 if (getBooleanValue(p))
176 xdg->Flags |= F_TERMINAL;
177 } else if (strcmp(key, "Categories") == 0) {
178 getStringValue(&xdg->Category, p);
179 getMenuHierarchyFor(&xdg->Category);
182 if (xdg->Category == NULL)
183 xdg->Category = wstrdup(_("Other"));
185 wfree(key);
186 key = NULL;
189 fclose(fp);
191 /* at the end of the file, might as well try to menuize what we have
192 * unless there was no group at all or it was marked as hidden
194 if (InGroup && xdg_to_wm(xdg, wm))
195 (*addWMMenuEntryCallback)(wm);
200 /* coerce an xdg entry type into a wm entry type
202 static Bool xdg_to_wm(XDGMenuEntry *xdg, WMMenuEntry *wm)
204 char *p;
206 /* Exec or TryExec is mandatory */
207 if (!(xdg->Exec || xdg->TryExec))
208 return False;
210 /* if there's no Name, use the first word of Exec or TryExec
212 if (xdg->Name) {
213 wm->Name = xdg->Name;
214 } else {
215 if (xdg->TryExec)
216 wm->Name = wstrdup(xdg->TryExec);
217 else /* xdg->Exec */
218 wm->Name = wstrdup(xdg->Exec);
220 p = strchr(wm->Name, ' ');
221 if (p)
222 *p = '\0';
225 if (xdg->TryExec)
226 wm->CmdLine = xdg->TryExec;
227 else { /* xdg->Exec */
228 wm->CmdLine = parse_xdg_exec(xdg->Exec);
229 if (!wm->CmdLine)
230 return False;
233 wm->SubMenu = xdg->Category;
234 wm->Flags = xdg->Flags;
235 if (wm->CmdLine != xdg->TryExec)
236 wm->Flags |= F_FREE_CMD_LINE;
238 return True;
241 static char *parse_xdg_exec(char *exec)
243 char *cmd_line, *dst, *src;
244 Bool quoted = False;
246 cmd_line = wstrdup(exec);
248 for (dst = src = cmd_line; *src; src++) {
249 if (quoted) {
250 if (*src == '"')
251 quoted = False;
252 else if (*src == '\\')
253 switch (*++src) {
254 case '"':
255 case '`':
256 case '$':
257 case '\\':
258 *dst++ = *src;
259 break;
260 default:
261 goto err_out;
263 else
264 *dst++ = *src;
265 } else {
266 if (*src == '"')
267 quoted = True;
268 else if (*src == '%') {
269 src++;
270 if (*src == '%')
271 *dst++ = *src;
272 else if (strchr ("fFuUdDnNickvm", *src))
274 * Skip valid field-code.
277 else
279 * Invalid field-code.
281 goto err_out;
282 } else
283 *dst++ = *src;
287 if (quoted)
288 goto err_out;
291 *dst = '\0';
292 while (dst > cmd_line && isspace(*--dst));
294 return cmd_line;
296 err_out:
297 wfree(cmd_line);
298 return NULL;
301 /* (re-)initialize a XDGMenuEntry storage
303 static void init_xdg_storage(XDGMenuEntry *xdg)
306 if (xdg->Name)
307 wfree(xdg->Name);
308 if (xdg->TryExec)
309 wfree(xdg->TryExec);
310 if (xdg->Exec)
311 wfree(xdg->Exec);
312 if (xdg->Category)
313 wfree(xdg->Category);
314 if (xdg->Path)
315 wfree(xdg->Path);
317 xdg->Name = NULL;
318 xdg->TryExec = NULL;
319 xdg->Exec = NULL;
320 xdg->Category = NULL;
321 xdg->Path = NULL;
322 xdg->Flags = 0;
323 xdg->MatchLevel = -1;
326 /* (re-)initialize a WMMenuEntry storage
328 static void init_wm_storage(WMMenuEntry *wm)
330 if (wm->Flags & F_FREE_CMD_LINE)
331 wfree(wm->CmdLine);
333 wm->Name = NULL;
334 wm->CmdLine = NULL;
335 wm->Flags = 0;
338 /* get a key from line. allocates target, which must be wfreed later */
339 static void getKey(char **target, const char *line)
341 const char *p;
342 int kstart, kend;
344 p = line;
346 if (strchr(p, '=') == NULL) { /* not `key' = `value' */
347 *target = NULL;
348 return;
351 kstart = 0;
353 /* skip whitespace */
354 while (isspace(*(p + kstart)))
355 kstart++;
357 /* skip up until first whitespace or '[' (localestring) or '=' */
358 kend = kstart + 1;
359 while (*(p + kend) && !isspace(*(p + kend)) && *(p + kend) != '=' && *(p + kend) != '[')
360 kend++;
362 *target = wstrndup(p + kstart, kend - kstart);
365 /* get a string value from line. allocates target, which must be wfreed later. */
366 static void getStringValue(char **target, const char *line)
368 const char *p;
369 char *q;
370 int kstart;
372 p = line;
373 kstart = 0;
375 /* skip until after '=' */
376 while (*(p + kstart) && *(p + kstart) != '=')
377 kstart++;
378 kstart++;
380 /* skip whitespace */
381 while (*(p + kstart) && isspace(*(p + kstart)))
382 kstart++;
384 *target = wstrdup(p + kstart);
386 for (p = q = *target; *p; p++) {
387 if (*p != '\\') {
388 *q++ = *p;
389 } else {
390 switch (*++p) {
391 case 's': *q++ = ' '; break;
392 case 'n': *q++ = '\n'; break;
393 case 't': *q++ = '\t'; break;
394 case 'r': *q++ = '\r'; break;
395 case '\\': *q++ = '\\'; break;
396 default:
398 * Skip invalid escape.
400 break;
404 *q = '\0';
407 /* get a localized string value from line. allocates target, which must be wfreed later.
408 * matching is dependent on the current value of target as well as on the
409 * level the current value is matched on. guts matching algorithm is in
410 * compare_matchlevel().
412 static void getLocalizedStringValue(char **target, const char *line, int *match_level)
414 const char *p;
415 char *locale;
416 int kstart;
417 int sqbstart, sqbend;
419 p = line;
420 kstart = 0;
421 sqbstart = 0;
422 sqbend = 0;
423 locale = NULL;
425 /* skip until after '=', mark if '[' and ']' is found */
426 while (*(p + kstart) && *(p + kstart) != '=') {
427 switch (*(p + kstart)) {
428 case '[': sqbstart = kstart + 1;break;
429 case ']': sqbend = kstart; break;
430 default : break;
432 kstart++;
434 kstart++;
436 /* skip whitespace */
437 while (isspace(*(p + kstart)))
438 kstart++;
440 if (sqbstart > 0 && sqbend > sqbstart)
441 locale = wstrndup(p + sqbstart, sqbend - sqbstart);
443 /* if there is no value yet and this is the default key, return */
444 if (!*target && !locale) {
445 *match_level = MATCH_DEFAULT;
446 *target = wstrdup(p + kstart);
447 return;
450 if (compare_matchlevel(match_level, locale)) {
451 *target = wstrdup(p + kstart);
454 wfree(locale);
456 return;
459 /* get a boolean value from line */
460 static Bool getBooleanValue(const char *line)
462 char *p;
463 int ret;
465 getStringValue(&p, line);
466 ret = strcmp(p, "true") == 0 ? True : False;
467 wfree(p);
469 return ret;
472 /* perform locale matching by implementing the algorithm specified in
473 * xdg desktop entry specification, section "localized values for keys".
475 static Bool compare_matchlevel(int *current_level, const char *found_locale)
477 /* current key locale */
478 char *key_lang, *key_ctry, *key_enc, *key_mod;
480 parse_locale(found_locale, &key_lang, &key_ctry, &key_enc, &key_mod);
482 if (env_lang && key_lang && strcmp(env_lang, key_lang) != 0) {
484 * Shortcut: if key and env languages don't match,
485 * don't even bother. This takes care of the great
486 * majority of the cases without having to go through
487 * the more theoretical parts of the spec'd algo.
489 wfree(key_lang);
490 wfree(key_ctry);
491 wfree(key_enc);
492 wfree(key_mod);
493 return False;
496 if (!env_mod && key_mod) {
498 * If LC_MESSAGES does not have a MODIFIER field,
499 * then no key with a modifier will be matched.
501 wfree(key_lang);
502 wfree(key_ctry);
503 wfree(key_enc);
504 wfree(key_mod);
505 return False;
508 if (!env_ctry && key_ctry) {
510 * Similarly, if LC_MESSAGES does not have a COUNTRY field,
511 * then no key with a country specified will be matched.
513 wfree(key_lang);
514 wfree(key_ctry);
515 wfree(key_enc);
516 wfree(key_mod);
517 return False;
520 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
521 if (env_lang && env_ctry && env_mod) { /* lang_COUNTRY@MODIFIER */
522 if (key_lang && key_ctry && key_mod &&
523 strcmp(env_lang, key_lang) == 0 &&
524 strcmp(env_ctry, key_ctry) == 0 &&
525 strcmp(env_mod, key_mod) == 0) {
526 *current_level = MATCH_LANG_COUNTRY_MODIFIER;
527 wfree(key_lang);
528 wfree(key_ctry);
529 wfree(key_enc);
530 wfree(key_mod);
531 return True;
532 } else if (key_lang && key_ctry && /* lang_COUNTRY */
533 strcmp(env_lang, key_lang) == 0 &&
534 strcmp(env_ctry, key_ctry) == 0 &&
535 *current_level < MATCH_LANG_COUNTRY) {
536 *current_level = MATCH_LANG_COUNTRY;
537 wfree(key_lang);
538 wfree(key_ctry);
539 wfree(key_enc);
540 wfree(key_mod);
541 return True;
542 } else if (key_lang && key_mod && /* lang@MODIFIER */
543 strcmp(env_lang, key_lang) == 0 &&
544 strcmp(env_mod, key_mod) == 0 &&
545 *current_level < MATCH_LANG_MODIFIER) {
546 *current_level = MATCH_LANG_MODIFIER;
547 wfree(key_lang);
548 wfree(key_ctry);
549 wfree(key_enc);
550 wfree(key_mod);
551 return True;
552 } else if (key_lang && /* lang */
553 strcmp(env_lang, key_lang) == 0 &&
554 *current_level < MATCH_LANG) {
555 *current_level = MATCH_LANG;
556 wfree(key_lang);
557 wfree(key_ctry);
558 wfree(key_enc);
559 wfree(key_mod);
560 return True;
561 } else {
562 wfree(key_lang);
563 wfree(key_ctry);
564 wfree(key_enc);
565 wfree(key_mod);
566 return False;
570 /* LC_MESSAGES value: lang_COUNTRY */
571 if (env_lang && env_ctry) { /* lang_COUNTRY */
572 if (key_lang && key_ctry &&
573 strcmp(env_lang, key_lang) == 0 &&
574 strcmp(env_ctry, key_ctry) == 0 &&
575 *current_level < MATCH_LANG_COUNTRY) {
576 *current_level = MATCH_LANG_COUNTRY;
577 wfree(key_lang);
578 wfree(key_ctry);
579 wfree(key_enc);
580 wfree(key_mod);
581 return True;
582 } else if (key_lang && /* lang */
583 strcmp(env_lang, key_lang) == 0 &&
584 *current_level < MATCH_LANG) {
585 *current_level = MATCH_LANG;
586 wfree(key_lang);
587 wfree(key_ctry);
588 wfree(key_enc);
589 wfree(key_mod);
590 return True;
591 } else {
592 wfree(key_lang);
593 wfree(key_ctry);
594 wfree(key_enc);
595 wfree(key_mod);
596 return False;
600 /* LC_MESSAGES value: lang@MODIFIER */
601 if (env_lang && env_mod) { /* lang@MODIFIER */
602 if (key_lang && key_mod &&
603 strcmp(env_lang, key_lang) == 0 &&
604 strcmp(env_mod, key_mod) == 0 &&
605 *current_level < MATCH_LANG_MODIFIER) {
606 *current_level = MATCH_LANG_MODIFIER;
607 wfree(key_lang);
608 wfree(key_ctry);
609 wfree(key_enc);
610 wfree(key_mod);
611 return True;
612 } else if (key_lang && /* lang */
613 strcmp(env_lang, key_lang) == 0 &&
614 *current_level < MATCH_LANG) {
615 *current_level = MATCH_LANG;
616 wfree(key_lang);
617 wfree(key_ctry);
618 wfree(key_enc);
619 wfree(key_mod);
620 return True;
621 } else {
622 wfree(key_lang);
623 wfree(key_ctry);
624 wfree(key_enc);
625 wfree(key_mod);
626 return False;
630 /* LC_MESSAGES value: lang */
631 if (env_lang) { /* lang */
632 if (key_lang &&
633 strcmp(env_lang, key_lang) == 0 &&
634 *current_level < MATCH_LANG) {
635 *current_level = MATCH_LANG;
636 wfree(key_lang);
637 wfree(key_ctry);
638 wfree(key_enc);
639 wfree(key_mod);
640 return True;
641 } else {
642 wfree(key_lang);
643 wfree(key_ctry);
644 wfree(key_enc);
645 wfree(key_mod);
646 return False;
650 /* MATCH_DEFAULT is handled in getLocalizedStringValue */
652 wfree(key_lang);
653 wfree(key_ctry);
654 wfree(key_enc);
655 wfree(key_mod);
656 return False;
659 /* get the (first) xdg main category from a list of categories
661 static void getMenuHierarchyFor(char **xdgmenuspec)
663 char *category, *p;
664 char buf[1024];
666 if (!*xdgmenuspec || !**xdgmenuspec)
667 return;
669 category = wstrdup(*xdgmenuspec);
670 wfree(*xdgmenuspec);
671 memset(buf, 0, sizeof(buf));
673 p = strtok(category, ";");
674 while (p) { /* get a known category */
675 if (strcmp(p, "AudioVideo") == 0) {
676 snprintf(buf, sizeof(buf), "%s", _("Audio & Video"));
677 break;
678 } else if (strcmp(p, "Audio") == 0) {
679 snprintf(buf, sizeof(buf), "%s", _("Audio"));
680 break;
681 } else if (strcmp(p, "Video") == 0) {
682 snprintf(buf, sizeof(buf), "%s", _("Video"));
683 break;
684 } else if (strcmp(p, "Development") == 0) {
685 snprintf(buf, sizeof(buf), "%s", _("Development"));
686 break;
687 } else if (strcmp(p, "Education") == 0) {
688 snprintf(buf, sizeof(buf), "%s", _("Education"));
689 break;
690 } else if (strcmp(p, "Game") == 0) {
691 snprintf(buf, sizeof(buf), "%s", _("Game"));
692 break;
693 } else if (strcmp(p, "Graphics") == 0) {
694 snprintf(buf, sizeof(buf), "%s", _("Graphics"));
695 break;
696 } else if (strcmp(p, "Network") == 0) {
697 snprintf(buf, sizeof(buf), "%s", _("Network"));
698 break;
699 } else if (strcmp(p, "Office") == 0) {
700 snprintf(buf, sizeof(buf), "%s", _("Office"));
701 break;
702 } else if (strcmp(p, "Science") == 0) {
703 snprintf(buf, sizeof(buf), "%s", _("Science"));
704 break;
705 } else if (strcmp(p, "Settings") == 0) {
706 snprintf(buf, sizeof(buf), "%s", _("Settings"));
707 break;
708 } else if (strcmp(p, "System") == 0) {
709 snprintf(buf, sizeof(buf), "%s", _("System"));
710 break;
711 } else if (strcmp(p, "Utility") == 0) {
712 snprintf(buf, sizeof(buf), "%s", _("Utility"));
713 break;
714 /* reserved categories */
715 } else if (strcmp(p, "Screensaver") == 0) {
716 snprintf(buf, sizeof(buf), "%s", _("Screensaver"));
717 break;
718 } else if (strcmp(p, "TrayIcon") == 0) {
719 snprintf(buf, sizeof(buf), "%s", _("Tray Icon"));
720 break;
721 } else if (strcmp(p, "Applet") == 0) {
722 snprintf(buf, sizeof(buf), "%s", _("Applet"));
723 break;
724 } else if (strcmp(p, "Shell") == 0) {
725 snprintf(buf, sizeof(buf), "%s", _("Shell"));
726 break;
728 p = strtok(NULL, ";");
731 wfree(category);
733 if (!*buf) /* come up with something if nothing found */
734 snprintf(buf, sizeof(buf), "%s", _("Other"));
736 *xdgmenuspec = wstrdup(buf);