]> git.mdlowis.com Git - proto/labwc.git/commitdiff
menu: refactor parser
authortokyo4j <hrak1529@gmail.com>
Mon, 4 Aug 2025 03:55:13 +0000 (12:55 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Mon, 4 Aug 2025 20:41:14 +0000 (21:41 +0100)
...with the same approach as rcxml.c

- `If` actions now works for menus
- `name` argument no longer have to be the first argument of <action>
- `label` argument no longer have to be the first argument of <item>

docs/labwc.1.scd
src/menu/menu.c

index 4e64391874efb45f520bd26f5d590b7d284b06a4..4840c0fa72ea99825251bf82f2f48c604924656e 100644 (file)
@@ -134,8 +134,7 @@ example: *LABWC_DEBUG_FOO=1 labwc*.
        Increase logging of paths for config files (for example rc.xml,
        autostart, environment and menu.xml) as well as titlebar buttons.
 
-*LABWC_DEBUG_CONFIG_NODENAMES*++
-*LABWC_DEBUG_MENU_NODENAMES*
+*LABWC_DEBUG_CONFIG_NODENAMES*
        Enable logging of all nodenames (for example *policy.placement: Cascade*
        for *<placement><policy>Cascade</policy></placement>*) for config and
        menu files respectively.
index 8d80231fde396c5ce011db2593e58c404d7cef07..0232e2d5cd89b431275f70afa05d10185b9b672a 100644 (file)
 #include "common/lab-scene-rect.h"
 #include "common/list.h"
 #include "common/mem.h"
-#include "common/nodename.h"
 #include "common/scaled-font-buffer.h"
 #include "common/scaled-icon-buffer.h"
 #include "common/scene-helpers.h"
 #include "common/spawn.h"
 #include "common/string-helpers.h"
+#include "common/xml.h"
 #include "labwc.h"
 #include "output.h"
 #include "workspaces.h"
 
 #define ICON_SIZE (rc.theme->menu_item_height - 2 * rc.theme->menu_items_padding_y)
 
-/* state-machine variables for processing <item></item> */
-struct menu_parse_context {
-       struct server *server;
-       struct menu *menu;
-       struct menuitem *item;
-       struct action *action;
-       bool in_item;
-};
-
 static bool waiting_for_pipe_menu;
 static struct menuitem *selected_item;
 
@@ -140,7 +131,7 @@ validate(struct server *server)
 }
 
 static struct menuitem *
