action: add If and ForEach actions

Add If and ForEach actions as described in OpenBox specification.

Limitations:

- If and ForEach cannot contain nested If and ForEach.
This commit is contained in:
Consus 2023-08-28 19:14:04 +03:00 committed by Johan Malm
parent 12f6a8975a
commit 22e3be40e5
4 changed files with 307 additions and 3 deletions

View 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)

View 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);

View 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;

View 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,11 +618,33 @@ entry(xmlNode *node, char *nodename, char *content)
fill_usable_area_override(nodename, content);
}
if (in_keybind) {
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) {
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;