]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Rewrite action handling to allow multiple actions at once
authorConsolatis <35009135+Consolatis@users.noreply.github.com>
Wed, 5 Jan 2022 08:11:24 +0000 (09:11 +0100)
committerJohan Malm <johanmalm@users.noreply.github.com>
Wed, 5 Jan 2022 16:22:41 +0000 (16:22 +0000)
12 files changed:
include/action.h [new file with mode: 0644]
include/config/keybind.h
include/config/mousebind.h
include/labwc.h
include/menu/menu.h
src/action.c
src/config/keybind.c
src/config/mousebind.c
src/config/rcxml.c
src/cursor.c
src/keyboard.c
src/menu/menu.c

diff --git a/include/action.h b/include/action.h
new file mode 100644 (file)
index 0000000..78f3e2a
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __LABWC_ACTION_H
+#define __LABWC_ACTION_H
+
+struct action {
+       uint32_t type;
+       char *arg;
+       struct wl_list link;
+};
+
+struct action *action_create(const char *action_str);
+
+#endif
index 78caf9a311ccfc502e7f807bad824c053acebcb1..86eb4b52d05ae482534799040b7a49e7f8396e68 100644 (file)
@@ -11,8 +11,7 @@ struct keybind {
        uint32_t modifiers;
        xkb_keysym_t *keysyms;
        size_t keysyms_len;
-       char *action;
-       char *command;
+       struct wl_list actions;
        struct wl_list link;
 };
 
