]> git.mdlowis.com Git - proto/labwc.git/commitdiff
menu.c: parse menu.xml root-menu
authorJohan Malm <jgm323@gmail.com>
Wed, 17 Feb 2021 20:38:16 +0000 (20:38 +0000)
committerJohan Malm <jgm323@gmail.com>
Wed, 17 Feb 2021 20:38:16 +0000 (20:38 +0000)
README.md
src/menu/menu.c

index ee13705c7854889ace59ffdeba39f266423f9e9d..5686627c1609255f20bfc75bf4d6a2ac48c71207 100644 (file)
--- a/README.md
+++ b/README.md
 
 ## 1. What is this?
 
-Labwc is a [WIP] free, stacking compositor for Wayland based on wlroots.
+Labwc is a [WIP] free, wlroots-based stacking compositor for Wayland.
 
 It has the following aims:
 
 - Be light-weight, small and fast
 - Have the look and feel of [openbox](https://github.com/danakj/openbox) albeit
-  with smaller feature set
+  with smaller feature set
 - Where practicable, use clients to show wall-paper, take screenshots, and so on
 - Stay in keeping with wlroots and sway in terms of approach and coding style
 
@@ -24,8 +24,6 @@ It is in early development, so expect bugs and missing features.
 
 Labwc has been inspired and influenced by [openbox](https://github.com/danakj/openbox), [sway](https://github.com/swaywm/sway), [cage](https://www.hjdskes.nl/blog/cage-01/), [wio](https://wio-project.org/) and [rootston](https://github.com/swaywm/rootston)
 
-The following were considered before choosing wlroots: [qtwayland](https://github.com/qt/qtwayland), [grefsen](https://github.com/ec1oud/grefsen), [mir](https://mir-server.io) and [egmde](https://github.com/AlanGriffiths/egmde).
-
 ![](https://raw.githubusercontent.com/wiki/johanmalm/labwc/images/scrot2.png)
 
 ## 2. Build
@@ -64,7 +62,9 @@ See full details in the following:
 
 ## 4. Run
 
-    ./build/labwc -s <some-application>
+    ./build/labwc [-s <some-application>]
+
+Click on the background to launch a menu.
 
 If you have not created an rc.xml configuration file, default keybinds will be:
 
@@ -96,7 +96,7 @@ No acceptance criteria exists, but the following list indicates the inteded high
 - [x] Catch SIGHUP to re-load config file and theme
 - [x] Support layer-shell protocol ('exclusive' not yet implemented)
 - [x] Support damage tracking to reduce CPU usage
-- [ ] Support root-menu and parse menu.xml (very simple implementation, no submenus yet)
+- [x] Support root-menu and parse menu.xml (very simple implementation - no submenus, separators or titles)
 - [ ] Support 'maximize'
 - [ ] Support wlr-output-management protocol and [kanshi](https://github.com/emersion/kanshi.git)
 - [ ] Support foreign-toplevel protocol (e.g. to integrate with wlroots panels/bars)
@@ -129,4 +129,5 @@ The following items are out-of-scope:
 - Any theme option (probably at least half of them) not required to reasonably render common themes
 - Any configuration option not required to provide a simple openbox-like experience
 - Multiple desktops
+- Pipe-menus
 
index bc260f8c44a6a26d8c3b77b54bf1153248333414..213ccfa3b7ec80be29afe1e1b1f0cd4f81df2767 100644 (file)
@@ -1,17 +1,33 @@
 #define _POSIX_C_SOURCE 200809L
+#include <assert.h>
 #include <cairo/cairo.h>
+#include <ctype.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
 #include <pango/pangocairo.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
+#include "common/buf.h"
+#include "common/dir.h"
 #include "common/font.h"
+#include "common/nodename.h"
+#include "common/string-helpers.h"
 #include "labwc.h"
 #include "menu/menu.h"
 #include "theme/theme.h"
 
 static const char font[] = "Sans 8";
 
-#define MENUWIDTH (100)
+static struct server *g_server;
+static struct menu *g_menu;
+
+/* state-machine variables for processing <item></item> */
+static bool in_item = false;
+static struct menuitem *current_item;
+
+#define MENUWIDTH (120)
 #define MENUHEIGHT (25)
 #define MENU_PADDING_WIDTH (7)
 
@@ -57,15 +73,12 @@ texture_create(struct server *server, struct wlr_box *geo, const char *text,
 }
 
 struct menuitem *
-menuitem_create(struct server *server, struct menu *menu, const char *text,
-       const char *action, const char *command)
+menuitem_create(struct server *server, struct menu *menu, const char *text)
 {
        struct menuitem *menuitem = calloc(1, sizeof(struct menuitem));
        if (!menuitem) {
                return NULL;
        }
-       menuitem->action = action ? strdup(action) : NULL;
-       menuitem->command = command ? strdup(command) : NULL;
        menuitem->geo_box.width = MENUWIDTH;
        menuitem->geo_box.height = MENUHEIGHT;
        menuitem->active_texture = texture_create(server, &menuitem->geo_box,
@@ -77,13 +90,170 @@ menuitem_create(struct server *server, struct menu *menu, const char *text,
        return menuitem;
 }
 
+static void fill_item(char *nodename, char *content)
+{
+       string_truncate_at_pattern(nodename, ".item.menu");
+
+       if (getenv("LABWC_DEBUG_MENU_NODENAMES")) {
+               printf("%s: %s\n", nodename, content);
+       }
+
+       /*
+        * Handle the following:
+        * <item label="">
+        *   <action name="">
+        *     <command></command>
+        *   </action>
+        * </item>
+        */
+       if (!strcmp(nodename, "label")) {
+               current_item = menuitem_create(g_server, g_menu, content);
+       }
+       assert(current_item);
+       if (!strcmp(nodename, "name.action")) {
+               current_item->action = strdup(content);
+       } else if (!strcmp(nodename, "command.action")) {
+               current_item->command = strdup(content);
+       }
+}
+
+static void
+entry(xmlNode *node, char *nodename, char *content)
+{
+       static bool in_root_menu = false;
+
+       if (!nodename) {
+               return;
+       }
+       string_truncate_at_pattern(nodename, ".openbox_menu");
+       if (!content) {
+               return;
+       }
+       if (!strcmp(nodename, "id.menu")) {
+               in_root_menu = !strcmp(content, "root-menu") ? true : false;
+       }
+
+       /* We only handle the root-menu for the time being */
+       if (!in_root_menu) {
+               return;
+       }
+       if (in_item) {
+               fill_item(nodename, content);
+       }
+}
+
+static void
+process_node(xmlNode *node)
+{
+       char *content;
+       static char buffer[256];
+       char *name;
+
+       content = (char *)node->content;
+       if (xmlIsBlankNode(node)) {
+               return;
+       }
+       name = nodename(node, buffer, sizeof(buffer));
+       entry(node, name, content);
+}
+
+static void xml_tree_walk(xmlNode *node);
+
+static void
+traverse(xmlNode *n)
+{
+       process_node(n);
+       for (xmlAttr *attr = n->properties; attr; attr = attr->next) {
+               xml_tree_walk(attr->children);
+       }
+       xml_tree_walk(n->children);
+}
+
+static void
+xml_tree_walk(xmlNode *node)
+{
+       for (xmlNode *n = node; n && n->name; n = n->next) {
+               if (!strcasecmp((char *)n->name, "comment")) {
+                       continue;
+               }
+               if (!strcasecmp((char *)n->name, "item")) {
+                       in_item = true;
+                       traverse(n);
+                       in_item = false;
+                       continue;
+               }
+               traverse(n);
+       }
+}
+
+static void
+parse_xml(struct buf *b)
+{
+       xmlDoc *d = xmlParseMemory(b->buf, b->len);
+       if (!d) {
+               warn("xmlParseMemory()");
+               exit(EXIT_FAILURE);
+       }
+       xml_tree_walk(xmlDocGetRootElement(d));
+       xmlFreeDoc(d);
+       xmlCleanupParser();
+}
+
+static void
+menu_read(void)
+{
+       FILE *stream;
+       char *line = NULL;
+       size_t len = 0;
+       struct buf b;
+       static char menuxml[4096] = { 0 };
+
+       if (!strlen(config_dir())) {
+               return;
+       }
+       snprintf(menuxml, sizeof(menuxml), "%s/menu.xml", config_dir());
+
+       stream = fopen(menuxml, "r");
+       if (!stream) {
+               warn("cannot read (%s)", menuxml);
+               return;
+       }
+       info("read menu file (%s)", menuxml);
+       buf_init(&b);
+       while (getline(&line, &len, stream) != -1) {
+               char *p = strrchr(line, '\n');
+               if (p)
+                       *p = '\0';
+               buf_add(&b, line);
+       }
+       free(line);
+       fclose(stream);
+       parse_xml(&b);
+       free(b.buf);
+}
+
 void
 menu_init(struct server *server, struct menu *menu)
 {
+       static bool has_run;
+
+       if (has_run) {
+               goto not_first_run;
+       }
+       LIBXML_TEST_VERSION
        wl_list_init(&menu->menuitems);
-       menuitem_create(server, menu, "Terminal", "Execute", "sakura");
-       menuitem_create(server, menu, "Reconfigure", "Reconfigure", NULL);
-       menuitem_create(server, menu, "Exit", "Exit", NULL);
+       g_server = server;
+       g_menu = menu;
+
+not_first_run:
+       menu_read();
+       /* Default menu if no menu.xml found */
+       if (!current_item) {
+               current_item = menuitem_create(server, menu, "Reconfigure");
+               current_item->action = strdup("Reconfigure");
+               current_item = menuitem_create(server, menu, "Exit");
+               current_item->action = strdup("Exit");
+       }
        menu_move(menu, 100, 100);
 }