[experiment] support menu.yaml

This commit is contained in:
tokyo4j 2024-08-23 15:27:08 +09:00
parent 85b6e25484
commit 52a1c1c880
4 changed files with 119 additions and 33 deletions

View file

@ -106,13 +106,23 @@ ID attributes are unique. Duplicates are ignored.
When writing pipe menu scripts, make sure to escape XML special characters such When writing pipe menu scripts, make sure to escape XML special characters such
as "&" ("&amp;"), "<" ("&lt;"), and ">" ("&gt;"). as "&" ("&amp;"), "<" ("&lt;"), and ">" ("&gt;").
# LOCALISATION # LOCALISATION
Available localisation for the default "client-menu" is only shown if no 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 "client-menu" is present in menu.xml. Any menu definition in menu.xml is
interpreted as a user-override. 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 # SEE ALSO
labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5) labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5)

33
docs/menu.yaml Normal file
View file

@ -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 }

View file

@ -82,6 +82,10 @@ process_value(yaml_parser_t *parser, struct buf *b,
key_name = "font"; key_name = "font";
} else if (!strcasecmp(key_name, "contexts")) { } else if (!strcasecmp(key_name, "contexts")) {
key_name = "context"; key_name = "context";
} else if (!strcasecmp(key_name, "items")) {
key_name = "item";
} else if (!strcasecmp(key_name, "menus")) {
key_name = "menu";
} }
if (parent_name) { if (parent_name) {
buf_add_fmt(b, "<%s>", parent_name); buf_add_fmt(b, "<%s>", parent_name);

View file

@ -26,6 +26,10 @@
#include "node.h" #include "node.h"
#include "theme.h" #include "theme.h"
#if HAVE_LIBYAML
#include "common/yaml2xml.h"
#endif
#define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */ #define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */
#define PIPEMENU_TIMEOUT_IN_MS 4000 /* 4 seconds */ #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; 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;
}
/* /*
* <menu> elements have three different roles: * <menu> elements have three different roles:
* * Definition of (sub)menu - has ID, LABEL and CONTENT * * Definition of (sub)menu - has ID, LABEL and CONTENT
@ -575,9 +608,9 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id)
static void static void
handle_menu_element(xmlNode *n, struct server *server) handle_menu_element(xmlNode *n, struct server *server)
{ {
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); char *label = get_property(n, "label");
char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute"); char *execute = get_property(n, "execute");
char *id = (char *)xmlGetProp(n, (const xmlChar *)"id"); char *id = get_property(n, "id");
if (execute && label && id) { if (execute && label && id) {
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute); wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
@ -676,7 +709,7 @@ error:
static void static void
handle_separator_element(xmlNode *n) 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); current_item = separator_create(current_menu, label);
free(label); free(label);
} }
@ -725,36 +758,13 @@ parse_buf(struct server *server, struct buf *buf)
return true; return true;
} }
/* static bool
* @stream can come from either of the following: parse_menu_file(const char *filename, struct server *server)
* - 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)
{ {
struct wl_list paths; struct wl_list paths;
paths_config_create(&paths, filename); paths_config_create(&paths, filename);
bool file_found = false;
bool should_merge_config = rc.merge_config; bool should_merge_config = rc.merge_config;
struct wl_list *(*iter)(struct wl_list *list); struct wl_list *(*iter)(struct wl_list *list);
iter = should_merge_config ? paths_get_prev : paths_get_next; iter = should_merge_config ? paths_get_prev : paths_get_next;
@ -765,14 +775,37 @@ parse_xml(const char *filename, struct server *server)
if (!stream) { if (!stream) {
continue; continue;
} }
file_found = true;
wlr_log(WLR_INFO, "read menu file %s", path->string); 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); fclose(stream);
if (b.len > 0) {
parse_buf(server, &b);
}
buf_reset(&b);
if (!should_merge_config) { if (!should_merge_config) {
break; break;
} }
} }
paths_destroy(&paths); paths_destroy(&paths);
return file_found;
} }
static int static int
@ -983,7 +1016,13 @@ void
menu_init(struct server *server) menu_init(struct server *server)
{ {
wl_list_init(&server->menus); 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_rootmenu(server);
init_windowmenu(server); init_windowmenu(server);
post_processing(server); post_processing(server);