diff options
author | Ryan Dwyer <ryandwyer1@gmail.com> | 2018-05-09 14:23:20 +1000 |
---|---|---|
committer | Ryan Dwyer <ryandwyer1@gmail.com> | 2018-05-11 09:38:53 +1000 |
commit | 3b0c26d149dfe5e05df338692db8255a01f0998d (patch) | |
tree | c39e377cb96297df8547311a0aa4cfe8fa85b517 /sway/criteria.c | |
parent | Merge pull request #1948 from RyanDwyer/focus-parent-border (diff) | |
download | sway-3b0c26d149dfe5e05df338692db8255a01f0998d.tar.gz sway-3b0c26d149dfe5e05df338692db8255a01f0998d.tar.zst sway-3b0c26d149dfe5e05df338692db8255a01f0998d.zip |
Overhaul criteria implementation
The criteria struct now uses properties for each token type rather than
the list_t list of tokens. The reason for this is that different token
types have different data types: pcre, string and number to name a few.
This solution should be more flexible moving forward. A bonus of this is
that criteria is now easier to understand when looking at the struct
definition.
The criteria parser has been rewritten because the previous one didn't
support valueless pairs (eg. [class="foo" floating]).
Criteria now has types. Types at the moment are CT_COMMAND,
CT_ASSIGN_WORKSPACE and CT_ASSIGN_OUTPUT. i3 uses types as well.
Previously the assign command was creating a criteria with 'move to
workspace <name>' as its command, but this caused the window to appear
briefly on the focused workspace before being moved to the assigned
workspace. It now creates the view directly in the assigned workspace.
Each view will only execute a given criteria once. This is achieved by
storing a list of executed criteria in the view. This is the same
strategy used by i3.
Escaping now works properly. Previously you could do things like
[class="Fire\"fox"] and the stored value would be 'Fire\"fox', but it
should be (and now is) 'Fire"fox'.
The public functions in criteria.c are now all prefixed with criteria_.
Xwayland views now listen to the set_title, set_class and
set_window_type events and criteria will be run when these happen. XDG
shell has none of these events so it continues to update the title in
handle_commit.
Each view type's get_prop function has been split into get_string_prop
and get_int_prop because some properties like the X11 window ID and
window type are numeric.
The following new criteria tokens are now supported:
* id (X11 window ID)
* instance
* tiling
* workspace
Diffstat (limited to 'sway/criteria.c')
-rw-r--r-- | sway/criteria.c | 685 |
1 files changed, 316 insertions, 369 deletions
diff --git a/sway/criteria.c b/sway/criteria.c index 22e9a49b..6abe24af 100644 --- a/sway/criteria.c +++ b/sway/criteria.c | |||
@@ -11,435 +11,382 @@ | |||
11 | #include "list.h" | 11 | #include "list.h" |
12 | #include "log.h" | 12 | #include "log.h" |
13 | 13 | ||
14 | enum criteria_type { // *must* keep in sync with criteria_strings[] | 14 | bool criteria_is_empty(struct criteria *criteria) { |
15 | CRIT_APP_ID, | 15 | return !criteria->title |
16 | CRIT_CLASS, | 16 | && !criteria->app_id |
17 | CRIT_CON_ID, | 17 | && !criteria->class |
18 | CRIT_CON_MARK, | 18 | && !criteria->instance |
19 | CRIT_FLOATING, | 19 | && !criteria->con_mark |
20 | CRIT_ID, | 20 | && !criteria->con_id |
21 | CRIT_INSTANCE, | 21 | && !criteria->id |
22 | CRIT_TILING, | 22 | && !criteria->window_role |
23 | CRIT_TITLE, | 23 | && !criteria->window_type |
24 | CRIT_URGENT, | 24 | && !criteria->floating |
25 | CRIT_WINDOW_ROLE, | 25 | && !criteria->tiling |
26 | CRIT_WINDOW_TYPE, | 26 | && !criteria->urgent |
27 | CRIT_WORKSPACE, | 27 | && !criteria->workspace; |
28 | CRIT_LAST | 28 | } |
29 | }; | ||
30 | |||
31 | static const char * const criteria_strings[CRIT_LAST] = { | ||
32 | [CRIT_APP_ID] = "app_id", | ||
33 | [CRIT_CLASS] = "class", | ||
34 | [CRIT_CON_ID] = "con_id", | ||
35 | [CRIT_CON_MARK] = "con_mark", | ||
36 | [CRIT_FLOATING] = "floating", | ||
37 | [CRIT_ID] = "id", | ||
38 | [CRIT_INSTANCE] = "instance", | ||
39 | [CRIT_TILING] = "tiling", | ||
40 | [CRIT_TITLE] = "title", | ||
41 | [CRIT_URGENT] = "urgent", // either "latest" or "oldest" ... | ||
42 | [CRIT_WINDOW_ROLE] = "window_role", | ||
43 | [CRIT_WINDOW_TYPE] = "window_type", | ||
44 | [CRIT_WORKSPACE] = "workspace" | ||
45 | }; | ||
46 | 29 | ||
47 | /** | 30 | void criteria_destroy(struct criteria *criteria) { |
48 | * A single criteria token (ie. value/regex pair), | 31 | pcre_free(criteria->title); |
49 | * e.g. 'class="some class regex"'. | 32 | pcre_free(criteria->app_id); |
50 | */ | 33 | pcre_free(criteria->class); |
51 | struct crit_token { | 34 | pcre_free(criteria->instance); |
52 | enum criteria_type type; | 35 | pcre_free(criteria->con_mark); |
53 | pcre *regex; | 36 | pcre_free(criteria->window_role); |
54 | char *raw; | 37 | free(criteria->workspace); |
55 | }; | 38 | |
39 | free(criteria->raw); | ||
40 | free(criteria); | ||
41 | } | ||
56 | 42 | ||
57 | static void free_crit_token(struct crit_token *crit) { | 43 | static int regex_cmp(const char *item, const pcre *regex) { |
58 | pcre_free(crit->regex); | 44 | return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); |
59 | free(crit->raw); | ||
60 | free(crit); | ||
61 | } | 45 | } |
62 | 46 | ||
63 | static void free_crit_tokens(list_t *crit_tokens) { | 47 | static bool criteria_matches_view(struct criteria *criteria, |
64 | for (int i = 0; i < crit_tokens->length; i++) { | 48 | struct sway_view *view) { |
65 | free_crit_token(crit_tokens->items[i]); | 49 | if (criteria->title) { |
50 | const char *title = view_get_title(view); | ||
51 | if (!title || regex_cmp(title, criteria->title) != 0) { | ||
52 | return false; | ||
53 | } | ||
66 | } | 54 | } |
67 | list_free(crit_tokens); | ||
68 | } | ||
69 | 55 | ||
70 | // Extracts criteria string from its brackets. Returns new (duplicate) | 56 | if (criteria->app_id) { |
71 | // substring. | 57 | const char *app_id = view_get_app_id(view); |
72 | static char *criteria_from(const char *arg) { | 58 | if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { |
73 | char *criteria = NULL; | 59 | return false; |
74 | if (*arg == '[') { | 60 | } |
75 | criteria = strdup(arg + 1); | ||
76 | } else { | ||
77 | criteria = strdup(arg); | ||
78 | } | 61 | } |
79 | 62 | ||
80 | int last = strlen(criteria) - 1; | 63 | if (criteria->class) { |
81 | if (criteria[last] == ']') { | 64 | const char *class = view_get_class(view); |
82 | criteria[last] = '\0'; | 65 | if (!class || regex_cmp(class, criteria->class) != 0) { |
66 | return false; | ||
67 | } | ||
83 | } | 68 | } |
84 | return criteria; | ||
85 | } | ||
86 | 69 | ||
87 | // Return instances of c found in str. | 70 | if (criteria->instance) { |
88 | static int countchr(char *str, char c) { | 71 | const char *instance = view_get_instance(view); |
89 | int found = 0; | 72 | if (!instance || regex_cmp(instance, criteria->instance) != 0) { |
90 | for (int i = 0; str[i]; i++) { | 73 | return false; |
91 | if (str[i] == c) { | ||
92 | ++found; | ||
93 | } | 74 | } |
94 | } | 75 | } |
95 | return found; | ||
96 | } | ||
97 | 76 | ||
98 | // criteria_str is e.g. '[class="some class regex" instance="instance name"]'. | 77 | if (criteria->con_mark) { |
99 | // | 78 | // TODO |
100 | // Will create array of pointers in buf, where first is duplicate of given | 79 | return false; |
101 | // string (must be freed) and the rest are pointers to names and values in the | 80 | } |
102 | // base string (every other, naturally). argc will be populated with the length | 81 | |
103 | // of buf. | 82 | if (criteria->con_id) { // Internal ID |
104 | // | 83 | if (!view->swayc || view->swayc->id != criteria->con_id) { |
105 | // Returns error string or NULL if successful. | 84 | return false; |
106 | static char *crit_tokens(int *argc, char ***buf, | ||
107 | const char * const criteria_str) { | ||
108 | wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str); | ||
109 | char *base = criteria_from(criteria_str); | ||
110 | char *head = base; | ||
111 | char *namep = head; // start of criteria name | ||
112 | char *valp = NULL; // start of value | ||
113 | |||
114 | // We're going to place EOS markers where we need to and fill up an array | ||
115 | // of pointers to the start of each token (either name or value). | ||
116 | int pairs = countchr(base, '='); | ||
117 | int max_tokens = pairs * 2 + 1; // this gives us at least enough slots | ||
118 | |||
119 | char **argv = *buf = calloc(max_tokens, sizeof(char*)); | ||
120 | argv[0] = base; // this needs to be freed by caller | ||
121 | bool quoted = true; | ||
122 | |||
123 | *argc = 1; // uneven = name, even = value | ||
124 | while (*head && *argc < max_tokens) { | ||
125 | if (namep != head && *(head - 1) == '\\') { | ||
126 | // escaped character: don't try to parse this | ||
127 | } else if (*head == '=' && namep != head) { | ||
128 | if (*argc % 2 != 1) { | ||
129 | // we're not expecting a name | ||
130 | return strdup("Unable to parse criteria: " | ||
131 | "Found out of place equal sign"); | ||
132 | } else { | ||
133 | // name ends here | ||
134 | char *end = head; // don't want to rewind the head | ||
135 | while (*(end - 1) == ' ') { | ||
136 | --end; | ||
137 | } | ||
138 | *end = '\0'; | ||
139 | if (*(namep) == ' ') { | ||
140 | namep = strrchr(namep, ' ') + 1; | ||
141 | } | ||
142 | argv[*argc] = namep; | ||
143 | *argc += 1; | ||
144 | } | ||
145 | } else if (*head == '"') { | ||
146 | if (*argc % 2 != 0) { | ||
147 | // we're not expecting a value | ||
148 | return strdup("Unable to parse criteria: " | ||
149 | "Found quoted value where it was not expected"); | ||
150 | } else if (!valp) { // value starts here | ||
151 | valp = head + 1; | ||
152 | quoted = true; | ||
153 | } else { | ||
154 | // value ends here | ||
155 | argv[*argc] = valp; | ||
156 | *argc += 1; | ||
157 | *head = '\0'; | ||
158 | valp = NULL; | ||
159 | namep = head + 1; | ||
160 | } | ||
161 | } else if (*argc % 2 == 0 && *head != ' ') { | ||
162 | // parse unquoted values | ||
163 | if (!valp) { | ||
164 | quoted = false; | ||
165 | valp = head; // value starts here | ||
166 | } | ||
167 | } else if (valp && !quoted && *head == ' ') { | ||
168 | // value ends here | ||
169 | argv[*argc] = valp; | ||
170 | *argc += 1; | ||
171 | *head = '\0'; | ||
172 | valp = NULL; | ||
173 | namep = head + 1; | ||
174 | } | 85 | } |
175 | head++; | ||
176 | } | 86 | } |
177 | 87 | ||
178 | // catch last unquoted value if needed | 88 | if (criteria->id) { // X11 window ID |
179 | if (valp && !quoted && !*head) { | 89 | uint32_t x11_window_id = view_get_x11_window_id(view); |
180 | argv[*argc] = valp; | 90 | if (!x11_window_id || x11_window_id != criteria->id) { |
181 | *argc += 1; | 91 | return false; |
92 | } | ||
182 | } | 93 | } |
183 | 94 | ||
184 | return NULL; | 95 | if (criteria->window_role) { |
96 | // TODO | ||
97 | } | ||
98 | |||
99 | if (criteria->window_type) { | ||
100 | uint32_t type = view_get_window_type(view); | ||
101 | if (!type || type != criteria->window_type) { | ||
102 | return false; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | if (criteria->floating) { | ||
107 | // TODO | ||
108 | return false; | ||
109 | } | ||
110 | |||
111 | if (criteria->tiling) { | ||
112 | // TODO | ||
113 | } | ||
114 | |||
115 | if (criteria->urgent) { | ||
116 | // TODO | ||
117 | return false; | ||
118 | } | ||
119 | |||
120 | if (criteria->workspace) { | ||
121 | if (!view->swayc) { | ||
122 | return false; | ||
123 | } | ||
124 | struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); | ||
125 | if (!ws || strcmp(ws->name, criteria->workspace) != 0) { | ||
126 | return false; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | return true; | ||
185 | } | 131 | } |
186 | 132 | ||
187 | // Returns error string on failure or NULL otherwise. | 133 | list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { |
188 | static char *parse_criteria_name(enum criteria_type *type, char *name) { | 134 | list_t *criterias = config->criteria; |
189 | *type = CRIT_LAST; | 135 | list_t *matches = create_list(); |
190 | for (int i = 0; i < CRIT_LAST; i++) { | 136 | for (int i = 0; i < criterias->length; ++i) { |
191 | if (strcmp(criteria_strings[i], name) == 0) { | 137 | struct criteria *criteria = criterias->items[i]; |
192 | *type = (enum criteria_type) i; | 138 | if ((criteria->type & types) && criteria_matches_view(criteria, view)) { |
193 | break; | 139 | list_add(matches, criteria); |
194 | } | 140 | } |
195 | } | 141 | } |
196 | if (*type == CRIT_LAST) { | 142 | return matches; |
197 | const char *fmt = "Criteria type '%s' is invalid or unsupported."; | 143 | } |
198 | int len = strlen(name) + strlen(fmt) - 1; | 144 | |
199 | char *error = malloc(len); | 145 | struct match_data { |
200 | snprintf(error, len, fmt, name); | 146 | struct criteria *criteria; |
201 | return error; | 147 | list_t *matches; |
202 | } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || | 148 | }; |
203 | *type == CRIT_WINDOW_TYPE) { | 149 | |
204 | // (we're just being helpful here) | 150 | static void criteria_get_views_iterator(struct sway_container *container, |
205 | const char *fmt = "\"%s\" criteria currently unsupported, " | 151 | void *data) { |
206 | "no window will match this"; | 152 | struct match_data *match_data = data; |
207 | int len = strlen(fmt) + strlen(name) - 1; | 153 | if (container->type == C_VIEW) { |
208 | char *error = malloc(len); | 154 | if (criteria_matches_view(match_data->criteria, container->sway_view)) { |
209 | snprintf(error, len, fmt, name); | 155 | list_add(match_data->matches, container->sway_view); |
210 | return error; | 156 | } |
211 | } | 157 | } |
212 | return NULL; | ||
213 | } | 158 | } |
214 | 159 | ||
160 | list_t *criteria_get_views(struct criteria *criteria) { | ||
161 | list_t *matches = create_list(); | ||
162 | struct match_data data = { | ||
163 | .criteria = criteria, | ||
164 | .matches = matches, | ||
165 | }; | ||
166 | container_for_each_descendant_dfs(&root_container, | ||
167 | criteria_get_views_iterator, &data); | ||
168 | return matches; | ||
169 | } | ||
170 | |||
171 | // The error pointer is used for parsing functions, and saves having to pass it | ||
172 | // as an argument in several places. | ||
173 | char *error = NULL; | ||
174 | |||
215 | // Returns error string on failure or NULL otherwise. | 175 | // Returns error string on failure or NULL otherwise. |
216 | static char *generate_regex(pcre **regex, char *value) { | 176 | static bool generate_regex(pcre **regex, char *value) { |
217 | const char *reg_err; | 177 | const char *reg_err; |
218 | int offset; | 178 | int offset; |
219 | 179 | ||
220 | *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); | 180 | *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); |
221 | 181 | ||
222 | if (!*regex) { | 182 | if (!*regex) { |
223 | const char *fmt = "Regex compilation (for '%s') failed: %s"; | 183 | const char *fmt = "Regex compilation for '%s' failed: %s"; |
224 | int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; | 184 | int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; |
225 | char *error = malloc(len); | 185 | error = malloc(len); |
226 | snprintf(error, len, fmt, value, reg_err); | 186 | snprintf(error, len, fmt, value, reg_err); |
227 | return error; | 187 | return false; |
228 | } | 188 | } |
229 | return NULL; | ||
230 | } | ||
231 | 189 | ||
232 | // Test whether the criterion corresponds to the currently focused window | 190 | return true; |
233 | static bool crit_is_focused(const char *value) { | ||
234 | return !strcmp(value, "focused") || !strcmp(value, "__focused__"); | ||
235 | } | 191 | } |
236 | 192 | ||
237 | // Populate list with crit_tokens extracted from criteria string, returns error | 193 | static bool parse_token(struct criteria *criteria, char *name, char *value) { |
238 | // string or NULL if successful. | 194 | // Require value, unless token is floating or tiled |
239 | char *extract_crit_tokens(list_t *tokens, const char * const criteria) { | 195 | if (!value && (strcmp(name, "title") == 0 |
240 | int argc; | 196 | || strcmp(name, "app_id") == 0 |
241 | char **argv = NULL, *error = NULL; | 197 | || strcmp(name, "class") == 0 |
242 | if ((error = crit_tokens(&argc, &argv, criteria))) { | 198 | || strcmp(name, "instance") == 0 |
243 | goto ect_cleanup; | 199 | || strcmp(name, "con_id") == 0 |
200 | || strcmp(name, "con_mark") == 0 | ||
201 | || strcmp(name, "window_role") == 0 | ||
202 | || strcmp(name, "window_type") == 0 | ||
203 | || strcmp(name, "id") == 0 | ||
204 | || strcmp(name, "urgent") == 0 | ||
205 | || strcmp(name, "workspace") == 0)) { | ||
206 | const char *fmt = "Token '%s' requires a value"; | ||
207 | int len = strlen(fmt) + strlen(name) - 1; | ||
208 | error = malloc(len); | ||
209 | snprintf(error, len, fmt, name); | ||
210 | return false; | ||
244 | } | 211 | } |
245 | for (int i = 1; i + 1 < argc; i += 2) { | 212 | |
246 | char* name = argv[i], *value = argv[i + 1]; | 213 | if (strcmp(name, "title") == 0) { |
247 | struct crit_token *token = calloc(1, sizeof(struct crit_token)); | 214 | generate_regex(&criteria->title, value); |
248 | token->raw = strdup(value); | 215 | } else if (strcmp(name, "app_id") == 0) { |
249 | 216 | generate_regex(&criteria->app_id, value); | |
250 | if ((error = parse_criteria_name(&token->type, name))) { | 217 | } else if (strcmp(name, "class") == 0) { |
251 | free_crit_token(token); | 218 | generate_regex(&criteria->class, value); |
252 | goto ect_cleanup; | 219 | } else if (strcmp(name, "instance") == 0) { |
253 | } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { | 220 | generate_regex(&criteria->instance, value); |
254 | wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); | 221 | } else if (strcmp(name, "con_id") == 0) { |
255 | list_add(tokens, token); | 222 | char *endptr; |
256 | } else if((error = generate_regex(&token->regex, value))) { | 223 | criteria->con_id = strtoul(value, &endptr, 10); |
257 | free_crit_token(token); | 224 | if (*endptr != 0) { |
258 | goto ect_cleanup; | 225 | error = strdup("The value for 'con_id' should be numeric"); |
226 | } | ||
227 | } else if (strcmp(name, "con_mark") == 0) { | ||
228 | generate_regex(&criteria->con_mark, value); | ||
229 | } else if (strcmp(name, "window_role") == 0) { | ||
230 | generate_regex(&criteria->window_role, value); | ||
231 | } else if (strcmp(name, "window_type") == 0) { | ||
232 | // TODO: This is a string but will be stored as an enum or integer | ||
233 | } else if (strcmp(name, "id") == 0) { | ||
234 | char *endptr; | ||
235 | criteria->id = strtoul(value, &endptr, 10); | ||
236 | if (*endptr != 0) { | ||
237 | error = strdup("The value for 'id' should be numeric"); | ||
238 | } | ||
239 | } else if (strcmp(name, "floating") == 0) { | ||
240 | criteria->floating = true; | ||
241 | } else if (strcmp(name, "tiling") == 0) { | ||
242 | criteria->tiling = true; | ||
243 | } else if (strcmp(name, "urgent") == 0) { | ||
244 | if (strcmp(value, "latest") == 0) { | ||
245 | criteria->urgent = 'l'; | ||
246 | } else if (strcmp(value, "oldest") == 0) { | ||
247 | criteria->urgent = 'o'; | ||
259 | } else { | 248 | } else { |
260 | wlr_log(L_DEBUG, "%s -> /%s/", name, value); | 249 | error = |
261 | list_add(tokens, token); | 250 | strdup("The value for 'urgent' must be 'latest' or 'oldest'"); |
262 | } | 251 | } |
252 | } else if (strcmp(name, "workspace") == 0) { | ||
253 | criteria->workspace = strdup(value); | ||
254 | } else { | ||
255 | const char *fmt = "Token '%s' is not recognized"; | ||
256 | int len = strlen(fmt) + strlen(name) - 1; | ||
257 | error = malloc(len); | ||
258 | snprintf(error, len, fmt, name); | ||
263 | } | 259 | } |
264 | ect_cleanup: | ||
265 | free(argv[0]); // base string | ||
266 | free(argv); | ||
267 | return error; | ||
268 | } | ||
269 | |||
270 | static int regex_cmp(const char *item, const pcre *regex) { | ||
271 | return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); | ||
272 | } | ||
273 | 260 | ||
274 | // test a single view if it matches list of criteria tokens (all of them). | 261 | if (error) { |
275 | static bool criteria_test(struct sway_container *cont, list_t *tokens) { | ||
276 | if (cont->type != C_CONTAINER && cont->type != C_VIEW) { | ||
277 | return false; | 262 | return false; |
278 | } | 263 | } |
279 | int matches = 0; | ||
280 | for (int i = 0; i < tokens->length; i++) { | ||
281 | struct crit_token *crit = tokens->items[i]; | ||
282 | switch (crit->type) { | ||
283 | case CRIT_CLASS: | ||
284 | { | ||
285 | const char *class = view_get_class(cont->sway_view); | ||
286 | if (!class) { | ||
287 | break; | ||
288 | } | ||
289 | if (crit->regex && regex_cmp(class, crit->regex) == 0) { | ||
290 | matches++; | ||
291 | } | ||
292 | break; | ||
293 | } | ||
294 | case CRIT_CON_ID: | ||
295 | { | ||
296 | char *endptr; | ||
297 | size_t crit_id = strtoul(crit->raw, &endptr, 10); | ||
298 | |||
299 | if (*endptr == 0 && cont->id == crit_id) { | ||
300 | ++matches; | ||
301 | } | ||
302 | break; | ||
303 | } | ||
304 | case CRIT_CON_MARK: | ||
305 | // TODO | ||
306 | break; | ||
307 | case CRIT_FLOATING: | ||
308 | // TODO | ||
309 | break; | ||
310 | case CRIT_ID: | ||
311 | // TODO | ||
312 | break; | ||
313 | case CRIT_APP_ID: | ||
314 | { | ||
315 | const char *app_id = view_get_app_id(cont->sway_view); | ||
316 | if (!app_id) { | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | if (crit->regex && regex_cmp(app_id, crit->regex) == 0) { | ||
321 | matches++; | ||
322 | } | ||
323 | break; | ||
324 | } | ||
325 | case CRIT_INSTANCE: | ||
326 | { | ||
327 | const char *instance = view_get_instance(cont->sway_view); | ||
328 | if (!instance) { | ||
329 | break; | ||
330 | } | ||
331 | |||
332 | if (crit->regex && regex_cmp(instance, crit->regex) == 0) { | ||
333 | matches++; | ||
334 | } | ||
335 | break; | ||
336 | } | ||
337 | case CRIT_TILING: | ||
338 | // TODO | ||
339 | break; | ||
340 | case CRIT_TITLE: | ||
341 | { | ||
342 | const char *title = view_get_title(cont->sway_view); | ||
343 | if (!title) { | ||
344 | break; | ||
345 | } | ||
346 | 264 | ||
347 | if (crit->regex && regex_cmp(title, crit->regex) == 0) { | 265 | return true; |
348 | matches++; | ||
349 | } | ||
350 | break; | ||
351 | } | ||
352 | case CRIT_URGENT: | ||
353 | // TODO "latest" or "oldest" | ||
354 | break; | ||
355 | case CRIT_WINDOW_ROLE: | ||
356 | // TODO | ||
357 | break; | ||
358 | case CRIT_WINDOW_TYPE: | ||
359 | // TODO | ||
360 | break; | ||
361 | case CRIT_WORKSPACE: | ||
362 | // TODO | ||
363 | break; | ||
364 | default: | ||
365 | sway_abort("Invalid criteria type (%i)", crit->type); | ||
366 | break; | ||
367 | } | ||
368 | } | ||
369 | return matches == tokens->length; | ||
370 | } | 266 | } |
371 | 267 | ||
372 | int criteria_cmp(const void *a, const void *b) { | 268 | static void skip_spaces(char **head) { |
373 | if (a == b) { | 269 | while (**head == ' ') { |
374 | return 0; | 270 | ++*head; |
375 | } else if (!a) { | ||
376 | return -1; | ||
377 | } else if (!b) { | ||
378 | return 1; | ||
379 | } | 271 | } |
380 | const struct criteria *crit_a = a, *crit_b = b; | ||
381 | int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist); | ||
382 | if (cmp != 0) { | ||
383 | return cmp; | ||
384 | } | ||
385 | return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw); | ||
386 | } | 272 | } |
387 | 273 | ||
388 | void free_criteria(struct criteria *crit) { | 274 | // Remove escaping slashes from value |
389 | if (crit->tokens) { | 275 | static void unescape(char *value) { |
390 | free_crit_tokens(crit->tokens); | 276 | if (!strchr(value, '\\')) { |
391 | } | 277 | return; |
392 | if (crit->cmdlist) { | ||
393 | free(crit->cmdlist); | ||
394 | } | 278 | } |
395 | if (crit->crit_raw) { | 279 | char *copy = calloc(strlen(value) + 1, 1); |
396 | free(crit->crit_raw); | 280 | char *readhead = value; |
397 | } | 281 | char *writehead = copy; |
398 | free(crit); | 282 | while (*readhead) { |
399 | } | 283 | if (*readhead == '\\' && |
400 | 284 | (*(readhead + 1) == '"' || *(readhead + 1) == '\\')) { | |
401 | bool criteria_any(struct sway_container *cont, list_t *criteria) { | 285 | // skip the slash |
402 | for (int i = 0; i < criteria->length; i++) { | 286 | ++readhead; |
403 | struct criteria *bc = criteria->items[i]; | ||
404 | if (criteria_test(cont, bc->tokens)) { | ||
405 | return true; | ||
406 | } | 287 | } |
288 | *writehead = *readhead; | ||
289 | ++writehead; | ||
290 | ++readhead; | ||
407 | } | 291 | } |
408 | return false; | 292 | strcpy(value, copy); |
293 | free(copy); | ||
409 | } | 294 | } |
410 | 295 | ||
411 | list_t *criteria_for(struct sway_container *cont) { | 296 | /** |
412 | list_t *criteria = config->criteria, *matches = create_list(); | 297 | * Parse a raw criteria string such as [class="foo" instance="bar"] into a |
413 | for (int i = 0; i < criteria->length; i++) { | 298 | * criteria struct. |
414 | struct criteria *bc = criteria->items[i]; | 299 | * |
415 | if (criteria_test(cont, bc->tokens)) { | 300 | * If errors are found, NULL will be returned and the error argument will be |
416 | list_add(matches, bc); | 301 | * populated with an error string. |
302 | */ | ||
303 | struct criteria *criteria_parse(char *raw, char **error_arg) { | ||
304 | free(error); | ||
305 | error = NULL; | ||
306 | |||
307 | char *head = raw; | ||
308 | skip_spaces(&head); | ||
309 | if (*head != '[') { | ||
310 | *error_arg = strdup("No criteria"); | ||
311 | return NULL; | ||
312 | } | ||
313 | ++head; | ||
314 | |||
315 | struct criteria *criteria = calloc(sizeof(struct criteria), 1); | ||
316 | char *name = NULL, *value = NULL; | ||
317 | bool in_quotes = false; | ||
318 | |||
319 | while (*head && *head != ']') { | ||
320 | skip_spaces(&head); | ||
321 | // Parse token name | ||
322 | char *namestart = head; | ||
323 | while ((*head >= 'a' && *head <= 'z') || *head == '_') { | ||
324 | ++head; | ||
417 | } | 325 | } |
326 | name = calloc(head - namestart + 1, 1); | ||
327 | strncpy(name, namestart, head - namestart); | ||
328 | // Parse token value | ||
329 | skip_spaces(&head); | ||
330 | value = NULL; | ||
331 | if (*head == '=') { | ||
332 | ++head; | ||
333 | skip_spaces(&head); | ||
334 | if (*head == '"') { | ||
335 | in_quotes = true; | ||
336 | ++head; | ||
337 | } | ||
338 | char *valuestart = head; | ||
339 | if (in_quotes) { | ||
340 | while (*head && (*head != '"' || *(head - 1) == '\\')) { | ||
341 | ++head; | ||
342 | } | ||
343 | if (!*head) { | ||
344 | *error_arg = strdup("Quote mismatch in criteria"); | ||
345 | goto cleanup; | ||
346 | } | ||
347 | } else { | ||
348 | while (*head && *head != ' ' && *head != ']') { | ||
349 | ++head; | ||
350 | } | ||
351 | } | ||
352 | value = calloc(head - valuestart + 1, 1); | ||
353 | strncpy(value, valuestart, head - valuestart); | ||
354 | if (in_quotes) { | ||
355 | ++head; | ||
356 | in_quotes = false; | ||
357 | } | ||
358 | unescape(value); | ||
359 | } | ||
360 | wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); | ||
361 | if (!parse_token(criteria, name, value)) { | ||
362 | *error_arg = error; | ||
363 | goto cleanup; | ||
364 | } | ||
365 | skip_spaces(&head); | ||
366 | free(name); | ||
367 | free(value); | ||
368 | name = NULL; | ||
369 | value = NULL; | ||
418 | } | 370 | } |
419 | return matches; | 371 | if (*head != ']') { |
420 | } | 372 | *error_arg = strdup("No closing brace found in criteria"); |
421 | 373 | goto cleanup; | |
422 | struct list_tokens { | ||
423 | list_t *list; | ||
424 | list_t *tokens; | ||
425 | }; | ||
426 | |||
427 | static void container_match_add(struct sway_container *container, | ||
428 | struct list_tokens *list_tokens) { | ||
429 | if (criteria_test(container, list_tokens->tokens)) { | ||
430 | list_add(list_tokens->list, container); | ||
431 | } | 374 | } |
432 | } | ||
433 | 375 | ||
434 | list_t *container_for_crit_tokens(list_t *tokens) { | 376 | if (criteria_is_empty(criteria)) { |
435 | struct list_tokens list_tokens = | 377 | *error_arg = strdup("Criteria is empty"); |
436 | (struct list_tokens){create_list(), tokens}; | 378 | goto cleanup; |
379 | } | ||
437 | 380 | ||
438 | container_for_each_descendant_dfs(&root_container, | 381 | ++head; |
439 | (void (*)(struct sway_container *, void *))container_match_add, | 382 | int len = head - raw; |
440 | &list_tokens); | 383 | criteria->raw = calloc(len + 1, 1); |
384 | strncpy(criteria->raw, raw, len); | ||
385 | return criteria; | ||
441 | 386 | ||
442 | // TODO look in the scratchpad | 387 | cleanup: |
443 | 388 | free(name); | |
444 | return list_tokens.list; | 389 | free(value); |
390 | criteria_destroy(criteria); | ||
391 | return NULL; | ||
445 | } | 392 | } |