index dc96315f1c278c044c9d2f6dd00e66ae6353f906..da43594c7e1c6dcb5b037ba68926bd78194d4475 100644 (file)
@@ -24,8 +24,7 @@ struct mousebind {
 
        /* ex: doubleclick, press, drag */
        enum mouse_event mouse_event;
-       const char *action;
-       const char *command;
+       struct wl_list actions;
 
        struct wl_list link; /* rcxml::mousebinds */
        bool pressed_in_context; /* used in click events */
@@ -34,6 +33,5 @@ struct mousebind {
 enum mouse_event mousebind_event_from_str(const char *str);
 uint32_t mousebind_button_from_str(const char *str, uint32_t *modifiers);
 struct mousebind *mousebind_create(const char *context);
-struct mousebind *mousebind_create_from(struct mousebind *from, const char *context);
 
 #endif /* __LABWC_MOUSEBIND_H */
index e6a579fcd6a2435fb9fd6b4bd1e5add41e9877e6..8823f3efb9481fdc13ec1c1d1760513bd25e6c1a 100644 (file)
@@ -500,8 +500,8 @@ void server_init(struct server *server);
 void server_start(struct server *server);
 void server_finish(struct server *server);
 
-void action(struct view *activator, struct server *server, const char *action,
-       const char *command, uint32_t resize_edges);
+void action(struct view *activator, struct server *server,
+       struct wl_list *actions, uint32_t resize_edges);
 
 /* update onscreen display 'alt-tab' texture */
 void osd_update(struct server *server);
index 8f688c5cdb6ff0f03cf8f42964592bacc46189ef..627b9b63a678ef1be103f83f55353abae6a4a6d7 100644 (file)
@@ -6,8 +6,7 @@
 #include <wlr/render/wlr_renderer.h>
 
 struct menuitem {
-       char *action;
-       char *command;
+       struct wl_list actions;
        struct menu *submenu;
        struct wlr_box box;
        struct {
index 926db0639ac035dc3056953f52c6f8fe9e878347..ab2205583c93dc150a998d2e3e91a37345f581f2 100644 (file)
@@ -4,6 +4,78 @@
 #include "common/spawn.h"
 #include "labwc.h"
 #include "menu/menu.h"
+#include "action.h"
+
+enum action_type {
+       ACTION_TYPE_NONE = 0,
+       ACTION_TYPE_CLOSE,
+       ACTION_TYPE_DEBUG,
+       ACTION_TYPE_EXECUTE,
+       ACTION_TYPE_EXIT,
+       ACTION_TYPE_MOVE_TO_EDGE,
+       ACTION_TYPE_SNAP_TO_EDGE,
+       ACTION_TYPE_NEXT_WINDOW,
+       ACTION_TYPE_PREVIOUS_WINDOW,
+       ACTION_TYPE_RECONFIGURE,
+       ACTION_TYPE_SHOW_MENU,
+       ACTION_TYPE_TOGGLE_MAXIMIZE,
+       ACTION_TYPE_TOGGLE_FULLSCREEN,
+       ACTION_TYPE_TOGGLE_DECORATIONS,
+       ACTION_TYPE_FOCUS,
+       ACTION_TYPE_ICONIFY,
+       ACTION_TYPE_MOVE,
+       ACTION_TYPE_RAISE,
+       ACTION_TYPE_RESIZE,
+};
+
+const char *action_names[] = {
+       "NoOp",
+       "Close",
+       "Debug",
+       "Execute",
+       "Exit",
+       "MoveToEdge",
+       "SnapToEdge",
+       "NextWindow",
+       "PreviousWindow",
+       "Reconfigure",
+       "ShowMenu",
+       "ToggleMaximize",
+       "ToggleFullscreen",
+       "ToggleDecorations",
+       "Focus",
+       "Iconify",
+       "Move",
+       "Raise",
+       "Resize",
+       NULL
+};
+
+
+static enum action_type
+action_type_from_str(const char *action_name)
+{
+       for (size_t i=1; action_names[i] != NULL; i++) {
+               if (!strcasecmp(action_name, action_names[i])) {
+                       return i;
+               }
+       }
+       wlr_log(WLR_ERROR, "Invalid action: %s", action_name);
+       return ACTION_TYPE_NONE;
+}
+
+struct action *
+action_create(const char *action_name)
+{
+       if (!action_name) {
+               wlr_log(WLR_ERROR, "action name not specified");
+               return NULL;
+       }
+       struct action *action = calloc(1, sizeof(struct action));
+       action->type = action_type_from_str(action_name);
+       return action;
+}
+
 
 static void
 show_menu(struct server *server, const char *menu)
@@ -26,85 +98,123 @@ activator_or_focused_view(struct view *activator, struct server *server)
 }
 
 void
-action(struct view *activator, struct server *server, const char *action, const char *command, uint32_t resize_edges)
+action(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges)
 {
-       if (!action)
+       if (!actions) {
+               wlr_log(WLR_ERROR, "empty actions");
                return;
-       if (!strcasecmp(action, "Close")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       view_close(view);
-               }
-       } else if (!strcasecmp(action, "Debug")) {
-               /* nothing */
-       } else if (!strcasecmp(action, "Execute")) {
-               struct buf cmd;
-               buf_init(&cmd);
-               buf_add(&cmd, command);
-               buf_expand_shell_variables(&cmd);
-               spawn_async_no_shell(cmd.buf);
-               free(cmd.buf);
-       } else if (!strcasecmp(action, "Exit")) {
-               wl_display_terminate(server->wl_display);
-       } else if (!strcasecmp(action, "MoveToEdge")) {
-               view_move_to_edge(activator_or_focused_view(activator, server), command);
-       } else if (!strcasecmp(action, "SnapToEdge")) {
-               view_snap_to_edge(activator_or_focused_view(activator, server), command);
-       } else if (!strcasecmp(action, "NextWindow")) {
-               server->cycle_view =
-                       desktop_cycle_view(server, server->cycle_view, LAB_CYCLE_DIR_FORWARD);
-               osd_update(server);
-       } else if (!strcasecmp(action, "PreviousWindow")) {
-               server->cycle_view =
-                       desktop_cycle_view(server, server->cycle_view, LAB_CYCLE_DIR_BACKWARD);
-               osd_update(server);
-       } else if (!strcasecmp(action, "Reconfigure")) {
-               spawn_async_no_shell("killall -SIGHUP labwc");
-       } else if (!strcasecmp(action, "ShowMenu")) {
-               show_menu(server, command);
-       } else if (!strcasecmp(action, "ToggleMaximize")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       view_toggle_maximize(view);
-               }
-       } else if (!strcasecmp(action, "ToggleFullscreen")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       view_toggle_fullscreen(view);
-               }
-       } else if (!strcasecmp(action, "ToggleDecorations")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       view_toggle_decorations(view);
-               }
-       } else if (!strcasecmp(action, "Focus")) {
-               struct view *view = desktop_view_at_cursor(server);
-               if (view) {
-                       desktop_focus_and_activate_view(&server->seat, view);
-                       damage_all_outputs(server);
-               }
-       } else if (!strcasecmp(action, "Iconify")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       view_minimize(view, true);
-               }
-       } else if (!strcasecmp(action, "Move")) {
-               struct view *view = desktop_view_at_cursor(server);
-               if (view) {
-                       interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
-               }
-       } else if (!strcasecmp(action, "Raise")) {
-               struct view *view = activator_or_focused_view(activator, server);
-               if (view) {
-                       desktop_move_to_front(view);
-                       damage_all_outputs(server);
-               }
-       } else if (!strcasecmp(action, "Resize")) {
-               struct view *view = desktop_view_at_cursor(server);
-               if (view) {
-                       interactive_begin(view, LAB_INPUT_STATE_RESIZE, resize_edges);
+       }
+
+       struct view *view;
+       struct action *action;
+       wl_list_for_each(action, actions, link) {
+               wlr_log(WLR_DEBUG, "Handling action %s (%u) with arg %s",
+                        action_names[action->type], action->type, action->arg);
+
+               /* Refetch view because it may have been changed due to the previous action */
+               view = activator_or_focused_view(activator, server);
+
+               switch(action->type) {
+               case ACTION_TYPE_CLOSE:;
+                       if (view) {
+                               view_close(view);
+                       }
+                       break;
+               case ACTION_TYPE_DEBUG:;
+                       /* nothing */
+                       break;
+               case ACTION_TYPE_EXECUTE:;
+                       {
+                               struct buf cmd;
+                               buf_init(&cmd);
+                               buf_add(&cmd, action->arg);
+                               buf_expand_shell_variables(&cmd);
+                               spawn_async_no_shell(cmd.buf);
+                               free(cmd.buf);
+                       }
+                       break;
+               case ACTION_TYPE_EXIT:;
+                       wl_display_terminate(server->wl_display);
+                       break;
+               case ACTION_TYPE_MOVE_TO_EDGE:;
+                       view_move_to_edge(view, action->arg);
+                       break;
+               case ACTION_TYPE_SNAP_TO_EDGE:;
+                       view_snap_to_edge(view, action->arg);
+                       break;
+               case ACTION_TYPE_NEXT_WINDOW:;
+                       server->cycle_view =
+                               desktop_cycle_view(server, server->cycle_view, LAB_CYCLE_DIR_FORWARD);
+                       osd_update(server);
+                       break;
+               case ACTION_TYPE_PREVIOUS_WINDOW:;
+                       server->cycle_view =
+                               desktop_cycle_view(server, server->cycle_view, LAB_CYCLE_DIR_BACKWARD);
+                       osd_update(server);
+                       break;
+               case ACTION_TYPE_RECONFIGURE:;
+                       /* Should be changed to signal() */
+                       spawn_async_no_shell("killall -SIGHUP labwc");
+                       break;
+               case ACTION_TYPE_SHOW_MENU:;
+                       show_menu(server, action->arg);
+                       break;
+               case ACTION_TYPE_TOGGLE_MAXIMIZE:;
+                       if (view) {
+                               view_toggle_maximize(view);
+                       }
+                       break;
+               case ACTION_TYPE_TOGGLE_FULLSCREEN:;
+                       if (view) {
+                               view_toggle_fullscreen(view);
+                       }
+                       break;
+               case ACTION_TYPE_TOGGLE_DECORATIONS:;
+                       if (view) {
+                               view_toggle_decorations(view);
+                       }
+                       break;
+               case ACTION_TYPE_FOCUS:;
+                       view = desktop_view_at_cursor(server);
+                       if (view) {
+                               desktop_focus_and_activate_view(&server->seat, view);
+                               damage_all_outputs(server);
+                       }
+                       break;
+               case ACTION_TYPE_ICONIFY:;
+                       if (view) {
+                               view_minimize(view, true);
+                       }
+                       break;
+               case ACTION_TYPE_MOVE:;
+                       view = desktop_view_at_cursor(server);
+                       if (view) {
+                               interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
+                       }
+                       break;
+               case ACTION_TYPE_RAISE:;
+                       if (view) {
+                               desktop_move_to_front(view);
+                               damage_all_outputs(server);
+                       }
+                       break;
+               case ACTION_TYPE_RESIZE:;
+                       view = desktop_view_at_cursor(server);
+                       if (view) {
+                               interactive_begin(view, LAB_INPUT_STATE_RESIZE, resize_edges);
+                       }
+                       break;
+               case ACTION_TYPE_NONE:;
+                       wlr_log(WLR_ERROR, "Not executing unknown action with arg %s", action->arg);
+                       break;
+               default:;
+                       /*
+                        * If we get here it must be a BUG caused most likely by
+                        * action_names and action_type being out of sync or by
+                        * adding a new action without installing a handler here.
+                        */
+                       wlr_log(WLR_ERROR, "Not executing invalid action (%u) with arg %s"
+                               "This is a BUG. Please report.", action->type, action->arg);
                }
-       } else {
-               wlr_log(WLR_ERROR, "(%s) not supported", action);
        }
 }
