From 96da82c0851d1a55021baaa63794f489a3eb3f57 Mon Sep 17 00:00:00 2001 From: Orfeas <38209077+0xfea5@users.noreply.github.com> Date: Fri, 18 Oct 2024 02:07:52 +0300 Subject: [PATCH] query: support additional conditions for matching clients Co-authored-by: Andrew J. Hesford Closes: #2245. --- docs/labwc-actions.5.scd | 35 +++++++++ include/ssd.h | 3 +- include/view.h | 15 ++++ src/action.c | 9 ++- src/config/rcxml.c | 20 +++++ src/ssd/ssd.c | 6 +- src/view.c | 163 ++++++++++++++++++++++++++++++++------- 7 files changed, 217 insertions(+), 34 deletions(-) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 8f37fdca..89f3eb04 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -390,6 +390,41 @@ Actions that execute other actions. Used in keyboard/mouse bindings. Internal heuristics for Wayland clients, NET_WM_WINDOW_TYPE for XWayland clients. + *shaded* [yes|no] + Whether or not the client is rolled up. + + *maximized* [both|horizontal|vertical|none] + Whether the client is maximized along both axes, the + horizontal axis only, the vertical axis only, or neither + axis (none). + + *iconified* [yes|no] + Whether or not the client is iconified. + + *focused* [yes|no] + Whether or not the client is focused. + + *omnipresent* [yes|no] + Whether or not the client is visible on all desktops. + + *desktop* + The desktop the client is currently on. This can be the + number or name of a desktop, or special relative values + "current", "other", "left", "right" or "last". The + "left" and "right" directions will not wrap. + + *tiled* [up|right|down|left|center] + Whether the client is tiled (snapped) along the the + indicated screen edge. + + *tiled_region* + Whether the client is tiled (snapped) to the indicated + region. + + *decoration* [full|border|none] + Whether the client has full server-side decorations, + borders only, or no server-side decorations. + This argument is optional. *then* diff --git a/include/ssd.h b/include/ssd.h index d5b66c08..fe56be54 100644 --- a/include/ssd.h +++ b/include/ssd.h @@ -62,9 +62,10 @@ enum ssd_part_type { }; enum ssd_mode { + LAB_SSD_MODE_INVALID, LAB_SSD_MODE_NONE, LAB_SSD_MODE_BORDER, - LAB_SSD_MODE_FULL + LAB_SSD_MODE_FULL, }; /* Forward declare arguments */ diff --git a/include/view.h b/include/view.h index 7a1b5dde..6fc870e7 100644 --- a/include/view.h +++ b/include/view.h @@ -57,6 +57,11 @@ enum view_axis { VIEW_AXIS_HORIZONTAL = (1 << 0), VIEW_AXIS_VERTICAL = (1 << 1), VIEW_AXIS_BOTH = (VIEW_AXIS_HORIZONTAL | VIEW_AXIS_VERTICAL), + /* + * If view_axis is treated as a bitfield, INVALID should never + * set the HORIZONTAL or VERTICAL bits. + */ + VIEW_AXIS_INVALID = (1 << 2), }; enum view_edge { @@ -290,6 +295,16 @@ struct view_query { int window_type; char *sandbox_engine; char *sandbox_app_id; + enum three_state shaded; + enum view_axis maximized; + enum three_state iconified; + enum three_state focused; + enum three_state omnipresent; + enum view_edge tiled; + char *tiled_region; + char *desktop; + enum ssd_mode decoration; + char *monitor; }; struct xdg_toplevel_view { diff --git a/src/action.c b/src/action.c index 5c0f2a8d..0afce126 100644 --- a/src/action.c +++ b/src/action.c @@ -366,7 +366,7 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char case ACTION_TYPE_UNMAXIMIZE: if (!strcmp(argument, "direction")) { enum view_axis axis = view_axis_parse(content); - if (axis == VIEW_AXIS_NONE) { + if (axis == VIEW_AXIS_NONE || axis == VIEW_AXIS_INVALID) { wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", action_names[action->type], argument, content); } else { @@ -378,7 +378,12 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char case ACTION_TYPE_SET_DECORATIONS: if (!strcmp(argument, "decorations")) { enum ssd_mode mode = ssd_mode_parse(content); - action_arg_add_int(action, argument, mode); + if (mode != LAB_SSD_MODE_INVALID) { + action_arg_add_int(action, argument, mode); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } goto cleanup; } if (!strcasecmp(argument, "forceSSD")) { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index d6d9c76f..198fe38b 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -473,6 +473,26 @@ fill_action_query(char *nodename, char *content, struct action *action) current_view_query->sandbox_engine = xstrdup(content); } else if (!strcasecmp(nodename, "sandboxAppId")) { current_view_query->sandbox_app_id = xstrdup(content); + } else if (!strcasecmp(nodename, "shaded")) { + current_view_query->shaded = parse_bool(content, -1); + } else if (!strcasecmp(nodename, "maximized")) { + current_view_query->maximized = view_axis_parse(content); + } else if (!strcasecmp(nodename, "iconified")) { + current_view_query->iconified = parse_bool(content, -1); + } else if (!strcasecmp(nodename, "focused")) { + current_view_query->focused = parse_bool(content, -1); + } else if (!strcasecmp(nodename, "omnipresent")) { + current_view_query->omnipresent = parse_bool(content, -1); + } else if (!strcasecmp(nodename, "tiled")) { + current_view_query->tiled = view_edge_parse(content); + } else if (!strcasecmp(nodename, "tiled_region")) { + current_view_query->tiled_region = xstrdup(content); + } else if (!strcasecmp(nodename, "desktop")) { + current_view_query->desktop = xstrdup(content); + } else if (!strcasecmp(nodename, "decoration")) { + current_view_query->decoration = ssd_mode_parse(content); + } else if (!strcasecmp(nodename, "monitor")) { + current_view_query->monitor = xstrdup(content); } } diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c index c1e4e0e5..a9fc1abc 100644 --- a/src/ssd/ssd.c +++ b/src/ssd/ssd.c @@ -350,14 +350,16 @@ enum ssd_mode ssd_mode_parse(const char *mode) { if (!mode) { - return LAB_SSD_MODE_FULL; + return LAB_SSD_MODE_INVALID; } if (!strcasecmp(mode, "none")) { return LAB_SSD_MODE_NONE; } else if (!strcasecmp(mode, "border")) { return LAB_SSD_MODE_BORDER; - } else { + } else if (!strcasecmp(mode, "full")) { return LAB_SSD_MODE_FULL; + } else { + return LAB_SSD_MODE_INVALID; } } diff --git a/src/view.c b/src/view.c index 720a9cd5..e80b6b33 100644 --- a/src/view.c +++ b/src/view.c @@ -8,6 +8,7 @@ #include "common/macros.h" #include "common/match.h" #include "common/mem.h" +#include "common/parse-bool.h" #include "common/scene-helpers.h" #include "input/keyboard.h" #include "labwc.h" @@ -22,6 +23,7 @@ #include "ssd.h" #include "view.h" #include "window-rules.h" +#include "wlr/util/log.h" #include "workspaces.h" #include "xwayland.h" @@ -76,53 +78,154 @@ void view_query_free(struct view_query *query) { wl_list_remove(&query->link); - free(query->identifier); - free(query->title); - free(query->sandbox_engine); - free(query->sandbox_app_id); - free(query); + zfree(query->identifier); + zfree(query->title); + zfree(query->sandbox_engine); + zfree(query->sandbox_app_id); + zfree(query->tiled_region); + zfree(query->desktop); + zfree(query->monitor); + zfree(query); +} + +static enum three_state +bool_to_tristate(bool b) +{ + return b ? LAB_STATE_ENABLED : LAB_STATE_DISABLED; +} + +static enum three_state +match_tristate(enum three_state desired, bool actual, enum three_state old_match) +{ + switch (desired) { + case LAB_STATE_ENABLED: + return bool_to_tristate(actual); + case LAB_STATE_DISABLED: + return bool_to_tristate(!actual); + default: + return old_match; + } } bool view_matches_query(struct view *view, struct view_query *query) { - bool match = true; - bool empty = true; + enum three_state match = LAB_STATE_UNSPECIFIED; - const char *identifier = view_get_string_prop(view, "app_id"); - if (match && query->identifier) { - empty = false; - match &= identifier && match_glob(query->identifier, identifier); + if (query->identifier) { + const char *identifier = view_get_string_prop(view, "app_id"); + if (!(identifier && match_glob(query->identifier, identifier))) { + return false; + } } - const char *title = view_get_string_prop(view, "title"); - if (match && query->title) { - empty = false; - match &= title && match_glob(query->title, title); + if (query->title) { + const char *title = view_get_string_prop(view, "title"); + if (!(title && match_glob(query->title, title))) { + return false; + } } - if (match && query->window_type >= 0) { - empty = false; - match &= view_contains_window_type(view, query->window_type); + if (query->window_type >= 0) { + if (!view_contains_window_type(view, query->window_type)) { + return false; + } } - if (match && query->sandbox_engine) { + if (query->sandbox_engine) { const struct wlr_security_context_v1_state *security_context = security_context_from_view(view); - empty = false; - match &= security_context && security_context->sandbox_engine - && match_glob(query->sandbox_engine, security_context->sandbox_engine); + if (!(security_context && security_context->sandbox_engine && + match_glob(query->sandbox_engine, security_context->sandbox_engine))) { + return false; + } } - if (match && query->sandbox_app_id) { + if (query->sandbox_app_id) { const struct wlr_security_context_v1_state *security_context = security_context_from_view(view); - empty = false; - match &= security_context && security_context->app_id - && match_glob(query->sandbox_app_id, security_context->app_id); + if (!(security_context && security_context->app_id && + match_glob(query->sandbox_app_id, security_context->app_id))) { + return false; + } + } + + match = match_tristate(query->shaded, view->shaded, match); + if (match == LAB_STATE_DISABLED) { + return false; + } + + if (query->maximized != VIEW_AXIS_INVALID) { + match = bool_to_tristate(view->maximized == query->maximized); + if (match == LAB_STATE_DISABLED) { + return false; + } + } + + match = match_tristate(query->iconified, view->minimized, match); + if (match == LAB_STATE_DISABLED) { + return false; + } + + match = match_tristate(query->focused, view->server->active_view == view, match); + if (match == LAB_STATE_DISABLED) { + return false; + } + + match = match_tristate(query->omnipresent, view->visible_on_all_workspaces, match); + if (match == LAB_STATE_DISABLED) { + return false; + } + + if (query->tiled != VIEW_EDGE_INVALID) { + match = bool_to_tristate(query->tiled == view->tiled); + if (match == LAB_STATE_DISABLED) { + return false; + } + } + + if (query->tiled_region) { + match = bool_to_tristate(view->tiled_region && + !strcasecmp(query->tiled_region, view->tiled_region->name)); + if (match == LAB_STATE_DISABLED) { + return false; + } + } + + if (query->desktop) { + if (!strcasecmp(query->desktop, "other")) { + struct workspace *current = view->server->workspaces.current; + match = bool_to_tristate(strcasecmp(view->workspace->name, current->name)); + } else { + // TODO: perhaps allow wrapping for "left" and "right" workspaces + struct workspace *target = + workspaces_find(view->server->workspaces.current, + query->desktop, false); + match = bool_to_tristate(target && + !strcasecmp(view->workspace->name, target->name)); + } + if (match == LAB_STATE_DISABLED) { + return false; + } + } + + enum ssd_mode decoration = view_get_ssd_mode(view); + if (query->decoration != LAB_SSD_MODE_INVALID) { + match = bool_to_tristate(query->decoration == decoration); + if (match == LAB_STATE_DISABLED) { + return false; + } } - return !empty && match; + if (query->monitor) { + struct output *target = output_from_name(view->server, query->monitor); + match = bool_to_tristate(target == view->output); + if (match == LAB_STATE_DISABLED) { + return false; + } + } + + return match == LAB_STATE_ENABLED; } static bool @@ -1949,7 +2052,7 @@ enum view_axis view_axis_parse(const char *direction) { if (!direction) { - return VIEW_AXIS_NONE; + return VIEW_AXIS_INVALID; } if (!strcasecmp(direction, "horizontal")) { return VIEW_AXIS_HORIZONTAL; @@ -1957,8 +2060,10 @@ view_axis_parse(const char *direction) return VIEW_AXIS_VERTICAL; } else if (!strcasecmp(direction, "both")) { return VIEW_AXIS_BOTH; - } else { + } else if (!strcasecmp(direction, "none")) { return VIEW_AXIS_NONE; + } else { + return VIEW_AXIS_INVALID; } } -- 2.52.0