]> git.mdlowis.com Git - proto/labwc.git/commitdiff
action: add If and ForEach actions
authorConsus <consus@ftml.net>
Mon, 28 Aug 2023 16:14:04 +0000 (19:14 +0300)
committerJohan Malm <johanmalm@users.noreply.github.com>
Sun, 1 Oct 2023 13:17:29 +0000 (14:17 +0100)
Add If and ForEach actions as described in OpenBox specification.

Limitations:

- If and ForEach cannot contain nested If and ForEach.

docs/labwc-actions.5.scd
include/action.h
src/action.c
src/config/rcxml.c

index 287eb28e8be21dac768a7d00c342d0d5af8a58ff..d21a79b8c475a20a6fe0ded00d53ec7dcee36ae2 100644 (file)
@@ -148,6 +148,56 @@ Actions are used in menus and keyboard/mouse bindings.
 *<action name="None" />*
        If used as the only action for a binding: clear an earlier defined binding.
 
+# CONDITIONAL ACTIONS
+
+Actions that execute other actions. Used in keyboard/mouse bindings.
+
+*<action name="If">*
+       This action will execute one set of actions if the focused window
+       matches the criteria, or another if it does not.
+
+       The arguments are as follows:
+
+       ```
+       <action name="If">
+         <query/>
+         <then><action/></then>
+         <else><action/></else>
+       </action>
+       ```
+
+       *query*
+               Define a query with zero or more conditions. All conditions must
+               be evaluated as true in order for the window to match this
+               query. Multiple queries can be defined.
+
+               Pattern matching is done according to glob(7) and is
+               case-insensitive.
+
+               Conditions are as follows:
+
+               *identifier*
+                       XDG shell app_id for Wayland clients, WM_CLASS for
+                       XWayland clients.
+
+               *title*
+                       XDG shell title for Wayland clients, WM_NAME for
+                       XWayland clients.
+
+               This argument is optional.
+       
+       *then*
+               A list of actions to be executed if the window matches any
+               query. This argument is optional.
+
+       *else*
+               A list of actions to be executed if the window does not match
+               any query. This argument is optional.
+
+*<action name="ForEach">*
+       Identical to "If" action, but applies to all windows, not just the
+       focused one.
+
 # SEE ALSO
 
-labwc(1), labwc-config(5), labwc-theme(5)
+labwc(1), labwc-config(5), labwc-theme(5), glob(7)
index a76aecf50c88ac9f870e7779d7bf1c4f6fb90236..76dd54f82f64490201f6eca28225644d99c90fbe 100644 (file)
@@ -23,6 +23,11 @@ struct action *action_create(const char *action_name);
 bool action_is_valid(struct action *action);
 
 void action_arg_add_str(struct action *action, const char *key, const char *value);
+void action_arg_add_actionlist(struct action *action, const char *key);
+void action_arg_add_querylist(struct action *action, const char *key);
+
+struct wl_list *action_get_actionlist(struct action *action, const char *key);
+struct wl_list *action_get_querylist(struct action *action, const char *key);
 
 void action_arg_from_xml_node(struct action *action, const char *nodename, const char *content);
 
index 7d91c49a05459f10d92be9a2535a044c80f6e6c7..414c84928f21b3f6bd3a3c2d16c84440e76e68ac 100644 (file)
@@ -7,6 +7,7 @@
 #include <unistd.h>
 #include <wlr/util/log.h>
 #include "action.h"
+#include "common/array-size.h"
 #include "common/list.h"
 #include "common/mem.h"
 #include "common/parse-bool.h"