-item_create(struct menu *menu, const char *text, bool show_arrow)
+item_create(struct menu *menu, const char *text, const char *icon_name, bool show_arrow)
 {
        assert(menu);
        assert(text);
@@ -152,6 +143,13 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
        menuitem->text = xstrdup(text);
        menuitem->arrow = show_arrow ? "›" : NULL;
 
+#if HAVE_LIBSFDO
+       if (rc.menu_show_icons && !string_null_or_empty(icon_name)) {
+               menuitem->icon_name = xstrdup(icon_name);
+               menu->has_icons = true;
+       }
+#endif
+
        menuitem->native_width = font_width(&rc.font_menuitem, text);
        if (menuitem->arrow) {
                menuitem->native_width += font_width(&rc.font_menuitem, menuitem->arrow);
@@ -473,34 +471,22 @@ menu_create_scene(struct menu *menu)
  * </item>
  */
 static void
-fill_item(struct menu_parse_context *ctx, const char *nodename,
-               const char *content)
+fill_item(struct menu *menu, xmlNode *node)
 {
-       /* <item label=""> defines the start of a new item */
-       if (!strcmp(nodename, "label")) {
-               ctx->item = item_create(ctx->menu, content, false);
-               ctx->action = NULL;
-       } else if (!ctx->item) {
-               wlr_log(WLR_ERROR, "expect <item label=\"\"> element first. "
-                       "nodename: '%s' content: '%s'", nodename, content);
-       } else if (!strcmp(nodename, "icon")) {
-#if HAVE_LIBSFDO
-               if (rc.menu_show_icons && !string_null_or_empty(content)) {
-                       xstrdup_replace(ctx->item->icon_name, content);
-                       ctx->menu->has_icons = true;
-               }
-#endif
-       } else if (!strcmp(nodename, "name.action")) {
-               ctx->action = action_create(content);
-               if (ctx->action) {
-                       wl_list_append(&ctx->item->actions, &ctx->action->link);
-               }
-       } else if (!ctx->action) {
-               wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. "
-                       "nodename: '%s' content: '%s'", nodename, content);
-       } else {
-               action_arg_from_xml_node(ctx->action, nodename, content);
+       char *label = (char *)xmlGetProp(node, (xmlChar *)"label");
+       char *icon_name = (char *)xmlGetProp(node, (xmlChar *)"icon");
+       if (!label) {
+               wlr_log(WLR_ERROR, "missing label in <item>");
+               goto out;
        }
+
+       struct menuitem *item = item_create(menu, (char *)label, icon_name, false);
+       lab_xml_expand_dotted_attributes(node);
+       append_parsed_actions(node, &item->actions);
+
+out:
+       free(label);
+       free(icon_name);
 }
 
 static void
@@ -516,103 +502,10 @@ item_destroy(struct menuitem *item)
        free(item);
 }
 
-/*
- * We support XML CDATA for <command> in menu.xml in order to provide backward
- * compatibility with obmenu-generator. For example:
- *
- * <menu id="" label="">
- *   <item label="">
- *     <action name="Execute">
- *       <command><![CDATA[xdg-open .]]></command>
- *     </action>
- *   </item>
- * </menu>
- *
- * <execute> is an old, deprecated openbox variety of <command>. We support it
- * for backward compatibility with old openbox-menu generators. It has the same
- * function and <command>
- *
- * The following nodenames support CDATA.
- *  - command.action.item.*menu.openbox_menu
- *  - execute.action.item.*menu.openbox_menu
- *  - command.action.item.openbox_pipe_menu
- *  - execute.action.item.openbox_pipe_menu
- *  - command.action.item.*menu.openbox_pipe_menu
- *  - execute.action.item.*menu.openbox_pipe_menu
- *
- * The *menu allows nested menus with nodenames such as ...menu.menu... or
- * ...menu.menu.menu... and so on. We could use match_glob() for all of the
- * above but it seems simpler to just check the first three fields.
- */
-static bool
-nodename_supports_cdata(char *nodename)
-{
-       return !strncmp("command.action.", nodename, 15)
-               || !strncmp("execute.action.", nodename, 15);
-}
-
-static void
-entry(struct menu_parse_context *ctx, xmlNode *node, char *nodename,
-               char *content)
-{
-       if (!nodename) {
-               return;
-       }
-       xmlChar *cdata = NULL;
-       if (!content && nodename_supports_cdata(nodename)) {
-               cdata = xmlNodeGetContent(node);
-       }
-       if (!content && !cdata) {
-               return;
-       }
-       string_truncate_at_pattern(nodename, ".openbox_menu");
-       string_truncate_at_pattern(nodename, ".openbox_pipe_menu");
-       if (getenv("LABWC_DEBUG_MENU_NODENAMES")) {
-               printf("%s: %s\n", nodename, content ? content : (char *)cdata);
-       }
-       if (ctx->in_item) {
-               /*
-                * Nodenames for most menu-items end with '.item.menu'
-                * but top-level pipemenu items do not have the associated
-                * <menu> element so merely end with '.item'
-                */
-               string_truncate_at_pattern(nodename, ".item.menu");
-               string_truncate_at_pattern(nodename, ".item");
-               fill_item(ctx, nodename, content ? content : (char *)cdata);
-       }
-       xmlFree(cdata);
-}
-
-static void
-process_node(struct menu_parse_context *ctx, xmlNode *node)
-{
-       static char buffer[256];
-
-       char *content = (char *)node->content;
-       if (xmlIsBlankNode(node)) {
-               return;
-       }
-       char *name = nodename(node, buffer, sizeof(buffer));
-       entry(ctx, node, name, content);
-}
-
-static void xml_tree_walk(struct menu_parse_context *ctx, xmlNode *node);
-
-static void
-traverse(struct menu_parse_context *ctx, xmlNode *n)
-{
-       xmlAttr *attr;
-
-       process_node(ctx, n);
-       for (attr = n->properties; attr; attr = attr->next) {
-               xml_tree_walk(ctx, attr->children);
-       }
-       xml_tree_walk(ctx, n->children);
-}
-
-static bool parse_buf(struct menu_parse_context *ctx, struct buf *buf);
+static bool parse_buf(struct server *server, struct menu *menu, struct buf *buf);
 static int handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx);
 static int handle_pipemenu_timeout(void *_ctx);
