aboutsummaryrefslogtreecommitdiffstats
path: root/sway/criteria.c
diff options
context:
space:
mode:
authorLibravatar Ryan Dwyer <ryandwyer1@gmail.com>2018-05-09 14:23:20 +1000
committerLibravatar Ryan Dwyer <ryandwyer1@gmail.com>2018-05-11 09:38:53 +1000
commit3b0c26d149dfe5e05df338692db8255a01f0998d (patch)
treec39e377cb96297df8547311a0aa4cfe8fa85b517 /sway/criteria.c
parentMerge pull request #1948 from RyanDwyer/focus-parent-border (diff)
downloadsway-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.c685
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
14enum criteria_type { // *must* keep in sync with criteria_strings[] 14bool 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
31static 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/** 30void 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);
51struct 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
57static void free_crit_token(struct crit_token *crit) { 43static 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
63static void free_crit_tokens(list_t *crit_tokens) { 47static 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);
72static 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) {
88static 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;
106static 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. 133list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) {
188static 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); 145struct 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) 150static 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
160list_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.
173char *error = NULL;
174
215// Returns error string on failure or NULL otherwise. 175// Returns error string on failure or NULL otherwise.
216static char *generate_regex(pcre **regex, char *value) { 176static 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, &reg_err, &offset, NULL); 180 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_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;
233static 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 193static 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
239char *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 }
264ect_cleanup:
265 free(argv[0]); // base string
266 free(argv);
267 return error;
268}
269
270static 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) {
275static 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
372int criteria_cmp(const void *a, const void *b) { 268static 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
388void free_criteria(struct criteria *crit) { 274// Remove escaping slashes from value
389 if (crit->tokens) { 275static 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) == '\\')) {
401bool 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
411list_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 */
303struct 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;
422struct list_tokens {
423 list_t *list;
424 list_t *tokens;
425};
426
427static 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
434list_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 387cleanup:
443 388 free(name);
444 return list_tokens.list; 389 free(value);
390 criteria_destroy(criteria);
391 return NULL;
445} 392}