@@ -24,6 +25,8 @@ enum action_arg_type {
        LAB_ACTION_ARG_STR = 0,
        LAB_ACTION_ARG_BOOL,
        LAB_ACTION_ARG_INT,
+       LAB_ACTION_ARG_QUERY_LIST,
+       LAB_ACTION_ARG_ACTION_LIST,
 };
 
 struct action_arg {
@@ -48,6 +51,11 @@ struct action_arg_int {
        int value;
 };
 
+struct action_arg_list {
+       struct action_arg base;
+       struct wl_list value;
+};
+
 enum action_type {
        ACTION_TYPE_INVALID = 0,
        ACTION_TYPE_NONE,
@@ -82,6 +90,8 @@ enum action_type {
        ACTION_TYPE_SNAP_TO_REGION,
        ACTION_TYPE_TOGGLE_KEYBINDS,
        ACTION_TYPE_FOCUS_OUTPUT,
+       ACTION_TYPE_IF,
+       ACTION_TYPE_FOR_EACH,
 };
 
 const char *action_names[] = {
@@ -118,6 +128,8 @@ const char *action_names[] = {
        "SnapToRegion",
        "ToggleKeybinds",
        "FocusOutput",
+       "If",
+       "ForEach",
        NULL
 };
 
@@ -158,6 +170,30 @@ action_arg_add_int(struct action *action, const char *key, int value)
        wl_list_append(&action->args, &arg->base.link);
 }
 
+static void
+action_arg_add_list(struct action *action, const char *key, enum action_arg_type type)
+{
+       assert(action);
+       assert(key);
+       struct action_arg_list *arg = znew(*arg);
+       arg->base.type = type;
+       arg->base.key = xstrdup(key);
+       wl_list_init(&arg->value);
+       wl_list_append(&action->args, &arg->base.link);
+}
+
+void
+action_arg_add_querylist(struct action *action, const char *key)
+{
+       action_arg_add_list(action, key, LAB_ACTION_ARG_QUERY_LIST);
+}
+
+void
+action_arg_add_actionlist(struct action *action, const char *key)
+{
+       action_arg_add_list(action, key, LAB_ACTION_ARG_ACTION_LIST);
+}
+
 static void *
 action_get_arg(struct action *action, const char *key, enum action_arg_type type)
 {
@@ -193,6 +229,20 @@ action_get_int(struct action *action, const char *key, int default_value)
        return arg ? arg->value : default_value;
 }
 
+struct wl_list *
+action_get_querylist(struct action *action, const char *key)
+{
+       struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_QUERY_LIST);
+       return arg ? &arg->value : NULL;
+}
+
+struct wl_list *
+action_get_actionlist(struct action *action, const char *key)
+{
+       struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_ACTION_LIST);
+       return arg ? &arg->value : NULL;
+}
+
 void
 action_arg_from_xml_node(struct action *action, const char *nodename, const char *content)
 {
@@ -327,6 +377,20 @@ actions_contain_toggle_keybinds(struct wl_list *action_list)
        return false;
 }
 
+static bool
+action_list_is_valid(struct wl_list *actions)
+{
+       assert(actions);
+
+       struct action *action;
+       wl_list_for_each(action, actions, link) {
+               if (!action_is_valid(action)) {
+                       return false;
+               }
+       }
+       return true;
+}
+
 /* Checks for *required* arguments */
 bool
 action_is_valid(struct action *action)
@@ -356,6 +420,19 @@ action_is_valid(struct action *action)
        case ACTION_TYPE_FOCUS_OUTPUT:
                arg_name = "output";
                break;
+       case ACTION_TYPE_IF:
+       case ACTION_TYPE_FOR_EACH:
+               ; /* works around "a label can only be part of a statement" */
+               static const char * const branches[] = { "then", "else" };
+               for (size_t i = 0; i < ARRAY_SIZE(branches); i++) {
+                       struct wl_list *children = action_get_actionlist(action, branches[i]);
+                       if (children && !action_list_is_valid(children)) {
+                               wlr_log(WLR_ERROR, "Invalid action in %s '%s' branch",
+                                       action_names[action->type], branches[i]);
+                               return false;
+                       }
+               }
+               return true;
        default:
                /* No arguments required */
                return true;
