diff options
| author | Drew DeVault <sir@cmpwn.com> | 2018-05-14 08:00:08 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-05-14 08:00:08 -0400 | 
| commit | 4e6cb2b4b2cff21ca1ec751986d8a2e138ea1ed5 (patch) | |
| tree | 20e1308a555f14212ac246af0cd559d3a37a5065 | |
| parent | 270c1ee7e507f1d2960920a7f4f0cc70f4e13d26 (diff) | |
| parent | 92b8497a0b7cb866dfea2214625c5a58724aa4d6 (diff) | |
| download | sway-4e6cb2b4b2cff21ca1ec751986d8a2e138ea1ed5.tar.xz | |
Merge pull request #1968 from RyanDwyer/fix-criteria
Fix double free in criteria
| -rw-r--r-- | include/sway/criteria.h | 73 | ||||
| -rw-r--r-- | include/sway/tree/view.h | 23 | ||||
| -rw-r--r-- | sway/commands.c | 42 | ||||
| -rw-r--r-- | sway/commands/assign.c | 58 | ||||
| -rw-r--r-- | sway/commands/for_window.c | 35 | ||||
| -rw-r--r-- | sway/criteria.c | 794 | ||||
| -rw-r--r-- | sway/desktop/wl_shell.c | 4 | ||||
| -rw-r--r-- | sway/desktop/xdg_shell.c | 4 | ||||
| -rw-r--r-- | sway/desktop/xdg_shell_v6.c | 4 | ||||
| -rw-r--r-- | sway/desktop/xwayland.c | 69 | ||||
| -rw-r--r-- | sway/tree/view.c | 105 | 
11 files changed, 707 insertions, 504 deletions
| diff --git a/include/sway/criteria.h b/include/sway/criteria.h index ec256ddb..74da132c 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -1,42 +1,61 @@  #ifndef _SWAY_CRITERIA_H  #define _SWAY_CRITERIA_H -#include "tree/container.h" +#include <pcre.h>  #include "list.h" +#include "tree/view.h" -/** - * Maps criteria (as a list of criteria tokens) to a command list. - * - * A list of tokens together represent a single criteria string (e.g. - * '[class="abc" title="xyz"]' becomes two criteria tokens). - * - * for_window: Views matching all criteria will have the bound command list - * executed on them. - * - * Set via `for_window <criteria> <cmd list>`. - */ -struct criteria { -	list_t *tokens; // struct crit_token, contains compiled regex. -	char *crit_raw; // entire criteria string (for logging) +enum criteria_type { +	CT_COMMAND = 1 << 0, +	CT_ASSIGN_OUTPUT = 1 << 1, +	CT_ASSIGN_WORKSPACE = 1 << 2, +}; +struct criteria { +	enum criteria_type type; +	char *raw; // entire criteria string (for logging)  	char *cmdlist; +	char *target; // workspace or output name for `assign` criteria + +	pcre *title; +	pcre *app_id; +	pcre *class; +	pcre *instance; +	pcre *con_mark; +	uint32_t con_id; // internal ID +	uint32_t id; // X11 window ID +	pcre *window_role; +	uint32_t window_type; +	bool floating; +	bool tiling; +	char urgent; // 'l' for latest or 'o' for oldest +	char *workspace;  }; -int criteria_cmp(const void *item, const void *data); -void free_criteria(struct criteria *crit); +bool criteria_is_empty(struct criteria *criteria); -// Pouplate list with crit_tokens extracted from criteria string, returns error -// string or NULL if successful. -char *extract_crit_tokens(list_t *tokens, const char *criteria); +void criteria_destroy(struct criteria *criteria); -// Returns list of criteria that match given container. These criteria have -// been set with `for_window` commands and have an associated cmdlist. -list_t *criteria_for(struct sway_container *cont); +/** + * Generate a criteria struct from a raw criteria string such as + * [class="foo" instance="bar"] (brackets inclusive). + * + * The error argument is expected to be an address of a null pointer. If an + * error is encountered, the function will return NULL and the pointer will be + * changed to point to the error string. This string should be freed afterwards. + */ +struct criteria *criteria_parse(char *raw, char **error); -// Returns a list of all containers that match the given list of tokens. -list_t *container_for_crit_tokens(list_t *tokens); +/** + * Compile a list of criterias matching the given view. + * + * Criteria types can be bitwise ORed. + */ +list_t *criteria_for_view(struct sway_view *view, enum criteria_type types); -// Returns true if any criteria in the given list matches this container -bool criteria_any(struct sway_container *cont, list_t *criteria); +/** + * Compile a list of views matching the given criteria. + */ +list_t *criteria_get_views(struct criteria *criteria);  #endif diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 17e579c8..7c07842b 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -21,11 +21,15 @@ enum sway_view_prop {  	VIEW_PROP_APP_ID,  	VIEW_PROP_CLASS,  	VIEW_PROP_INSTANCE, +	VIEW_PROP_WINDOW_TYPE, +	VIEW_PROP_WINDOW_ROLE, +	VIEW_PROP_X11_WINDOW_ID,  };  struct sway_view_impl { -	const char *(*get_prop)(struct sway_view *view, +	const char *(*get_string_prop)(struct sway_view *view,  			enum sway_view_prop prop); +	uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop);  	void (*configure)(struct sway_view *view, double ox, double oy, int width,  		int height);  	void (*set_activated)(struct sway_view *view, bool activated); @@ -53,6 +57,8 @@ struct sway_view {  	enum sway_container_border border;  	int border_thickness; +	list_t *executed_criteria; +  	union {  		struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6;  		struct wlr_xdg_surface *wlr_xdg_surface; @@ -109,6 +115,9 @@ struct sway_xwayland_view {  	struct wl_listener request_maximize;  	struct wl_listener request_configure;  	struct wl_listener request_fullscreen; +	struct wl_listener set_title; +	struct wl_listener set_class; +	struct wl_listener set_window_type;  	struct wl_listener map;  	struct wl_listener unmap;  	struct wl_listener destroy; @@ -191,6 +200,12 @@ const char *view_get_class(struct sway_view *view);  const char *view_get_instance(struct sway_view *view); +uint32_t view_get_x11_window_id(struct sway_view *view); + +const char *view_get_window_role(struct sway_view *view); + +uint32_t view_get_window_type(struct sway_view *view); +  const char *view_get_type(struct sway_view *view);  void view_configure(struct sway_view *view, double ox, double oy, int width, @@ -243,4 +258,10 @@ void view_child_destroy(struct sway_view_child *child);   */  void view_update_title(struct sway_view *view, bool force); +/** + * Run any criteria that match the view and haven't been run on this view + * before. + */ +void view_execute_criteria(struct sway_view *view); +  #endif diff --git a/sway/commands.c b/sway/commands.c index 37ead367..60c64776 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -12,6 +12,7 @@  #include "sway/security.h"  #include "sway/input/input-manager.h"  #include "sway/input/seat.h" +#include "sway/tree/view.h"  #include "stringop.h"  #include "log.h" @@ -284,7 +285,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {  	char *head = exec;  	char *cmdlist;  	char *cmd; -	list_t *containers = NULL; +	list_t *views = NULL;  	if (seat == NULL) {  		// passing a NULL seat means we just pick the default seat @@ -301,31 +302,18 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {  		// Extract criteria (valid for this command list only).  		bool has_criteria = false;  		if (*head == '[') { -			has_criteria = true; -			++head; -			char *criteria_string = argsep(&head, "]"); -			if (head) { -				++head; -				list_t *tokens = create_list(); -				char *error; - -				if ((error = extract_crit_tokens(tokens, criteria_string))) { -					wlr_log(L_DEBUG, "criteria string parse error: %s", error); -					results = cmd_results_new(CMD_INVALID, criteria_string, -						"Can't parse criteria string: %s", error); -					free(error); -					free(tokens); -					goto cleanup; -				} -				containers = container_for_crit_tokens(tokens); - -				free(tokens); -			} else { -				if (!results) { -					results = cmd_results_new(CMD_INVALID, criteria_string, "Unmatched ["); -				} +			char *error = NULL; +			struct criteria *criteria = criteria_parse(head, &error); +			if (!criteria) { +				results = cmd_results_new(CMD_INVALID, head, +					"%s", error); +				free(error);  				goto cleanup;  			} +			views = criteria_get_views(criteria); +			head += strlen(criteria->raw); +			criteria_destroy(criteria); +			has_criteria = true;  			// Skip leading whitespace  			head += strspn(head, whitespace);  		} @@ -382,8 +370,9 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {  				}  				free_cmd_results(res);  			} else { -				for (int i = 0; i < containers->length; ++i) { -					config->handler_context.current_container = containers->items[i]; +				for (int i = 0; i < views->length; ++i) { +					struct sway_view *view = views->items[i]; +					config->handler_context.current_container = view->swayc;  					struct cmd_results *res = handler->handle(argc-1, argv+1);  					if (res->status != CMD_SUCCESS) {  						free_argv(argc, argv); @@ -401,6 +390,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {  	} while(head);  cleanup:  	free(exec); +	free(views);  	if (!results) {  		results = cmd_results_new(CMD_SUCCESS, NULL, NULL);  	} diff --git a/sway/commands/assign.c b/sway/commands/assign.c index eb7329aa..9d15e166 100644 --- a/sway/commands/assign.c +++ b/sway/commands/assign.c @@ -5,6 +5,7 @@  #include "sway/criteria.h"  #include "list.h"  #include "log.h" +#include "stringop.h"  struct cmd_results *cmd_assign(int argc, char **argv) {  	struct cmd_results *error = NULL; @@ -12,46 +13,39 @@ struct cmd_results *cmd_assign(int argc, char **argv) {  		return error;  	} -	char *criteria = *argv++; +	// Create criteria +	char *err_str = NULL; +	struct criteria *criteria = criteria_parse(argv[0], &err_str); +	if (!criteria) { +		error = cmd_results_new(CMD_INVALID, "assign", err_str); +		free(err_str); +		return error; +	} + +	++argv; +	int target_len = argc - 1;  	if (strncmp(*argv, "→", strlen("→")) == 0) {  		if (argc < 3) {  			return cmd_results_new(CMD_INVALID, "assign", "Missing workspace");  		} -		argv++; +		++argv; +		--target_len;  	} -	char *movecmd = "move container to workspace "; -	size_t arglen = strlen(movecmd) + strlen(*argv) + 1; -	char *cmdlist = calloc(1, arglen); -	if (!cmdlist) { -		return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate command list"); +	if (strcmp(*argv, "output") == 0) { +		criteria->type = CT_ASSIGN_OUTPUT; +		++argv; +		--target_len; +	} else { +		criteria->type = CT_ASSIGN_WORKSPACE;  	} -	snprintf(cmdlist, arglen, "%s%s", movecmd, *argv); -	struct criteria *crit = malloc(sizeof(struct criteria)); -	if (!crit) { -		free(cmdlist); -		return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate criteria"); -	} -	crit->crit_raw = strdup(criteria); -	crit->cmdlist = cmdlist; -	crit->tokens = create_list(); -	char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw); +	criteria->target = join_args(argv, target_len); -	if (err_str) { -		error = cmd_results_new(CMD_INVALID, "assign", err_str); -		free(err_str); -		free_criteria(crit); -	} else if (crit->tokens->length == 0) { -		error = cmd_results_new(CMD_INVALID, "assign", "Found no name/value pairs in criteria"); -		free_criteria(crit); -	} else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) { -		wlr_log(L_DEBUG, "assign: Duplicate, skipping."); -		free_criteria(crit); -	} else { -		wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist); -		list_add(config->criteria, crit); -	} -	return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); +	list_add(config->criteria, criteria); +	wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", criteria->raw, +			criteria->target); + +	return cmd_results_new(CMD_SUCCESS, NULL, NULL);  } diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c index dd5461f0..8c425a1d 100644 --- a/sway/commands/for_window.c +++ b/sway/commands/for_window.c @@ -11,31 +11,20 @@ struct cmd_results *cmd_for_window(int argc, char **argv) {  	if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) {  		return error;  	} -	// add command to a criteria/command pair that is run against views when they appear. -	char *criteria = argv[0], *cmdlist = join_args(argv + 1, argc - 1); -	struct criteria *crit = calloc(sizeof(struct criteria), 1); -	if (!crit) { -		return cmd_results_new(CMD_FAILURE, "for_window", "Unable to allocate criteria"); -	} -	crit->crit_raw = strdup(criteria); -	crit->cmdlist = cmdlist; -	crit->tokens = create_list(); -	char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw); - -	if (err_str) { +	char *err_str = NULL; +	struct criteria *criteria = criteria_parse(argv[0], &err_str); +	if (!criteria) {  		error = cmd_results_new(CMD_INVALID, "for_window", err_str);  		free(err_str); -		free_criteria(crit); -	} else if (crit->tokens->length == 0) { -		error = cmd_results_new(CMD_INVALID, "for_window", "Found no name/value pairs in criteria"); -		free_criteria(crit); -	} else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) { -		wlr_log(L_DEBUG, "for_window: Duplicate, skipping."); -		free_criteria(crit); -	} else { -		wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist); -		list_add(config->criteria, crit); +		return error;  	} -	return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); + +	criteria->type = CT_COMMAND; +	criteria->cmdlist = join_args(argv + 1, argc - 1); + +	list_add(config->criteria, criteria); +	wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist); + +	return cmd_results_new(CMD_SUCCESS, NULL, NULL);  } diff --git a/sway/criteria.c b/sway/criteria.c index 22e9a49b..248260ec 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -11,435 +11,501 @@  #include "list.h"  #include "log.h" -enum criteria_type { // *must* keep in sync with criteria_strings[] -	CRIT_APP_ID, -	CRIT_CLASS, -	CRIT_CON_ID, -	CRIT_CON_MARK, -	CRIT_FLOATING, -	CRIT_ID, -	CRIT_INSTANCE, -	CRIT_TILING, -	CRIT_TITLE, -	CRIT_URGENT, -	CRIT_WINDOW_ROLE, -	CRIT_WINDOW_TYPE, -	CRIT_WORKSPACE, -	CRIT_LAST -}; - -static const char * const criteria_strings[CRIT_LAST] = { -	[CRIT_APP_ID] = "app_id", -	[CRIT_CLASS] = "class", -	[CRIT_CON_ID] = "con_id", -	[CRIT_CON_MARK] = "con_mark", -	[CRIT_FLOATING] = "floating", -	[CRIT_ID] = "id", -	[CRIT_INSTANCE] = "instance", -	[CRIT_TILING] = "tiling", -	[CRIT_TITLE] = "title", -	[CRIT_URGENT] = "urgent", // either "latest" or "oldest" ... -	[CRIT_WINDOW_ROLE] = "window_role", -	[CRIT_WINDOW_TYPE] = "window_type", -	[CRIT_WORKSPACE] = "workspace" -}; +bool criteria_is_empty(struct criteria *criteria) { +	return !criteria->title +		&& !criteria->app_id +		&& !criteria->class +		&& !criteria->instance +		&& !criteria->con_mark +		&& !criteria->con_id +		&& !criteria->id +		&& !criteria->window_role +		&& !criteria->window_type +		&& !criteria->floating +		&& !criteria->tiling +		&& !criteria->urgent +		&& !criteria->workspace; +} -/** - * A single criteria token (ie. value/regex pair), - * e.g. 'class="some class regex"'. - */ -struct crit_token { -	enum criteria_type type; -	pcre *regex; -	char *raw; -}; +void criteria_destroy(struct criteria *criteria) { +	pcre_free(criteria->title); +	pcre_free(criteria->app_id); +	pcre_free(criteria->class); +	pcre_free(criteria->instance); +	pcre_free(criteria->con_mark); +	pcre_free(criteria->window_role); +	free(criteria->workspace); + +	free(criteria->raw); +	free(criteria); +} -static void free_crit_token(struct crit_token *crit) { -	pcre_free(crit->regex); -	free(crit->raw); -	free(crit); +static int regex_cmp(const char *item, const pcre *regex) { +	return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);  } -static void free_crit_tokens(list_t *crit_tokens) { -	for (int i = 0; i < crit_tokens->length; i++) { -		free_crit_token(crit_tokens->items[i]); +static bool criteria_matches_view(struct criteria *criteria, +		struct sway_view *view) { +	if (criteria->title) { +		const char *title = view_get_title(view); +		if (!title || regex_cmp(title, criteria->title) != 0) { +			return false; +		}  	} -	list_free(crit_tokens); -} -// Extracts criteria string from its brackets. Returns new (duplicate) -// substring. -static char *criteria_from(const char *arg) { -	char *criteria = NULL; -	if (*arg == '[') { -		criteria = strdup(arg + 1); -	} else { -		criteria = strdup(arg); +	if (criteria->app_id) { +		const char *app_id = view_get_app_id(view); +		if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { +			return false; +		}  	} -	int last = strlen(criteria) - 1; -	if (criteria[last] == ']') { -		criteria[last] = '\0'; +	if (criteria->class) { +		const char *class = view_get_class(view); +		if (!class || regex_cmp(class, criteria->class) != 0) { +			return false; +		}  	} -	return criteria; -} -// Return instances of c found in str. -static int countchr(char *str, char c) { -	int found = 0; -	for (int i = 0; str[i]; i++) { -		if (str[i] == c) { -			++found; +	if (criteria->instance) { +		const char *instance = view_get_instance(view); +		if (!instance || regex_cmp(instance, criteria->instance) != 0) { +			return false;  		}  	} -	return found; -} -// criteria_str is e.g. '[class="some class regex" instance="instance name"]'. -// -// Will create array of pointers in buf, where first is duplicate of given -// string (must be freed) and the rest are pointers to names and values in the -// base string (every other, naturally). argc will be populated with the length -// of buf. -// -// Returns error string or NULL if successful. -static char *crit_tokens(int *argc, char ***buf, -		const char * const criteria_str) { -	wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str); -	char *base = criteria_from(criteria_str); -	char *head = base; -	char *namep = head; // start of criteria name -	char *valp = NULL; // start of value - -	// We're going to place EOS markers where we need to and fill up an array -	// of pointers to the start of each token (either name or value). -	int pairs = countchr(base, '='); -	int max_tokens = pairs * 2 + 1; // this gives us at least enough slots - -	char **argv = *buf = calloc(max_tokens, sizeof(char*)); -	argv[0] = base; // this needs to be freed by caller -	bool quoted = true; - -	*argc = 1; // uneven = name, even = value -	while (*head && *argc < max_tokens) { -		if (namep != head && *(head - 1) == '\\') { -			// escaped character: don't try to parse this -		} else if (*head == '=' && namep != head) { -			if (*argc % 2 != 1) { -				// we're not expecting a name -				return strdup("Unable to parse criteria: " -					"Found out of place equal sign"); -			} else { -				// name ends here -				char *end = head; // don't want to rewind the head -				while (*(end - 1) == ' ') { -					--end; -				} -				*end = '\0'; -				if (*(namep) == ' ') { -					namep = strrchr(namep, ' ') + 1; -				} -				argv[*argc] = namep; -				*argc += 1; -			} -		} else if (*head == '"') { -			if (*argc % 2 != 0) { -				// we're not expecting a value -				return strdup("Unable to parse criteria: " -					"Found quoted value where it was not expected"); -			} else if (!valp) { // value starts here -				valp = head + 1; -				quoted = true; -			} else { -				// value ends here -				argv[*argc] = valp; -				*argc += 1; -				*head = '\0'; -				valp = NULL; -				namep = head + 1; -			} -		} else if (*argc % 2 == 0 && *head != ' ') { -			// parse unquoted values -			if (!valp) { -				quoted = false; -				valp = head;  // value starts here -			} -		} else if (valp && !quoted && *head == ' ') { -			// value ends here -			argv[*argc] = valp; -			*argc += 1; -			*head = '\0'; -			valp = NULL; -			namep = head + 1; +	if (criteria->con_mark) { +		// TODO +		return false; +	} + +	if (criteria->con_id) { // Internal ID +		if (!view->swayc || view->swayc->id != criteria->con_id) { +			return false;  		} -		head++;  	} -	// catch last unquoted value if needed -	if (valp && !quoted && !*head) { -		argv[*argc] = valp; -		*argc += 1; +	if (criteria->id) { // X11 window ID +		uint32_t x11_window_id = view_get_x11_window_id(view); +		if (!x11_window_id || x11_window_id != criteria->id) { +			return false; +		}  	} -	return NULL; +	if (criteria->window_role) { +		// TODO +	} + +	if (criteria->window_type) { +		uint32_t type = view_get_window_type(view); +		if (!type || type != criteria->window_type) { +			return false; +		} +	} + +	if (criteria->floating) { +		// TODO +		return false; +	} + +	if (criteria->tiling) { +		// TODO +	} + +	if (criteria->urgent) { +		// TODO +		return false; +	} + +	if (criteria->workspace) { +		if (!view->swayc) { +			return false; +		} +		struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); +		if (!ws || strcmp(ws->name, criteria->workspace) != 0) { +			return false; +		} +	} + +	return true;  } -// Returns error string on failure or NULL otherwise. -static char *parse_criteria_name(enum criteria_type *type, char *name) { -	*type = CRIT_LAST; -	for (int i = 0; i < CRIT_LAST; i++) { -		if (strcmp(criteria_strings[i], name) == 0) { -			*type = (enum criteria_type) i; -			break; +list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { +	list_t *criterias = config->criteria; +	list_t *matches = create_list(); +	for (int i = 0; i < criterias->length; ++i) { +		struct criteria *criteria = criterias->items[i]; +		if ((criteria->type & types) && criteria_matches_view(criteria, view)) { +			list_add(matches, criteria);  		}  	} -	if (*type == CRIT_LAST) { -		const char *fmt = "Criteria type '%s' is invalid or unsupported."; -		int len = strlen(name) + strlen(fmt) - 1; -		char *error = malloc(len); -		snprintf(error, len, fmt, name); -		return error; -	} else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || -			*type == CRIT_WINDOW_TYPE) { -		// (we're just being helpful here) -		const char *fmt = "\"%s\" criteria currently unsupported, " -			"no window will match this"; -		int len = strlen(fmt) + strlen(name) - 1; -		char *error = malloc(len); -		snprintf(error, len, fmt, name); -		return error; +	return matches; +} + +struct match_data { +	struct criteria *criteria; +	list_t *matches; +}; + +static void criteria_get_views_iterator(struct sway_container *container, +		void *data) { +	struct match_data *match_data = data; +	if (container->type == C_VIEW) { +		if (criteria_matches_view(match_data->criteria, container->sway_view)) { +			list_add(match_data->matches, container->sway_view); +		}  	} -	return NULL;  } +list_t *criteria_get_views(struct criteria *criteria) { +	list_t *matches = create_list(); +	struct match_data data = { +		.criteria = criteria, +		.matches = matches, +	}; +	container_for_each_descendant_dfs(&root_container, +		criteria_get_views_iterator, &data); +	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 char *generate_regex(pcre **regex, char *value) { +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"; +		const char *fmt = "Regex compilation for '%s' failed: %s";  		int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; -		char *error = malloc(len); +		error = malloc(len);  		snprintf(error, len, fmt, value, reg_err); -		return error; +		return false;  	} -	return NULL; -} -// Test whether the criterion corresponds to the currently focused window -static bool crit_is_focused(const char *value) { -	return !strcmp(value, "focused") || !strcmp(value, "__focused__"); +	return true;  } -// Populate list with crit_tokens extracted from criteria string, returns error -// string or NULL if successful. -char *extract_crit_tokens(list_t *tokens, const char * const criteria) { -	int argc; -	char **argv = NULL, *error = NULL; -	if ((error = crit_tokens(&argc, &argv, criteria))) { -		goto ect_cleanup; -	} -	for (int i = 1; i + 1 < argc; i += 2) { -		char* name = argv[i], *value = argv[i + 1]; -		struct crit_token *token = calloc(1, sizeof(struct crit_token)); -		token->raw = strdup(value); - -		if ((error = parse_criteria_name(&token->type, name))) { -			free_crit_token(token); -			goto ect_cleanup; -		} else if (token->type == CRIT_URGENT || crit_is_focused(value)) { -			wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); -			list_add(tokens, token); -		} else if((error = generate_regex(&token->regex, value))) { -			free_crit_token(token); -			goto ect_cleanup; -		} else { -			wlr_log(L_DEBUG, "%s -> /%s/", name, value); -			list_add(tokens, token); -		} +enum criteria_token { +	T_APP_ID, +	T_CLASS, +	T_CON_ID, +	T_CON_MARK, +	T_FLOATING, +	T_ID, +	T_INSTANCE, +	T_TILING, +	T_TITLE, +	T_URGENT, +	T_WINDOW_ROLE, +	T_WINDOW_TYPE, +	T_WORKSPACE, + +	T_INVALID, +}; + +static enum criteria_token token_from_name(char *name) { +	if (strcmp(name, "app_id") == 0) { +		return T_APP_ID; +	} else if (strcmp(name, "class") == 0) { +		return T_CLASS; +	} else if (strcmp(name, "con_id") == 0) { +		return T_CON_ID; +	} else if (strcmp(name, "con_mark") == 0) { +		return T_CON_MARK; +	} else if (strcmp(name, "id") == 0) { +		return T_ID; +	} else if (strcmp(name, "instance") == 0) { +		return T_INSTANCE; +	} else if (strcmp(name, "title") == 0) { +		return T_TITLE; +	} else if (strcmp(name, "urgent") == 0) { +		return T_URGENT; +	} else if (strcmp(name, "window_role") == 0) { +		return T_WINDOW_ROLE; +	} else if (strcmp(name, "window_type") == 0) { +		return T_WINDOW_TYPE; +	} else if (strcmp(name, "workspace") == 0) { +		return T_WORKSPACE;  	} -ect_cleanup: -	free(argv[0]); // base string -	free(argv); -	return error; +	return T_INVALID;  } -static int regex_cmp(const char *item, const pcre *regex) { -	return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); -} +/** + * 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) { +	struct sway_seat *seat = input_manager_current_seat(input_manager); +	struct sway_container *focus = seat_get_focus(seat); -// test a single view if it matches list of criteria tokens (all of them). -static bool criteria_test(struct sway_container *cont, list_t *tokens) { -	if (cont->type != C_CONTAINER && cont->type != C_VIEW) { -		return false; +	if (!focus || focus->type != C_VIEW) { +		return NULL;  	} -	int matches = 0; -	for (int i = 0; i < tokens->length; i++) { -		struct crit_token *crit = tokens->items[i]; -		switch (crit->type) { -		case CRIT_CLASS: -			{ -				const char *class = view_get_class(cont->sway_view); -				if (!class) { -					break; -				} -				if (crit->regex && regex_cmp(class, crit->regex) == 0) { -					matches++; -				} -				break; -			} -		case CRIT_CON_ID: -			{ -				char *endptr; -				size_t crit_id = strtoul(crit->raw, &endptr, 10); - -				if (*endptr == 0 && cont->id == crit_id) { -					++matches; -				} -				break; -			} -		case CRIT_CON_MARK: -			// TODO -			break; -		case CRIT_FLOATING: -			// TODO -			break; -		case CRIT_ID: -			// TODO -			break; -		case CRIT_APP_ID: -			{ -				const char *app_id = view_get_app_id(cont->sway_view); -				if (!app_id) { -					break; -				} - -				if (crit->regex && regex_cmp(app_id, crit->regex) == 0) { -					matches++; -				} -				break; +	struct sway_view *view = focus->sway_view; +	const char *value = NULL; + +	switch (token) { +	case T_APP_ID: +		value = view_get_app_id(view); +		break; +	case T_CLASS: +		value = view_get_class(view); +		break; +	case T_INSTANCE: +		value = view_get_instance(view); +		break; +	case T_TITLE: +		value = view_get_class(view); +		break; +	case T_WINDOW_ROLE: +		value = view_get_class(view); +		break; +	case T_WORKSPACE: +		{ +			struct sway_container *ws = container_parent(focus, C_WORKSPACE); +			if (ws) { +				value = ws->name;  			} -		case CRIT_INSTANCE: -			{ -				const char *instance = view_get_instance(cont->sway_view); -				if (!instance) { -					break; -				} - -				if (crit->regex && regex_cmp(instance, crit->regex) == 0) { -					matches++; -				} -				break; -			} -		case CRIT_TILING: -			// TODO -			break; -		case CRIT_TITLE: -			{ -				const char *title = view_get_title(cont->sway_view); -				if (!title) { -					break; -				} - -				if (crit->regex && regex_cmp(title, crit->regex) == 0) { -					matches++; -				} -				break; -			} -		case CRIT_URGENT: -			// TODO "latest" or "oldest" -			break; -		case CRIT_WINDOW_ROLE: -			// TODO -			break; -		case CRIT_WINDOW_TYPE: -			// TODO -			break; -		case CRIT_WORKSPACE: -			// TODO -			break; -		default: -			sway_abort("Invalid criteria type (%i)", crit->type); -			break;  		} +		break; +	case T_CON_ID: // These do not support __focused__ +	case T_CON_MARK: +	case T_FLOATING: +	case T_ID: +	case T_TILING: +	case T_URGENT: +	case T_WINDOW_TYPE: +	case T_INVALID: +		break; +	} +	if (value) { +		return strdup(value);  	} -	return matches == tokens->length; +	return NULL;  } -int criteria_cmp(const void *a, const void *b) { -	if (a == b) { -		return 0; -	} else if (!a) { -		return -1; -	} else if (!b) { -		return 1; +static bool parse_token(struct criteria *criteria, char *name, char *value) { +	enum criteria_token token = token_from_name(name); +	if (token == T_INVALID) { +		const char *fmt = "Token '%s' is not recognized"; +		int len = strlen(fmt) + strlen(name) - 1; +		error = malloc(len); +		snprintf(error, len, fmt, name); +		return false;  	} -	const struct criteria *crit_a = a, *crit_b = b; -	int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist); -	if (cmp != 0) { -		return cmp; + +	char *effective_value = NULL; +	if (value && strcmp(value, "__focused__") == 0) { +		effective_value = get_focused_prop(token); +	} else if (value) { +		effective_value = strdup(value);  	} -	return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw); -} -void free_criteria(struct criteria *crit) { -	if (crit->tokens) { -		free_crit_tokens(crit->tokens); +	// Require value, unless token is floating or tiled +	if (!effective_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); +		snprintf(error, len, fmt, name); +		return false;  	} -	if (crit->cmdlist) { -		free(crit->cmdlist); + +	char *endptr = NULL; +	switch (token) { +	case T_TITLE: +		generate_regex(&criteria->title, effective_value); +		break; +	case T_APP_ID: +		generate_regex(&criteria->app_id, effective_value); +		break; +	case T_CLASS: +		generate_regex(&criteria->class, effective_value); +		break; +	case T_INSTANCE: +		generate_regex(&criteria->instance, effective_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 numeric"); +		} +		break; +	case T_CON_MARK: +		generate_regex(&criteria->con_mark, effective_value); +		break; +	case T_WINDOW_ROLE: +		generate_regex(&criteria->window_role, effective_value); +		break; +	case T_WINDOW_TYPE: +		// TODO: This is a string but will be stored as an enum or integer +		break; +	case T_ID: +		criteria->id = strtoul(effective_value, &endptr, 10); +		if (*endptr != 0) { +			error = strdup("The value for 'id' should be numeric"); +		} +		break; +	case T_FLOATING: +		criteria->floating = true; +		break; +	case T_TILING: +		criteria->tiling = true; +		break; +	case T_URGENT: +		if (strcmp(effective_value, "latest") == 0) { +			criteria->urgent = 'l'; +		} else if (strcmp(effective_value, "oldest") == 0) { +			criteria->urgent = 'o'; +		} else { +			error = +				strdup("The value for 'urgent' must be 'latest' or 'oldest'"); +		} +		break; +	case T_WORKSPACE: +		criteria->workspace = strdup(effective_value); +		break; +	case T_INVALID: +		break;  	} -	if (crit->crit_raw) { -		free(crit->crit_raw); +	free(effective_value); + +	if (error) { +		return false;  	} -	free(crit); + +	return true;  } -bool criteria_any(struct sway_container *cont, list_t *criteria) { -	for (int i = 0; i < criteria->length; i++) { -		struct criteria *bc = criteria->items[i]; -		if (criteria_test(cont, bc->tokens)) { -			return true; -		} +static void skip_spaces(char **head) { +	while (**head == ' ') { +		++*head;  	} -	return false;  } -list_t *criteria_for(struct sway_container *cont) { -	list_t *criteria = config->criteria, *matches = create_list(); -	for (int i = 0; i < criteria->length; i++) { -		struct criteria *bc = criteria->items[i]; -		if (criteria_test(cont, bc->tokens)) { -			list_add(matches, bc); +// Remove escaping slashes from value +static void unescape(char *value) { +	if (!strchr(value, '\\')) { +		return; +	} +	char *copy = calloc(strlen(value) + 1, 1); +	char *readhead = value; +	char *writehead = copy; +	while (*readhead) { +		if (*readhead == '\\' && *(readhead + 1) == '"') { +			// skip the slash +			++readhead;  		} +		*writehead = *readhead; +		++writehead; +		++readhead;  	} -	return matches; +	strcpy(value, copy); +	free(copy);  } -struct list_tokens { -	list_t *list; -	list_t *tokens; -}; - -static void container_match_add(struct sway_container *container, -		struct list_tokens *list_tokens) { -	if (criteria_test(container, list_tokens->tokens)) { -		list_add(list_tokens->list, container); +/** + * Parse a raw criteria string such as [class="foo" instance="bar"] into a + * criteria struct. + * + * If errors are found, NULL will be returned and the error argument will be + * populated with an error string. It is up to the caller to free the error. + */ +struct criteria *criteria_parse(char *raw, char **error_arg) { +	*error_arg = NULL; +	error = NULL; + +	char *head = raw; +	skip_spaces(&head); +	if (*head != '[') { +		*error_arg = strdup("No criteria"); +		return NULL; +	} +	++head; + +	struct criteria *criteria = calloc(sizeof(struct criteria), 1); +	char *name = NULL, *value = NULL; +	bool in_quotes = false; + +	while (*head && *head != ']') { +		skip_spaces(&head); +		// Parse token name +		char *namestart = head; +		while ((*head >= 'a' && *head <= 'z') || *head == '_') { +			++head; +		} +		name = calloc(head - namestart + 1, 1); +		strncpy(name, namestart, head - namestart); +		// Parse token value +		skip_spaces(&head); +		value = NULL; +		if (*head == '=') { +			++head; +			skip_spaces(&head); +			if (*head == '"') { +				in_quotes = true; +				++head; +			} +			char *valuestart = head; +			if (in_quotes) { +				while (*head && (*head != '"' || *(head - 1) == '\\')) { +					++head; +				} +				if (!*head) { +					*error_arg = strdup("Quote mismatch in criteria"); +					goto cleanup; +				} +			} else { +				while (*head && *head != ' ' && *head != ']') { +					++head; +				} +			} +			value = calloc(head - valuestart + 1, 1); +			strncpy(value, valuestart, head - valuestart); +			if (in_quotes) { +				++head; +				in_quotes = false; +			} +			unescape(value); +		} +		wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); +		if (!parse_token(criteria, name, value)) { +			*error_arg = error; +			goto cleanup; +		} +		skip_spaces(&head); +		free(name); +		free(value); +		name = NULL; +		value = NULL; +	} +	if (*head != ']') { +		*error_arg = strdup("No closing brace found in criteria"); +		goto cleanup;  	} -} -list_t *container_for_crit_tokens(list_t *tokens) { -	struct list_tokens list_tokens = -		(struct list_tokens){create_list(), tokens}; +	if (criteria_is_empty(criteria)) { +		*error_arg = strdup("Criteria is empty"); +		goto cleanup; +	} -	container_for_each_descendant_dfs(&root_container, -		(void (*)(struct sway_container *, void *))container_match_add, -		&list_tokens); +	++head; +	int len = head - raw; +	criteria->raw = calloc(len + 1, 1); +	strncpy(criteria->raw, raw, len); +	return criteria; -	// TODO look in the scratchpad -	 -	return list_tokens.list; +cleanup: +	free(name); +	free(value); +	criteria_destroy(criteria); +	return NULL;  } diff --git a/sway/desktop/wl_shell.c b/sway/desktop/wl_shell.c index 99e8947b..cb3774f7 100644 --- a/sway/desktop/wl_shell.c +++ b/sway/desktop/wl_shell.c @@ -20,7 +20,7 @@ static struct sway_wl_shell_view *wl_shell_view_from_view(  	return (struct sway_wl_shell_view *)view;  } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {  	if (wl_shell_view_from_view(view) == NULL) {  		return NULL;  	} @@ -70,7 +70,7 @@ static void set_fullscreen(struct sway_view *view, bool fullscreen) {  }  static const struct sway_view_impl view_impl = { -	.get_prop = get_prop, +	.get_string_prop = get_string_prop,  	.configure = configure,  	.close = _close,  	.destroy = destroy, diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 48b659f2..9a0d282b 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -72,7 +72,7 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view(  	return (struct sway_xdg_shell_view *)view;  } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {  	if (xdg_shell_view_from_view(view) == NULL) {  		return NULL;  	} @@ -150,7 +150,7 @@ static void destroy(struct sway_view *view) {  }  static const struct sway_view_impl view_impl = { -	.get_prop = get_prop, +	.get_string_prop = get_string_prop,  	.configure = configure,  	.set_activated = set_activated,  	.set_fullscreen = set_fullscreen, diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index e9051b6c..d098c797 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c @@ -72,7 +72,7 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view(  	return (struct sway_xdg_shell_v6_view *)view;  } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {  	if (xdg_shell_v6_view_from_view(view) == NULL) {  		return NULL;  	} @@ -150,7 +150,7 @@ static void destroy(struct sway_view *view) {  }  static const struct sway_view_impl view_impl = { -	.get_prop = get_prop, +	.get_string_prop = get_string_prop,  	.configure = configure,  	.set_activated = set_activated,  	.set_fullscreen = set_fullscreen, diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index 1d3c857d..6a99a66a 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c @@ -122,7 +122,7 @@ static struct sway_xwayland_view *xwayland_view_from_view(  	return (struct sway_xwayland_view *)view;  } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {  	if (xwayland_view_from_view(view) == NULL) {  		return NULL;  	} @@ -131,11 +131,27 @@ static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) {  		return view->wlr_xwayland_surface->title;  	case VIEW_PROP_CLASS:  		return view->wlr_xwayland_surface->class; +	case VIEW_PROP_INSTANCE: +		return view->wlr_xwayland_surface->instance;  	default:  		return NULL;  	}  } +static uint32_t get_int_prop(struct sway_view *view, enum sway_view_prop prop) { +	if (xwayland_view_from_view(view) == NULL) { +		return 0; +	} +	switch (prop) { +	case VIEW_PROP_X11_WINDOW_ID: +		return view->wlr_xwayland_surface->window_id; +	case VIEW_PROP_WINDOW_TYPE: +		return *view->wlr_xwayland_surface->window_type; +	default: +		return 0; +	} +} +  static void configure(struct sway_view *view, double ox, double oy, int width,  		int height) {  	struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); @@ -196,13 +212,17 @@ static void destroy(struct sway_view *view) {  	wl_list_remove(&xwayland_view->destroy.link);  	wl_list_remove(&xwayland_view->request_configure.link);  	wl_list_remove(&xwayland_view->request_fullscreen.link); +	wl_list_remove(&xwayland_view->set_title.link); +	wl_list_remove(&xwayland_view->set_class.link); +	wl_list_remove(&xwayland_view->set_window_type.link);  	wl_list_remove(&xwayland_view->map.link);  	wl_list_remove(&xwayland_view->unmap.link);  	free(xwayland_view);  }  static const struct sway_view_impl view_impl = { -	.get_prop = get_prop, +	.get_string_prop = get_string_prop, +	.get_int_prop = get_int_prop,  	.configure = configure,  	.set_activated = set_activated,  	.set_fullscreen = set_fullscreen, @@ -219,7 +239,6 @@ static void handle_commit(struct wl_listener *listener, void *data) {  	view_update_size(view, xwayland_view->pending_width,  		xwayland_view->pending_height);  	view_damage_from(view); -	view_update_title(view, false);  }  static void handle_unmap(struct wl_listener *listener, void *data) { @@ -281,6 +300,40 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)  	view_set_fullscreen(view, xsurface->fullscreen);  } +static void handle_set_title(struct wl_listener *listener, void *data) { +	struct sway_xwayland_view *xwayland_view = +		wl_container_of(listener, xwayland_view, set_title); +	struct sway_view *view = &xwayland_view->view; +	struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; +	if (!xsurface->mapped) { +		return; +	} +	view_update_title(view, false); +	view_execute_criteria(view); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { +	struct sway_xwayland_view *xwayland_view = +		wl_container_of(listener, xwayland_view, set_class); +	struct sway_view *view = &xwayland_view->view; +	struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; +	if (!xsurface->mapped) { +		return; +	} +	view_execute_criteria(view); +} + +static void handle_set_window_type(struct wl_listener *listener, void *data) { +	struct sway_xwayland_view *xwayland_view = +		wl_container_of(listener, xwayland_view, set_window_type); +	struct sway_view *view = &xwayland_view->view; +	struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; +	if (!xsurface->mapped) { +		return; +	} +	view_execute_criteria(view); +} +  void handle_xwayland_surface(struct wl_listener *listener, void *data) {  	struct sway_server *server = wl_container_of(listener, server,  		xwayland_surface); @@ -319,6 +372,16 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {  		&xwayland_view->request_fullscreen);  	xwayland_view->request_fullscreen.notify = handle_request_fullscreen; +	wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); +	xwayland_view->set_title.notify = handle_set_title; + +	wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_class); +	xwayland_view->set_class.notify = handle_set_class; + +	wl_signal_add(&xsurface->events.set_window_type, +			&xwayland_view->set_window_type); +	xwayland_view->set_window_type.notify = handle_set_window_type; +  	wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap);  	xwayland_view->unmap.notify = handle_unmap; diff --git a/sway/tree/view.c b/sway/tree/view.c index 2fdb14a2..41dee1c4 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -3,6 +3,7 @@  #include <wayland-server.h>  #include <wlr/render/wlr_renderer.h>  #include <wlr/types/wlr_output_layout.h> +#include "list.h"  #include "log.h"  #include "sway/criteria.h"  #include "sway/commands.h" @@ -21,6 +22,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,  		const struct sway_view_impl *impl) {  	view->type = type;  	view->impl = impl; +	view->executed_criteria = create_list();  	wl_signal_init(&view->events.unmap);  } @@ -33,6 +35,8 @@ void view_destroy(struct sway_view *view) {  		view_unmap(view);  	} +	list_free(view->executed_criteria); +  	container_destroy(view->swayc);  	if (view->impl->destroy) { @@ -43,33 +47,54 @@ void view_destroy(struct sway_view *view) {  }  const char *view_get_title(struct sway_view *view) { -	if (view->impl->get_prop) { -		return view->impl->get_prop(view, VIEW_PROP_TITLE); +	if (view->impl->get_string_prop) { +		return view->impl->get_string_prop(view, VIEW_PROP_TITLE);  	}  	return NULL;  }  const char *view_get_app_id(struct sway_view *view) { -	if (view->impl->get_prop) { -		return view->impl->get_prop(view, VIEW_PROP_APP_ID); +	if (view->impl->get_string_prop) { +		return view->impl->get_string_prop(view, VIEW_PROP_APP_ID);  	}  	return NULL;  }  const char *view_get_class(struct sway_view *view) { -	if (view->impl->get_prop) { -		return view->impl->get_prop(view, VIEW_PROP_CLASS); +	if (view->impl->get_string_prop) { +		return view->impl->get_string_prop(view, VIEW_PROP_CLASS);  	}  	return NULL;  }  const char *view_get_instance(struct sway_view *view) { -	if (view->impl->get_prop) { -		return view->impl->get_prop(view, VIEW_PROP_INSTANCE); +	if (view->impl->get_string_prop) { +		return view->impl->get_string_prop(view, VIEW_PROP_INSTANCE);  	}  	return NULL;  } +uint32_t view_get_x11_window_id(struct sway_view *view) { +	if (view->impl->get_int_prop) { +		return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); +	} +	return 0; +} + +const char *view_get_window_role(struct sway_view *view) { +	if (view->impl->get_string_prop) { +		return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE); +	} +	return NULL; +} + +uint32_t view_get_window_type(struct sway_view *view) { +	if (view->impl->get_int_prop) { +		return view->impl->get_int_prop(view, VIEW_PROP_WINDOW_TYPE); +	} +	return 0; +} +  const char *view_get_type(struct sway_view *view) {  	switch(view->type) {  	case SWAY_VIEW_WL_SHELL: @@ -327,19 +352,36 @@ static void view_handle_container_reparent(struct wl_listener *listener,  	}  } -static void view_execute_criteria(struct sway_view *view) { -	if (!sway_assert(view->swayc, "cannot run criteria for unmapped view")) { +static bool view_has_executed_criteria(struct sway_view *view, +		struct criteria *criteria) { +	for (int i = 0; i < view->executed_criteria->length; ++i) { +		struct criteria *item = view->executed_criteria->items[i]; +		if (item == criteria) { +			return true; +		} +	} +	return false; +} + +void view_execute_criteria(struct sway_view *view) { +	if (!view->swayc) {  		return;  	}  	struct sway_seat *seat = input_manager_current_seat(input_manager);  	struct sway_container *prior_workspace =  		container_parent(view->swayc, C_WORKSPACE); -	list_t *criteria = criteria_for(view->swayc); -	for (int i = 0; i < criteria->length; i++) { -		struct criteria *crit = criteria->items[i]; -		wlr_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'", -				crit->crit_raw, view, crit->cmdlist); -		struct cmd_results *res = execute_command(crit->cmdlist, NULL); +	list_t *criterias = criteria_for_view(view, CT_COMMAND); +	for (int i = 0; i < criterias->length; i++) { +		struct criteria *criteria = criterias->items[i]; +		wlr_log(L_DEBUG, "Checking criteria %s", criteria->raw); +		if (view_has_executed_criteria(view, criteria)) { +			wlr_log(L_DEBUG, "Criteria already executed"); +			continue; +		} +		wlr_log(L_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", +				criteria->raw, view, criteria->cmdlist); +		list_add(view->executed_criteria, criteria); +		struct cmd_results *res = execute_command(criteria->cmdlist, NULL);  		if (res->status != CMD_SUCCESS) {  			wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);  		} @@ -348,7 +390,7 @@ static void view_execute_criteria(struct sway_view *view) {  		// so always refocus in-between command lists  		seat_set_focus(seat, view->swayc);  	} -	list_free(criteria); +	list_free(criterias);  	seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace));  } @@ -358,9 +400,27 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {  	}  	struct sway_seat *seat = input_manager_current_seat(input_manager); -	struct sway_container *focus = seat_get_focus_inactive(seat, -		&root_container); -	struct sway_container *cont = container_view_create(focus, view); +	struct sway_container *focus = +		seat_get_focus_inactive(seat, &root_container); +	struct sway_container *cont = NULL; + +	// Check if there's any `assign` criteria for the view +	list_t *criterias = criteria_for_view(view, +			CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); +	if (criterias->length) { +		struct criteria *criteria = criterias->items[0]; +		if (criteria->type == CT_ASSIGN_WORKSPACE) { +			struct sway_container *workspace = workspace_by_name(criteria->target); +			if (!workspace) { +				workspace = workspace_create(NULL, criteria->target); +			} +			focus = seat_get_focus_inactive(seat, workspace); +		} else { +			// TODO: CT_ASSIGN_OUTPUT +		} +	} +	free(criterias); +	cont = container_view_create(focus, view);  	view->surface = wlr_surface;  	view->swayc = cont; @@ -378,10 +438,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {  	arrange_children_of(cont->parent);  	input_manager_set_focus(input_manager, cont); +	view_update_title(view, false); +	view_execute_criteria(view); +  	container_damage_whole(cont);  	view_handle_container_reparent(&view->container_reparent, NULL); - -	view_execute_criteria(view);  }  void view_unmap(struct sway_view *view) { | 
