criteria: make literal comparison for __focused__ values

This commit is contained in:
Ronan Pigott 2019-10-10 19:44:56 -07:00 committed by Drew DeVault
parent f5f12a69f6
commit 7c9b71f5c6
2 changed files with 205 additions and 171 deletions

View file

@ -14,29 +14,38 @@ enum criteria_type {
CT_NO_FOCUS = 1 << 4, CT_NO_FOCUS = 1 << 4,
}; };
enum pattern_type {
PATTERN_PCRE,
PATTERN_FOCUSED,
};
struct pattern {
enum pattern_type match_type;
pcre *regex;
};
struct criteria { struct criteria {
enum criteria_type type; enum criteria_type type;
char *raw; // entire criteria string (for logging) char *raw; // entire criteria string (for logging)
char *cmdlist; char *cmdlist;
char *target; // workspace or output name for `assign` criteria char *target; // workspace or output name for `assign` criteria
bool autofail; // __focused__ while no focus or n/a for focused view struct pattern *title;
pcre *title; struct pattern *shell;
pcre *shell; struct pattern *app_id;
pcre *app_id; struct pattern *con_mark;
pcre *con_mark;
uint32_t con_id; // internal ID uint32_t con_id; // internal ID
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
pcre *class; struct pattern *class;
uint32_t id; // X11 window ID uint32_t id; // X11 window ID
pcre *instance; struct pattern *instance;
pcre *window_role; struct pattern *window_role;
enum atom_name window_type; enum atom_name window_type;
#endif #endif
bool floating; bool floating;
bool tiling; bool tiling;
char urgent; // 'l' for latest or 'o' for oldest char urgent; // 'l' for latest or 'o' for oldest
pcre *workspace; struct pattern *workspace;
}; };
bool criteria_is_empty(struct criteria *criteria); bool criteria_is_empty(struct criteria *criteria);

View file