@@ -381,6 +458,15 @@ action_free(struct action *action)
                if (arg->type == LAB_ACTION_ARG_STR) {
                        struct action_arg_str *str_arg = (struct action_arg_str *)arg;
                        zfree(str_arg->value);
+               } else if (arg->type == LAB_ACTION_ARG_ACTION_LIST) {
+                       struct action_arg_list *list_arg = (struct action_arg_list *)arg;
+                       action_list_free(&list_arg->value);
+               } else if (arg->type == LAB_ACTION_ARG_QUERY_LIST) {
+                       struct action_arg_list *list_arg = (struct action_arg_list *)arg;
+                       struct view_query *elm, *next;
+                       wl_list_for_each_safe(elm, next, &list_arg->value, link) {
+                               view_query_free(elm);
+                       }
                }
                zfree(arg);
        }
@@ -466,6 +552,31 @@ view_for_action(struct view *activator, struct server *server,
        }
 }
 
+static void
+run_if_action(struct view *view, struct server *server, struct action *action)
+{
+       struct view_query *query;
+       struct wl_list *queries, *actions;
+       const char *branch = "then";
+
+       queries = action_get_querylist(action, "query");
+       if (queries) {
+               branch = "else";
+               /* All queries are OR'ed */
+               wl_list_for_each(query, queries, link) {
+                       if (view_matches_query(view, query)) {
+                               branch = "then";
+                               break;
+                       }
+               }
+       }
+
+       actions = action_get_actionlist(action, branch);
+       if (actions) {
+               actions_run(view, server, actions, 0);
+       }
+}
+
 void
 actions_run(struct view *activator, struct server *server,
        struct wl_list *actions, uint32_t resize_edges)
@@ -693,6 +804,23 @@ actions_run(struct view *activator, struct server *server,
                                desktop_focus_output(output_from_name(server, output_name));
                        }
                        break;
+               case ACTION_TYPE_IF:
+                       if (view) {
+                               run_if_action(view, server, action);
+                       }
+                       break;
+               case ACTION_TYPE_FOR_EACH:
+                       {
+                               struct wl_array views;
+                               struct view **item;
+                               wl_array_init(&views);
+                               view_array_append(server, &views, LAB_VIEW_CRITERIA_NONE);
+                               wl_array_for_each(item, &views) {
+                                       run_if_action(*item, server, action);
+                               }
+                               wl_array_release(&views);
+                       }
+                       break;
                case ACTION_TYPE_INVALID:
                        wlr_log(WLR_ERROR, "Not executing unknown action");
                        break;
index c8da660b44745096d25aee95fa5cd2b9dec2d618..ed0ae75941a9ae481f08a7d7d06331a6f4dfff9c 100644 (file)
@@ -27,6 +27,7 @@
 #include "config/rcxml.h"
 #include "labwc.h"
 #include "regions.h"
+#include "view.h"
 #include "window-rules.h"
 #include "workspaces.h"
 
@@ -37,6 +38,9 @@ static bool in_mousebind;
 static bool in_libinput_category;
 static bool in_window_switcher_field;
 static bool in_window_rules;
+static bool in_action_query;
+static bool in_action_then_branch;
+static bool in_action_else_branch;
 
 static struct usable_area_override *current_usable_area_override;
 static struct keybind *current_keybind;
@@ -49,6 +53,8 @@ static struct region *current_region;
 static struct window_switcher_field *current_field;
 static struct window_rule *current_window_rule;
 static struct action *current_window_rule_action;
+static struct view_query *current_view_query;
+static struct action *current_child_action;
 
 enum font_place {
        FONT_PLACE_NONE = 0,
@@ -246,6 +252,79 @@ fill_region(char *nodename, char *content)
        }
 }
 
