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.
38 (_XOPEN_SOURCE && _XOPEN_SOURCE < 500) || \
40 #define _XOPEN_SOURCE 500 /* nftw */
43 #include <sys/types.h>
55 #include "wmmenugen.h"
57 /* LocaleString match levels */
63 MATCH_LANG_COUNTRY_MODIFIER
67 char *Name
; /* Name */ /* localestring */
68 int MatchLevel
; /* LocaleString match type */ /* int */
69 char *TryExec
; /* TryExec */ /* string */
70 char *Exec
; /* Exec */ /* string */
71 char *Path
; /* Path */ /* string */
72 int Flags
; /* Flags */
73 char *Category
; /* Categories (first item only) */ /* string */
76 static void getKey(char **target
, const char *line
);
77 static void getStringValue(char **target
, const char *line
);
78 static void getLocalizedStringValue(char **target
, const char *line
, int *match_level
);
79 static int getBooleanValue(const char *line
);
80 static int compare_matchlevel(int *current_level
, const char *found_locale
);
81 static Bool
xdg_to_wm(XDGMenuEntry
**xdg
, WMMenuEntry
**wmentry
);
82 static void init_xdg_storage(XDGMenuEntry
**xdg
);
83 static void init_wm_storage(WMMenuEntry
**wm
);
85 void parse_xdg(const char *file
, void (*addWMMenuEntryCallback
)(WMMenuEntry
*aEntry
))
94 fp
= fopen(file
, "r");
97 fprintf(stderr
, "Error opening file %s: %s\n", file
, strerror(errno
));
102 xdg
= (XDGMenuEntry
*)wmalloc(sizeof(XDGMenuEntry
));
103 wm
= (WMMenuEntry
*)wmalloc(sizeof(WMMenuEntry
));
105 memset(buf
, 0, sizeof(buf
));
107 while (fgets(buf
, sizeof(buf
), fp
)) {
111 /* skip whitespaces */
114 /* skip comments, empty lines */
115 if (*p
== '\r' || *p
== '\n' || *p
== '#') {
116 memset(buf
, 0, sizeof(buf
));
120 buf
[strcspn(buf
, "\r\n")] = '\0';
121 if (strlen(buf
) == 0)
124 if (strcmp(p
, "[Desktop Entry]") == 0) {
126 /* if currently processing a group, we've just hit the
127 * end of its definition, try processing it
129 if (xdg_to_wm(&xdg
, &wm
)) {
130 (*addWMMenuEntryCallback
)(wm
);
133 init_xdg_storage(&xdg
);
134 init_wm_storage(&wm
);
136 /* start processing group */
137 memset(buf
, 0, sizeof(buf
));
142 memset(buf
, 0, sizeof(buf
));
147 if (key
== NULL
) { /* not `key' = `value' */
148 memset(buf
, 0, sizeof(buf
));
152 if (strcmp(key
, "Type") == 0) {
153 getStringValue(&tmp
, p
);
154 if (strcmp(tmp
, "Application") != 0)
155 InGroup
= 0; /* if not application, skip current group */
158 } else if (strcmp(key
, "Name") == 0) {
159 getLocalizedStringValue(&xdg
->Name
, p
, &xdg
->MatchLevel
);
160 } else if (strcmp(key
, "NoDisplay") == 0) {
161 if (getBooleanValue(p
)) /* if nodisplay, skip current group */
163 } else if (strcmp(key
, "Hidden") == 0) {
164 if (getBooleanValue(p
))
165 InGroup
= 0; /* if hidden, skip current group */
166 } else if (strcmp(key
, "TryExec") == 0) {
167 getStringValue(&xdg
->TryExec
, p
);
168 } else if (strcmp(key
, "Exec") == 0) {
169 getStringValue(&xdg
->Exec
, p
);
170 } else if (strcmp(key
, "Path") == 0) {
171 getStringValue(&xdg
->Path
, p
);
172 } else if (strcmp(key
, "Terminal") == 0) {
173 if (getBooleanValue(p
))
174 xdg
->Flags
|= F_TERMINAL
;
175 } else if (strcmp(key
, "Categories") == 0) {
176 /* use only the first item */
177 getStringValue(&tmp
, p
);
179 tmp
[strcspn(tmp
, ";")] = '\0';
180 tmp
[strcspn(tmp
, "\t ")] = '\0';
181 xdg
->Category
= wstrdup(tmp
);
193 /* at the end of the file, might as well try to menuize what we have */
194 if (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
)
207 if ((*xdg
)->Exec
|| (*xdg
)->TryExec
) {
208 (*wm
)->Flags
= (*xdg
)->Flags
;
210 (*wm
)->Name
= (*xdg
)->Name
;
214 (*wm
)->Name
= (*xdg
)->Exec
;
215 (*wm
)->CmdLine
= (*xdg
)->Exec
;
217 if ((*xdg
)->TryExec
) {
219 (*wm
)->Name
= (*xdg
)->TryExec
;
221 (*wm
)->CmdLine
= (*xdg
)->TryExec
;
224 if ((*wm
)->Name
&& (*wm
)->CmdLine
)
230 /* (re-)initialize a XDGMenuEntry storage
232 static void init_xdg_storage(XDGMenuEntry
**xdg
)
238 wfree((*xdg
)->TryExec
);
241 if ((*xdg
)->Category
)
242 wfree((*xdg
)->Category
);
247 (*xdg
)->TryExec
= NULL
;
249 (*xdg
)->Category
= NULL
;
252 (*xdg
)->MatchLevel
= -1;
255 /* (re-)initialize a WMMenuEntry storage
257 static void init_wm_storage(WMMenuEntry
**wm
)
260 (*wm
)->CmdLine
= NULL
;
264 /* get a key from line. allocates target, which must be wfreed later */
265 static void getKey(char **target
, const char *line
)
272 if (strchr(p
, '=') == NULL
) { /* not `key' = `value' */
279 /* skip whitespace */
280 while (isspace(*(p
+ kstart
)))
283 /* skip up until first whitespace or '[' (localestring) or '=' */
285 while (!isspace(*(p
+ kend
)) && *(p
+ kend
) != '=' && *(p
+ kend
) != '[')
288 *target
= wstrndup(p
+ kstart
, kend
- kstart
);
291 /* get a string value from line. allocates target, which must be wfreed later. */
292 static void getStringValue(char **target
, const char *line
)
300 /* skip until after '=' */
301 while (*(p
+ kstart
) != '=')
305 /* skip whitespace */
306 while (isspace(*(p
+ kstart
)))
309 *target
= wstrdup(p
+ kstart
);
312 /* get a localized string value from line. allocates target, which must be wfreed later.
313 * matching is dependent on the current value of target as well as on the
314 * level the current value is matched on. guts matching algorithm is in
315 * compare_matchlevel().
317 static void getLocalizedStringValue(char **target
, const char *line
, int *match_level
)
322 int sqbstart
, sqbend
;
329 /* skip until after '=', mark if '[' and ']' is found */
330 while (*(p
+ kstart
) != '=') {
331 switch (*(p
+ kstart
)) {
332 case '[': sqbstart
= kstart
+ 1;break;
333 case ']': sqbend
= kstart
; break;
340 /* skip whitespace */
341 while (isspace(*(p
+ kstart
)))
344 if (sqbstart
> 0 && sqbend
> sqbstart
)
345 locale
= wstrndup(p
+ sqbstart
, sqbend
- sqbstart
);
347 /* if there is no value yet and this is the default key, return */
348 if (!*target
&& !locale
) {
349 *match_level
= MATCH_DEFAULT
;
350 *target
= wstrdup(p
+ kstart
);
354 if (compare_matchlevel(match_level
, locale
)) {
356 *target
= wstrdup(p
+ kstart
);
363 /* get a boolean value from line */
364 static Bool
getBooleanValue(const char *line
)
370 getStringValue(&p
, line
);
371 ret
= strcmp(p
, "true") == 0 ? True
: False
;
377 /* perform locale matching by implementing the algorithm specified in
378 * xdg desktop entry specification, section "localized values for keys".
380 static Bool
compare_matchlevel(int *current_level
, const char *found_locale
)
382 /* current key locale */
383 char *key_lang
, *key_ctry
, *key_enc
, *key_mod
;
385 parse_locale(found_locale
, &key_lang
, &key_ctry
, &key_enc
, &key_mod
);
387 if (env_lang
&& key_lang
&& /* Shortcut: if key and env languages don't match, */
388 strcmp(env_lang
, key_lang
) != 0) /* don't even bother. This takes care of the great */
389 return False
; /* majority of the cases without having to go through */
390 /* the more theoretical parts of the spec'd algo. */
392 if (!env_mod
&& key_mod
) /* If LC_MESSAGES does not have a MODIFIER field, */
393 return False
; /* then no key with a modifier will be matched. */
395 if (!env_ctry
&& key_ctry
) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */
396 return False
; /* then no key with a country specified will be matched. */
398 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
399 if (env_lang
&& env_ctry
&& env_mod
) { /* lang_COUNTRY@MODIFIER */
400 if (key_lang
&& key_ctry
&& key_mod
&&
401 strcmp(env_lang
, key_lang
) == 0 &&
402 strcmp(env_ctry
, key_ctry
) == 0 &&
403 strcmp(env_mod
, key_mod
) == 0) {
404 *current_level
= MATCH_LANG_COUNTRY_MODIFIER
;
406 } else if (key_lang
&& key_ctry
&& /* lang_COUNTRY */
407 strcmp(env_lang
, key_lang
) == 0 &&
408 strcmp(env_ctry
, key_ctry
) == 0 &&
409 *current_level
< MATCH_LANG_COUNTRY
) {
410 *current_level
= MATCH_LANG_COUNTRY
;
412 } else if (key_lang
&& key_mod
&& /* lang@MODIFIER */
413 strcmp(env_lang
, key_lang
) == 0 &&
414 strcmp(env_mod
, key_mod
) == 0 &&
415 *current_level
< MATCH_LANG_MODIFIER
) {
416 *current_level
= MATCH_LANG_MODIFIER
;
418 } else if (key_lang
&& /* lang */
419 strcmp(env_lang
, key_lang
) == 0 &&
420 *current_level
< MATCH_LANG
) {
421 *current_level
= MATCH_LANG
;
428 /* LC_MESSAGES value: lang_COUNTRY */
429 if (env_lang
&& env_ctry
) { /* lang_COUNTRY */
430 if (key_lang
&& key_ctry
&&
431 strcmp(env_lang
, key_lang
) == 0 &&
432 strcmp(env_ctry
, key_ctry
) == 0 &&
433 *current_level
< MATCH_LANG_COUNTRY
) {
434 *current_level
= MATCH_LANG_COUNTRY
;
436 } else if (key_lang
&& /* lang */
437 strcmp(env_lang
, key_lang
) == 0 &&
438 *current_level
< MATCH_LANG
) {
439 *current_level
= MATCH_LANG
;
446 /* LC_MESSAGES value: lang@MODIFIER */
447 if (env_lang
&& env_mod
) { /* lang@MODIFIER */
448 if (key_lang
&& key_mod
&&
449 strcmp(env_lang
, key_lang
) == 0 &&
450 strcmp(env_mod
, key_mod
) == 0 &&
451 *current_level
< MATCH_LANG_MODIFIER
) {
452 *current_level
= MATCH_LANG_MODIFIER
;
454 } else if (key_lang
&& /* lang */
455 strcmp(env_lang
, key_lang
) == 0 &&
456 *current_level
< MATCH_LANG
) {
457 *current_level
= MATCH_LANG
;
464 /* LC_MESSAGES value: lang */
465 if (env_lang
) { /* lang */
467 strcmp(env_lang
, key_lang
) == 0 &&
468 *current_level
< MATCH_LANG
) {
469 *current_level
= MATCH_LANG
;
476 /* MATCH_DEFAULT is handled in getLocalizedStringValue */