index 3a6066ee6cf81412260937a4e8442c407656baa7..88883b8f82fc2e54d8cfd662ac589a598dfd97c0 100644 (file)
@@ -61,5 +61,6 @@ keybind_create(const char *keybind)
        wl_list_insert(&rc.keybinds, &k->link);
        k->keysyms = malloc(k->keysyms_len * sizeof(xkb_keysym_t));
        memcpy(k->keysyms, keysyms, k->keysyms_len * sizeof(xkb_keysym_t));
+       wl_list_init(&k->actions);
        return k;
 }
index 903be153c647050d50622b2d885b678073fd9aae..091e582453a02e421a6b02717b0955e2b300d68f 100644 (file)
@@ -108,19 +108,6 @@ mousebind_create(const char *context)
        if (m->context != LAB_SSD_NONE) {
                wl_list_insert(&rc.mousebinds, &m->link);
        }
-       return m;
-}
-
-struct mousebind *
-mousebind_create_from(struct mousebind *from, const char *context)
-{
-       if (!from) {
-               wlr_log(WLR_ERROR, "invalid mousebind instance specified");
-               return NULL;
-       }
-       struct mousebind *m = mousebind_create(context);
-       m->button = from->button;
-       m->modifiers = from->modifiers;
-       m->mouse_event = from->mouse_event;
+       wl_list_init(&m->actions);
        return m;
 }