+static void fill_menu_children(struct server *server, struct menu *parent, xmlNode *n);
 
 /*
  * <menu> elements have three different roles:
@@ -621,7 +514,7 @@ static int handle_pipemenu_timeout(void *_ctx);
  *  * Menuitem of submenu type - has ID only
  */
 static void
-handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
+fill_menu(struct server *server, struct menu *parent, xmlNode *n)
 {
        char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
        char *icon_name = (char *)xmlGetProp(n, (const xmlChar *)"icon");
@@ -636,10 +529,9 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
        if (execute && label) {
                wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
 
-               struct menu *pipemenu =
-                       menu_create(ctx->server, ctx->menu, id, label);
+               struct menu *pipemenu = menu_create(server, parent, id, label);
                pipemenu->execute = xstrdup(execute);
-               if (!ctx->menu) {
+               if (!parent) {
                        /*
                         * A pipemenu may not have its parent like:
                         *
@@ -649,18 +541,16 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
                         * </openbox_menu>
                         */
                } else {
-                       ctx->item = item_create(ctx->menu, label,
-                               /* arrow */ true);
-                       fill_item(ctx, "icon", icon_name);
-                       ctx->action = NULL;
-                       ctx->item->submenu = pipemenu;
+                       struct menuitem *item = item_create(parent, label,
+                               icon_name, /* arrow */ true);
+                       item->submenu = pipemenu;
                }
-       } else if ((label && ctx->menu) || !ctx->menu) {
+       } else if ((label && parent) || !parent) {
                /*
-                * (label && ctx->menu) refers to <menu id="" label="">
+                * (label && parent) refers to <menu id="" label="">
                 * which is an nested (inline) menu definition.
                 *
-                * (!ctx->menu) catches:
+                * (!parent) catches:
                 *     <openbox_menu>
                 *       <menu id=""></menu>
                 *     </openbox_menu>
@@ -676,22 +566,20 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
                 * attribute to make it easier for users to define "root-menu"
                 * and "client-menu".
                 */
-               struct menu *parent_menu = ctx->menu;
-               ctx->menu = menu_create(ctx->server, parent_menu, id, label);
+               struct menu *menu = menu_create(server, parent, id, label);
                if (icon_name) {
-                       ctx->menu->icon_name = xstrdup(icon_name);
+                       menu->icon_name = xstrdup(icon_name);
                }
-               if (label && parent_menu) {
+               if (label && parent) {
                        /*
                         * In a nested (inline) menu definition we need to
                         * create an item pointing to the new submenu
                         */
-                       ctx->item = item_create(parent_menu, label, true);
-                       fill_item(ctx, "icon", icon_name);
-                       ctx->item->submenu = ctx->menu;
+                       struct menuitem *item = item_create(parent, label,
+                               icon_name, true);
+                       item->submenu = menu;
                }
-               traverse(ctx, n);
-               ctx->menu = parent_menu;
+               fill_menu_children(server, menu, n);
        } else {
                /*
                 * <menu id=""> (when inside another <menu> element) creates an
@@ -708,13 +596,13 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
                        goto error;
                }
 
-               struct menu *menu = menu_get_by_id(ctx->server, id);
+               struct menu *menu = menu_get_by_id(server, id);
                if (!menu) {
                        wlr_log(WLR_ERROR, "no menu with id '%s'", id);
                        goto error;
                }
 
-               struct menu *iter = ctx->menu;
+               struct menu *iter = parent;
                while (iter) {
                        if (iter == menu) {
                                wlr_log(WLR_ERROR, "menus with the same id '%s' "
@@ -724,9 +612,9 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
                        iter = iter->parent;
                }
 
-               ctx->item = item_create(ctx->menu, menu->label, true);
-               fill_item(ctx, "icon", menu->icon_name);
-               ctx->item->submenu = menu;
+               struct menuitem *item = item_create(parent, menu->label,
+                       parent->icon_name, true);
+               item->submenu = menu;
        }
 error:
        free(label);
@@ -737,50 +625,42 @@ error:
 
 /* This can be one of <separator> and <separator label=""> */
 static void
-handle_separator_element(struct menu_parse_context *ctx, xmlNode *n)
+fill_separator(struct menu *menu, xmlNode *n)
 {
        char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
-       ctx->item = separator_create(ctx->menu, label);
+       separator_create(menu, label);
        free(label);
 }
 
+/* parent==NULL when processing toplevel menus in menu.xml */
 static void
-xml_tree_walk(struct menu_parse_context *ctx, xmlNode *node)
+fill_menu_children(struct server *server, struct menu *parent, xmlNode *n)
 {
-       for (xmlNode *n = node; n && n->name; n = n->next) {
-               if (!strcasecmp((char *)n->name, "comment")) {
-                       continue;
-               }
-               if (!strcasecmp((char *)n->name, "menu")) {
-                       handle_menu_element(ctx, n);
-                       continue;
-               }
-               if (!strcasecmp((char *)n->name, "separator")) {
-                       if (!ctx->menu) {
+       xmlNode *child;
+       char *key, *content;
+       LAB_XML_FOR_EACH(n, child, key, content) {
+               if (!strcasecmp(key, "menu")) {
+                       fill_menu(server, parent, child);
+               } else if (!strcasecmp(key, "separator")) {
+                       if (!parent) {
                                wlr_log(WLR_ERROR,
                                        "ignoring <separator> without parent <menu>");
                                continue;
                        }
-                       handle_separator_element(ctx, n);
-                       continue;
-               }
-               if (!strcasecmp((char *)n->name, "item")) {
-                       if (!ctx->menu) {
+                       fill_separator(parent, child);
+               } else if (!strcasecmp(key, "item")) {
+                       if (!parent) {
                                wlr_log(WLR_ERROR,
                                        "ignoring <item> without parent <menu>");
                                continue;
                        }
-                       ctx->in_item = true;
-                       traverse(ctx, n);
-                       ctx->in_item = false;
-                       continue;
+                       fill_item(parent, child);
                }
-               traverse(ctx, n);
        }
 }
 
 static bool
-parse_buf(struct menu_parse_context *ctx, struct buf *buf)
+parse_buf(struct server *server, struct menu *parent, struct buf *buf)
 {
        int options = 0;
        xmlDoc *d = xmlReadMemory(buf->data, buf->len, NULL, NULL, options);
@@ -788,7 +668,10 @@ parse_buf(struct menu_parse_context *ctx, struct buf *buf)
                wlr_log(WLR_ERROR, "xmlParseMemory()");
                return false;
        }
-       xml_tree_walk(ctx, xmlDocGetRootElement(d));
+
+       xmlNode *root = xmlDocGetRootElement(d);
+       fill_menu_children(server, parent, root);
+
        xmlFreeDoc(d);
        xmlCleanupParser();
        return true;
@@ -814,8 +697,7 @@ parse_stream(struct server *server, FILE *stream)
                buf_add(&b, line);
        }
        free(line);
-       struct menu_parse_context ctx = {.server = server};
-       parse_buf(&ctx, &b);
+       parse_buf(server, NULL, &b);
        buf_reset(&b);
 }
 
@@ -939,6 +821,14 @@ init_client_send_to_menu(struct server *server)
        menu_create(server, NULL, "client-send-to-menu", "");
 }
 
+static struct action *
+item_add_action(struct menuitem *item, const char *action_name)
+{
+       struct action *action = action_create(action_name);
+       wl_list_append(&item->actions, &action->link);
+       return action;
+}
+
 /*
  * This is client-send-to-menu
  * an internal menu similar to root-menu and client-menu
@@ -955,21 +845,22 @@ update_client_send_to_menu(struct server *server)
 
        reset_menu(menu);
 
-       struct menu_parse_context ctx = {.server = server};
        struct workspace *workspace;
 
        wl_list_for_each(workspace, &server->workspaces.all, link) {
+               struct buf buf = BUF_INIT;
                if (workspace == server->workspaces.current) {
-                       char *label = strdup_printf(">%s<", workspace->name);
-                       ctx.item = item_create(menu, label,
-                               /*show arrow*/ false);
-                       free(label);
+                       buf_add_fmt(&buf, ">%s<", workspace->name);
                } else {
-                       ctx.item = item_create(menu, workspace->name,
-                               /*show arrow*/ false);
+                       buf_add(&buf, workspace->name);
                }
-               fill_item(&ctx, "name.action", "SendToDesktop");
-               fill_item(&ctx, "to.action", workspace->name);
+               struct menuitem *item = item_create(menu, buf.data,
+                       NULL, /*show arrow*/ false);
+
+               struct action *action = item_add_action(item, "SendToDesktop");
+               action_arg_add_str(action, "to", "name");
+
+               buf_clear(&buf);
        }
 
        menu_create_scene(menu);
@@ -998,7 +889,7 @@ update_client_list_combined_menu(struct server *server)
 
        reset_menu(menu);
 
-       struct menu_parse_context ctx = {.server = server};
+       struct menuitem *item;
        struct workspace *workspace;
        struct view *view;
        struct buf buffer = BUF_INIT;
@@ -1006,7 +897,7 @@ update_client_list_combined_menu(struct server *server)
        wl_list_for_each(workspace, &server->workspaces.all, link) {
                buf_add_fmt(&buffer, workspace == server->workspaces.current ? ">%s<" : "%s",
                                workspace->name);
-               ctx.item = separator_create(menu, buffer.data);
+               separator_create(menu, buffer.data);
                buf_clear(&buffer);
 
                wl_list_for_each(view, &server->views, link) {
@@ -1021,19 +912,19 @@ update_client_list_combined_menu(struct server *server)
                                }
                                buf_add(&buffer, title);
 
-                               ctx.item = item_create(menu, buffer.data,
+                               item = item_create(menu, buffer.data, NULL,
                                        /*show arrow*/ false);
-                               ctx.item->client_list_view = view;
-                               fill_item(&ctx, "name.action", "Focus");
-                               fill_item(&ctx, "name.action", "Raise");
+                               item->client_list_view = view;
+                               item_add_action(item, "Focus");
+                               item_add_action(item, "Raise");
                                buf_clear(&buffer);
                                menu->has_icons = true;
                        }
                }
-               ctx.item = item_create(menu, _("Go there..."),
+               item = item_create(menu, _("Go there..."), NULL,
                        /*show arrow*/ false);
-               fill_item(&ctx, "name.action", "GoToDesktop");
-               fill_item(&ctx, "to.action", workspace->name);
+               struct action *action = item_add_action(item, "GoToDesktop");
+               action_arg_add_str(action, "to", workspace->name);
        }
        buf_reset(&buffer);
        menu_create_scene(menu);
@@ -1043,22 +934,22 @@ static void
 init_rootmenu(struct server *server)
 {
        struct menu *menu = menu_get_by_id(server, "root-menu");
+       struct menuitem *item;
 
        /* Default menu if no menu.xml found */
        if (!menu) {
-               struct menu_parse_context ctx = {.server = server};
                menu = menu_create(server, NULL, "root-menu", "");
 
-               ctx.item = item_create(menu, _("Terminal"), false);
-               fill_item(&ctx, "name.action", "Execute");
-               fill_item(&ctx, "command.action", "lab-sensible-terminal");
+               item = item_create(menu, _("Terminal"), NULL, false);
+               struct action *action = item_add_action(item, "Execute");
+               action_arg_add_str(action, "command", "lab-sensible-terminal");
 
-               ctx.item = separator_create(menu, NULL);
+               separator_create(menu, NULL);
 
-               ctx.item = item_create(menu, _("Reconfigure"), false);
-               fill_item(&ctx, "name.action", "Reconfigure");
-               ctx.item = item_create(menu, _("Exit"), false);
-               fill_item(&ctx, "name.action", "Exit");
+               item = item_create(menu, _("Reconfigure"), NULL, false);
+               item_add_action(item, "Reconfigure");
+               item = item_create(menu, _("Exit"), NULL, false);
+               item_add_action(item, "Exit");
        }
 }
 
@@ -1066,47 +957,48 @@ static void
 init_windowmenu(struct server *server)
 {
        struct menu *menu = menu_get_by_id(server, "client-menu");
+       struct menuitem *item;
+       struct action *action;
 
        /* Default menu if no menu.xml found */
        if (!menu) {
-               struct menu_parse_context ctx = {.server = server};
                menu = menu_create(server, NULL, "client-menu", "");
-               ctx.item = item_create(menu, _("Minimize"), false);
-               fill_item(&ctx, "name.action", "Iconify");
-               ctx.item = item_create(menu, _("Maximize"), false);
-               fill_item(&ctx, "name.action", "ToggleMaximize");
-               ctx.item = item_create(menu, _("Fullscreen"), false);
-               fill_item(&ctx, "name.action", "ToggleFullscreen");
-               ctx.item = item_create(menu, _("Roll Up/Down"), false);
-               fill_item(&ctx, "name.action", "ToggleShade");
-               ctx.item = item_create(menu, _("Decorations"), false);
-               fill_item(&ctx, "name.action", "ToggleDecorations");
-               ctx.item = item_create(menu, _("Always on Top"), false);
-               fill_item(&ctx, "name.action", "ToggleAlwaysOnTop");
+               item = item_create(menu, _("Minimize"), NULL, false);
+               item_add_action(item, "Iconify");
+               item = item_create(menu, _("Maximize"), NULL, false);
+               item_add_action(item, "ToggleMaximize");
+               item = item_create(menu, _("Fullscreen"), NULL, false);
+               item_add_action(item, "ToggleFullscreen");
+               item = item_create(menu, _("Roll Up/Down"), NULL, false);
+               item_add_action(item, "ToggleShade");
+               item = item_create(menu, _("Decorations"), NULL, false);
+               item_add_action(item, "ToggleDecorations");
+               item = item_create(menu, _("Always on Top"), NULL, false);
+               item_add_action(item, "ToggleAlwaysOnTop");
 
                /* Workspace sub-menu */
                struct menu *workspace_menu =
                        menu_create(server, NULL, "workspaces", "");
-               ctx.item = item_create(workspace_menu, _("Move Left"), false);
+               item = item_create(workspace_menu, _("Move Left"), NULL, false);
                /*
                 * <action name="SendToDesktop"><follow> is true by default so
                 * GoToDesktop will be called as part of the action.
                 */
-               fill_item(&ctx, "name.action", "SendToDesktop");
-               fill_item(&ctx, "to.action", "left");
-               ctx.item = item_create(workspace_menu, _("Move Right"), false);
-               fill_item(&ctx, "name.action", "SendToDesktop");
-               fill_item(&ctx, "to.action", "right");
-               ctx.item = separator_create(workspace_menu, "");
-               ctx.item = item_create(workspace_menu,
-                       _("Always on Visible Workspace"), false);
-               fill_item(&ctx, "name.action", "ToggleOmnipresent");
+               action = item_add_action(item, "SendToDesktop");
+               action_arg_add_str(action, "to", "left");
+               item = item_create(workspace_menu, _("Move Right"), NULL, false);
+               action = item_add_action(item, "SendToDesktop");
+               action_arg_add_str(action, "to", "right");
+               separator_create(workspace_menu, "");
+               item = item_create(workspace_menu,
+                       _("Always on Visible Workspace"), NULL, false);
+               item_add_action(item, "ToggleOmnipresent");
 
-               ctx.item = item_create(menu, _("Workspace"), true);
-               ctx.item->submenu = workspace_menu;
+               item = item_create(menu, _("Workspace"), NULL, true);
+               item->submenu = workspace_menu;
 
-               ctx.item = item_create(menu, _("Close"), false);
-               fill_item(&ctx, "name.action", "Close");
+               item = item_create(menu, _("Close"), NULL, false);
+               item_add_action(item, "Close");
        }
 
        if (wl_list_length(&rc.workspace_config.workspaces) == 1) {
@@ -1348,11 +1240,7 @@ static void
 create_pipe_menu(struct menu_pipe_context *ctx)
 {
        struct server *server = ctx->pipemenu->server;
-       struct menu_parse_context parse_ctx = {
-               .server = server,
-               .menu = ctx->pipemenu,
-       };
-       if (!parse_buf(&parse_ctx, &ctx->buf)) {
+       if (!parse_buf(server, ctx->pipemenu, &ctx->buf)) {
                return;
        }
        /* TODO: apply validate() only for generated pipemenus */