it's extensible, and it generates proplist menus.
here's a quick rundown.
any menu format is converted to an abstract representation, which is
basically a label, a command to execute, and a menu specification. any
implemented parser converts its input to this format. this abstract
format is then ground, chewed, digested, then spit out in proplist
format.
any parser must implement one function ((*parse)(const char *file,
void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry))) which will
receive exactly one file name (regular file only) and a callback
function that does (half of) the abstract->wmpl conversion.
(in the not too distant future a new optional callback will be added,
which will receive one file name, and decides whether or not said
parser will act on that file -- this is for example to have a tree of
.desktop files parsed, but not waste time (or generate false
positives) on a README or jessica_alba.avi).
in its current form, the core is quite ok (though it does lots and
lots of shoveling data from here to there, not sure if this can easily
be cut).
the wmconfig parser is also pretty complete, though there are some
stuff in wmconfig files that are not handled well (this is mostly
stuff that is not applications per se, but wm-specific functions like
restart and the like -- i think these were not really designed into
wmconfig either). this is easy (but boring) to fix. it can quite well
parse up the sample stuff in the wmconfig distribution (though, as a
side note, the resulting menu of 500-string entries triggers a bug in
either wm, wutil or efence :).
the xdg parser is in an incredibly sorry state. some error checking
needs to be added to the actual parser part (this is the easy part),
and i couldn't really figure out and/or decide what to do with the
menus (i've been impulse jumping between "what the fuck is this",
"what jerk came up with this" and "the authors are officially on my
shitlist" for the past week) -- the xdg desktop entry spec is
basically "do whatever you want to" spread over 15 pages.
i need some input on how to covert this delusional excuse for a
specification into a wm menu, (not) recommended readings are at
http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html
and a bit of
http://standards.freedesktop.org/menu-spec/menu-spec-1.1.html
apart from this, what it generates is nicely usable, just... flat.
i suspect adding debian's menu format shouldn't be hard (/me eyes
brad:), and converting the current wmgenmenu to a "parser" should also
not be a problem (hello, carlos!:)
i need a bit of a break from this now, and i really need some input,
too.
--
[-]
mkdir /nonexistent
From 9f750c144b01f84f90c4d7fddeb0e462452298b8 Mon Sep 17 00:00:00 2001
From: Tamas TEVESZ <[email protected]>
Date: Fri, 8 Oct 2010 00:19:29 +0200
Subject: [PATCH] Add wmmenugen, an extensible PropList-format menu generator
Signed-off-by: Tamas TEVESZ <[email protected]>
---
util/Makefile.am | 10 +-
util/wmmenugen.c | 296 ++++++++++++++++++++++++
util/wmmenugen.h | 55 +++++
util/wmmenugen_misc.c | 127 +++++++++++
util/wmmenugen_parse_wmconfig.c | 195 ++++++++++++++++
util/wmmenugen_parse_xdg.c | 470 +++++++++++++++++++++++++++++++++++++++
6 files changed, 1152 insertions(+), 1 deletions(-)
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/util/Makefile.am b/util/Makefile.am
index 9e32021..43fd988 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 0000000..30e5705
--- /dev/null
+++ b/util/wmmenugen.c
@@ -0,0 +1,296 @@
+/*
+ * wmmenugen - Window Maker PropList menu generator
+ *
+ * Copyright (c) 2010. Tamas Tevesz <[email protected]>
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <ftw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#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:<parser> fspec [fpsec...] "
+ "[-parser:<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 0000000..553cbd7
--- /dev/null
+++ b/util/wmmenugen.h
@@ -0,0 +1,55 @@
+/*
+ * wmmenugen - Window Maker PropList menu generator
+ *
+ * Copyright (c) 2010. Tamas Tevesz <[email protected]>
+ *
+ * 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 <WINGs/WUtil.h>
+
+/* 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 0000000..6699d19
--- /dev/null
+++ b/util/wmmenugen_misc.c
@@ -0,0 +1,127 @@
+/*
+ * wmmenugen - Window Maker PropList menu generator
+ *
+ * miscellaneous functions
+ *
+ * Copyright (c) 2010. Tamas Tevesz <[email protected]>
+ *
+ * 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 <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <WINGs/WUtil.h>
+
+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][.codese...@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 0000000..6c53c9f
--- /dev/null
+++ b/util/wmmenugen_parse_wmconfig.c
@@ -0,0 +1,195 @@
+/*
+ * wmmenugen - Window Maker PropList menu generator
+ *
+ * Wmconfig <http://www.arrishq.net/> parser functions
+ *
+ * Copyright (c) 2010. Tamas Tevesz <[email protected]>
+ *
+ * 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 <ctype.h>
+#if DEBUG
+#include <errno.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 0000000..e19c0a2
--- /dev/null
+++ b/util/wmmenugen_parse_xdg.c
@@ -0,0 +1,470 @@
+/*
+ * wmmenugen - Window Maker PropList menu generator
+ *
+ * Desktop Entry Specification parser functions
+ *
+ * Copyright (c) 2010. Tamas Tevesz <[email protected]>
+ *
+ * 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 <ctype.h>
+#if DEBUG
+#include <errno.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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_coun...@modifier */
+ if (env_lang && env_ctry && env_mod) { /* lang_coun...@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 && /* l...@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: l...@modifier */
+ if (env_lang && env_mod) { /* l...@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;
+}
+
--
1.7.0.4