+static void
+fill_action_query(char *nodename, char *content, struct action *action)
+{
+       string_truncate_at_pattern(nodename, ".keybind.keyboard");
+       string_truncate_at_pattern(nodename, ".mousebind.context.mouse");
+
+       if (!strcasecmp(nodename, "query.action")) {
+               current_view_query = NULL;
+       }
+
+       string_truncate_at_pattern(nodename, ".query.action");
+
+       if (!content) {
+               return;
+       }
+
+       if (!current_view_query) {
+               struct wl_list *queries = action_get_querylist(action, "query");
+               if (!queries) {
+                       action_arg_add_querylist(action, "query");
+                       queries = action_get_querylist(action, "query");
+               }
+               current_view_query = znew(*current_view_query);
+               wl_list_append(queries, &current_view_query->link);
+       }
+
+       if (!strcasecmp(nodename, "identifier")) {
+               current_view_query->identifier = xstrdup(content);
+       } else if (!strcasecmp(nodename, "title")) {
+               current_view_query->title = xstrdup(content);
+       }
+}
+
+static void
+fill_child_action(char *nodename, char *content, struct action *parent,
+       const char *branch_name)
+{
+       string_truncate_at_pattern(nodename, ".keybind.keyboard");
+       string_truncate_at_pattern(nodename, ".mousebind.context.mouse");
+       string_truncate_at_pattern(nodename, ".then.action");
+       string_truncate_at_pattern(nodename, ".else.action");
+
+       if (!strcasecmp(nodename, "action")) {
+               current_child_action = NULL;
+       }
+
+       if (!content) {
+               return;
+       }
+
+       struct wl_list *siblings = action_get_actionlist(parent, branch_name);
+       if (!siblings) {
+               action_arg_add_actionlist(parent, branch_name);
+               siblings = action_get_actionlist(parent, branch_name);
+       }
+
+       if (!strcasecmp(nodename, "name.action")) {
+               if (!strcasecmp(content, "If") || !strcasecmp(content, "ForEach")) {
+                       wlr_log(WLR_ERROR, "action '%s' cannot be a child action", content);
+                       return;
+               }
+               current_child_action = action_create(content);
+               if (current_child_action) {
+                       wl_list_append(siblings, &current_child_action->link);
+               }
+       } else if (!current_child_action) {
+               wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. "
+                       "nodename: '%s' content: '%s'", nodename, content);
+       } else {
+               action_arg_from_xml_node(current_child_action, nodename, content);
+       }
+}
+
 static void
 fill_keybind(char *nodename, char *content)
 {
@@ -539,10 +618,32 @@ entry(xmlNode *node, char *nodename, char *content)
                fill_usable_area_override(nodename, content);
        }
        if (in_keybind) {
-               fill_keybind(nodename, content);
+               if (in_action_query) {
+                       fill_action_query(nodename, content,
+                               current_keybind_action);
+               } else if (in_action_then_branch) {
+                       fill_child_action(nodename, content,
+                               current_keybind_action, "then");
+               } else if (in_action_else_branch) {
+                       fill_child_action(nodename, content,
+                               current_keybind_action, "else");
+               } else {
+                       fill_keybind(nodename, content);
+               }
        }
        if (in_mousebind) {
-               fill_mousebind(nodename, content);
+               if (in_action_query) {
+                       fill_action_query(nodename, content,
+                               current_mousebind_action);
+               } else if (in_action_then_branch) {
+                       fill_child_action(nodename, content,
+                               current_mousebind_action, "then");
+               } else if (in_action_else_branch) {
+                       fill_child_action(nodename, content,
+                               current_mousebind_action, "else");
+               } else {
+                       fill_mousebind(nodename, content);
+               }
        }
        if (in_libinput_category) {
                fill_libinput_category(nodename, content);
@@ -771,6 +872,24 @@ xml_tree_walk(xmlNode *node)
                        in_window_rules = false;
                        continue;
                }
+               if (!strcasecmp((char *)n->name, "query")) {
+                       in_action_query = true;
+                       traverse(n);
+                       in_action_query = false;
+                       continue;
+               }
+               if (!strcasecmp((char *)n->name, "then")) {
+                       in_action_then_branch = true;
+                       traverse(n);
+                       in_action_then_branch = false;
+                       continue;
+               }
+               if (!strcasecmp((char *)n->name, "else")) {
+                       in_action_else_branch = true;
+                       traverse(n);
+                       in_action_else_branch = false;
+                       continue;
+               }
                traverse(n);
        }
 }
@@ -1366,6 +1485,8 @@ rcxml_finish(void)
        current_mouse_context = NULL;
        current_keybind_action = NULL;
        current_mousebind_action = NULL;
+       current_child_action = NULL;
+       current_view_query = NULL;
        current_region = NULL;
        current_field = NULL;
        current_window_rule = NULL;