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 <action>
- `label` argument no longer have to be the first argument of <item>
This commit is contained in:
tokyo4j 2025-08-04 12:55:13 +09:00 committed by Johan Malm
parent bfaab101af
commit 17d66e5603
2 changed files with 133 additions and 246 deletions

View file

@ -134,8 +134,7 @@ example: *LABWC_DEBUG_FOO=1 labwc*.
Increase logging of paths for config files (for example rc.xml, Increase logging of paths for config files (for example rc.xml,
autostart, environment and menu.xml) as well as titlebar buttons. autostart, environment and menu.xml) as well as titlebar buttons.
*LABWC_DEBUG_CONFIG_NODENAMES*++ *LABWC_DEBUG_CONFIG_NODENAMES*
*LABWC_DEBUG_MENU_NODENAMES*
Enable logging of all nodenames (for example *policy.placement: Cascade* Enable logging of all nodenames (for example *policy.placement: Cascade*
for *<placement><policy>Cascade</policy></placement>*) for config and for *<placement><policy>Cascade</policy></placement>*) for config and
menu files respectively. menu files respectively.

View file

@ -20,12 +20,12 @@
#include "common/lab-scene-rect.h" #include "common/lab-scene-rect.h"
#include "common/list.h" #include "common/list.h"
#include "common/mem.h" #include "common/mem.h"
#include "common/nodename.h"
#include "common/scaled-font-buffer.h" #include "common/scaled-font-buffer.h"
#include "common/scaled-icon-buffer.h" #include "common/scaled-icon-buffer.h"
#include "common/scene-helpers.h" #include "common/scene-helpers.h"
#include "common/spawn.h" #include "common/spawn.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "common/xml.h"
#include "labwc.h" #include "labwc.h"
#include "output.h" #include "output.h"
#include "workspaces.h" #include "workspaces.h"
@ -38,15 +38,6 @@
#define ICON_SIZE (rc.theme->menu_item_height - 2 * rc.theme->menu_items_padding_y) #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 bool waiting_for_pipe_menu;
static struct menuitem *selected_item; static struct menuitem *selected_item;
@ -140,7 +131,7 @@ validate(struct server *server)
} }
static struct menuitem * 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(menu);
assert(text); assert(text);
@ -152,6 +143,13 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
menuitem->text = xstrdup(text); menuitem->text = xstrdup(text);
menuitem->arrow = show_arrow ? "" : NULL; 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); menuitem->native_width = font_width(&rc.font_menuitem, text);
if (menuitem->arrow) { if (menuitem->arrow) {
menuitem->native_width += font_width(&rc.font_menuitem, menuitem->arrow); menuitem->native_width += font_width(&rc.font_menuitem, menuitem->arrow);
@ -473,34 +471,22 @@ menu_create_scene(struct menu *menu)
* </item> * </item>
*/ */
static void static void
fill_item(struct menu_parse_context *ctx, const char *nodename, fill_item(struct menu *menu, xmlNode *node)
const char *content)
{ {
/* <item label=""> defines the start of a new item */ char *label = (char *)xmlGetProp(node, (xmlChar *)"label");
if (!strcmp(nodename, "label")) { char *icon_name = (char *)xmlGetProp(node, (xmlChar *)"icon");
ctx->item = item_create(ctx->menu, content, false); if (!label) {
ctx->action = NULL; wlr_log(WLR_ERROR, "missing label in <item>");
} else if (!ctx->item) { goto out;
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);
} }
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 static void
@ -516,103 +502,10 @@ item_destroy(struct menuitem *item)
free(item); free(item);
} }
/* static bool parse_buf(struct server *server, struct menu *menu, struct buf *buf);
* 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 int handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx); static int handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx);
static int handle_pipemenu_timeout(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: * <menu> elements have three different roles:
@ -621,7 +514,7 @@ static int handle_pipemenu_timeout(void *_ctx);
* * Menuitem of submenu type - has ID only * * Menuitem of submenu type - has ID only
*/ */
static void 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 *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
char *icon_name = (char *)xmlGetProp(n, (const xmlChar *)"icon"); 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) { if (execute && label) {
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute); wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
struct menu *pipemenu = struct menu *pipemenu = menu_create(server, parent, id, label);
menu_create(ctx->server, ctx->menu, id, label);
pipemenu->execute = xstrdup(execute); pipemenu->execute = xstrdup(execute);
if (!ctx->menu) { if (!parent) {
/* /*
* A pipemenu may not have its parent like: * A pipemenu may not have its parent like:
* *
@ -649,18 +541,16 @@ handle_menu_element(struct menu_parse_context *ctx, xmlNode *n)
* </openbox_menu> * </openbox_menu>
*/ */
} else { } else {
ctx->item = item_create(ctx->menu, label, struct menuitem *item = item_create(parent, label,
/* arrow */ true); icon_name, /* arrow */ true);
fill_item(ctx, "icon", icon_name); item->submenu = pipemenu;
ctx->action = NULL;
ctx->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. * which is an nested (inline) menu definition.
* *
* (!ctx->menu) catches: * (!parent) catches:
* <openbox_menu> * <openbox_menu>
* <menu id=""></menu> * <menu id=""></menu>
* </openbox_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" * attribute to make it easier for users to define "root-menu"
* and "client-menu". * and "client-menu".
*/ */
struct menu *parent_menu = ctx->menu; struct menu *menu = menu_create(server, parent, id, label);
ctx->menu = menu_create(ctx->server, parent_menu, id, label);
if (icon_name) { 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 * In a nested (inline) menu definition we need to
* create an item pointing to the new submenu * create an item pointing to the new submenu
*/ */
ctx->item = item_create(parent_menu, label, true); struct menuitem *item = item_create(parent, label,
fill_item(ctx, "icon", icon_name); icon_name, true);
ctx->item->submenu = ctx->menu; item->submenu = menu;
} }
traverse(ctx, n); fill_menu_children(server, menu, n);
ctx->menu = parent_menu;
} else { } else {
/* /*
* <menu id=""> (when inside another <menu> element) creates an * <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; goto error;
} }
struct menu *menu = menu_get_by_id(ctx->server, id); struct menu *menu = menu_get_by_id(server, id);
if (!menu) { if (!menu) {
wlr_log(WLR_ERROR, "no menu with id '%s'", id); wlr_log(WLR_ERROR, "no menu with id '%s'", id);
goto error; goto error;
} }
struct menu *iter = ctx->menu; struct menu *iter = parent;
while (iter) { while (iter) {
if (iter == menu) { if (iter == menu) {
wlr_log(WLR_ERROR, "menus with the same id '%s' " 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; iter = iter->parent;
} }
ctx->item = item_create(ctx->menu, menu->label, true); struct menuitem *item = item_create(parent, menu->label,
fill_item(ctx, "icon", menu->icon_name); parent->icon_name, true);
ctx->item->submenu = menu; item->submenu = menu;
} }
error: error:
free(label); free(label);
@ -737,50 +625,42 @@ error:
/* This can be one of <separator> and <separator label=""> */ /* This can be one of <separator> and <separator label=""> */
static void 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"); char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
ctx->item = separator_create(ctx->menu, label); separator_create(menu, label);
free(label); free(label);
} }
/* parent==NULL when processing toplevel menus in menu.xml */
static void 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) { xmlNode *child;
if (!strcasecmp((char *)n->name, "comment")) { char *key, *content;
continue; LAB_XML_FOR_EACH(n, child, key, content) {
} if (!strcasecmp(key, "menu")) {
if (!strcasecmp((char *)n->name, "menu")) { fill_menu(server, parent, child);
handle_menu_element(ctx, n); } else if (!strcasecmp(key, "separator")) {
continue; if (!parent) {
}
if (!strcasecmp((char *)n->name, "separator")) {
if (!ctx->menu) {
wlr_log(WLR_ERROR, wlr_log(WLR_ERROR,
"ignoring <separator> without parent <menu>"); "ignoring <separator> without parent <menu>");
continue; continue;
} }
handle_separator_element(ctx, n); fill_separator(parent, child);
continue; } else if (!strcasecmp(key, "item")) {
} if (!parent) {
if (!strcasecmp((char *)n->name, "item")) {
if (!ctx->menu) {
wlr_log(WLR_ERROR, wlr_log(WLR_ERROR,
"ignoring <item> without parent <menu>"); "ignoring <item> without parent <menu>");
continue; continue;
} }
ctx->in_item = true; fill_item(parent, child);
traverse(ctx, n);
ctx->in_item = false;
continue;
} }
traverse(ctx, n);
} }
} }
static bool 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; int options = 0;
xmlDoc *d = xmlReadMemory(buf->data, buf->len, NULL, NULL, options); 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()"); wlr_log(WLR_ERROR, "xmlParseMemory()");
return false; return false;
} }
xml_tree_walk(ctx, xmlDocGetRootElement(d));
xmlNode *root = xmlDocGetRootElement(d);
fill_menu_children(server, parent, root);
xmlFreeDoc(d); xmlFreeDoc(d);
xmlCleanupParser(); xmlCleanupParser();
return true; return true;
@ -814,8 +697,7 @@ parse_stream(struct server *server, FILE *stream)
buf_add(&b, line); buf_add(&b, line);
} }
free(line); free(line);
struct menu_parse_context ctx = {.server = server}; parse_buf(server, NULL, &b);
parse_buf(&ctx, &b);
buf_reset(&b); buf_reset(&b);
} }
@ -939,6 +821,14 @@ init_client_send_to_menu(struct server *server)
menu_create(server, NULL, "client-send-to-menu", ""); 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 * This is client-send-to-menu
* an internal menu similar to root-menu and client-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); reset_menu(menu);
struct menu_parse_context ctx = {.server = server};
struct workspace *workspace; struct workspace *workspace;
wl_list_for_each(workspace, &server->workspaces.all, link) { wl_list_for_each(workspace, &server->workspaces.all, link) {
struct buf buf = BUF_INIT;
if (workspace == server->workspaces.current) { if (workspace == server->workspaces.current) {
char *label = strdup_printf(">%s<", workspace->name); buf_add_fmt(&buf, ">%s<", workspace->name);
ctx.item = item_create(menu, label,
/*show arrow*/ false);
free(label);
} else { } else {
ctx.item = item_create(menu, workspace->name, buf_add(&buf, workspace->name);
/*show arrow*/ false);
} }
fill_item(&ctx, "name.action", "SendToDesktop"); struct menuitem *item = item_create(menu, buf.data,
fill_item(&ctx, "to.action", workspace->name); 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); menu_create_scene(menu);
@ -998,7 +889,7 @@ update_client_list_combined_menu(struct server *server)
reset_menu(menu); reset_menu(menu);
struct menu_parse_context ctx = {.server = server}; struct menuitem *item;
struct workspace *workspace; struct workspace *workspace;
struct view *view; struct view *view;
struct buf buffer = BUF_INIT; 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) { wl_list_for_each(workspace, &server->workspaces.all, link) {
buf_add_fmt(&buffer, workspace == server->workspaces.current ? ">%s<" : "%s", buf_add_fmt(&buffer, workspace == server->workspaces.current ? ">%s<" : "%s",
workspace->name); workspace->name);
ctx.item = separator_create(menu, buffer.data); separator_create(menu, buffer.data);
buf_clear(&buffer); buf_clear(&buffer);
wl_list_for_each(view, &server->views, link) { wl_list_for_each(view, &server->views, link) {
@ -1021,19 +912,19 @@ update_client_list_combined_menu(struct server *server)
} }
buf_add(&buffer, title); buf_add(&buffer, title);
ctx.item = item_create(menu, buffer.data, item = item_create(menu, buffer.data, NULL,
/*show arrow*/ false); /*show arrow*/ false);
ctx.item->client_list_view = view; item->client_list_view = view;
fill_item(&ctx, "name.action", "Focus"); item_add_action(item, "Focus");
fill_item(&ctx, "name.action", "Raise"); item_add_action(item, "Raise");
buf_clear(&buffer); buf_clear(&buffer);
menu->has_icons = true; menu->has_icons = true;
} }
} }
ctx.item = item_create(menu, _("Go there..."), item = item_create(menu, _("Go there..."), NULL,
/*show arrow*/ false); /*show arrow*/ false);
fill_item(&ctx, "name.action", "GoToDesktop"); struct action *action = item_add_action(item, "GoToDesktop");
fill_item(&ctx, "to.action", workspace->name); action_arg_add_str(action, "to", workspace->name);
} }
buf_reset(&buffer); buf_reset(&buffer);
menu_create_scene(menu); menu_create_scene(menu);
@ -1043,22 +934,22 @@ static void
init_rootmenu(struct server *server) init_rootmenu(struct server *server)
{ {
struct menu *menu = menu_get_by_id(server, "root-menu"); struct menu *menu = menu_get_by_id(server, "root-menu");
struct menuitem *item;
/* Default menu if no menu.xml found */ /* Default menu if no menu.xml found */
if (!menu) { if (!menu) {
struct menu_parse_context ctx = {.server = server};
menu = menu_create(server, NULL, "root-menu", ""); menu = menu_create(server, NULL, "root-menu", "");
ctx.item = item_create(menu, _("Terminal"), false); item = item_create(menu, _("Terminal"), NULL, false);
fill_item(&ctx, "name.action", "Execute"); struct action *action = item_add_action(item, "Execute");
fill_item(&ctx, "command.action", "lab-sensible-terminal"); 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); item = item_create(menu, _("Reconfigure"), NULL, false);
fill_item(&ctx, "name.action", "Reconfigure"); item_add_action(item, "Reconfigure");
ctx.item = item_create(menu, _("Exit"), false); item = item_create(menu, _("Exit"), NULL, false);
fill_item(&ctx, "name.action", "Exit"); item_add_action(item, "Exit");
} }
} }
@ -1066,47 +957,48 @@ static void
init_windowmenu(struct server *server) init_windowmenu(struct server *server)
{ {
struct menu *menu = menu_get_by_id(server, "client-menu"); struct menu *menu = menu_get_by_id(server, "client-menu");
struct menuitem *item;
struct action *action;
/* Default menu if no menu.xml found */ /* Default menu if no menu.xml found */
if (!menu) { if (!menu) {
struct menu_parse_context ctx = {.server = server};
menu = menu_create(server, NULL, "client-menu", ""); menu = menu_create(server, NULL, "client-menu", "");
ctx.item = item_create(menu, _("Minimize"), false); item = item_create(menu, _("Minimize"), NULL, false);
fill_item(&ctx, "name.action", "Iconify"); item_add_action(item, "Iconify");
ctx.item = item_create(menu, _("Maximize"), false); item = item_create(menu, _("Maximize"), NULL, false);
fill_item(&ctx, "name.action", "ToggleMaximize"); item_add_action(item, "ToggleMaximize");
ctx.item = item_create(menu, _("Fullscreen"), false); item = item_create(menu, _("Fullscreen"), NULL, false);
fill_item(&ctx, "name.action", "ToggleFullscreen"); item_add_action(item, "ToggleFullscreen");
ctx.item = item_create(menu, _("Roll Up/Down"), false); item = item_create(menu, _("Roll Up/Down"), NULL, false);
fill_item(&ctx, "name.action", "ToggleShade"); item_add_action(item, "ToggleShade");
ctx.item = item_create(menu, _("Decorations"), false); item = item_create(menu, _("Decorations"), NULL, false);
fill_item(&ctx, "name.action", "ToggleDecorations"); item_add_action(item, "ToggleDecorations");
ctx.item = item_create(menu, _("Always on Top"), false); item = item_create(menu, _("Always on Top"), NULL, false);
fill_item(&ctx, "name.action", "ToggleAlwaysOnTop"); item_add_action(item, "ToggleAlwaysOnTop");
/* Workspace sub-menu */ /* Workspace sub-menu */
struct menu *workspace_menu = struct menu *workspace_menu =
menu_create(server, NULL, "workspaces", ""); 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 * <action name="SendToDesktop"><follow> is true by default so
* GoToDesktop will be called as part of the action. * GoToDesktop will be called as part of the action.
*/ */
fill_item(&ctx, "name.action", "SendToDesktop"); action = item_add_action(item, "SendToDesktop");
fill_item(&ctx, "to.action", "left"); action_arg_add_str(action, "to", "left");
ctx.item = item_create(workspace_menu, _("Move Right"), false); item = item_create(workspace_menu, _("Move Right"), NULL, false);
fill_item(&ctx, "name.action", "SendToDesktop"); action = item_add_action(item, "SendToDesktop");
fill_item(&ctx, "to.action", "right"); action_arg_add_str(action, "to", "right");
ctx.item = separator_create(workspace_menu, ""); separator_create(workspace_menu, "");
ctx.item = item_create(workspace_menu, item = item_create(workspace_menu,
_("Always on Visible Workspace"), false); _("Always on Visible Workspace"), NULL, false);
fill_item(&ctx, "name.action", "ToggleOmnipresent"); item_add_action(item, "ToggleOmnipresent");
ctx.item = item_create(menu, _("Workspace"), true); item = item_create(menu, _("Workspace"), NULL, true);
ctx.item->submenu = workspace_menu; item->submenu = workspace_menu;
ctx.item = item_create(menu, _("Close"), false); item = item_create(menu, _("Close"), NULL, false);
fill_item(&ctx, "name.action", "Close"); item_add_action(item, "Close");
} }
if (wl_list_length(&rc.workspace_config.workspaces) == 1) { if (wl_list_length(&rc.workspace_config.workspaces) == 1) {
@ -1348,11 +1240,7 @@ static void
create_pipe_menu(struct menu_pipe_context *ctx) create_pipe_menu(struct menu_pipe_context *ctx)
{ {
struct server *server = ctx->pipemenu->server; struct server *server = ctx->pipemenu->server;
struct menu_parse_context parse_ctx = { if (!parse_buf(server, ctx->pipemenu, &ctx->buf)) {
.server = server,
.menu = ctx->pipemenu,
};
if (!parse_buf(&parse_ctx, &ctx->buf)) {
return; return;
} }
/* TODO: apply validate() only for generated pipemenus */ /* TODO: apply validate() only for generated pipemenus */