static struct menuitem *selected_item;
struct menu_pipe_context {
- struct server *server;
- struct menuitem *item;
- struct menu *top_level_menu;
+ struct wlr_box anchor_rect;
+ struct menu *pipemenu;
struct buf buf;
struct wl_event_source *event_read;
struct wl_event_source *event_timeout;
menu->label = xstrdup(label ? label : id);
menu->parent = current_menu;
menu->server = server;
- menu->is_pipemenu = waiting_for_pipe_menu;
- menu->size.width = server->theme->menu_min_width;
+ menu->is_pipemenu_child = waiting_for_pipe_menu;
return menu;
}
static void
item_destroy(struct menuitem *item)
{
- if (item->pipe_ctx) {
- item->pipe_ctx->item = NULL;
- }
wl_list_remove(&item->link);
action_list_free(&item->actions);
if (item->tree) {
wlr_scene_node_destroy(&item->tree->node);
}
- free(item->execute);
- free(item->id);
free(item->text);
free(item->icon_name);
free(item);
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:
* * Definition of (sub)menu - has ID, LABEL and CONTENT
if (execute && label && id) {
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
+
+ struct menu *pipemenu = menu_create(server, id, label);
+ pipemenu->execute = xstrdup(execute);
if (!current_menu) {
/*
- * Handle pipemenu as the root-menu such this:
+ * A pipemenu may not have its parent like:
*
* <?xml version="1.0" encoding="UTF-8"?>
* <openbox_menu>
* <menu id="root-menu" label="foo" execute="bar"/>
* </openbox_menu>
*/
- struct menu *menu = menu_create(server, id, label);
- parse_root_pipemenu(menu, execute);
} else {
current_item = item_create(current_menu, label,
/* arrow */ true);
fill_item("icon", icon_name);
current_item_action = NULL;
- current_item->execute = xstrdup(execute);
- current_item->id = xstrdup(id);
+ current_item->submenu = pipemenu;
}
} else if ((label && id) || is_toplevel_static_menu_definition(n, id)) {
/*
* pipemenu opening the "root-menu" or similar.
*/
- if (current_menu && current_menu->is_pipemenu) {
+ if (waiting_for_pipe_menu) {
wlr_log(WLR_ERROR,
"cannot link to static menu from pipemenu");
goto error;
}
}
+static void pipemenu_ctx_destroy(struct menu_pipe_context *ctx);
+
static void
menu_free(struct menu *menu)
{
}
if (menu->pipe_ctx) {
- menu->pipe_ctx->top_level_menu = NULL;
+ pipemenu_ctx_destroy(menu->pipe_ctx);
+ assert(!menu->pipe_ctx);
}
/*
zfree(menu->id);
zfree(menu->label);
zfree(menu->icon_name);
+ zfree(menu->execute);
zfree(menu);
}
* item may be selected multiple times.
*/
static void
-destroy_pipemenus(struct server *server)
+reset_pipemenus(struct server *server)
{
wlr_log(WLR_DEBUG, "number of menus before close=%d",
wl_list_length(&server->menus));
struct menu *iter, *tmp;
wl_list_for_each_safe(iter, tmp, &server->menus, link) {
- if (iter->is_pipemenu) {
+ if (iter->is_pipemenu_child) {
+ /* Destroy submenus of pipemenus */
menu_free(iter);
+ } else if (iter->execute) {
+ /*
+ * Destroy items and scene-nodes of pipemenus so that
+ * they are generated again when being opened
+ */
+ reset_menu(iter);
}
}
static void
_close(struct menu *menu)
{
- wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
+ if (menu->scene_tree) {
+ wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
+ }
menu_set_selection(menu, NULL);
if (menu->selection.menu) {
_close(menu->selection.menu);
menu->selection.menu = NULL;
}
+ if (menu->pipe_ctx) {
+ pipemenu_ctx_destroy(menu->pipe_ctx);
+ assert(!menu->pipe_ctx);
+ }
}
static void
wlr_scene_node_set_enabled(&menu->scene_tree->node, true);
}
+static void open_pipemenu_async(struct menu *pipemenu, struct wlr_box anchor_rect);
+
void
menu_open_root(struct menu *menu, int x, int y)
{
assert(!menu->server->menu_current);
- open_menu(menu, (struct wlr_box){.x = x, .y = y});
+ struct wlr_box anchor_rect = {.x = x, .y = y};
+ if (menu->execute) {
+ open_pipemenu_async(menu, anchor_rect);
+ } else {
+ open_menu(menu, anchor_rect);
+ }
+
menu->server->menu_current = menu;
selected_item = NULL;
seat_focus_override_begin(&menu->server->seat,
static void
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--;
- validate(ctx->server);
- return;
- }
-
- assert(ctx->item);
-
- struct menu *pipe_parent = ctx->item->parent;
- if (!pipe_parent) {
- wlr_log(WLR_INFO, "[pipemenu %ld] invalid parent",
- (long)ctx->pid);
- return;
- }
- if (!pipe_parent->scene_tree->node.enabled) {
- wlr_log(WLR_INFO, "[pipemenu %ld] parent menu already closed",
- (long)ctx->pid);
- return;
- }
-
- /*
- * Pipemenus do not contain a toplevel <menu> element so we have to
- * create that first `struct menu`.
- */
- struct menu *pipe_menu = menu_create(ctx->server, ctx->item->id, /*label*/ NULL);
- pipe_menu->is_pipemenu = true;
- pipe_menu->triggered_by_view = pipe_parent->triggered_by_view;
- pipe_menu->parent = pipe_parent;
-
+ struct server *server = ctx->pipemenu->server;
+ struct menu *old_current_menu = current_menu;
menu_level++;
- current_menu = pipe_menu;
- if (!parse_buf(ctx->server, &ctx->buf)) {
- menu_free(pipe_menu);
- ctx->item->submenu = NULL;
+ current_menu = ctx->pipemenu;
+ if (!parse_buf(server, &ctx->buf)) {
goto restore_menus;
}
- ctx->item->submenu = pipe_menu;
-
- /*
- * TODO: refactor validate() and post_processing() to only
- * operate from current point onwards
- */
-
- struct wlr_box anchor_rect =
- get_item_anchor_rect(ctx->server->theme, ctx->item);
- open_menu(pipe_menu, anchor_rect);
-
- validate(ctx->server);
+ /* TODO: apply validate() only for generated pipemenus */
+ validate(server);
/* Finally open the new submenu tree */
- wlr_scene_node_set_enabled(&pipe_menu->scene_tree->node, true);
- pipe_parent->selection.menu = pipe_menu;
+ open_menu(ctx->pipemenu, ctx->anchor_rect);
restore_menus:
- current_menu = pipe_parent;
+ current_menu = old_current_menu;
menu_level--;
}
wl_event_source_remove(ctx->event_timeout);
spawn_piped_close(ctx->pid, ctx->pipe_fd);
buf_reset(&ctx->buf);
- if (ctx->item) {
- ctx->item->pipe_ctx = NULL;
- }
- if (ctx->top_level_menu) {
- ctx->top_level_menu->pipe_ctx = NULL;
+ if (ctx->pipemenu) {
+ ctx->pipemenu->pipe_ctx = NULL;
}
free(ctx);
waiting_for_pipe_menu = false;
{
struct menu_pipe_context *ctx = _ctx;
wlr_log(WLR_ERROR, "[pipemenu %ld] timeout reached, killing %s",
- (long)ctx->pid, ctx->item ? ctx->item->execute : "n/a");
+ (long)ctx->pid, ctx->pipemenu->execute);
kill(ctx->pid, SIGTERM);
pipemenu_ctx_destroy(ctx);
return 0;
char data[8193];
ssize_t size;
- if (!ctx->item && !ctx->top_level_menu) {
- /* parent menu item got destroyed in the meantime */
- wlr_log(WLR_INFO, "[pipemenu %ld] parent menu item destroyed",
- (long)ctx->pid);
- kill(ctx->pid, SIGTERM);
- goto clean_up;
- }
-
do {
/* leave space for terminating NULL byte */
size = read(fd, data, sizeof(data) - 1);
if (size == -1) {
wlr_log_errno(WLR_ERROR, "[pipemenu %ld] failed to read data (%s)",
- (long)ctx->pid, ctx->item ? ctx->item->execute : "n/a");
+ (long)ctx->pid, ctx->pipemenu->execute);
goto clean_up;
}
if (ctx->buf.len + size > PIPEMENU_MAX_BUF_SIZE) {
wlr_log(WLR_ERROR, "[pipemenu %ld] too big (> %d bytes); killing %s",
(long)ctx->pid, PIPEMENU_MAX_BUF_SIZE,
- ctx->item ? ctx->item->execute : "n/a");
+ ctx->pipemenu->execute);
kill(ctx->pid, SIGTERM);
goto clean_up;
}
}
static void
-parse_pipemenu(struct menuitem *item)
+open_pipemenu_async(struct menu *pipemenu, struct wlr_box anchor_rect)
{
- if (!is_unique_id(item->parent->server, item->id)) {
- wlr_log(WLR_ERROR, "duplicate id '%s'; abort pipemenu", item->id);
- return;
- }
+ struct server *server = pipemenu->server;
- if (item->pipe_ctx) {
- wlr_log(WLR_ERROR, "item already has a pipe context attached");
- return;
- }
+ assert(!pipemenu->pipe_ctx);
+ assert(!pipemenu->scene_tree);
int pipe_fd = 0;
- pid_t pid = spawn_piped(item->execute, &pipe_fd);
+ pid_t pid = spawn_piped(pipemenu->execute, &pipe_fd);
if (pid <= 0) {
- wlr_log(WLR_ERROR, "Failed to spawn pipe menu process %s", item->execute);
+ wlr_log(WLR_ERROR, "Failed to spawn pipe menu process %s",
+ pipemenu->execute);
return;
}
waiting_for_pipe_menu = true;
struct menu_pipe_context *ctx = znew(*ctx);
- ctx->server = item->parent->server;
- ctx->item = item;
ctx->pid = pid;
ctx->pipe_fd = pipe_fd;
ctx->buf = BUF_INIT;
- item->pipe_ctx = ctx;
+ ctx->anchor_rect = anchor_rect;
+ ctx->pipemenu = pipemenu;
+ pipemenu->pipe_ctx = ctx;
- ctx->event_read = wl_event_loop_add_fd(ctx->server->wl_event_loop,
+ ctx->event_read = wl_event_loop_add_fd(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,
+ ctx->event_timeout = wl_event_loop_add_timer(server->wl_event_loop,
handle_pipemenu_timeout, ctx);
wl_event_source_timer_update(ctx->event_timeout, PIPEMENU_TIMEOUT_IN_MS);
- wlr_log(WLR_DEBUG, "[pipemenu %ld] executed: %s", (long)ctx->pid, ctx->item->execute);
+ wlr_log(WLR_DEBUG, "[pipemenu %ld] executed: %s",
+ (long)ctx->pid, ctx->pipemenu->execute);
}
static void
menu_close(item->parent->selection.menu);
}
- /* Pipemenu */
- if (item->execute && !item->submenu) {
- /* pipemenus are generated async */
- parse_pipemenu(item);
- return;
- }
-
if (item->submenu) {
/* Sync the triggering view */
item->submenu->triggered_by_view = item->parent->triggered_by_view;
/* And open the new submenu tree */
struct wlr_box anchor_rect =
get_item_anchor_rect(item->submenu->server->theme, item);
- open_menu(item->submenu, anchor_rect);
+ if (item->submenu->execute && !item->submenu->scene_tree) {
+ open_pipemenu_async(item->submenu, anchor_rect);
+ } else {
+ open_menu(item->submenu, anchor_rect);
+ }
}
item->parent->selection.menu = item->submenu;
&item->actions, NULL);
}
- destroy_pipemenus(server);
+ reset_pipemenus(server);
return true;
}
menu_close(server->menu_current);
server->menu_current = NULL;
- destroy_pipemenus(server);
+ reset_pipemenus(server);
seat_focus_override_end(&server->seat);
}