diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd index a50fa254..ae6c425a 100644 --- a/docs/labwc-menu.5.scd +++ b/docs/labwc-menu.5.scd @@ -106,13 +106,23 @@ ID attributes are unique. Duplicates are ignored. When writing pipe menu scripts, make sure to escape XML special characters such as "&" ("&"), "<" ("<"), and ">" (">"). - # LOCALISATION Available localisation for the default "client-menu" is only shown if no "client-menu" is present in menu.xml. Any menu definition in menu.xml is interpreted as a user-override. +# YAML SUPPORT + +Like rc.yaml, labwc supports menu.yaml instead of menu.xml. See labwc-config(5) +for its syntax. Note that following keys in singular form can be expressed as +plural form in menu.yaml: + + - *menus* (converted to *menu*) + - *items* (converted to *item*) + +See /usr/share/docs/labwc/menu.yaml for an example menu configuration in YAML. + # SEE ALSO labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5) diff --git a/docs/menu.yaml b/docs/menu.yaml new file mode 100644 index 00000000..842799f8 --- /dev/null +++ b/docs/menu.yaml @@ -0,0 +1,33 @@ +menu: + - id: client-menu + item: + label: Minimize + action: { name: Iconify } + item: + label: Maximize + action: { name: ToggleMaximize } + menu: + id: workspaces + label: Workspace + item: + label: Move Left + action: { name: SendToDesktop, to: left } + item: + label: Move Right + action: { name: SendToDesktop, to: right } + separator: + item: + label: Always on Visible Workspace + action: { name: ToggleOmnipresent } + item: + label: Close + action: { name: Close } + + - id: root-menu + items: + - label: Terminal + action: { name: Execute, command: alacritty } + - label: Reconfigure + action: { name: Reconfigure } + - label: Exit + action: { name: Exit } diff --git a/src/common/yaml2xml.c b/src/common/yaml2xml.c index 5ac1e6e5..82fa7f65 100644 --- a/src/common/yaml2xml.c +++ b/src/common/yaml2xml.c @@ -82,6 +82,10 @@ process_value(yaml_parser_t *parser, struct buf *b, key_name = "font"; } else if (!strcasecmp(key_name, "contexts")) { key_name = "context"; + } else if (!strcasecmp(key_name, "items")) { + key_name = "item"; + } else if (!strcasecmp(key_name, "menus")) { + key_name = "menu"; } if (parent_name) { buf_add_fmt(b, "<%s>", parent_name); diff --git a/src/menu/menu.c b/src/menu/menu.c index f8653d64..a8039a09 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -26,6 +26,10 @@ #include "node.h" #include "theme.h" +#if HAVE_LIBYAML +#include "common/yaml2xml.h" +#endif + #define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */ #define PIPEMENU_TIMEOUT_IN_MS 4000 /* 4 seconds */ @@ -566,6 +570,35 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id) return id && nr_parents(n) == 2; } +static char * +get_property(xmlNode *n, const char *name) +{ + /* First, search from attributes */ + char *prop = (char *)xmlGetProp(n, (const xmlChar *)name); + if (prop) { + return prop; + } + + /* Then search from child nodes */ + xmlNode *child; + for (child = n->children; child && child->name; + child = child->next) { + if (!strcmp((char *)child->name, name)) { + goto found_child_node; + } + } + return NULL; + +found_child_node: + for (child = child->children; child && child->name; + child = child->next) { + if (child->type == XML_TEXT_NODE) { + return xstrdup((char *)child->content); + } + } + return NULL; +} + /* * elements have three different roles: * * Definition of (sub)menu - has ID, LABEL and CONTENT @@ -575,9 +608,9 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id) static void handle_menu_element(xmlNode *n, struct server *server) { - char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); - char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute"); - char *id = (char *)xmlGetProp(n, (const xmlChar *)"id"); + char *label = get_property(n, "label"); + char *execute = get_property(n, "execute"); + char *id = get_property(n, "id"); if (execute && label && id) { wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute); @@ -676,7 +709,7 @@ error: static void handle_separator_element(xmlNode *n) { - char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); + char *label = get_property(n, "label"); current_item = separator_create(current_menu, label); free(label); } @@ -725,36 +758,13 @@ parse_buf(struct server *server, struct buf *buf) return true; } -/* - * @stream can come from either of the following: - * - fopen() in the case of reading a file such as menu.xml - * - popen() when processing pipemenus - */ -static void -parse_stream(struct server *server, FILE *stream) -{ - char *line = NULL; - size_t len = 0; - struct buf b = BUF_INIT; - - while (getline(&line, &len, stream) != -1) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&b, line); - } - free(line); - parse_buf(server, &b); - buf_reset(&b); -} - -static void -parse_xml(const char *filename, struct server *server) +static bool +parse_menu_file(const char *filename, struct server *server) { struct wl_list paths; paths_config_create(&paths, filename); + bool file_found = false; bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; @@ -765,14 +775,37 @@ parse_xml(const char *filename, struct server *server) if (!stream) { continue; } + file_found = true; wlr_log(WLR_INFO, "read menu file %s", path->string); - parse_stream(server, stream); + + struct buf b = BUF_INIT; + if (HAVE_LIBYAML && str_endswith(path->string, ".yaml")) { +#if HAVE_LIBYAML + b = yaml_to_xml(stream, "openbox_menu"); +#endif + } else { + char *line = NULL; + size_t len = 0; + while (getline(&line, &len, stream) != -1) { + char *p = strrchr(line, '\n'); + if (p) { + *p = '\0'; + } + buf_add(&b, line); + } + free(line); + } fclose(stream); + if (b.len > 0) { + parse_buf(server, &b); + } + buf_reset(&b); if (!should_merge_config) { break; } } paths_destroy(&paths); + return file_found; } static int @@ -983,7 +1016,13 @@ void menu_init(struct server *server) { wl_list_init(&server->menus); - parse_xml("menu.xml", server); + bool file_found = parse_menu_file("menu.xml", server); + (void)file_found; +#if HAVE_LIBYAML + if (!file_found) { + parse_menu_file("menu.yaml", server); + } +#endif init_rootmenu(server); init_windowmenu(server); post_processing(server);