Add If and ForEach actions as described in OpenBox specification.
Limitations:
- If and ForEach cannot contain nested If and ForEach.
*<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)
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);
#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"
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 {
int value;
};
+struct action_arg_list {
+ struct action_arg base;
+ struct wl_list value;
+};
+
enum action_type {
ACTION_TYPE_INVALID = 0,
ACTION_TYPE_NONE,
ACTION_TYPE_SNAP_TO_REGION,
ACTION_TYPE_TOGGLE_KEYBINDS,
ACTION_TYPE_FOCUS_OUTPUT,
+ ACTION_TYPE_IF,
+ ACTION_TYPE_FOR_EACH,
};
const char *action_names[] = {
"SnapToRegion",
"ToggleKeybinds",
"FocusOutput",
+ "If",
+ "ForEach",
NULL
};
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)
{
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)
{
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)
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;
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);
}
}
}
+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)
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;
#include "config/rcxml.h"
#include "labwc.h"
#include "regions.h"
+#include "view.h"
#include "window-rules.h"
#include "workspaces.h"
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;
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,
}
}
+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, ¤t_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, ¤t_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)
{
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);
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);
}
}
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;