aboutsummaryrefslogtreecommitdiffstats
path: root/sway/criteria.c
diff options
context:
space:
mode:
authorLibravatar Ronan Pigott <rpigott@berkeley.edu>2019-10-10 19:44:56 -0700
committerLibravatar Drew DeVault <sir@cmpwn.com>2019-10-27 11:06:05 -0400
commit7c9b71f5c61764be2e1459750dcd7978b8f45e5b (patch)
treee9dc557723783e5515a16899b8f77c1379ed9d3e /sway/criteria.c
parentfocus: support focus prev|next [sibling] (diff)
downloadsway-7c9b71f5c61764be2e1459750dcd7978b8f45e5b.tar.gz
sway-7c9b71f5c61764be2e1459750dcd7978b8f45e5b.tar.zst
sway-7c9b71f5c61764be2e1459750dcd7978b8f45e5b.zip
criteria: make literal comparison for __focused__ values
Diffstat (limited to 'sway/criteria.c')
-rw-r--r--sway/criteria.c349
1 files changed, 187 insertions, 162 deletions
diff --git a/sway/criteria.c b/sway/criteria.c
index b2582851..eec625af 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -16,8 +16,7 @@
16#include "config.h" 16#include "config.h"
17 17
18bool criteria_is_empty(struct criteria *criteria) { 18bool criteria_is_empty(struct criteria *criteria) {
19 return !criteria->autofail 19 return !criteria->title
20 && !criteria->title
21 && !criteria->shell 20 && !criteria->shell
22 && !criteria->app_id 21 && !criteria->app_id
23 && !criteria->con_mark 22 && !criteria->con_mark
@@ -35,16 +34,64 @@ bool criteria_is_empty(struct criteria *criteria) {
35 && !criteria->workspace; 34 && !criteria->workspace;
36} 35}
37 36
37// The error pointer is used for parsing functions, and saves having to pass it
38// as an argument in several places.
39char *error = NULL;
40
41// Returns error string on failure or NULL otherwise.
42static bool generate_regex(pcre **regex, char *value) {
43 const char *reg_err;
44 int offset;
45
46 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
47
48 if (!*regex) {
49 const char *fmt = "Regex compilation for '%s' failed: %s";
50 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
51 error = malloc(len);
52 snprintf(error, len, fmt, value, reg_err);
53 return false;
54 }
55
56 return true;
57}
58
59static bool pattern_create(struct pattern **pattern, char *value) {
60 *pattern = calloc(1, sizeof(struct pattern));
61 if (!*pattern) {
62 sway_log(SWAY_ERROR, "Failed to allocate pattern");
63 }
64
65 if (strcmp(value, "__focused__") == 0) {
66 (*pattern)->match_type = PATTERN_FOCUSED;
67 } else {
68 (*pattern)->match_type = PATTERN_PCRE;
69 if (!generate_regex(&(*pattern)->regex, value)) {
70 return false;
71 };
72 }
73 return true;
74}
75
76static void pattern_destroy(struct pattern *pattern) {
77 if (pattern) {
78 if (pattern->regex) {
79 pcre_free(pattern->regex);
80 }
81 free(pattern);
82 }
83}
84
38void criteria_destroy(struct criteria *criteria) { 85void criteria_destroy(struct criteria *criteria) {
39 pcre_free(criteria->title); 86 pattern_destroy(criteria->title);
40 pcre_free(criteria->shell); 87 pattern_destroy(criteria->shell);
41 pcre_free(criteria->app_id); 88 pattern_destroy(criteria->app_id);
42#if HAVE_XWAYLAND 89#if HAVE_XWAYLAND
43 pcre_free(criteria->class); 90 pattern_destroy(criteria->class);
44 pcre_free(criteria->instance); 91 pattern_destroy(criteria->instance);
45 pcre_free(criteria->window_role); 92 pattern_destroy(criteria->window_role);
46#endif 93#endif
47 pcre_free(criteria->con_mark); 94 pattern_destroy(criteria->con_mark);
48 free(criteria->workspace); 95 free(criteria->workspace);
49 free(criteria->cmdlist); 96 free(criteria->cmdlist);
50 free(criteria->raw); 97 free(criteria->raw);
@@ -99,36 +146,75 @@ static void find_urgent_iterator(struct sway_container *con, void *data) {
99 146
100static bool criteria_matches_view(struct criteria *criteria, 147static bool criteria_matches_view(struct criteria *criteria,
101 struct sway_view *view) { 148 struct sway_view *view) {
102 if (criteria->autofail) { 149 struct sway_seat *seat = input_manager_current_seat();
103 return false; 150 struct sway_container *focus = seat_get_focused_container(seat);
104 } 151 struct sway_view *focused = focus ? focus->view : NULL;
105 152
106 if (criteria->title) { 153 if (criteria->title) {
107 const char *title = view_get_title(view); 154 const char *title = view_get_title(view);
108 if (!title || regex_cmp(title, criteria->title) != 0) { 155 if (!title) {
109 return false; 156 return false;
110 } 157 }
158
159 switch (criteria->title->match_type) {
160 case PATTERN_FOCUSED:
161 if (focused && strcmp(title, view_get_title(focused))) {
162 return false;
163 }
164 break;
165 case PATTERN_PCRE:
166 if (regex_cmp(title, criteria->title->regex) != 0) {
167 return false;
168 }
169 break;
170 }
111 } 171 }
112 172
113 if (criteria->shell) { 173 if (criteria->shell) {
114 const char *shell = view_get_shell(view); 174 const char *shell = view_get_shell(view);
115 if (!shell || regex_cmp(shell, criteria->shell) != 0) { 175 if (!shell) {
116 return false; 176 return false;
117 } 177 }
178
179 switch (criteria->shell->match_type) {
180 case PATTERN_FOCUSED:
181 if (focused && strcmp(shell, view_get_shell(focused))) {
182 return false;
183 }
184 break;
185 case PATTERN_PCRE:
186 if (regex_cmp(shell, criteria->shell->regex) != 0) {
187 return false;
188 }
189 break;
190 }
118 } 191 }
119 192
120 if (criteria->app_id) { 193 if (criteria->app_id) {
121 const char *app_id = view_get_app_id(view); 194 const char *app_id = view_get_app_id(view);
122 if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { 195 if (!app_id) {
123 return false; 196 return false;
124 } 197 }
198
199 switch (criteria->app_id->match_type) {
200 case PATTERN_FOCUSED:
201 if (focused && strcmp(app_id, view_get_app_id(focused))) {
202 return false;
203 }
204 break;
205 case PATTERN_PCRE:
206 if (regex_cmp(app_id, criteria->app_id->regex) != 0) {
207 return false;
208 }
209 break;
210 }
125 } 211 }
126 212
127 if (criteria->con_mark) { 213 if (criteria->con_mark) {
128 bool exists = false; 214 bool exists = false;
129 struct sway_container *con = view->container; 215 struct sway_container *con = view->container;
130 for (int i = 0; i < con->marks->length; ++i) { 216 for (int i = 0; i < con->marks->length; ++i) {
131 if (regex_cmp(con->marks->items[i], criteria->con_mark) == 0) { 217 if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) == 0) {
132 exists = true; 218 exists = true;
133 break; 219 break;
134 } 220 }
@@ -154,23 +240,62 @@ static bool criteria_matches_view(struct criteria *criteria,
154 240
155 if (criteria->class) { 241 if (criteria->class) {
156 const char *class = view_get_class(view); 242 const char *class = view_get_class(view);
157 if (!class || regex_cmp(class, criteria->class) != 0) { 243 if (!class) {
158 return false; 244 return false;
159 } 245 }
246
247 switch (criteria->class->match_type) {
248 case PATTERN_FOCUSED:
249 if (focused && strcmp(class, view_get_class(focused))) {
250 return false;
251 }
252 break;
253 case PATTERN_PCRE:
254 if (regex_cmp(class, criteria->class->regex) != 0) {
255 return false;
256 }
257 break;
258 }
160 } 259 }
161 260
162 if (criteria->instance) { 261 if (criteria->instance) {
163 const char *instance = view_get_instance(view); 262 const char *instance = view_get_instance(view);
164 if (!instance || regex_cmp(instance, criteria->instance) != 0) { 263 if (!instance) {
165 return false; 264 return false;
166 } 265 }
266
267 switch (criteria->instance->match_type) {
268 case PATTERN_FOCUSED:
269 if (focused && strcmp(instance, view_get_instance(focused))) {
270 return false;
271 }
272 break;
273 case PATTERN_PCRE:
274 if (regex_cmp(instance, criteria->instance->regex) != 0) {
275 return false;
276 }
277 break;
278 }
167 } 279 }
168 280
169 if (criteria->window_role) { 281 if (criteria->window_role) {
170 const char *role = view_get_window_role(view); 282 const char *window_role = view_get_window_role(view);
171 if (!role || regex_cmp(role, criteria->window_role) != 0) { 283 if (!window_role) {
172 return false; 284 return false;
173 } 285 }
286
287 switch (criteria->window_role->match_type) {
288 case PATTERN_FOCUSED:
289 if (focused && strcmp(window_role, view_get_window_role(focused))) {
290 return false;
291 }
292 break;
293 case PATTERN_PCRE:
294 if (regex_cmp(window_role, criteria->window_role->regex) != 0) {
295 return false;
296 }
297 break;
298 }
174 } 299 }
175 300
176 if (criteria->window_type != ATOM_LAST) { 301 if (criteria->window_type != ATOM_LAST) {
@@ -213,9 +338,23 @@ static bool criteria_matches_view(struct criteria *criteria,
213 338
214 if (criteria->workspace) { 339 if (criteria->workspace) {
215 struct sway_workspace *ws = view->container->workspace; 340 struct sway_workspace *ws = view->container->workspace;
216 if (!ws || regex_cmp(ws->name, criteria->workspace) != 0) { 341 if (!ws) {
217 return false; 342 return false;
218 } 343 }
344
345 switch (criteria->workspace->match_type) {
346 case PATTERN_FOCUSED:
347 if (focused &&
348 strcmp(ws->name, focused->container->workspace->name)) {
349 return false;
350 }
351 break;
352 case PATTERN_PCRE:
353 if (regex_cmp(ws->name, criteria->workspace->regex) != 0) {
354 return false;
355 }
356 break;
357 }
219 } 358 }
220 359
221 return true; 360 return true;
@@ -258,28 +397,6 @@ list_t *criteria_get_views(struct criteria *criteria) {
258 return matches; 397 return matches;
259} 398}
260 399
261// The error pointer is used for parsing functions, and saves having to pass it
262// as an argument in several places.
263char *error = NULL;
264
265// Returns error string on failure or NULL otherwise.
266static bool generate_regex(pcre **regex, char *value) {
267 const char *reg_err;
268 int offset;
269
270 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
271
272 if (!*regex) {
273 const char *fmt = "Regex compilation for '%s' failed: %s";
274 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
275 error = malloc(len);
276 snprintf(error, len, fmt, value, reg_err);
277 return false;
278 }
279
280 return true;
281}
282
283#if HAVE_XWAYLAND 400#if HAVE_XWAYLAND
284static enum atom_name parse_window_type(const char *type) { 401static enum atom_name parse_window_type(const char *type) {
285 if (strcasecmp(type, "normal") == 0) { 402 if (strcasecmp(type, "normal") == 0) {
@@ -363,92 +480,6 @@ static enum criteria_token token_from_name(char *name) {
363 return T_INVALID; 480 return T_INVALID;
364} 481}
365 482
366/**
367 * Get a property of the focused view.
368 *
369 * Note that we are taking the focused view at the time of criteria parsing, not
370 * at the time of execution. This is because __focused__ only makes sense when
371 * using criteria via IPC. Using __focused__ in config is not useful because
372 * criteria is only executed once per view.
373 */
374static char *get_focused_prop(enum criteria_token token, bool *autofail) {
375 struct sway_seat *seat = input_manager_current_seat();
376 struct sway_container *focus = seat_get_focused_container(seat);
377
378 struct sway_view *view = focus ? focus->view : NULL;
379 const char *value = NULL;
380
381 switch (token) {
382 case T_APP_ID:
383 *autofail = true;
384 if (view) {
385 value = view_get_app_id(view);
386 }
387 break;
388 case T_SHELL:
389 *autofail = true;
390 if (view) {
391 value = view_get_shell(view);
392 }
393 break;
394 case T_TITLE:
395 *autofail = true;
396 if (view) {
397 value = view_get_title(view);
398 }
399 break;
400 case T_WORKSPACE:
401 *autofail = true;
402 if (focus && focus->workspace) {
403 value = focus->workspace->name;
404 }
405 break;
406 case T_CON_ID:
407 *autofail = true;
408 if (view && view->container) {
409 size_t id = view->container->node.id;
410 size_t id_size = snprintf(NULL, 0, "%zu", id) + 1;
411 char *id_str = malloc(id_size);
412 snprintf(id_str, id_size, "%zu", id);
413 value = id_str;
414 }
415 break;
416#if HAVE_XWAYLAND
417 case T_CLASS:
418 *autofail = true;
419 if (view) {
420 value = view_get_class(view);
421 }
422 break;
423 case T_INSTANCE:
424 *autofail = true;
425 if (view) {
426 value = view_get_instance(view);
427 }
428 break;
429 case T_WINDOW_ROLE:
430 *autofail = true;
431 if (view) {
432 value = view_get_window_role(view);
433 }
434 break;
435 case T_WINDOW_TYPE: // These do not support __focused__
436 case T_ID:
437#endif
438 case T_CON_MARK:
439 case T_FLOATING:
440 case T_TILING:
441 case T_URGENT:
442 case T_INVALID:
443 *autofail = false;
444 break;
445 }
446 if (value) {
447 return strdup(value);
448 }
449 return NULL;
450}
451
452static bool parse_token(struct criteria *criteria, char *name, char *value) { 483static bool parse_token(struct criteria *criteria, char *name, char *value) {
453 enum criteria_token token = token_from_name(name); 484 enum criteria_token token = token_from_name(name);
454 if (token == T_INVALID) { 485 if (token == T_INVALID) {
@@ -459,20 +490,8 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
459 return false; 490 return false;
460 } 491 }
461 492
462 char *effective_value = NULL;
463 if (value && strcmp(value, "__focused__") == 0) {
464 bool autofail = false;
465 effective_value = get_focused_prop(token, &autofail);
466 if (!effective_value && autofail) {
467 criteria->autofail = true;
468 return true;
469 }
470 } else if (value) {
471 effective_value = strdup(value);
472 }
473
474 // Require value, unless token is floating or tiled 493 // Require value, unless token is floating or tiled
475 if (!effective_value && token != T_FLOATING && token != T_TILING) { 494 if (!value && token != T_FLOATING && token != T_TILING) {
476 const char *fmt = "Token '%s' requires a value"; 495 const char *fmt = "Token '%s' requires a value";
477 int len = strlen(fmt) + strlen(name) - 1; 496 int len = strlen(fmt) + strlen(name) - 1;
478 error = malloc(len); 497 error = malloc(len);
@@ -483,41 +502,48 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
483 char *endptr = NULL; 502 char *endptr = NULL;
484 switch (token) { 503 switch (token) {
485 case T_TITLE: 504 case T_TITLE:
486 generate_regex(&criteria->title, effective_value); 505 pattern_create(&criteria->title, value);
487 break; 506 break;
488 case T_SHELL: 507 case T_SHELL:
489 generate_regex(&criteria->shell, effective_value); 508 pattern_create(&criteria->shell, value);
490 break; 509 break;
491 case T_APP_ID: 510 case T_APP_ID:
492 generate_regex(&criteria->app_id, effective_value); 511 pattern_create(&criteria->app_id, value);
493 break; 512 break;
494 case T_CON_ID: 513 case T_CON_ID:
495 criteria->con_id = strtoul(effective_value, &endptr, 10); 514 if (strcmp(value, "__focused__") == 0) {
496 if (*endptr != 0) { 515 struct sway_seat *seat = input_manager_current_seat();
497 error = strdup("The value for 'con_id' should be '__focused__' or numeric"); 516 struct sway_container *focus = seat_get_focused_container(seat);
517 struct sway_view *view = focus ? focus->view : NULL;
518 criteria->con_id = view ? view->container->node.id : 0;
519 } else {
520 criteria->con_id = strtoul(value, &endptr, 10);
521 if (*endptr != 0) {
522 error = strdup("The value for 'con_id' should be '__focused__' or numeric");
523 }
498 } 524 }
499 break; 525 break;
500 case T_CON_MARK: 526 case T_CON_MARK:
501 generate_regex(&criteria->con_mark, effective_value); 527 pattern_create(&criteria->con_mark, value);
502 break; 528 break;
503#if HAVE_XWAYLAND 529#if HAVE_XWAYLAND
504 case T_CLASS: 530 case T_CLASS:
505 generate_regex(&criteria->class, effective_value); 531 pattern_create(&criteria->class, value);
506 break; 532 break;
507 case T_ID: 533 case T_ID:
508 criteria->id = strtoul(effective_value, &endptr, 10); 534 criteria->id = strtoul(value, &endptr, 10);
509 if (*endptr != 0) { 535 if (*endptr != 0) {
510 error = strdup("The value for 'id' should be numeric"); 536 error = strdup("The value for 'id' should be numeric");
511 } 537 }
512 break; 538 break;
513 case T_INSTANCE: 539 case T_INSTANCE:
514 generate_regex(&criteria->instance, effective_value); 540 pattern_create(&criteria->instance, value);
515 break; 541 break;
516 case T_WINDOW_ROLE: 542 case T_WINDOW_ROLE:
517 generate_regex(&criteria->window_role, effective_value); 543 pattern_create(&criteria->window_role, value);
518 break; 544 break;
519 case T_WINDOW_TYPE: 545 case T_WINDOW_TYPE:
520 criteria->window_type = parse_window_type(effective_value); 546 criteria->window_type = parse_window_type(value);
521 break; 547 break;
522#endif 548#endif
523 case T_FLOATING: 549 case T_FLOATING:
@@ -527,13 +553,13 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
527 criteria->tiling = true; 553 criteria->tiling = true;
528 break; 554 break;
529 case T_URGENT: 555 case T_URGENT:
530 if (strcmp(effective_value, "latest") == 0 || 556 if (strcmp(value, "latest") == 0 ||
531 strcmp(effective_value, "newest") == 0 || 557 strcmp(value, "newest") == 0 ||
532 strcmp(effective_value, "last") == 0 || 558 strcmp(value, "last") == 0 ||
533 strcmp(effective_value, "recent") == 0) { 559 strcmp(value, "recent") == 0) {
534 criteria->urgent = 'l'; 560 criteria->urgent = 'l';
535 } else if (strcmp(effective_value, "oldest") == 0 || 561 } else if (strcmp(value, "oldest") == 0 ||
536 strcmp(effective_value, "first") == 0) { 562 strcmp(value, "first") == 0) {
537 criteria->urgent = 'o'; 563 criteria->urgent = 'o';
538 } else { 564 } else {
539 error = 565 error =
@@ -542,12 +568,11 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
542 } 568 }
543 break; 569 break;
544 case T_WORKSPACE: 570 case T_WORKSPACE:
545 generate_regex(&criteria->workspace, effective_value); 571 pattern_create(&criteria->workspace, value);
546 break; 572 break;
547 case T_INVALID: 573 case T_INVALID:
548 break; 574 break;
549 } 575 }
550 free(effective_value);
551 576
552 if (error) { 577 if (error) {
553 return false; 578 return false;