mirror of
https://github.com/labwc/labwc.git
synced 2025-11-04 13:30:07 -05:00
menu: support pipemenu with the toplevel <menu> element
For example:
<?xml version="1.0"?>
<openbox_menu>
<menu id="root-menu" label="" execute="obmenu-generator"/>
</openbox_menu>
Fixes: #2238
Co-Authored-By: @Consolatis
This commit is contained in:
parent
a93eb84335
commit
956b271f9b
3 changed files with 90 additions and 15 deletions
|
|
@ -112,6 +112,16 @@ 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 "&" ("&"), "<" ("<"), and ">" (">").
|
as "&" ("&"), "<" ("<"), and ">" (">").
|
||||||
|
|
||||||
|
A pipemenu can also be used to define the toplevel *<menu>* element. In this
|
||||||
|
case the entire menu.xml file would be reduced to something like this (replacing
|
||||||
|
obmenu-generator with the menu generator of your choice):
|
||||||
|
|
||||||
|
```
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openbox_menu>
|
||||||
|
<menu id="root-menu" label="" execute="obmenu-generator"/>
|
||||||
|
</openbox_menu>
|
||||||
|
```
|
||||||
|
|
||||||
# LOCALISATION
|
# LOCALISATION
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ struct menu {
|
||||||
char *id;
|
char *id;
|
||||||
char *label;
|
char *label;
|
||||||
struct menu *parent;
|
struct menu *parent;
|
||||||
|
struct menu_pipe_context *pipe_ctx;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int width;
|
int width;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ static struct menuitem *selected_item;
|
||||||
struct menu_pipe_context {
|
struct menu_pipe_context {
|
||||||
struct server *server;
|
struct server *server;
|
||||||
struct menuitem *item;
|
struct menuitem *item;
|
||||||
|
struct menu *top_level_menu;
|
||||||
struct buf buf;
|
struct buf buf;
|
||||||
struct wl_event_source *event_read;
|
struct wl_event_source *event_read;
|
||||||
struct wl_event_source *event_timeout;
|
struct wl_event_source *event_timeout;
|
||||||
|
|
@ -606,6 +607,36 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id)
|
||||||
return id && nr_parents(n) == 2;
|
return id && nr_parents(n) == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool parse_buf(struct server *server, struct buf *buf);
|
||||||
|
static int handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx);
|
||||||
|
static int handle_pipemenu_timeout(void *_ctx);
|
||||||
|
|
||||||
|
static void
|
||||||
|
parse_root_pipemenu(struct menu *top_level_menu, const char *execute)
|
||||||
|
{
|
||||||
|
int pipe_fd = 0;
|
||||||
|
pid_t pid = spawn_piped(execute, &pipe_fd);
|
||||||
|
if (pid <= 0) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to spawn pipe menu process %s", execute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct menu_pipe_context *ctx = znew(*ctx);
|
||||||
|
ctx->server = top_level_menu->server;
|
||||||
|
ctx->top_level_menu = top_level_menu;
|
||||||
|
ctx->pid = pid;
|
||||||
|
ctx->pipe_fd = pipe_fd;
|
||||||
|
ctx->buf = BUF_INIT;
|
||||||
|
top_level_menu->pipe_ctx = ctx;
|
||||||
|
|
||||||
|
ctx->event_read = wl_event_loop_add_fd(ctx->server->wl_event_loop,
|
||||||
|
pipe_fd, WL_EVENT_READABLE, handle_pipemenu_readable, ctx);
|
||||||
|
|
||||||
|
ctx->event_timeout = wl_event_loop_add_timer(ctx->server->wl_event_loop,
|
||||||
|
handle_pipemenu_timeout, ctx);
|
||||||
|
wl_event_source_timer_update(ctx->event_timeout, PIPEMENU_TIMEOUT_IN_MS);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* <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
|
||||||
|
|
@ -623,25 +654,22 @@ handle_menu_element(xmlNode *n, struct server *server)
|
||||||
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
|
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
|
||||||
if (!current_menu) {
|
if (!current_menu) {
|
||||||
/*
|
/*
|
||||||
* We currently do not support pipemenus without a
|
* Handle pipemenu as the root-menu such this:
|
||||||
* parent <item> such as the one the example below:
|
|
||||||
*
|
*
|
||||||
* <?xml version="1.0" encoding="UTF-8"?>
|
* <?xml version="1.0" encoding="UTF-8"?>
|
||||||
* <openbox_menu>
|
* <openbox_menu>
|
||||||
* <menu id="root-menu" label="foo" execute="bar"/>
|
* <menu id="root-menu" label="foo" execute="bar"/>
|
||||||
* </openbox_menu>
|
* </openbox_menu>
|
||||||
*
|
|
||||||
* TODO: Consider supporting this
|
|
||||||
*/
|
*/
|
||||||
wlr_log(WLR_ERROR,
|
struct menu *menu = menu_create(server, id, label);
|
||||||
"pipemenu '%s:%s:%s' has no parent <menu>",
|
parse_root_pipemenu(menu, execute);
|
||||||
id, label, execute);
|
} else {
|
||||||
goto error;
|
current_item = item_create(current_menu, label,
|
||||||
|
/* arrow */ true);
|
||||||
|
current_item_action = NULL;
|
||||||
|
current_item->execute = xstrdup(execute);
|
||||||
|
current_item->id = xstrdup(id);
|
||||||
}
|
}
|
||||||
current_item = item_create(current_menu, label, /* arrow */ true);
|
|
||||||
current_item_action = NULL;
|
|
||||||
current_item->execute = xstrdup(execute);
|
|
||||||
current_item->id = xstrdup(id);
|
|
||||||
} else if ((label && id) || is_toplevel_static_menu_definition(n, id)) {
|
} else if ((label && id) || is_toplevel_static_menu_definition(n, id)) {
|
||||||
/*
|
/*
|
||||||
* (label && id) refers to <menu id="" label=""> which is an
|
* (label && id) refers to <menu id="" label=""> which is an
|
||||||
|
|
@ -1167,6 +1195,10 @@ menu_free(struct menu *menu)
|
||||||
item_destroy(item);
|
item_destroy(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (menu->pipe_ctx) {
|
||||||
|
menu->pipe_ctx->top_level_menu = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Destroying the root node will destroy everything,
|
* Destroying the root node will destroy everything,
|
||||||
* including node descriptors and scaled_font_buffers.
|
* including node descriptors and scaled_font_buffers.
|
||||||
|
|
@ -1344,6 +1376,34 @@ menu_open_root(struct menu *menu, int x, int y)
|
||||||
static void
|
static void
|
||||||
create_pipe_menu(struct menu_pipe_context *ctx)
|
create_pipe_menu(struct menu_pipe_context *ctx)
|
||||||
{
|
{
|
||||||
|
if (ctx->top_level_menu) {
|
||||||
|
/*
|
||||||
|
* We execute the scripts for the toplevel pipemenus at startup
|
||||||
|
* or Reconfigure, but they can be opened before they finish
|
||||||
|
* execution, usually with their content empty. Make sure they
|
||||||
|
* are closed and emptied.
|
||||||
|
*/
|
||||||
|
if (ctx->server->menu_current == ctx->top_level_menu) {
|
||||||
|
menu_close_root(ctx->server);
|
||||||
|
}
|
||||||
|
struct menuitem *item, *tmp;
|
||||||
|
wl_list_for_each_safe(item, tmp, &ctx->top_level_menu->menuitems, link) {
|
||||||
|
item_destroy(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_level++;
|
||||||
|
current_menu = ctx->top_level_menu;
|
||||||
|
if (!parse_buf(ctx->server, &ctx->buf)) {
|
||||||
|
wlr_log(WLR_ERROR, "Failed to parse piped top level menu %s",
|
||||||
|
ctx->top_level_menu->id);
|
||||||
|
}
|
||||||
|
menu_level--;
|
||||||
|
post_processing(ctx->server);
|
||||||
|
validate(ctx->server);
|
||||||
|
menu_update_scene(current_menu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(ctx->item);
|
assert(ctx->item);
|
||||||
|
|
||||||
struct menu *pipe_parent = ctx->item->parent;
|
struct menu *pipe_parent = ctx->item->parent;
|
||||||
|
|
@ -1409,6 +1469,9 @@ pipemenu_ctx_destroy(struct menu_pipe_context *ctx)
|
||||||
if (ctx->item) {
|
if (ctx->item) {
|
||||||
ctx->item->pipe_ctx = NULL;
|
ctx->item->pipe_ctx = NULL;
|
||||||
}
|
}
|
||||||
|
if (ctx->top_level_menu) {
|
||||||
|
ctx->top_level_menu->pipe_ctx = NULL;
|
||||||
|
}
|
||||||
free(ctx);
|
free(ctx);
|
||||||
waiting_for_pipe_menu = false;
|
waiting_for_pipe_menu = false;
|
||||||
}
|
}
|
||||||
|
|
@ -1432,7 +1495,7 @@ handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx)
|
||||||
char data[8193];
|
char data[8193];
|
||||||
ssize_t size;
|
ssize_t size;
|
||||||
|
|
||||||
if (!ctx->item) {
|
if (!ctx->item && !ctx->top_level_menu) {
|
||||||
/* parent menu item got destroyed in the meantime */
|
/* parent menu item got destroyed in the meantime */
|
||||||
wlr_log(WLR_INFO, "[pipemenu %ld] parent menu item destroyed",
|
wlr_log(WLR_INFO, "[pipemenu %ld] parent menu item destroyed",
|
||||||
(long)ctx->pid);
|
(long)ctx->pid);
|
||||||
|
|
@ -1447,14 +1510,15 @@ handle_pipemenu_readable(int fd, uint32_t mask, void *_ctx)
|
||||||
|
|
||||||
if (size == -1) {
|
if (size == -1) {
|
||||||
wlr_log_errno(WLR_ERROR, "[pipemenu %ld] failed to read data (%s)",
|
wlr_log_errno(WLR_ERROR, "[pipemenu %ld] failed to read data (%s)",
|
||||||
(long)ctx->pid, ctx->item->execute);
|
(long)ctx->pid, ctx->item ? ctx->item->execute : "n/a");
|
||||||
goto clean_up;
|
goto clean_up;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Limit pipemenu buffer to 1 MiB for safety */
|
/* Limit pipemenu buffer to 1 MiB for safety */
|
||||||
if (ctx->buf.len + size > PIPEMENU_MAX_BUF_SIZE) {
|
if (ctx->buf.len + size > PIPEMENU_MAX_BUF_SIZE) {
|
||||||
wlr_log(WLR_ERROR, "[pipemenu %ld] too big (> %d bytes); killing %s",
|
wlr_log(WLR_ERROR, "[pipemenu %ld] too big (> %d bytes); killing %s",
|
||||||
(long)ctx->pid, PIPEMENU_MAX_BUF_SIZE, ctx->item->execute);
|
(long)ctx->pid, PIPEMENU_MAX_BUF_SIZE,
|
||||||
|
ctx->item ? ctx->item->execute : "n/a");
|
||||||
kill(ctx->pid, SIGTERM);
|
kill(ctx->pid, SIGTERM);
|
||||||
goto clean_up;
|
goto clean_up;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue