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>
51 #include "wmmenugen.h"
53 /* LocaleString match levels */
59 MATCH_LANG_COUNTRY_MODIFIER
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 */
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
);
83 void parse_xdg(const char *file
, cb_add_menu_entry
*addWMMenuEntryCallback
)
92 fp
= fopen(file
, "r");
95 fprintf(stderr
, "Error opening file %s: %s\n", file
, strerror(errno
));
100 xdg
= (XDGMenuEntry
*)wmalloc(sizeof(XDGMenuEntry
));
101 wm
= (WMMenuEntry
*)wmalloc(sizeof(WMMenuEntry
));
103 memset(buf
, 0, sizeof(buf
));
105 while (fgets(buf
, sizeof(buf
), fp
)) {
109 /* skip whitespaces */
112 /* skip comments, empty lines */
113 if (*p
== '\r' || *p
== '\n' || *p
== '#') {
114 memset(buf
, 0, sizeof(buf
));
118 buf
[strcspn(buf
, "\r\n")] = '\0';
119 if (strlen(buf
) == 0)
122 if (strcmp(p
, "[Desktop Entry]") == 0) {
123 /* if currently processing a group, we've just hit the
124 * end of its definition, try processing it
126 if (InGroup
&& xdg_to_wm(&xdg
, &wm
)) {
127 (*addWMMenuEntryCallback
)(wm
);
129 init_xdg_storage(&xdg
);
130 init_wm_storage(&wm
);
132 /* start processing group */
133 memset(buf
, 0, sizeof(buf
));
135 } else if (p
[0] == '[') {
136 /* If we find a new group and the previous group was the main one,
137 * we stop all further processing
143 memset(buf
, 0, sizeof(buf
));
148 if (key
== NULL
) { /* not `key' = `value' */
149 memset(buf
, 0, sizeof(buf
));
153 if (strcmp(key
, "Type") == 0) {
154 getStringValue(&tmp
, p
);
155 if (strcmp(tmp
, "Application") != 0)
156 InGroup
= 0; /* if not application, skip current group */
159 } else if (strcmp(key
, "Name") == 0) {
160 getLocalizedStringValue(&xdg
->Name
, p
, &xdg
->MatchLevel
);
161 } else if (strcmp(key
, "NoDisplay") == 0) {
162 if (getBooleanValue(p
)) /* if nodisplay, skip current group */
164 } else if (strcmp(key
, "Hidden") == 0) {
165 if (getBooleanValue(p
))
166 InGroup
= 0; /* if hidden, skip current group */
167 } else if (strcmp(key
, "TryExec") == 0) {
168 getStringValue(&xdg
->TryExec
, p
);
169 } else if (strcmp(key
, "Exec") == 0) {
170 getStringValue(&xdg
->Exec
, p
);
171 } else if (strcmp(key
, "Path") == 0) {
172 getStringValue(&xdg
->Path
, p
);
173 } else if (strcmp(key
, "Terminal") == 0) {
174 if (getBooleanValue(p
))
175 xdg
->Flags
|= F_TERMINAL
;
176 } else if (strcmp(key
, "Categories") == 0) {
177 getStringValue(&xdg
->Category
, p
);
178 getMenuHierarchyFor(&xdg
->Category
);
187 /* at the end of the file, might as well try to menuize what we have
188 * unless there was no group at all or it was marked as hidden
190 if (InGroup
&& xdg_to_wm(&xdg
, &wm
))
191 (*addWMMenuEntryCallback
)(wm
);
196 /* coerce an xdg entry type into a wm entry type
198 static Bool
xdg_to_wm(XDGMenuEntry
**xdg
, WMMenuEntry
**wm
)
202 /* Exec or TryExec is mandatory */
203 if (!((*xdg
)->Exec
|| (*xdg
)->TryExec
))
206 /* if there's no Name, use the first word of Exec or TryExec
209 (*wm
)->Name
= (*xdg
)->Name
;
212 (*wm
)->Name
= wstrdup((*xdg
)->Exec
);
213 else /* (*xdg)->TryExec */
214 (*wm
)->Name
= wstrdup((*xdg
)->TryExec
);
216 p
= strchr((*wm
)->Name
, ' ');
222 (*wm
)->CmdLine
= (*xdg
)->Exec
;
223 else /* (*xdg)->TryExec */
224 (*wm
)->CmdLine
= (*xdg
)->TryExec
;
226 (*wm
)->SubMenu
= (*xdg
)->Category
;
227 (*wm
)->Flags
= (*xdg
)->Flags
;
232 /* (re-)initialize a XDGMenuEntry storage
234 static void init_xdg_storage(XDGMenuEntry
**xdg
)
240 wfree((*xdg
)->TryExec
);
243 if ((*xdg
)->Category
)
244 wfree((*xdg
)->Category
);
249 (*xdg
)->TryExec
= NULL
;
251 (*xdg
)->Category
= NULL
;
254 (*xdg
)->MatchLevel
= -1;
257 /* (re-)initialize a WMMenuEntry storage
259 static void init_wm_storage(WMMenuEntry
**wm
)
262 (*wm
)->CmdLine
= NULL
;
266 /* get a key from line. allocates target, which must be wfreed later */
267 static void getKey(char **target
, const char *line
)
274 if (strchr(p
, '=') == NULL
) { /* not `key' = `value' */
281 /* skip whitespace */
282 while (isspace(*(p
+ kstart
)))
285 /* skip up until first whitespace or '[' (localestring) or '=' */
287 while (*(p
+ kend
) && !isspace(*(p
+ kend
)) && *(p
+ kend
) != '=' && *(p
+ kend
) != '[')
290 *target
= wstrndup(p
+ kstart
, kend
- kstart
);
293 /* get a string value from line. allocates target, which must be wfreed later. */
294 static void getStringValue(char **target
, const char *line
)
302 /* skip until after '=' */
303 while (*(p
+ kstart
) && *(p
+ kstart
) != '=')
307 /* skip whitespace */
308 while (*(p
+ kstart
) && isspace(*(p
+ kstart
)))
311 *target
= wstrdup(p
+ kstart
);
314 /* get a localized string value from line. allocates target, which must be wfreed later.
315 * matching is dependent on the current value of target as well as on the
316 * level the current value is matched on. guts matching algorithm is in
317 * compare_matchlevel().
319 static void getLocalizedStringValue(char **target
, const char *line
, int *match_level
)
324 int sqbstart
, sqbend
;
332 /* skip until after '=', mark if '[' and ']' is found */
333 while (*(p
+ kstart
) && *(p
+ kstart
) != '=') {
334 switch (*(p
+ kstart
)) {
335 case '[': sqbstart
= kstart
+ 1;break;
336 case ']': sqbend
= kstart
; break;
343 /* skip whitespace */
344 while (isspace(*(p
+ kstart
)))
347 if (sqbstart
> 0 && sqbend
> sqbstart
)
348 locale
= wstrndup(p
+ sqbstart
, sqbend
- sqbstart
);
350 /* if there is no value yet and this is the default key, return */
351 if (!*target
&& !locale
) {
352 *match_level
= MATCH_DEFAULT
;
353 *target
= wstrdup(p
+ kstart
);
357 if (compare_matchlevel(match_level
, locale
)) {
359 *target
= wstrdup(p
+ kstart
);
366 /* get a boolean value from line */
367 static Bool
getBooleanValue(const char *line
)
372 getStringValue(&p
, line
);
373 ret
= strcmp(p
, "true") == 0 ? True
: False
;
379 /* perform locale matching by implementing the algorithm specified in
380 * xdg desktop entry specification, section "localized values for keys".
382 static Bool
compare_matchlevel(int *current_level
, const char *found_locale
)
384 /* current key locale */
385 char *key_lang
, *key_ctry
, *key_enc
, *key_mod
;
387 parse_locale(found_locale
, &key_lang
, &key_ctry
, &key_enc
, &key_mod
);
389 if (env_lang
&& key_lang
&& /* Shortcut: if key and env languages don't match, */
390 strcmp(env_lang
, key_lang
) != 0) /* don't even bother. This takes care of the great */
391 return False
; /* majority of the cases without having to go through */
392 /* the more theoretical parts of the spec'd algo. */
394 if (!env_mod
&& key_mod
) /* If LC_MESSAGES does not have a MODIFIER field, */
395 return False
; /* then no key with a modifier will be matched. */
397 if (!env_ctry
&& key_ctry
) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */
398 return False
; /* then no key with a country specified will be matched. */
400 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
401 if (env_lang
&& env_ctry
&& env_mod
) { /* lang_COUNTRY@MODIFIER */
402 if (key_lang
&& key_ctry
&& key_mod
&&
403 strcmp(env_lang
, key_lang
) == 0 &&
404 strcmp(env_ctry
, key_ctry
) == 0 &&
405 strcmp(env_mod
, key_mod
) == 0) {
406 *current_level
= MATCH_LANG_COUNTRY_MODIFIER
;
408 } else if (key_lang
&& key_ctry
&& /* lang_COUNTRY */
409 strcmp(env_lang
, key_lang
) == 0 &&
410 strcmp(env_ctry
, key_ctry
) == 0 &&
411 *current_level
< MATCH_LANG_COUNTRY
) {
412 *current_level
= MATCH_LANG_COUNTRY
;
414 } else if (key_lang
&& key_mod
&& /* lang@MODIFIER */
415 strcmp(env_lang
, key_lang
) == 0 &&
416 strcmp(env_mod
, key_mod
) == 0 &&
417 *current_level
< MATCH_LANG_MODIFIER
) {
418 *current_level
= MATCH_LANG_MODIFIER
;
420 } else if (key_lang
&& /* lang */
421 strcmp(env_lang
, key_lang
) == 0 &&
422 *current_level
< MATCH_LANG
) {
423 *current_level
= MATCH_LANG
;
430 /* LC_MESSAGES value: lang_COUNTRY */
431 if (env_lang
&& env_ctry
) { /* lang_COUNTRY */
432 if (key_lang
&& key_ctry
&&
433 strcmp(env_lang
, key_lang
) == 0 &&
434 strcmp(env_ctry
, key_ctry
) == 0 &&
435 *current_level
< MATCH_LANG_COUNTRY
) {
436 *current_level
= MATCH_LANG_COUNTRY
;
438 } else if (key_lang
&& /* lang */
439 strcmp(env_lang
, key_lang
) == 0 &&
440 *current_level
< MATCH_LANG
) {
441 *current_level
= MATCH_LANG
;
448 /* LC_MESSAGES value: lang@MODIFIER */
449 if (env_lang
&& env_mod
) { /* lang@MODIFIER */
450 if (key_lang
&& key_mod
&&
451 strcmp(env_lang
, key_lang
) == 0 &&
452 strcmp(env_mod
, key_mod
) == 0 &&
453 *current_level
< MATCH_LANG_MODIFIER
) {
454 *current_level
= MATCH_LANG_MODIFIER
;
456 } else if (key_lang
&& /* lang */
457 strcmp(env_lang
, key_lang
) == 0 &&
458 *current_level
< MATCH_LANG
) {
459 *current_level
= MATCH_LANG
;
466 /* LC_MESSAGES value: lang */
467 if (env_lang
) { /* lang */
469 strcmp(env_lang
, key_lang
) == 0 &&
470 *current_level
< MATCH_LANG
) {
471 *current_level
= MATCH_LANG
;
478 /* MATCH_DEFAULT is handled in getLocalizedStringValue */
483 /* get the (first) xdg main category from a list of categories
485 static void getMenuHierarchyFor(char **xdgmenuspec
)
490 if (!*xdgmenuspec
|| !**xdgmenuspec
)
493 category
= wstrdup(*xdgmenuspec
);
495 memset(buf
, 0, sizeof(buf
));
497 p
= strtok(category
, ";");
498 while (p
) { /* get a known category */
499 if (strcmp(p
, "AudioVideo") == 0) {
500 snprintf(buf
, sizeof(buf
), "%s", _("Audio & Video"));
502 } else if (strcmp(p
, "Audio") == 0) {
503 snprintf(buf
, sizeof(buf
), "%s", _("Audio"));
505 } else if (strcmp(p
, "Video") == 0) {
506 snprintf(buf
, sizeof(buf
), "%s", _("Video"));
508 } else if (strcmp(p
, "Development") == 0) {
509 snprintf(buf
, sizeof(buf
), "%s", _("Development"));
511 } else if (strcmp(p
, "Education") == 0) {
512 snprintf(buf
, sizeof(buf
), "%s", _("Education"));
514 } else if (strcmp(p
, "Game") == 0) {
515 snprintf(buf
, sizeof(buf
), "%s", _("Game"));
517 } else if (strcmp(p
, "Graphics") == 0) {
518 snprintf(buf
, sizeof(buf
), "%s", _("Graphics"));
520 } else if (strcmp(p
, "Network") == 0) {
521 snprintf(buf
, sizeof(buf
), "%s", _("Network"));
523 } else if (strcmp(p
, "Office") == 0) {
524 snprintf(buf
, sizeof(buf
), "%s", _("Office"));
526 } else if (strcmp(p
, "Science") == 0) {
527 snprintf(buf
, sizeof(buf
), "%s", _("Science"));
529 } else if (strcmp(p
, "Settings") == 0) {
530 snprintf(buf
, sizeof(buf
), "%s", _("Settings"));
532 } else if (strcmp(p
, "System") == 0) {
533 snprintf(buf
, sizeof(buf
), "%s", _("System"));
535 } else if (strcmp(p
, "Utility") == 0) {
536 snprintf(buf
, sizeof(buf
), "%s", _("Utility"));
538 /* reserved categories */
539 } else if (strcmp(p
, "Screensaver") == 0) {
540 snprintf(buf
, sizeof(buf
), "%s", _("Screensaver"));
542 } else if (strcmp(p
, "TrayIcon") == 0) {
543 snprintf(buf
, sizeof(buf
), "%s", _("Tray Icon"));
545 } else if (strcmp(p
, "Applet") == 0) {
546 snprintf(buf
, sizeof(buf
), "%s", _("Applet"));
548 } else if (strcmp(p
, "Shell") == 0) {
549 snprintf(buf
, sizeof(buf
), "%s", _("Shell"));
552 p
= strtok(NULL
, ";");
556 if (!*buf
) /* come up with something if nothing found */
557 snprintf(buf
, sizeof(buf
), "%s", _("Other"));
559 *xdgmenuspec
= wstrdup(buf
);