index c73f3aa17949944eac0485aa0ec5f4a80ccadf1f..d65c657b6353317802d75b9d5baf08c563b4b8ce 100644 (file)
@@ -13,6 +13,7 @@
 #include <unistd.h>
 #include <wayland-server-core.h>
 #include <wlr/util/log.h>
+#include "action.h"
 #include "common/dir.h"
 #include "common/nodename.h"
 #include "common/string-helpers.h"
@@ -29,6 +30,8 @@ static struct keybind *current_keybind;
 static struct mousebind *current_mousebind;
 static struct libinput_category *current_libinput_category;
 static const char *current_mouse_context;
+static struct action *current_keybind_action;
+static struct action *current_mousebind_action;
 
 enum font_place {
        FONT_PLACE_UNKNOWN = 0,
@@ -59,13 +62,15 @@ fill_keybind(char *nodename, char *content)
                return;
        }
        if (!strcmp(nodename, "name.action")) {
-               current_keybind->action = strdup(content);
+               current_keybind_action = action_create(content);
+               wl_list_insert(current_keybind->actions.prev,
+                       &current_keybind_action->link);
        } else if (!strcmp(nodename, "command.action")) {
-               current_keybind->command = strdup(content);
+               current_keybind_action->arg = strdup(content);
        } else if (!strcmp(nodename, "direction.action")) {
-               current_keybind->command = strdup(content);
+               current_keybind_action->arg = strdup(content);
        } else if (!strcmp(nodename, "menu.action")) {
-               current_keybind->command = strdup(content);
+               current_keybind_action->arg = strdup(content);
        }
 }
 
@@ -99,15 +104,15 @@ fill_mousebind(char *nodename, char *content)
                current_mousebind->mouse_event =
                        mousebind_event_from_str(content);
        } else if (!strcmp(nodename, "name.action")) {
-               if (current_mousebind->action) {
-                       current_mousebind = mousebind_create_from(current_mousebind,
-                               current_mouse_context);
-               }
-               current_mousebind->action = strdup(content);
+               current_mousebind_action = action_create(content);
+               wl_list_insert(current_mousebind->actions.prev,
+                       &current_mousebind_action->link);
        } else if (!strcmp(nodename, "command.action")) {
-               current_mousebind->command = strdup(content);
+               current_mousebind_action->arg = strdup(content);
+       } else if (!strcmp(nodename, "direction.action")) {
+               current_mousebind_action->arg = strdup(content);
        } else if (!strcmp(nodename, "menu.action")) {
-               current_mousebind->command = strdup(content);
+               current_mousebind_action->arg = strdup(content);
        }
 }
 
