diff --git a/include/sway/criteria.h b/include/sway/criteria.h index dc8dcb986..1ee69a388 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -14,29 +14,38 @@ enum criteria_type { CT_NO_FOCUS = 1 << 4, }; +enum pattern_type { + PATTERN_PCRE, + PATTERN_FOCUSED, +}; + +struct pattern { + enum pattern_type match_type; + pcre *regex; +}; + struct criteria { enum criteria_type type; char *raw; // entire criteria string (for logging) char *cmdlist; char *target; // workspace or output name for `assign` criteria - bool autofail; // __focused__ while no focus or n/a for focused view - pcre *title; - pcre *shell; - pcre *app_id; - pcre *con_mark; + struct pattern *title; + struct pattern *shell; + struct pattern *app_id; + struct pattern *con_mark; uint32_t con_id; // internal ID #if HAVE_XWAYLAND - pcre *class; + struct pattern *class; uint32_t id; // X11 window ID - pcre *instance; - pcre *window_role; + struct pattern *instance; + struct pattern *window_role; enum atom_name window_type; #endif bool floating; bool tiling; char urgent; // 'l' for latest or 'o' for oldest - pcre *workspace; + struct pattern *workspace; }; bool criteria_is_empty(struct criteria *criteria); diff --git a/sway/criteria.c b/sway/criteria.c index b25828514..eec625aff 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -16,8 +16,7 @@ #include "config.h" bool criteria_is_empty(struct criteria *criteria) { - return !criteria->autofail - && !criteria->title + return !criteria->title && !criteria->shell && !criteria->app_id && !criteria->con_mark @@ -35,16 +34,64 @@ bool criteria_is_empty(struct criteria *criteria) { && !criteria->workspace; } +// The error pointer is used for parsing functions, and saves having to pass it +// as an argument in several places. +char *error = NULL; + +// Returns error string on failure or NULL otherwise. +static bool generate_regex(pcre **regex, char *value) { + const char *reg_err; + int offset; + + *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); + + if (!*regex) { + const char *fmt = "Regex compilation for '%s' failed: %s"; + int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; + error = malloc(len); + snprintf(error, len, fmt, value, reg_err); + return false; + } + + return true; +} + +static bool pattern_create(struct pattern **pattern, char *value) { + *pattern = calloc(1, sizeof(struct pattern)); + if (!*pattern) { + sway_log(SWAY_ERROR, "Failed to allocate pattern"); + } + + if (strcmp(value, "__focused__") == 0) { + (*pattern)->match_type = PATTERN_FOCUSED; + } else { + (*pattern)->match_type = PATTERN_PCRE; + if (!generate_regex(&(*pattern)->regex, value)) { + return false; + }; + } + return true; +} + +static void pattern_destroy(struct pattern *pattern) { + if (pattern) { + if (pattern->regex) { + pcre_free(pattern->regex); + } + free(pattern); + } +} + void criteria_destroy(struct criteria *criteria) { - pcre_free(criteria->title); - pcre_free(criteria->shell); - pcre_free(criteria->app_id); + pattern_destroy(criteria->title); + pattern_destroy(criteria->shell); + pattern_destroy(criteria->app_id); #if HAVE_XWAYLAND - pcre_free(criteria->class); - pcre_free(criteria->instance); - pcre_free(criteria->window_role); + pattern_destroy(criteria->class); + pattern_destroy(criteria->instance); + pattern_destroy(criteria->window_role); #endif - pcre_free(criteria->con_mark); + pattern_destroy(criteria->con_mark); free(criteria->workspace); free(criteria->cmdlist); free(criteria->raw); @@ -99,36 +146,75 @@ static void find_urgent_iterator(struct sway_container *con, void *data) { static bool criteria_matches_view(struct criteria *criteria, struct sway_view *view) { - if (criteria->autofail) { - return false; - } + struct sway_seat *seat = input_manager_current_seat(); + struct sway_container *focus = seat_get_focused_container(seat); + struct sway_view *focused = focus ? focus->view : NULL; if (criteria->title) { const char *title = view_get_title(view); - if (!title || regex_cmp(title, criteria->title) != 0) { + if (!title) { return false; } + + switch (criteria->title->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(title, view_get_title(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(title, criteria->title->regex) != 0) { + return false; + } + break; + } } if (criteria->shell) { const char *shell = view_get_shell(view); - if (!shell || regex_cmp(shell, criteria->shell) != 0) { + if (!shell) { return false; } + + switch (criteria->shell->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(shell, view_get_shell(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(shell, criteria->shell->regex) != 0) { + return false; + } + break; + } } if (criteria->app_id) { const char *app_id = view_get_app_id(view); - if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { + if (!app_id) { return false; } + + switch (criteria->app_id->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(app_id, view_get_app_id(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(app_id, criteria->app_id->regex) != 0) { + return false; + } + break; + } } if (criteria->con_mark) { bool exists = false; struct sway_container *con = view->container; for (int i = 0; i < con->marks->length; ++i) { - if (regex_cmp(con->marks->items[i], criteria->con_mark) == 0) { + if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) == 0) { exists = true; break; } @@ -154,23 +240,62 @@ static bool criteria_matches_view(struct criteria *criteria, if (criteria->class) { const char *class = view_get_class(view); - if (!class || regex_cmp(class, criteria->class) != 0) { + if (!class) { return false; } + + switch (criteria->class->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(class, view_get_class(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(class, criteria->class->regex) != 0) { + return false; + } + break; + } } if (criteria->instance) { const char *instance = view_get_instance(view); - if (!instance || regex_cmp(instance, criteria->instance) != 0) { + if (!instance) { return false; } + + switch (criteria->instance->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(instance, view_get_instance(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(instance, criteria->instance->regex) != 0) { + return false; + } + break; + } } if (criteria->window_role) { - const char *role = view_get_window_role(view); - if (!role || regex_cmp(role, criteria->window_role) != 0) { + const char *window_role = view_get_window_role(view); + if (!window_role) { return false; } + + switch (criteria->window_role->match_type) { + case PATTERN_FOCUSED: + if (focused && strcmp(window_role, view_get_window_role(focused))) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(window_role, criteria->window_role->regex) != 0) { + return false; + } + break; + } } if (criteria->window_type != ATOM_LAST) { @@ -213,9 +338,23 @@ static bool criteria_matches_view(struct criteria *criteria, if (criteria->workspace) { struct sway_workspace *ws = view->container->workspace; - if (!ws || regex_cmp(ws->name, criteria->workspace) != 0) { + if (!ws) { return false; } + + switch (criteria->workspace->match_type) { + case PATTERN_FOCUSED: + if (focused && + strcmp(ws->name, focused->container->workspace->name)) { + return false; + } + break; + case PATTERN_PCRE: + if (regex_cmp(ws->name, criteria->workspace->regex) != 0) { + return false; + } + break; + } } return true; @@ -258,28 +397,6 @@ list_t *criteria_get_views(struct criteria *criteria) { return matches; } -// The error pointer is used for parsing functions, and saves having to pass it -// as an argument in several places. -char *error = NULL; - -// Returns error string on failure or NULL otherwise. -static bool generate_regex(pcre **regex, char *value) { - const char *reg_err; - int offset; - - *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); - - if (!*regex) { - const char *fmt = "Regex compilation for '%s' failed: %s"; - int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; - error = malloc(len); - snprintf(error, len, fmt, value, reg_err); - return false; - } - - return true; -} - #if HAVE_XWAYLAND static enum atom_name parse_window_type(const char *type) { if (strcasecmp(type, "normal") == 0) { @@ -363,92 +480,6 @@ static enum criteria_token token_from_name(char *name) { return T_INVALID; } -/** - * Get a property of the focused view. - * - * Note that we are taking the focused view at the time of criteria parsing, not - * at the time of execution. This is because __focused__ only makes sense when - * using criteria via IPC. Using __focused__ in config is not useful because - * criteria is only executed once per view. - */ -static char *get_focused_prop(enum criteria_token token, bool *autofail) { - struct sway_seat *seat = input_manager_current_seat(); - struct sway_container *focus = seat_get_focused_container(seat); - - struct sway_view *view = focus ? focus->view : NULL; - const char *value = NULL; - - switch (token) { - case T_APP_ID: - *autofail = true; - if (view) { - value = view_get_app_id(view); - } - break; - case T_SHELL: - *autofail = true; - if (view) { - value = view_get_shell(view); - } - break; - case T_TITLE: - *autofail = true; - if (view) { - value = view_get_title(view); - } - break; - case T_WORKSPACE: - *autofail = true; - if (focus && focus->workspace) { - value = focus->workspace->name; - } - break; - case T_CON_ID: - *autofail = true; - if (view && view->container) { - size_t id = view->container->node.id; - size_t id_size = snprintf(NULL, 0, "%zu", id) + 1; - char *id_str = malloc(id_size); - snprintf(id_str, id_size, "%zu", id); - value = id_str; - } - break; -#if HAVE_XWAYLAND - case T_CLASS: - *autofail = true; - if (view) { - value = view_get_class(view); - } - break; - case T_INSTANCE: - *autofail = true; - if (view) { - value = view_get_instance(view); - } - break; - case T_WINDOW_ROLE: - *autofail = true; - if (view) { - value = view_get_window_role(view); - } - break; - case T_WINDOW_TYPE: // These do not support __focused__ - case T_ID: -#endif - case T_CON_MARK: - case T_FLOATING: - case T_TILING: - case T_URGENT: - case T_INVALID: - *autofail = false; - break; - } - if (value) { - return strdup(value); - } - return NULL; -} - static bool parse_token(struct criteria *criteria, char *name, char *value) { enum criteria_token token = token_from_name(name); if (token == T_INVALID) { @@ -459,20 +490,8 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { return false; } - char *effective_value = NULL; - if (value && strcmp(value, "__focused__") == 0) { - bool autofail = false; - effective_value = get_focused_prop(token, &autofail); - if (!effective_value && autofail) { - criteria->autofail = true; - return true; - } - } else if (value) { - effective_value = strdup(value); - } - // Require value, unless token is floating or tiled - if (!effective_value && token != T_FLOATING && token != T_TILING) { + if (!value && token != T_FLOATING && token != T_TILING) { const char *fmt = "Token '%s' requires a value"; int len = strlen(fmt) + strlen(name) - 1; error = malloc(len); @@ -483,41 +502,48 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { char *endptr = NULL; switch (token) { case T_TITLE: - generate_regex(&criteria->title, effective_value); + pattern_create(&criteria->title, value); break; case T_SHELL: - generate_regex(&criteria->shell, effective_value); + pattern_create(&criteria->shell, value); break; case T_APP_ID: - generate_regex(&criteria->app_id, effective_value); + pattern_create(&criteria->app_id, value); break; case T_CON_ID: - criteria->con_id = strtoul(effective_value, &endptr, 10); - if (*endptr != 0) { - error = strdup("The value for 'con_id' should be '__focused__' or numeric"); + if (strcmp(value, "__focused__") == 0) { + struct sway_seat *seat = input_manager_current_seat(); + struct sway_container *focus = seat_get_focused_container(seat); + struct sway_view *view = focus ? focus->view : NULL; + criteria->con_id = view ? view->container->node.id : 0; + } else { + criteria->con_id = strtoul(value, &endptr, 10); + if (*endptr != 0) { + error = strdup("The value for 'con_id' should be '__focused__' or numeric"); + } } break; case T_CON_MARK: - generate_regex(&criteria->con_mark, effective_value); + pattern_create(&criteria->con_mark, value); break; #if HAVE_XWAYLAND case T_CLASS: - generate_regex(&criteria->class, effective_value); + pattern_create(&criteria->class, value); break; case T_ID: - criteria->id = strtoul(effective_value, &endptr, 10); + criteria->id = strtoul(value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'id' should be numeric"); } break; case T_INSTANCE: - generate_regex(&criteria->instance, effective_value); + pattern_create(&criteria->instance, value); break; case T_WINDOW_ROLE: - generate_regex(&criteria->window_role, effective_value); + pattern_create(&criteria->window_role, value); break; case T_WINDOW_TYPE: - criteria->window_type = parse_window_type(effective_value); + criteria->window_type = parse_window_type(value); break; #endif case T_FLOATING: @@ -527,13 +553,13 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { criteria->tiling = true; break; case T_URGENT: - if (strcmp(effective_value, "latest") == 0 || - strcmp(effective_value, "newest") == 0 || - strcmp(effective_value, "last") == 0 || - strcmp(effective_value, "recent") == 0) { + if (strcmp(value, "latest") == 0 || + strcmp(value, "newest") == 0 || + strcmp(value, "last") == 0 || + strcmp(value, "recent") == 0) { criteria->urgent = 'l'; - } else if (strcmp(effective_value, "oldest") == 0 || - strcmp(effective_value, "first") == 0) { + } else if (strcmp(value, "oldest") == 0 || + strcmp(value, "first") == 0) { criteria->urgent = 'o'; } else { error = @@ -542,12 +568,11 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { } break; case T_WORKSPACE: - generate_regex(&criteria->workspace, effective_value); + pattern_create(&criteria->workspace, value); break; case T_INVALID: break; } - free(effective_value); if (error) { return false;