From: tokyo4j Date: Mon, 4 Aug 2025 03:55:13 +0000 (+0900) Subject: menu: refactor parser X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=17d66e560321ee2d76b89db23131cc40d11022a2;p=proto%2Flabwc.git menu: refactor parser ...with the same approach as rcxml.c - `If` actions now works for menus - `name` argument no longer have to be the first argument of - `label` argument no longer have to be the first argument of --- diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 4e643918..4840c0fa 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -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 *Cascade*) for config and menu files respectively. diff --git a/src/menu/menu.c b/src/menu/menu.c index 8d80231f..0232e2d5 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -20,12 +20,12 @@ #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" @@ -38,15 +38,6 @@ #define ICON_SIZE (rc.theme->menu_item_height - 2 * rc.theme->menu_items_padding_y) -/* state-machine variables for processing */ -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) * */ static void -fill_item(struct menu_parse_context *ctx, const char *nodename, - const char *content) +fill_item(struct menu *menu, xmlNode *node) { - /* 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 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 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 "); + 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 in menu.xml in order to provide backward - * compatibility with obmenu-generator. For example: - * - * - * - * - * - * - * - * - * - * is an old, deprecated openbox variety of . We support it - * for backward compatibility with old openbox-menu generators. It has the same - * function and - * - * 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 - * 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); /* * 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) * */ } 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 + * (label && parent) refers to * which is an nested (inline) menu definition. * - * (!ctx->menu) catches: + * (!parent) catches: * * * @@ -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 { /* * (when inside another 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 and */ 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 without parent "); 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 without parent "); 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); /* * 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 */