@@ -476,14 +481,19 @@ static struct {
 static void
 load_default_key_bindings(void)
 {
+       struct keybind *k;
+       struct action *action;
        for (int i = 0; key_combos[i].binding; i++) {
-               struct keybind *k = keybind_create(key_combos[i].binding);
+               k = keybind_create(key_combos[i].binding);
                if (!k) {
                        continue;
                }
-               k->action = strdup(key_combos[i].action);
+
+               action = action_create(key_combos[i].action);
+               wl_list_insert(k->actions.prev, &action->link);
+
                if (key_combos[i].command) {
-                       k->command = strdup(key_combos[i].command);
+                       action->arg = strdup(key_combos[i].command);
                }
        }
 }
@@ -527,14 +537,19 @@ static struct {
 static void
 load_default_mouse_bindings(void)
 {
+       struct mousebind *m;
+       struct action *action;
        for (int i = 0; mouse_combos[i].context; i++) {
-               struct mousebind *m = mousebind_create(mouse_combos[i].context);
+               m = mousebind_create(mouse_combos[i].context);
                m->button = mousebind_button_from_str(mouse_combos[i].button,
                        &m->modifiers);
                m->mouse_event = mousebind_event_from_str(mouse_combos[i].event);
-               m->action = strdup(mouse_combos[i].action);
+
+               action = action_create(mouse_combos[i].action);
+               wl_list_insert(m->actions.prev, &action->link);
+
                if (mouse_combos[i].command) {
-                       m->command = strdup(mouse_combos[i].command);
+                       action->arg = strdup(mouse_combos[i].command);
                }
        }
 }
@@ -635,6 +650,8 @@ no_config:
 void
 rcxml_finish(void)
 {
+       struct action *action, *action_tmp;
+
        zfree(rc.font_name_activewindow);
        zfree(rc.font_name_menuitem);
        zfree(rc.font_name_osd);
@@ -643,8 +660,11 @@ rcxml_finish(void)
        struct keybind *k, *k_tmp;
        wl_list_for_each_safe(k, k_tmp, &rc.keybinds, link) {
                wl_list_remove(&k->link);
-               zfree(k->command);
-               zfree(k->action);
+               wl_list_for_each_safe(action, action_tmp, &k->actions, link) {
+                       wl_list_remove(&action->link);
+                       zfree(action->arg);
+                       zfree(action);
+               }
                zfree(k->keysyms);
                zfree(k);
        }
@@ -652,8 +672,11 @@ rcxml_finish(void)
        struct mousebind *m, *m_tmp;
        wl_list_for_each_safe(m, m_tmp, &rc.mousebinds, link) {
                wl_list_remove(&m->link);
-               zfree(m->command);
-               zfree(m->action);
+               wl_list_for_each_safe(action, action_tmp, &m->actions, link) {
+                       wl_list_remove(&action->link);
+                       zfree(action->arg);
+                       zfree(action);
+               }
                zfree(m);
        }
 
index 55da096cebea9bccb9ab56903a67eb21ce0de549..d9836beee1de797fb232a1fb364895e522aaf1ff 100644 (file)
@@ -470,8 +470,7 @@ handle_release_mousebinding(struct view *view, struct server *server,
                        }
                        activated_any = true;
                        activated_any_frame |= mousebind->context == LAB_SSD_FRAME;
-                       action(view, server, mousebind->action,
-                               mousebind->command, resize_edges);
+                       action(view, server, &mousebind->actions, resize_edges);
                }
        }
        return activated_any && activated_any_frame;
@@ -532,8 +531,7 @@ handle_press_mousebinding(struct view *view, struct server *server,
                        }
                        activated_any = true;
                        activated_any_frame |= mousebind->context == LAB_SSD_FRAME;
-                       action(view, server, mousebind->action,
-                               mousebind->command, resize_edges);
+                       action(view, server, &mousebind->actions, resize_edges);
                }
        }
        return activated_any && activated_any_frame;