@ -16,8 +16,7 @@
#include "config.h" #include "config.h"
bool criteria_is_empty(struct criteria *criteria) { bool criteria_is_empty(struct criteria *criteria) {
return !criteria->autofail return !criteria->title
&& !criteria->title
&& !criteria->shell && !criteria->shell
&& !criteria->app_id && !criteria->app_id
&& !criteria->con_mark && !criteria->con_mark
@ -35,16 +34,64 @@ bool criteria_is_empty(struct criteria *criteria) {
&& !criteria->workspace; && !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, &reg_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) { void criteria_destroy(struct criteria *criteria) {
pcre_free(criteria->title); pattern_destroy(criteria->title);
pcre_free(criteria->shell); pattern_destroy(criteria->shell);
pcre_free(criteria->app_id); pattern_destroy(criteria->app_id);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
pcre_free(criteria->class); pattern_destroy(criteria->class);
pcre_free(criteria->instance); pattern_destroy(criteria->instance);
pcre_free(criteria->window_role); pattern_destroy(criteria->window_role);
#endif #endif
pcre_free(criteria->con_mark); pattern_destroy(criteria->con_mark);
free(criteria->workspace); free(criteria->workspace);
free(criteria->cmdlist); free(criteria->cmdlist);
free(criteria->raw); 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, static bool criteria_matches_view(struct criteria *criteria,
struct sway_view *view) { struct sway_view *view) {
if (criteria->autofail) { struct sway_seat *seat = input_manager_current_seat();
return false; struct sway_container *focus = seat_get_focused_container(seat);
} struct sway_view *focused = focus ? focus->view : NULL;
if (criteria->title) { if (criteria->title) {
const char *title = view_get_title(view); const char *title = view_get_title(view);
if (!title || regex_cmp(title, criteria->title) != 0) { if (!title) {
return false; 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) { if (criteria->shell) {
const char *shell = view_get_shell(view); const char *shell = view_get_shell(view);
if (!shell || regex_cmp(shell, criteria->shell) != 0) { if (!shell) {
return false; 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) { if (criteria->app_id) {
const char *app_id = view_get_app_id(view); 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; 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) { if (criteria->con_mark) {
bool exists = false; bool exists = false;
struct sway_container *con = view->container; struct sway_container *con = view->container;
for (int i = 0; i < con->marks->length; ++i) { 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; exists = true;
break; break;
} }
@ -154,23 +240,62 @@ static bool criteria_matches_view(struct criteria *criteria,
if (criteria->class) { if (criteria->class) {
const char *class = view_get_class(view); const char *class = view_get_class(view);
if (!class || regex_cmp(class, criteria->class) != 0) { if (!class) {
return false; 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) { if (criteria->instance) {
const char *instance = view_get_instance(view); const char *instance = view_get_instance(view);
if (!instance || regex_cmp(instance, criteria->instance) != 0) { if (!instance) {
return false; 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) { if (criteria->window_role) {
const char *role = view_get_window_role(view); const char *window_role = view_get_window_role(view);
if (!role || regex_cmp(role, criteria->window_role) != 0) { if (!window_role) {
return false; 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) { if (criteria->window_type != ATOM_LAST) {
@ -213,9 +338,23 @@ static bool criteria_matches_view(struct criteria *criteria,
if (criteria->workspace) { if (criteria->workspace) {
struct sway_workspace *ws = view->container->workspace; struct sway_workspace *ws = view->container->workspace;
if (!ws || regex_cmp(ws->name, criteria->workspace) != 0) { if (!ws) {
return false; 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; return true;
@ -258,28 +397,6 @@ list_t *criteria_get_views(struct criteria *criteria) {
return matches; 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, &reg_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 #if HAVE_XWAYLAND
static enum atom_name parse_window_type(const char *type) { static enum atom_name parse_window_type(const char *type) {
if (strcasecmp(type, "normal") == 0) { if (strcasecmp(type, "normal") == 0) {
@ -363,92 +480,6 @@ static enum criteria_token token_from_name(char *name) {
return T_INVALID; 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) { static bool parse_token(struct criteria *criteria, char *name, char *value) {
enum criteria_token token = token_from_name(name); enum criteria_token token = token_from_name(name);
if (token == T_INVALID) { if (token == T_INVALID) {
@ -459,20 +490,8 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
return false; 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 // 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"; const char *fmt = "Token '%s' requires a value";
int len = strlen(fmt) + strlen(name) - 1; int len = strlen(fmt) + strlen(name) - 1;
error = malloc(len); error = malloc(len);
@ -483,41 +502,48 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
char *endptr = NULL; char *endptr = NULL;
switch (token) { switch (token) {
case T_TITLE: case T_TITLE:
generate_regex(&criteria->title, effective_value); pattern_create(&criteria->title, value);
break; break;
case T_SHELL: case T_SHELL:
generate_regex(&criteria->shell, effective_value); pattern_create(&criteria->shell, value);
break; break;
case T_APP_ID: case T_APP_ID:
generate_regex(&criteria->app_id, effective_value); pattern_create(&criteria->app_id, value);
break; break;
case T_CON_ID: case T_CON_ID:
criteria->con_id = strtoul(effective_value, &endptr, 10); if (strcmp(value, "__focused__") == 0) {
if (*endptr != 0) { struct sway_seat *seat = input_manager_current_seat();
error = strdup("The value for 'con_id' should be '__focused__' or numeric"); 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; break;
case T_CON_MARK: case T_CON_MARK:
generate_regex(&criteria->con_mark, effective_value); pattern_create(&criteria->con_mark, value);
break; break;
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
case T_CLASS: case T_CLASS:
generate_regex(&criteria->class, effective_value); pattern_create(&criteria->class, value);
break; break;
case T_ID: case T_ID:
criteria->id = strtoul(effective_value, &endptr, 10); criteria->id = strtoul(value, &endptr, 10);
if (*endptr != 0) { if (*endptr != 0) {
error = strdup("The value for 'id' should be numeric"); error = strdup("The value for 'id' should be numeric");
} }
break; break;
case T_INSTANCE: case T_INSTANCE:
generate_regex(&criteria->instance, effective_value); pattern_create(&criteria->instance, value);
break; break;
case T_WINDOW_ROLE: case T_WINDOW_ROLE:
generate_regex(&criteria->window_role, effective_value); pattern_create(&criteria->window_role, value);
break; break;
case T_WINDOW_TYPE: case T_WINDOW_TYPE:
criteria->window_type = parse_window_type(effective_value); criteria->window_type = parse_window_type(value);
break; break;
#endif #endif
case T_FLOATING: case T_FLOATING:
@ -527,13 +553,13 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
criteria->tiling = true; criteria->tiling = true;
break; break;
case T_URGENT: case T_URGENT:
if (strcmp(effective_value, "latest") == 0 || if (strcmp(value, "latest") == 0 ||
strcmp(effective_value, "newest") == 0 || strcmp(value, "newest") == 0 ||
strcmp(effective_value, "last") == 0 || strcmp(value, "last") == 0 ||
strcmp(effective_value, "recent") == 0) { strcmp(value, "recent") == 0) {
criteria->urgent = 'l'; criteria->urgent = 'l';
} else if (strcmp(effective_value, "oldest") == 0 || } else if (strcmp(value, "oldest") == 0 ||
strcmp(effective_value, "first") == 0) { strcmp(value, "first") == 0) {
criteria->urgent = 'o'; criteria->urgent = 'o';
} else { } else {
error = error =
@ -542,12 +568,11 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
} }
break; break;
case T_WORKSPACE: case T_WORKSPACE:
generate_regex(&criteria->workspace, effective_value); pattern_create(&criteria->workspace, value);
break; break;
case T_INVALID: case T_INVALID:
break; break;
} }
free(effective_value);
if (error) { if (error) {
return false; return false;