From 8b68042b302d554181109898ae9cdbee71ae4926 Mon Sep 17 00:00:00 2001 From: Tamas TEVESZ Date: Fri, 8 Oct 2010 00:19:29 +0200 Subject: [PATCH] Add wmmenugen, an extensible PropList-format menu generator Signed-off-by: Tamas TEVESZ --- .gitignore | 1 + util/Makefile.am | 10 +- util/wmmenugen.c | 294 ++++++++++++++++++++ util/wmmenugen.h | 54 ++++ util/wmmenugen_misc.c | 126 +++++++++ util/wmmenugen_parse_wmconfig.c | 195 +++++++++++++ util/wmmenugen_parse_xdg.c | 469 ++++++++++++++++++++++++++++++++ 7 files changed, 1148 insertions(+), 1 deletion(-) create mode 100644 util/wmmenugen.c create mode 100644 util/wmmenugen.h create mode 100644 util/wmmenugen_misc.c create mode 100644 util/wmmenugen_parse_wmconfig.c create mode 100644 util/wmmenugen_parse_xdg.c diff --git a/.gitignore b/.gitignore index dfdfbb3e..f197c971 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ util/wdwrite util/wmagnify util/wmaker.inst util/wmgenmenu +util/wmmenugen util/wmsetbg util/wmsetup util/wxcopy diff --git a/util/Makefile.am b/util/Makefile.am index 9e32021d..43fd9883 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -5,7 +5,7 @@ AUTOMAKE_OPTIONS = pkgdatadir = $(datadir)/@PACKAGE@ bin_PROGRAMS = wxcopy wxpaste wdwrite wdread getstyle setstyle convertfonts \ - seticons geticonset wmsetbg wmagnify wmgenmenu + seticons geticonset wmsetbg wmagnify wmgenmenu wmmenugen bin_SCRIPTS = wmaker.inst wm-oldmenu2new wkdemenu.pl @@ -63,6 +63,14 @@ wmgenmenu_LDADD = \ wmgenmenu_SOURCES = wmgenmenu.c wmgenmenu.h +wmmenugen_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + @INTLIBS@ + +wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \ + wmmenugen_parse_wmconfig.c \ + wmmenugen_parse_xdg.c + CLEANFILES = wmaker.inst wmaker.inst: $(srcdir)/wmaker.inst.in ./Makefile diff --git a/util/wmmenugen.c b/util/wmmenugen.c new file mode 100644 index 00000000..030e7bab --- /dev/null +++ b/util/wmmenugen.c @@ -0,0 +1,294 @@ +/* + * wmmenugen - Window Maker PropList menu generator + * + * Copyright (c) 2010. Tamas Tevesz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#if __GLIBC__ && \ + (_XOPEN_SOURCE && _XOPEN_SOURCE < 500) || \ + !_XOPEN_SOURCE +#define _XOPEN_SOURCE 500 /* nftw */ +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "wmmenugen.h" + +static void addWMMenuEntryCallback(WMMenuEntry *aEntry); +static void assemblePLMenuFunc(WMTreeNode *aNode, void *data); +static int dirParseFunc(const char *filename, const struct stat *st, int tflags, struct FTW *ftw); +static int menuSortFunc(const void *left, const void *right); +static int nodeFindSubMenuByNameFunc(const void *item, const void *cdata); +static WMTreeNode *findPositionInMenu(char *submenu); +static void (*parse)(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry)); + +static WMArray *plMenuNodes; + +extern char *__progname; + +int main(int argc, char **argv) +{ + char *terminal; + struct stat st; + int i; + int *previousDepth; + + plMenuNodes = WMCreateArray(8); /* grows on demand */ + menu = (WMTreeNode *)NULL; + parse = NULL; + + /* assemblePLMenuFunc passes this around */ + previousDepth = (int *)wmalloc(sizeof(int)); + *previousDepth = -1; + + /* currently this is used only by the xdg parser, but it might be useful + * in the future localizing other menus, so it won't hurt to have it here. + */ + parse_locale(NULL, &env_lang, &env_ctry, &env_enc, &env_mod); + terminal = find_terminal_emulator(); + + if (argc < 3) { + fprintf(stderr, "Usage: %s -parser: fspec [fpsec...] " + "[-parser: fspec [fpsec...]...]\n", __progname); + fputs( "Known parsers: xdg wmconfig\n", stderr); + return 1; + } + + for (i = 1; i < argc; i++) + { + if (strncmp(argv[i], "-parser:", 8) == 0) { + if (strcmp(argv[i] + 8, "xdg") == 0) { +#if DEBUG + fputs("Using parser \"xdg\"\n", stderr); +#endif + parse = &parse_xdg; + } else if (strcmp(argv[i] + 8, "wmconfig") == 0) { +#if DEBUG + fputs("Using parser \"wmconfig\"\n", stderr); +#endif + parse = &parse_wmconfig; + } else { + fprintf(stderr, "%s: Unknown parser \"%s\"\n", __progname, argv[i] + 1); + } + continue; + } + + if (parse) { + if (stat(argv[i], &st) == -1) { + fprintf(stderr, "%s: unable to stat \"%s\"\n", __progname, argv[i]); + } else if (S_ISREG(st.st_mode)) { + parse(argv[i], addWMMenuEntryCallback); + } else if (S_ISDIR(st.st_mode)) { + nftw(argv[i], dirParseFunc, 16, FTW_PHYS); + } else { + fprintf(stderr, "%s: \"%s\" is not a file or directory\n", __progname, argv[i]); + } + } else { + fprintf(stderr, "%s: argument \"%s\" with no valid parser\n", __progname, argv[i]); + } + } + + if (!menu) { + fprintf(stderr, "%s: parsers failed to create a valid menu\n", __progname); + return 1; + } + + WMSortTree(menu, menuSortFunc); + WMTreeWalk(menu, assemblePLMenuFunc, previousDepth, True); + + i = WMGetArrayItemCount(plMenuNodes); + if (i > 2) { /* more than one submenu unprocessed is almost certainly an error */ + fprintf(stderr, "%s: unprocessed levels on the stack. fishy.\n", __progname); + return 1; + } else if (i > 1 ) { /* possibly the top-level attachment is not yet done */ + WMPropList *first, *next; + next = WMPopFromArray(plMenuNodes); + first = WMPopFromArray(plMenuNodes); + WMAddToPLArray(first, next); + WMAddToArray(plMenuNodes, first); + } + + printf("%s\n", WMGetPropListDescription((WMPropList *)WMGetFromArray(plMenuNodes, 0), True)); + + return 0; +} + +static int dirParseFunc(const char *filename, const struct stat *st, int tflags, struct FTW *ftw) +{ + (void)st; + (void)tflags; + (void)ftw; + + parse(filename, addWMMenuEntryCallback); + return 0; +} + +/* upon fully deducing one particular menu entry, parsers call back to this + * function to have said menu entry added to the wm menu. initializes wm menu + * with a root element if needed. + */ +static void addWMMenuEntryCallback(WMMenuEntry *aEntry) +{ + WMMenuEntry *wm; + WMTreeNode *at; + + wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry)); /* this entry */ + at = (WMTreeNode *)NULL; /* will be a child of this entry */ + + if (!menu) { + WMMenuEntry *root; + + root = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry)); + root->Name = "Applications"; + root->CmdLine = NULL; + root->SubMenu = NULL; + root->Flags = 0; + menu = WMCreateTreeNode(root); + } + + if (aEntry->SubMenu) + at = findPositionInMenu(wstrdup(aEntry->SubMenu)); + + if (!at) + at = menu; + + wm->Flags = aEntry->Flags; + wm->Name = wstrdup(aEntry->Name); + wm->CmdLine = wstrdup(aEntry->CmdLine); + wm->SubMenu = NULL; + WMAddItemToTree(at, wm); + +} + +/* creates the proplist menu out of the abstract menu representation in `menu'. + */ +static void assemblePLMenuFunc(WMTreeNode *aNode, void *data) +{ + WMMenuEntry *wm; + WMPropList *pl; + int pDepth, cDepth; + + wm = (WMMenuEntry *)WMGetDataForTreeNode(aNode); + cDepth = WMGetTreeNodeDepth(aNode); + pDepth = *(int *)data; + + if (pDepth > cDepth) { /* just ascended out of a/several submenu(s) */ + WMPropList *last, *but; /* merge the differences */ + int i; + for (i = pDepth - cDepth; i > 0; i--) { + last = WMPopFromArray(plMenuNodes); + but = WMPopFromArray(plMenuNodes); + WMAddToPLArray(but, last); + WMAddToArray(plMenuNodes, but); + } + } + + if (!wm->CmdLine) { /* new submenu */ + WMAddToArray(plMenuNodes, WMCreatePLArray(WMCreatePLString(wm->Name), NULL)); + } else { /* new menu item */ + pl = WMPopFromArray(plMenuNodes); + WMAddToPLArray(pl, WMCreatePLArray( + WMCreatePLString(wm->Name), + WMCreatePLString(wm->Flags & F_RESTART ? "RESTART" : "SHEXEC"), + WMCreatePLString(wm->CmdLine), + NULL) + ); + WMAddToArray(plMenuNodes, pl); + } + + *(int *)data = cDepth; + return; +} + +/* sort the menu tree; callback for WMSortTree() + */ +static int menuSortFunc(const void *left, const void *right) +{ + WMMenuEntry *leftwm; + WMMenuEntry *rightwm; + + leftwm = (WMMenuEntry *)WMGetDataForTreeNode(*(WMTreeNode **)left); + rightwm = (WMMenuEntry *)WMGetDataForTreeNode(*(WMTreeNode **)right); + + /* submenus first */ + if (!leftwm->CmdLine && rightwm->CmdLine) + return -1; + if (leftwm->CmdLine && !rightwm->CmdLine) + return 1; + + /* the rest lexicographically */ + return strcasecmp(leftwm->Name, rightwm->Name); + +} + +/* returns the leaf an entry with the submenu spec `submenu' attaches to. + * creates `submenu' path (anchored to the root) along the way. + */ +static WMTreeNode *findPositionInMenu(char *submenu) +{ + char *q; + WMMenuEntry *wm; + WMTreeNode *node, *pnode; + char buf[1024]; + + /* qualify submenu with "Applications/" (the root node) */ + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "Applications/%s", submenu); + + /* start at the root */ + node = menu; + + q = strtok(buf, "/"); + while (q) { + pnode = node; + node = WMFindInTreeWithDepthLimit(pnode, nodeFindSubMenuByNameFunc, q, 1); + if (!node) { + wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry)); + wm->Flags = 0; + wm->Name = wstrdup(q); + wm->CmdLine = NULL; + wm->SubMenu = NULL; + node = WMAddNodeToTree(pnode, WMCreateTreeNode(wm)); + } + q = strtok(NULL, "/"); + } + + return node; +} + +/* find node where Name = cdata and node is a submenu + */ +static int nodeFindSubMenuByNameFunc(const void *item, const void *cdata) +{ + WMMenuEntry *wm; + + wm = (WMMenuEntry *)item; + + if (wm->CmdLine) /* if it has a cmdline, it can't be a submenu */ + return 0; + + return strcmp(wm->Name, (const char *)cdata) == 0; +} diff --git a/util/wmmenugen.h b/util/wmmenugen.h new file mode 100644 index 00000000..8b75ebc5 --- /dev/null +++ b/util/wmmenugen.h @@ -0,0 +1,54 @@ +/* + * wmmenugen - Window Maker PropList menu generator + * + * Copyright (c) 2010. Tamas Tevesz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +/* flags attached to a particular WMMenuEntry */ +#define F_TERMINAL (1 << 0) +#define F_RESTART (1 << 1) + + +/* a representation of a Window Maker menu entry. all menus are + * transformed into this form. + */ +typedef struct { + char *Name; /* display name; submenu path of submenu */ + char *CmdLine; /* command to execute, NULL if submenu */ + char *SubMenu; /* submenu to place entry in; only used when an entry is */ + /* added to the tree by the parser; new entries created in */ + /* main (submenu creation) should set this to NULL */ + int Flags; /* flags */ +} WMMenuEntry; + +/* the abstract menu tree + */ +WMTreeNode *menu; + +char *env_lang, *env_ctry, *env_enc, *env_mod; + +/* wmmenu_misc.c + */ +void parse_locale(const char *what, char **env_lang, char **env_ctry, char **env_enc, char **env_mod); +char *find_terminal_emulator(void); + +/* implemented parsers + */ +void parse_xdg(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry)); +void parse_wmconfig(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry)); diff --git a/util/wmmenugen_misc.c b/util/wmmenugen_misc.c new file mode 100644 index 00000000..22b74d7b --- /dev/null +++ b/util/wmmenugen_misc.c @@ -0,0 +1,126 @@ +/* + * wmmenugen - Window Maker PropList menu generator + * + * miscellaneous functions + * + * Copyright (c) 2010. Tamas Tevesz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include + +static char *terminals[] = { + "x-terminal-emulator", "aterm","eterm", "gnome-terminal", "konsole", + "kterm", "mlterm", "rxvt", "mrxvt", "pterm", "xterm", "dtterm", + NULL +}; + +/* pick a terminal emulator by finding the first existing entry of `terminals' + * in $PATH. the returned pointer should be wfreed later. + * if $WMMENU_TERMINAL exists in the environment, it's value overrides this + * detection. + */ +char *find_terminal_emulator(void) +{ + char *path, *t, *ret; + int i; + + path = t = ret = NULL; + + t = getenv("WMMENU_TERMINAL"); + if (t) { + ret = wstrdup(t); + return ret; + } + + path = getenv("PATH"); + if (!path) + return NULL; + + for (i = 0; terminals[i]; i++) { + t = wfindfile(path, terminals[i]); + if (t) + break; + } + + if (t) + ret = wstrdup(basename(t)); + else + ret = wstrdup(t); + + wfree(t); + + return ret; +} + +/* tokenize `what' (LC_MESSAGES or LANG if `what' is NULL) in the form of + * `language[_territory][.codeset][@modifier]' into separate language, country, + * encoding, modifier components, which are allocated on demand and should be + * wfreed later. components that do not exist in `what' are set to NULL. + */ +void parse_locale(const char *what, char **language, char **country, char **encoding, char **modifier) +{ + char *e, *p; + + *language = *country = *encoding = *modifier = NULL; + + if (what == NULL) { + e = getenv("LC_MESSAGES"); + if (e == NULL) { + e = getenv("LANG"); /* this violates the spec */ + if (e == NULL) + return; + } + e = wstrdup(e); + } else { + e = wstrdup(what); + } + + if (strlen(e) == 0 || + strcmp(e, "POSIX") == 0 || + strcmp(e, "C") == 0) + goto out; + + p = strchr(e, '@'); + if (p) { + *modifier = wstrdup(p + 1); + *p = '\0'; + } + + p = strchr(e, '.'); + if (p) { + *encoding = wstrdup(p + 1); + *p = '\0'; + } + + p = strchr(e, '_'); + if (p) { + *country = wstrdup(p + 1); + *p = '\0'; + } + + if (strlen(e) > 0) + *language = wstrdup(e); + +out: + free(e); + return; + +} diff --git a/util/wmmenugen_parse_wmconfig.c b/util/wmmenugen_parse_wmconfig.c new file mode 100644 index 00000000..6c53c9f1 --- /dev/null +++ b/util/wmmenugen_parse_wmconfig.c @@ -0,0 +1,195 @@ +/* + * wmmenugen - Window Maker PropList menu generator + * + * Wmconfig parser functions + * + * Copyright (c) 2010. Tamas Tevesz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include +#if DEBUG +#include +#endif +#include +#include +#include + +#include "wmmenugen.h" + +typedef struct { + char *Name; + char *Exec; + char *Category; + int Flags; +} WMConfigMenuEntry; + +static Bool wmc_to_wm(WMConfigMenuEntry **wmc, WMMenuEntry **wm); +static void parse_wmconfig_line(char **label, char **key, char **value, char *line); + +void parse_wmconfig(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry)) +{ + FILE *fp; + char buf[1024]; + char *p, *lastlabel, *label, *key, *value; + WMConfigMenuEntry *wmc; + WMMenuEntry *wm; + + lastlabel = label = key = value = NULL; + + fp = fopen(file, "r"); + if (!fp) { +#if DEBUG + fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno)); +#endif + return; + } + + wmc = (WMConfigMenuEntry *)wmalloc(sizeof(WMConfigMenuEntry)); + wmc->Name = NULL; + wmc->Exec = NULL; + wmc->Category = NULL; + wmc->Flags = 0; + + wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry)); + + memset(buf, 0, sizeof(buf)); + + while (fgets(buf, sizeof(buf), fp)) { + + p = buf; + + /* skip whitespaces */ + while (isspace(*p)) + p++; + /* skip comments, empty lines */ + if (*p == '\r' || *p == '\n' || *p == '#') { + memset(buf, 0, sizeof(buf)); + continue; + } + /* trim crlf */ + buf[strcspn(buf, "\r\n")] = '\0'; + if (strlen(buf) == 0) + continue; + + parse_wmconfig_line(&label, &key, &value, p); + + if (label && strlen(label) == 0) + continue; + if (!lastlabel && label) + lastlabel = wstrdup(label); + + if (strcmp(lastlabel, label) != 0) { + if (wmc->Name && wmc->Exec && wmc->Category && + wmc_to_wm(&wmc, &wm)) + (*addWMMenuEntryCallback)(wm); + + wfree(wmc->Name); + wmc->Name = NULL; + wfree(wmc->Exec); + wmc->Exec = NULL; + wfree(wmc->Category); + wmc->Category = NULL; + wmc->Flags = 0; + wfree(lastlabel); + lastlabel = wstrdup(label); + } + + if (key && value) { + if (strcmp(key, "name") == 0) + wmc->Name = value; + else if (strcmp(key, "exec") == 0) + wmc->Exec = value; + else if (strcmp(key, "group") == 0) + wmc->Category = value; + else if (strcmp(key, "restart") == 0) + wmc->Flags |= F_RESTART; + else if (strcmp(key, "terminal") == 0) + wmc->Flags |= F_TERMINAL; + } + + } + + fclose(fp); + + if (wmc_to_wm(&wmc, &wm)) + (*addWMMenuEntryCallback)(wm); + +} + +/* get a line allocating label, key and value as necessary */ +static void parse_wmconfig_line(char **label, char **key, char **value, char *line) +{ + char *p; + int kstart, kend; + + p = (char *)line; + *label = *key = *value = NULL; + kstart = kend = 0; + + /* skip whitespace */ + while (isspace(*(p + kstart))) + kstart++; + + kend = kstart; + /* find end of label */ + while (*(p + kend) && !isspace(*(p + kend))) + kend++; + + /* label */ + *label = wstrndup(p + kstart, kend - kstart); + kstart = kend + 1; + + /* skip whitespace */ + while (isspace(*(p + kstart))) + kstart++; + + kend = kstart; + /* find end of key */ + while (*(p + kend) && !isspace(*(p + kend))) + kend++; + + /* key */ + *key = wstrndup(p + kstart, kend - kstart); + kstart = kend + 1; + + /* skip until after " */ + while (*(p + kstart) && *(p + kstart) != '"') + kstart++; + kstart++; + + kend = kstart; + /* skip until " */ + while (*(p + kend) && *(p + kend) != '"') + kend++; + + /* value */ + *value = wstrndup(p + kstart, kend - kstart); +} + +static Bool wmc_to_wm(WMConfigMenuEntry **wmc, WMMenuEntry **wm) +{ + if (!*wmc || !(*wmc)->Name) + return False; + + (*wm)->Name = (*wmc)->Name; + (*wm)->CmdLine = (*wmc)->Exec; + (*wm)->SubMenu = (*wmc)->Category; + (*wm)->Flags = (*wmc)->Flags; + + return True; +} diff --git a/util/wmmenugen_parse_xdg.c b/util/wmmenugen_parse_xdg.c new file mode 100644 index 00000000..0ee23f89 --- /dev/null +++ b/util/wmmenugen_parse_xdg.c @@ -0,0 +1,469 @@ +/* + * wmmenugen - Window Maker PropList menu generator + * + * Desktop Entry Specification parser functions + * + * Copyright (c) 2010. Tamas Tevesz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html + * + * We will only deal with Type == "Application" entries in [Desktop Entry] + * groups. Since there is no passing of file name arguments or anything of + * the sort to applications from the menu, execname is determined as follows: + * - If `TryExec' is present, use that; + * - else use `Exec' with any switches stripped + * XXX: Only the first item of `Categories' is taken into consideration, + * which will be used as the sole category to put the entry into. + * + * Basic validation of the .desktop file is done. + */ + +#include +#if DEBUG +#include +#endif +#include +#include +#include + +#include "wmmenugen.h" + +/* LocaleString match levels */ +enum { + MATCH_DEFAULT, + MATCH_LANG, + MATCH_LANG_MODIFIER, + MATCH_LANG_COUNTRY, + MATCH_LANG_COUNTRY_MODIFIER +}; + +typedef struct { + char *Name; /* Name */ /* localestring */ + int MatchLevel; /* LocaleString match type */ /* int */ + char *TryExec; /* TryExec */ /* string */ + char *Exec; /* Exec */ /* string */ + char *Path; /* Path */ /* string */ + int Flags; /* Flags */ + char *Category; /* Categories (first item only) */ /* string */ +} XDGMenuEntry; + +static void getKey(char **target, const char *line); +static void getStringValue(char **target, const char *line); +static void getLocalizedStringValue(char **target, const char *line, int *match_level); +static int getBooleanValue(const char *line); +static int compare_matchlevel(int *current_level, const char *found_locale); +static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wmentry); +static void init_xdg_storage(XDGMenuEntry **xdg); +static void init_wm_storage(WMMenuEntry **wm); + +void parse_xdg(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry)) +{ + FILE *fp; + char buf[1024]; + char *p, *tmp, *key; + WMMenuEntry *wm; + XDGMenuEntry *xdg; + int InGroup; + + fp = fopen(file, "r"); + if (!fp) { +#if DEBUG + fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno)); +#endif + return; + } + + xdg = (XDGMenuEntry *)wmalloc(sizeof(XDGMenuEntry)); + wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry)); + InGroup = 0; + memset(buf, 0, sizeof(buf)); + + while (fgets(buf, sizeof(buf), fp)) { + + p = buf; + + /* skip whitespaces */ + while (isspace(*p)) + p++; + /* skip comments, empty lines */ + if (*p == '\r' || *p == '\n' || *p == '#') { + memset(buf, 0, sizeof(buf)); + continue; + } + /* trim crlf */ + buf[strcspn(buf, "\r\n")] = '\0'; + if (strlen(buf) == 0) + continue; + + if (strcmp(p, "[Desktop Entry]") == 0) { + if (InGroup) { + /* if currently processing a group, we've just hit the + * end of its definition, try processing it + */ + if (xdg_to_wm(&xdg, &wm)) { + (*addWMMenuEntryCallback)(wm); + } + } + init_xdg_storage(&xdg); + init_wm_storage(&wm); + InGroup = 1; + /* start processing group */ + memset(buf, 0, sizeof(buf)); + continue; + } + + if (!InGroup) { + memset(buf, 0, sizeof(buf)); + continue; + } + + getKey(&key, p); + if (key == NULL) { /* not `key' = `value' */ + memset(buf, 0, sizeof(buf)); + continue; + } + + if (strcmp(key, "Type") == 0) { + getStringValue(&tmp, p); + if (strcmp(tmp, "Application") != 0) + InGroup = 0; /* if not application, skip current group */ + wfree(tmp); + tmp = NULL; + } else if (strcmp(key, "Name") == 0) { + getLocalizedStringValue(&xdg->Name, p, &xdg->MatchLevel); + } else if (strcmp(key, "NoDisplay") == 0) { + if (getBooleanValue(p)) /* if nodisplay, skip current group */ + InGroup = 0; + } else if (strcmp(key, "Hidden") == 0) { + if (getBooleanValue(p)) + InGroup = 0; /* if hidden, skip current group */ + } else if (strcmp(key, "TryExec") == 0) { + getStringValue(&xdg->TryExec, p); + } else if (strcmp(key, "Exec") == 0) { + getStringValue(&xdg->Exec, p); + } else if (strcmp(key, "Path") == 0) { + getStringValue(&xdg->Path, p); + } else if (strcmp(key, "Terminal") == 0) { + if (getBooleanValue(p)) + xdg->Flags |= F_TERMINAL; + } else if (strcmp(key, "Categories") == 0) { + /* use only the first item */ + getStringValue(&tmp, p); + if (tmp != NULL) { + tmp[strcspn(tmp, ";")] = '\0'; + tmp[strcspn(tmp, "\t ")] = '\0'; + xdg->Category = wstrdup(tmp); + wfree(tmp); + tmp = NULL; + } + } + + wfree(key); + key = NULL; + } + + fclose(fp); + + /* at the end of the file, might as well try to menuize what we have */ + if (xdg_to_wm(&xdg, &wm)) + (*addWMMenuEntryCallback)(wm); + +} + + +/* coerce an xdg entry type into a wm entry type + */ +static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wm) +{ + if (!*xdg) + return False; + + if ((*xdg)->Exec || (*xdg)->TryExec) { + (*wm)->Flags = (*xdg)->Flags; + if ((*xdg)->Name) { + (*wm)->Name = (*xdg)->Name; + } + if ((*xdg)->Exec) { + if (!(*wm)->Name) + (*wm)->Name = (*xdg)->Exec; + (*wm)->CmdLine = (*xdg)->Exec; + } + if ((*xdg)->TryExec) { + if (!(*wm)->Name) + (*wm)->Name = (*xdg)->TryExec; + if (!(*wm)->CmdLine) + (*wm)->CmdLine = (*xdg)->TryExec; + } + } + if ((*wm)->Name && (*wm)->CmdLine) + return True; + + return False; +} + +/* (re-)initialize a XDGMenuEntry storage + */ +static void init_xdg_storage(XDGMenuEntry **xdg) +{ + + if ((*xdg)->Name) + wfree((*xdg)->Name); + if ((*xdg)->TryExec) + wfree((*xdg)->TryExec); + if ((*xdg)->Exec) + wfree((*xdg)->Exec); + if ((*xdg)->Category) + wfree((*xdg)->Category); + if ((*xdg)->Path) + wfree((*xdg)->Path); + + (*xdg)->Name = NULL; + (*xdg)->TryExec = NULL; + (*xdg)->Exec = NULL; + (*xdg)->Category = NULL; + (*xdg)->Path = NULL; + (*xdg)->Flags = 0; + (*xdg)->MatchLevel = -1; +} + +/* (re-)initialize a WMMenuEntry storage + */ +static void init_wm_storage(WMMenuEntry **wm) +{ + (*wm)->Name = NULL; + (*wm)->CmdLine = NULL; + (*wm)->Flags = 0; +} + +/* get a key from line. allocates target, which must be wfreed later */ +static void getKey(char **target, const char *line) +{ + const char *p; + int kstart, kend; + + p = line; + + if (strchr(p, '=') == NULL) { /* not `key' = `value' */ + *target = NULL; + return; + } + + kstart = 0; + + /* skip whitespace */ + while (isspace(*(p + kstart))) + kstart++; + + /* skip up until first whitespace or '[' (localestring) or '=' */ + kend = kstart + 1; + while (!isspace(*(p + kend)) && *(p + kend) != '=' && *(p + kend) != '[') + kend++; + + *target = wstrndup(p + kstart, kend - kstart); +} + +/* get a string value from line. allocates target, which must be wfreed later. */ +static void getStringValue(char **target, const char *line) +{ + const char *p; + int kstart; + + p = line; + kstart = 0; + + /* skip until after '=' */ + while (*(p + kstart) != '=') + kstart++; + kstart++; + + /* skip whitespace */ + while (isspace(*(p + kstart))) + kstart++; + + *target = wstrdup(p + kstart); +} + +/* get a localized string value from line. allocates target, which must be wfreed later. + * matching is dependent on the current value of target as well as on the + * level the current value is matched on. guts matching algorithm is in + * compare_matchlevel(). + */ +static void getLocalizedStringValue(char **target, const char *line, int *match_level) +{ + const char *p; + char *locale; + int kstart; + int sqbstart, sqbend; + + p = line; + kstart = 0; + sqbstart = 0; + locale = NULL; + + /* skip until after '=', mark if '[' and ']' is found */ + while (*(p + kstart) != '=') { + switch (*(p + kstart)) { + case '[': sqbstart = kstart + 1;break; + case ']': sqbend = kstart; break; + default : break; + } + kstart++; + } + kstart++; + + /* skip whitespace */ + while (isspace(*(p + kstart))) + kstart++; + + if (sqbstart > 0 && sqbend > sqbstart) + locale = wstrndup(p + sqbstart, sqbend - sqbstart); + + /* if there is no value yet and this is the default key, return */ + if (!*target && !locale) { + *match_level = MATCH_DEFAULT; + *target = wstrdup(p + kstart); + return; + } + + if (compare_matchlevel(match_level, locale)) { + wfree(locale); + *target = wstrdup(p + kstart); + return; + } + + return; +} + +/* get a boolean value from line */ +static Bool getBooleanValue(const char *line) +{ + char *p; + int ret; + + ret = 0; + getStringValue(&p, line); + ret = strcmp(p, "true") == 0 ? True : False; + wfree(p); + + return ret; +} + +/* perform locale matching by implementing the algorithm specified in + * xdg desktop entry specification, section "localized values for keys". + */ +static Bool compare_matchlevel(int *current_level, const char *found_locale) +{ + /* current key locale */ + char *key_lang, *key_ctry, *key_enc, *key_mod; + + parse_locale(found_locale, &key_lang, &key_ctry, &key_enc, &key_mod); + + if (env_lang && key_lang && /* Shortcut: if key and env languages don't match, */ + strcmp(env_lang, key_lang) != 0) /* don't even bother. This takes care of the great */ + return False; /* majority of the cases without having to go through */ + /* the more theoretical parts of the spec'd algo. */ + + if (!env_mod && key_mod) /* If LC_MESSAGES does not have a MODIFIER field, */ + return False; /* then no key with a modifier will be matched. */ + + if (!env_ctry && key_ctry) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */ + return False; /* then no key with a country specified will be matched. */ + + /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */ + if (env_lang && env_ctry && env_mod) { /* lang_COUNTRY@MODIFIER */ + if (key_lang && key_ctry && key_mod && + strcmp(env_lang, key_lang) == 0 && + strcmp(env_ctry, key_ctry) == 0 && + strcmp(env_mod, key_mod) == 0) { + *current_level = MATCH_LANG_COUNTRY_MODIFIER; + return True; + } else if (key_lang && key_ctry && /* lang_COUNTRY */ + strcmp(env_lang, key_lang) == 0 && + strcmp(env_ctry, key_ctry) == 0 && + *current_level < MATCH_LANG_COUNTRY) { + *current_level = MATCH_LANG_COUNTRY; + return True; + } else if (key_lang && key_mod && /* lang@MODIFIER */ + strcmp(env_lang, key_lang) == 0 && + strcmp(env_mod, key_mod) == 0 && + *current_level < MATCH_LANG_MODIFIER) { + *current_level = MATCH_LANG_MODIFIER; + return True; + } else if (key_lang && /* lang */ + strcmp(env_lang, key_lang) == 0 && + *current_level < MATCH_LANG) { + *current_level = MATCH_LANG; + return True; + } else { + return False; + } + } + + /* LC_MESSAGES value: lang_COUNTRY */ + if (env_lang && env_ctry) { /* lang_COUNTRY */ + if (key_lang && key_ctry && + strcmp(env_lang, key_lang) == 0 && + strcmp(env_ctry, key_ctry) == 0 && + *current_level < MATCH_LANG_COUNTRY) { + *current_level = MATCH_LANG_COUNTRY; + return True; + } else if (key_lang && /* lang */ + strcmp(env_lang, key_lang) == 0 && + *current_level < MATCH_LANG) { + *current_level = MATCH_LANG; + return True; + } else { + return False; + } + } + + /* LC_MESSAGES value: lang@MODIFIER */ + if (env_lang && env_mod) { /* lang@MODIFIER */ + if (key_lang && key_mod && + strcmp(env_lang, key_lang) == 0 && + strcmp(env_mod, key_mod) == 0 && + *current_level < MATCH_LANG_MODIFIER) { + *current_level = MATCH_LANG_MODIFIER; + return True; + } else if (key_lang && /* lang */ + strcmp(env_lang, key_lang) == 0 && + *current_level < MATCH_LANG) { + *current_level = MATCH_LANG; + return True; + } else { + return False; + } + } + + /* LC_MESSAGES value: lang */ + if (env_lang) { /* lang */ + if (key_lang && + strcmp(env_lang, key_lang) == 0 && + *current_level < MATCH_LANG) { + *current_level = MATCH_LANG; + return True; + } else { + return False; + } + } + + /* MATCH_DEFAULT is handled in getLocalizedStringValue */ + + return False; +}