index c10e3c9cf9a5493b7ed74d902f02239e6890b729..72bcb8756abd0879bdbea67ca1b8b315ae949fc7 100644 (file)
@@ -67,8 +67,7 @@ handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym)
                for (size_t i = 0; i < keybind->keysyms_len; i++) {
                        if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) {
                                wlr_keyboard_set_repeat_info(kb, 0, 0);
-                               action(NULL, server, keybind->action,
-                                      keybind->command, 0);
+                               action(NULL, server, &keybind->actions, 0);
                                return true;
                        }
                }
index e4c4201ed61ad0e26dd09af0174060abbffe5685..1fae6c01406b457557f4ce502d6f1b5bed8f70e9 100644 (file)
@@ -18,6 +18,7 @@
 #include "labwc.h"
 #include "menu/menu.h"
 #include "theme.h"
+#include "action.h"
 
 #define MENUWIDTH (110)
 #define MENU_ITEM_PADDING_Y (4)
@@ -26,6 +27,7 @@
 /* state-machine variables for processing <item></item> */
 static bool in_item;
 static struct menuitem *current_item;
+static struct action *current_item_action;
 
 static int menu_level;
 static struct menu *current_menu;
@@ -94,6 +96,7 @@ item_create(struct menu *menu, const char *text)
        menuitem->texture.offset_x = MENU_ITEM_PADDING_X;
 
        wl_list_insert(&menu->menuitems, &menuitem->link);
+       wl_list_init(&menuitem->actions);
        return menuitem;
 }
 
@@ -113,15 +116,17 @@ fill_item(char *nodename, char *content)
        /* <item label=""> defines the start of a new item */
        if (!strcmp(nodename, "label")) {
                current_item = item_create(current_menu, content);
-       }
-       if (!current_item) {
-               wlr_log(WLR_ERROR, "expect <item label=\"\"> element first");
-               return;
-       }
-       if (!strcmp(nodename, "name.action")) {
-               current_item->action = strdup(content);
+       } else if (!current_item) {
+               wlr_log(WLR_ERROR, "expect <item label=\"\"> element first. "
+                       "nodename: '%s' content: '%s'", nodename, content);
+       } else if (!strcmp(nodename, "name.action")) {
+               current_item_action = action_create(content);
+               wl_list_insert(current_item->actions.prev, &current_item_action->link);
+       } else if (!current_item_action) {
+               wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. "
+                       "nodename: '%s' content: '%s'", nodename, content);
        } else if (!strcmp(nodename, "command.action")) {
-               current_item->command = strdup(content);
+               current_item_action->arg = strdup(content);
        }
 }
 
@@ -310,9 +315,9 @@ menu_init_rootmenu(struct server *server)
        }
        if (wl_list_empty(&server->rootmenu->menuitems)) {
                current_item = item_create(server->rootmenu, "Reconfigure");
-               current_item->action = strdup("Reconfigure");
+               fill_item("name.action", "Reconfigure");
                current_item = item_create(server->rootmenu, "Exit");
-               current_item->action = strdup("Exit");
+               fill_item("name.action", "Exit");
        }
 
        server->rootmenu->visible = true;
@@ -323,13 +328,17 @@ void
 menu_finish(void)
 {
        struct menu *menu;
+       struct action *action, *action_tmp;
        for (int i = 0; i < nr_menus; ++i) {
                menu = menus + i;
                struct menuitem *item, *next;
                wl_list_for_each_safe(item, next, &menu->menuitems, link) {
-                       zfree(item->action);
-                       zfree(item->command);
                        wl_list_remove(&item->link);
+                       wl_list_for_each_safe(action, action_tmp, &item->actions, link) {
+                               wl_list_remove(&action->link);
+                               zfree(action->arg);
+                               zfree(action);
+                       }
                        free(item);
                }
        }
@@ -416,7 +425,7 @@ menu_action_selected(struct server *server, struct menu *menu)
        struct menuitem *menuitem;
        wl_list_for_each (menuitem, &menu->menuitems, link) {
                if (menuitem->selected && !menuitem->submenu) {
-                       action(NULL, server, menuitem->action, menuitem->command, 0);
+                       action(NULL, server, &menuitem->actions, 0);
                        break;
                }
                if (menuitem->submenu) {