1 // SPDX-License-Identifier: MIT
2
3 /*
4 * Compton - a compositor for X11
5 *
6 * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
7 *
8 * Copyright (c) 2011-2013, Christopher Jeffrey
9 * See LICENSE-mit for more information.
10 *
11 */
12
13 #include <ctype.h>
14 #include <fnmatch.h>
15 #include <stdio.h>
16 #include <string.h>
17
18 // libpcre
19 #ifdef CONFIG_REGEX_PCRE
20 #include <pcre.h>
21
22 // For compatibility with <libpcre-8.20
23 #ifndef PCRE_STUDY_JIT_COMPILE
24 #define PCRE_STUDY_JIT_COMPILE 0
25 #define LPCRE_FREE_STUDY(extra) pcre_free(extra)
26 #else
27 #define LPCRE_FREE_STUDY(extra) pcre_free_study(extra)
28 #endif
29
30 #endif
31
32 #include <X11/Xlib.h>
33 #include <xcb/xcb.h>
34
35 #include "atom.h"
36 #include "common.h"
37 #include "compiler.h"
38 #include "config.h"
39 #include "log.h"
40 #include "string_utils.h"
41 #include "utils.h"
42 #include "win.h"
43 #include "x.h"
44
45 #include "c2.h"
46
47 #pragma GCC diagnostic error "-Wunused-parameter"
48
49 #define C2_MAX_LEVELS 10
50
51 typedef struct _c2_b c2_b_t;
52 typedef struct _c2_l c2_l_t;
53
54 /// Pointer to a condition tree.
55 typedef struct {
56 bool isbranch : 1;
57 union {
58 c2_b_t *b;
59 c2_l_t *l;
60 };
61 } c2_ptr_t;
62
63 /// Initializer for c2_ptr_t.
64 #define C2_PTR_INIT \
65 { .isbranch = false, .l = NULL, }
66
67 static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
68
69 /// Operator of a branch element.
70 typedef enum {
71 C2_B_OUNDEFINED,
72 C2_B_OAND,
73 C2_B_OOR,
74 C2_B_OXOR,
75 } c2_b_op_t;
76
77 /// Structure for branch element in a window condition
78 struct _c2_b {
79 bool neg : 1;
80 c2_b_op_t op;
81 c2_ptr_t opr1;
82 c2_ptr_t opr2;
83 };
84
85 /// Initializer for c2_b_t.
86 #define C2_B_INIT \
87 { .neg = false, .op = C2_B_OUNDEFINED, .opr1 = C2_PTR_INIT, .opr2 = C2_PTR_INIT, }
88
89 /// Structure for leaf element in a window condition
90 struct _c2_l {
91 bool neg : 1;
92 enum { C2_L_OEXISTS,
93 C2_L_OEQ,
94 C2_L_OGT,
95 C2_L_OGTEQ,
96 C2_L_OLT,
97 C2_L_OLTEQ,
98 } op : 3;
99 enum { C2_L_MEXACT,
100 C2_L_MSTART,
101 C2_L_MCONTAINS,
102 C2_L_MWILDCARD,
103 C2_L_MPCRE,
104 } match : 3;
105 bool match_ignorecase : 1;
106 char *tgt;
107 xcb_atom_t tgtatom;
108 bool tgt_onframe;
109 int index;
110 enum { C2_L_PUNDEFINED = -1,
111 C2_L_PID = 0,
112 C2_L_PX,
113 C2_L_PY,
114 C2_L_PX2,
115 C2_L_PY2,
116 C2_L_PWIDTH,
117 C2_L_PHEIGHT,
118 C2_L_PWIDTHB,
119 C2_L_PHEIGHTB,
120 C2_L_PBDW,
121 C2_L_PFULLSCREEN,
122 C2_L_POVREDIR,
123 C2_L_PARGB,
124 C2_L_PFOCUSED,
125 C2_L_PWMWIN,
126 C2_L_PBSHAPED,
127 C2_L_PROUNDED,
128 C2_L_PCLIENT,
129 C2_L_PWINDOWTYPE,
130 C2_L_PLEADER,
131 C2_L_PNAME,
132 C2_L_PCLASSG,
133 C2_L_PCLASSI,
134 C2_L_PROLE,
135 } predef;
136 enum c2_l_type {
137 C2_L_TUNDEFINED,
138 C2_L_TSTRING,
139 C2_L_TCARDINAL,
140 C2_L_TWINDOW,
141 C2_L_TATOM,
142 C2_L_TDRAWABLE,
143 } type;
144 int format;
145 enum { C2_L_PTUNDEFINED,
146 C2_L_PTSTRING,
147 C2_L_PTINT,
148 } ptntype;
149 char *ptnstr;
150 long ptnint;
151 #ifdef CONFIG_REGEX_PCRE
152 pcre *regex_pcre;
153 pcre_extra *regex_pcre_extra;
154 #endif
155 };
156
157 /// Initializer for c2_l_t.
158 #define C2_L_INIT \
159 { \
160 .neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT, \
161 .match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \
162 .predef = C2_L_PUNDEFINED, .index = -1, .type = C2_L_TUNDEFINED, \
163 .format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \
164 }
165
166 static const c2_l_t leaf_def = C2_L_INIT;
167
168 /// Linked list type of conditions.
169 struct _c2_lptr {
170 c2_ptr_t ptr;
171 void *data;
172 struct _c2_lptr *next;
173 };
174
175 /// Initializer for c2_lptr_t.
176 #define C2_LPTR_INIT \
177 { .ptr = C2_PTR_INIT, .data = NULL, .next = NULL, }
178
179 /// Structure representing a predefined target.
180 typedef struct {
181 const char *name;
182 enum c2_l_type type;
183 int format;
184 } c2_predef_t;
185
186 // Predefined targets.
187 static const c2_predef_t C2_PREDEFS[] = {
188 [C2_L_PID] = {"id", C2_L_TCARDINAL, 0},
189 [C2_L_PX] = {"x", C2_L_TCARDINAL, 0},
190 [C2_L_PY] = {"y", C2_L_TCARDINAL, 0},
191 [C2_L_PX2] = {"x2", C2_L_TCARDINAL, 0},
192 [C2_L_PY2] = {"y2", C2_L_TCARDINAL, 0},
193 [C2_L_PWIDTH] = {"width", C2_L_TCARDINAL, 0},
194 [C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL, 0},
195 [C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL, 0},
196 [C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL, 0},
197 [C2_L_PBDW] = {"border_width", C2_L_TCARDINAL, 0},
198 [C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL, 0},
199 [C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL, 0},
200 [C2_L_PARGB] = {"argb", C2_L_TCARDINAL, 0},
201 [C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL, 0},
202 [C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL, 0},
203 [C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL, 0},
204 [C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL, 0},
205 [C2_L_PCLIENT] = {"client", C2_L_TWINDOW, 0},
206 [C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING, 0},
207 [C2_L_PLEADER] = {"leader", C2_L_TWINDOW, 0},
208 [C2_L_PNAME] = {"name", C2_L_TSTRING, 0},
209 [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0},
210 [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0},
211 [C2_L_PROLE] = {"role", C2_L_TSTRING, 0},
212 };
213
214 /**
215 * Get the numeric property value from a win_prop_t.
216 */
winprop_get_int(winprop_t prop)217 static inline long winprop_get_int(winprop_t prop) {
218 long tgt = 0;
219
220 if (!prop.nitems) {
221 return 0;
222 }
223
224 switch (prop.format) {
225 case 8: tgt = *(prop.p8); break;
226 case 16: tgt = *(prop.p16); break;
227 case 32: tgt = *(prop.p32); break;
228 default: assert(0); break;
229 }
230
231 return tgt;
232 }
233
234 /**
235 * Compare next word in a string with another string.
236 */
strcmp_wd(const char * needle,const char * src)237 static inline int strcmp_wd(const char *needle, const char *src) {
238 int ret = mstrncmp(needle, src);
239 if (ret)
240 return ret;
241
242 char c = src[strlen(needle)];
243 if (isalnum(c) || '_' == c)
244 return 1;
245 else
246 return 0;
247 }
248
249 /**
250 * Return whether a c2_ptr_t is empty.
251 */
c2_ptr_isempty(const c2_ptr_t p)252 static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) {
253 return !(p.isbranch ? (bool)p.b : (bool)p.l);
254 }
255
256 /**
257 * Reset a c2_ptr_t.
258 */
c2_ptr_reset(c2_ptr_t * pp)259 static inline void c2_ptr_reset(c2_ptr_t *pp) {
260 if (pp)
261 memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
262 }
263
264 /**
265 * Combine two condition trees.
266 */
c2h_comb_tree(c2_b_op_t op,c2_ptr_t p1,c2_ptr_t p2)267 static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
268 c2_ptr_t p = {.isbranch = true, .b = cmalloc(c2_b_t)};
269
270 p.b->neg = false;
271 p.b->op = op;
272 p.b->opr1 = p1;
273 p.b->opr2 = p2;
274
275 return p;
276 }
277
278 /**
279 * Get the precedence value of a condition branch operator.
280 */
c2h_b_opp(c2_b_op_t op)281 static inline int c2h_b_opp(c2_b_op_t op) {
282 switch (op) {
283 case C2_B_OAND: return 2;
284 case C2_B_OOR: return 1;
285 case C2_B_OXOR: return 1;
286 default: break;
287 }
288
289 assert(0);
290 return 0;
291 }
292
293 /**
294 * Compare precedence of two condition branch operators.
295 *
296 * Associativity is left-to-right, forever.
297 *
298 * @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
299 * negative number otherwise
300 */
c2h_b_opcmp(c2_b_op_t op1,c2_b_op_t op2)301 static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
302 return c2h_b_opp(op1) - c2h_b_opp(op2);
303 }
304
305 static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level);
306
307 static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult);
308
309 static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);
310
311 static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult);
312
313 static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult);
314
315 static void c2_free(c2_ptr_t p);
316
317 /**
318 * Wrapper of c2_free().
319 */
c2_freep(c2_ptr_t * pp)320 static inline void c2_freep(c2_ptr_t *pp) {
321 if (pp) {
322 c2_free(*pp);
323 c2_ptr_reset(pp);
324 }
325 }
326
327 static const char *c2h_dump_str_tgt(const c2_l_t *pleaf);
328
329 static const char *c2h_dump_str_type(const c2_l_t *pleaf);
330
331 static void attr_unused c2_dump(c2_ptr_t p);
332
333 static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf);
334
335 static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond);
336
337 /**
338 * Parse a condition string.
339 */
c2_parse(c2_lptr_t ** pcondlst,const char * pattern,void * data)340 c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
341 if (!pattern)
342 return NULL;
343
344 // Parse the pattern
345 c2_ptr_t result = C2_PTR_INIT;
346 int offset = -1;
347
348 if (strlen(pattern) >= 2 && ':' == pattern[1])
349 offset = c2_parse_legacy(pattern, 0, &result);
350 else
351 offset = c2_parse_grp(pattern, 0, &result, 0);
352
353 if (offset < 0) {
354 c2_freep(&result);
355 return NULL;
356 }
357
358 // Insert to pcondlst
359 {
360 static const c2_lptr_t lptr_def = C2_LPTR_INIT;
361 auto plptr = cmalloc(c2_lptr_t);
362 memcpy(plptr, &lptr_def, sizeof(c2_lptr_t));
363 plptr->ptr = result;
364 plptr->data = data;
365 if (pcondlst) {
366 plptr->next = *pcondlst;
367 *pcondlst = plptr;
368 }
369
370 #ifdef DEBUG_C2
371 log_trace("(\"%s\"): ", pattern);
372 c2_dump(plptr->ptr);
373 putchar('\n');
374 #endif
375
376 return plptr;
377 }
378 }
379
380 #define c2_error(format, ...) \
381 do { \
382 log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \
383 goto fail; \
384 } while (0)
385
386 // TODO Not a very good macro
387 #define C2H_SKIP_SPACES() \
388 { \
389 while (isspace(pattern[offset])) \
390 ++offset; \
391 }
392
393 /**
394 * Parse a group in condition string.
395 *
396 * @return offset of next character in string
397 */
c2_parse_grp(const char * pattern,int offset,c2_ptr_t * presult,int level)398 static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) {
399 // Check for recursion levels
400 if (level > C2_MAX_LEVELS)
401 c2_error("Exceeded maximum recursion levels.");
402
403 if (!pattern)
404 return -1;
405
406 // Expected end character
407 const char endchar = (offset ? ')' : '\0');
408
409 // We use a system that a maximum of 2 elements are kept. When we find
410 // the third element, we combine the elements according to operator
411 // precedence. This design limits operators to have at most two-levels
412 // of precedence and fixed left-to-right associativity.
413
414 // For storing branch operators. ops[0] is actually unused
415 c2_b_op_t ops[3] = {};
416 // For storing elements
417 c2_ptr_t eles[2] = {C2_PTR_INIT, C2_PTR_INIT};
418 // Index of next free element slot in eles
419 int elei = 0;
420 // Pointer to the position of next element
421 c2_ptr_t *pele = eles;
422 // Negation flag of next operator
423 bool neg = false;
424 // Whether we are expecting an element immediately, is true at first, or
425 // after encountering a logical operator
426 bool next_expected = true;
427
428 // Parse the pattern character-by-character
429 for (; pattern[offset]; ++offset) {
430 assert(elei <= 2);
431
432 // Jump over spaces
433 if (isspace(pattern[offset]))
434 continue;
435
436 // Handle end of group
437 if (')' == pattern[offset])
438 break;
439
440 // Handle "!"
441 if ('!' == pattern[offset]) {
442 if (!next_expected)
443 c2_error("Unexpected \"!\".");
444
445 neg = !neg;
446 continue;
447 }
448
449 // Handle AND and OR
450 if ('&' == pattern[offset] || '|' == pattern[offset]) {
451 if (next_expected)
452 c2_error("Unexpected logical operator.");
453
454 next_expected = true;
455 if (!mstrncmp("&&", pattern + offset)) {
456 ops[elei] = C2_B_OAND;
457 ++offset;
458 } else if (!mstrncmp("||", pattern + offset)) {
459 ops[elei] = C2_B_OOR;
460 ++offset;
461 } else
462 c2_error("Illegal logical operator.");
463
464 continue;
465 }
466
467 // Parsing an element
468 if (!next_expected)
469 c2_error("Unexpected expression.");
470
471 assert(!elei || ops[elei]);
472
473 // If we are out of space
474 if (2 == elei) {
475 --elei;
476 // If the first operator has higher or equal precedence, combine
477 // the first two elements
478 if (c2h_b_opcmp(ops[1], ops[2]) >= 0) {
479 eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
480 c2_ptr_reset(&eles[1]);
481 pele = &eles[elei];
482 ops[1] = ops[2];
483 }
484 // Otherwise, combine the second and the incoming one
485 else {
486 eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL);
487 assert(eles[1].isbranch);
488 pele = &eles[1].b->opr2;
489 }
490 // The last operator always needs to be reset
491 ops[2] = C2_B_OUNDEFINED;
492 }
493
494 // It's a subgroup if it starts with '('
495 if ('(' == pattern[offset]) {
496 if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0)
497 goto fail;
498 }
499 // Otherwise it's a leaf
500 else {
501 if ((offset = c2_parse_target(pattern, offset, pele)) < 0)
502 goto fail;
503
504 assert(!pele->isbranch && !c2_ptr_isempty(*pele));
505
506 if ((offset = c2_parse_op(pattern, offset, pele)) < 0)
507 goto fail;
508
509 if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0)
510 goto fail;
511 }
512 // Decrement offset -- we will increment it in loop update
513 --offset;
514
515 // Apply negation
516 if (neg) {
517 neg = false;
518 if (pele->isbranch)
519 pele->b->neg = !pele->b->neg;
520 else
521 pele->l->neg = !pele->l->neg;
522 }
523
524 next_expected = false;
525 ++elei;
526 pele = &eles[elei];
527 }
528
529 // Wrong end character?
530 if (pattern[offset] && !endchar)
531 c2_error("Expected end of string but found '%c'.", pattern[offset]);
532 if (!pattern[offset] && endchar)
533 c2_error("Expected '%c' but found end of string.", endchar);
534
535 // Handle end of group
536 if (!elei) {
537 c2_error("Empty group.");
538 } else if (next_expected) {
539 c2_error("Missing rule before end of group.");
540 } else if (elei > 1) {
541 assert(2 == elei);
542 assert(ops[1]);
543 eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
544 c2_ptr_reset(&eles[1]);
545 }
546
547 *presult = eles[0];
548
549 if (')' == pattern[offset])
550 ++offset;
551
552 return offset;
553
554 fail:
555 c2_freep(&eles[0]);
556 c2_freep(&eles[1]);
557
558 return -1;
559 }
560
561 /**
562 * Parse the target part of a rule.
563 */
c2_parse_target(const char * pattern,int offset,c2_ptr_t * presult)564 static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) {
565 // Initialize leaf
566 presult->isbranch = false;
567 presult->l = cmalloc(c2_l_t);
568
569 c2_l_t *const pleaf = presult->l;
570 memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
571
572 // Parse negation marks
573 while ('!' == pattern[offset]) {
574 pleaf->neg = !pleaf->neg;
575 ++offset;
576 C2H_SKIP_SPACES();
577 }
578
579 // Copy target name out
580 int tgtlen = 0;
581 for (; pattern[offset] && (isalnum(pattern[offset]) || '_' == pattern[offset]);
582 ++offset) {
583 ++tgtlen;
584 }
585 if (!tgtlen) {
586 c2_error("Empty target.");
587 }
588 pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen);
589
590 // Check for predefined targets
591 static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0]));
592 for (int i = 0; i < npredefs; ++i) {
593 if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) {
594 pleaf->predef = i;
595 pleaf->type = C2_PREDEFS[i].type;
596 pleaf->format = C2_PREDEFS[i].format;
597 break;
598 }
599 }
600
601 C2H_SKIP_SPACES();
602
603 // Parse target-on-frame flag
604 if ('@' == pattern[offset]) {
605 pleaf->tgt_onframe = true;
606 ++offset;
607 C2H_SKIP_SPACES();
608 }
609
610 // Parse index
611 if ('[' == pattern[offset]) {
612 offset++;
613
614 C2H_SKIP_SPACES();
615
616 long index = -1;
617 char *endptr = NULL;
618
619 index = strtol(pattern + offset, &endptr, 0);
620
621 if (!endptr || pattern + offset == endptr) {
622 c2_error("No index number found after bracket.");
623 }
624 if (index < 0) {
625 c2_error("Index number invalid.");
626 }
627 if (pleaf->predef != C2_L_PUNDEFINED) {
628 c2_error("Predefined targets can't have index.");
629 }
630
631 pleaf->index = to_int_checked(index);
632 offset = to_int_checked(endptr - pattern);
633
634 C2H_SKIP_SPACES();
635
636 if (pattern[offset] != ']') {
637 c2_error("Index end marker not found.");
638 }
639
640 ++offset;
641
642 C2H_SKIP_SPACES();
643 }
644
645 // Parse target type and format
646 if (':' == pattern[offset]) {
647 ++offset;
648 C2H_SKIP_SPACES();
649
650 // Look for format
651 bool hasformat = false;
652 long format = 0;
653 {
654 char *endptr = NULL;
655 format = strtol(pattern + offset, &endptr, 0);
656 assert(endptr);
657 if ((hasformat = (endptr && endptr != pattern + offset))) {
658 offset = to_int_checked(endptr - pattern);
659 }
660 C2H_SKIP_SPACES();
661 }
662
663 // Look for type
664 enum c2_l_type type = C2_L_TUNDEFINED;
665 switch (pattern[offset]) {
666 case 'w': type = C2_L_TWINDOW; break;
667 case 'd': type = C2_L_TDRAWABLE; break;
668 case 'c': type = C2_L_TCARDINAL; break;
669 case 's': type = C2_L_TSTRING; break;
670 case 'a': type = C2_L_TATOM; break;
671 default: c2_error("Invalid type character.");
672 }
673
674 if (type) {
675 if (pleaf->predef != C2_L_PUNDEFINED) {
676 log_warn("Type specified for a default target "
677 "will be ignored.");
678 } else {
679 if (pleaf->type && type != pleaf->type)
680 log_warn("Default type overridden on "
681 "target.");
682 pleaf->type = type;
683 }
684 }
685
686 offset++;
687 C2H_SKIP_SPACES();
688
689 // Default format
690 if (!pleaf->format) {
691 switch (pleaf->type) {
692 case C2_L_TWINDOW:
693 case C2_L_TDRAWABLE:
694 case C2_L_TATOM: pleaf->format = 32; break;
695 case C2_L_TSTRING: pleaf->format = 8; break;
696 default: break;
697 }
698 }
699
700 // Write format
701 if (hasformat) {
702 if (pleaf->predef != C2_L_PUNDEFINED) {
703 log_warn("Format \"%ld\" specified on a default target "
704 "will be ignored.",
705 format);
706 } else if (pleaf->type == C2_L_TSTRING) {
707 log_warn("Format \"%ld\" specified on a string target "
708 "will be ignored.",
709 format);
710 } else {
711 if (pleaf->format && pleaf->format != format)
712 log_warn("Default format %d overridden on "
713 "target.",
714 pleaf->format);
715 pleaf->format = to_int_checked(format);
716 }
717 }
718 }
719
720 if (!pleaf->type)
721 c2_error("Target type cannot be determined.");
722
723 // if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type)
724 // c2_error("Target format cannot be determined.");
725
726 if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format)
727 c2_error("Invalid format.");
728
729 return offset;
730
731 fail:
732 return -1;
733 }
734
735 /**
736 * Parse the operator part of a leaf.
737 */
c2_parse_op(const char * pattern,int offset,c2_ptr_t * presult)738 static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) {
739 c2_l_t *const pleaf = presult->l;
740
741 // Parse negation marks
742 C2H_SKIP_SPACES();
743 while ('!' == pattern[offset]) {
744 pleaf->neg = !pleaf->neg;
745 ++offset;
746 C2H_SKIP_SPACES();
747 }
748
749 // Parse qualifiers
750 if ('*' == pattern[offset] || '^' == pattern[offset] || '%' == pattern[offset] ||
751 '~' == pattern[offset]) {
752 switch (pattern[offset]) {
753 case '*': pleaf->match = C2_L_MCONTAINS; break;
754 case '^': pleaf->match = C2_L_MSTART; break;
755 case '%': pleaf->match = C2_L_MWILDCARD; break;
756 case '~': pleaf->match = C2_L_MPCRE; break;
757 default: assert(0);
758 }
759 ++offset;
760 C2H_SKIP_SPACES();
761 }
762
763 // Parse flags
764 while ('?' == pattern[offset]) {
765 pleaf->match_ignorecase = true;
766 ++offset;
767 C2H_SKIP_SPACES();
768 }
769
770 // Parse operator
771 while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) {
772 if ('=' == pattern[offset] && C2_L_OGT == pleaf->op)
773 pleaf->op = C2_L_OGTEQ;
774 else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op)
775 pleaf->op = C2_L_OLTEQ;
776 else if (pleaf->op) {
777 c2_error("Duplicate operator.");
778 } else {
779 switch (pattern[offset]) {
780 case '=': pleaf->op = C2_L_OEQ; break;
781 case '>': pleaf->op = C2_L_OGT; break;
782 case '<': pleaf->op = C2_L_OLT; break;
783 default: assert(0);
784 }
785 }
786 ++offset;
787 C2H_SKIP_SPACES();
788 }
789
790 // Check for problems
791 if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase))
792 c2_error("Exists/greater-than/less-than operators cannot have a "
793 "qualifier.");
794
795 return offset;
796
797 fail:
798 return -1;
799 }
800
801 /**
802 * Parse the pattern part of a leaf.
803 */
c2_parse_pattern(const char * pattern,int offset,c2_ptr_t * presult)804 static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) {
805 c2_l_t *const pleaf = presult->l;
806
807 // Exists operator cannot have pattern
808 if (!pleaf->op) {
809 return offset;
810 }
811
812 C2H_SKIP_SPACES();
813
814 char *endptr = NULL;
815 if (!strcmp_wd("true", &pattern[offset])) {
816 pleaf->ptntype = C2_L_PTINT;
817 pleaf->ptnint = true;
818 offset += 4; // length of "true";
819 } else if (!strcmp_wd("false", &pattern[offset])) {
820 pleaf->ptntype = C2_L_PTINT;
821 pleaf->ptnint = false;
822 offset += 5; // length of "false";
823 } else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0),
824 pattern + offset != endptr) {
825 pleaf->ptntype = C2_L_PTINT;
826 offset = to_int_checked(endptr - pattern);
827 // Make sure we are stopping at the end of a word
828 if (isalnum(pattern[offset])) {
829 c2_error("Trailing characters after a numeric pattern.");
830 }
831 } else {
832 // Parse string patterns
833 bool raw = false;
834 char delim = '\0';
835
836 // String flags
837 if (tolower(pattern[offset]) == 'r') {
838 raw = true;
839 ++offset;
840 C2H_SKIP_SPACES();
841 }
842
843 // Check for delimiters
844 if (pattern[offset] == '\"' || pattern[offset] == '\'') {
845 pleaf->ptntype = C2_L_PTSTRING;
846 delim = pattern[offset];
847 ++offset;
848 }
849
850 if (pleaf->ptntype != C2_L_PTSTRING) {
851 c2_error("Invalid pattern type.");
852 }
853
854 // Parse the string now
855 // We can't determine the length of the pattern, so we use the length
856 // to the end of the pattern string -- currently escape sequences
857 // cannot be converted to a string longer than itself.
858 auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char);
859 char *ptptnstr = tptnstr;
860 pleaf->ptnstr = tptnstr;
861 for (; pattern[offset] && delim != pattern[offset]; ++offset) {
862 // Handle escape sequences if it's not a raw string
863 if ('\\' == pattern[offset] && !raw) {
864 switch (pattern[++offset]) {
865 case '\\': *(ptptnstr++) = '\\'; break;
866 case '\'': *(ptptnstr++) = '\''; break;
867 case '\"': *(ptptnstr++) = '\"'; break;
868 case 'a': *(ptptnstr++) = '\a'; break;
869 case 'b': *(ptptnstr++) = '\b'; break;
870 case 'f': *(ptptnstr++) = '\f'; break;
871 case 'n': *(ptptnstr++) = '\n'; break;
872 case 'r': *(ptptnstr++) = '\r'; break;
873 case 't': *(ptptnstr++) = '\t'; break;
874 case 'v': *(ptptnstr++) = '\v'; break;
875 case 'o':
876 case 'x': {
877 char *tstr = strndup(pattern + offset + 1, 2);
878 char *pstr = NULL;
879 long val = strtol(
880 tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
881 free(tstr);
882 if (pstr != &tstr[2] || val <= 0)
883 c2_error("Invalid octal/hex escape "
884 "sequence.");
885 *(ptptnstr++) = to_char_checked(val);
886 offset += 2;
887 break;
888 }
889 default: c2_error("Invalid escape sequence.");
890 }
891 } else {
892 *(ptptnstr++) = pattern[offset];
893 }
894 }
895 if (!pattern[offset])
896 c2_error("Premature end of pattern string.");
897 ++offset;
898 *ptptnstr = '\0';
899 pleaf->ptnstr = strdup(tptnstr);
900 free(tptnstr);
901 }
902
903 C2H_SKIP_SPACES();
904
905 if (!pleaf->ptntype)
906 c2_error("Invalid pattern type.");
907
908 // Check if the type is correct
909 if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) &&
910 C2_L_PTSTRING == pleaf->ptntype) ||
911 ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type ||
912 C2_L_TDRAWABLE == pleaf->type) &&
913 C2_L_PTINT == pleaf->ptntype)))
914 c2_error("Pattern type incompatible with target type.");
915
916 if (C2_L_PTINT == pleaf->ptntype && pleaf->match)
917 c2_error("Integer/boolean pattern cannot have operator qualifiers.");
918
919 if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase)
920 c2_error("Integer/boolean pattern cannot have flags.");
921
922 if (C2_L_PTSTRING == pleaf->ptntype &&
923 (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op ||
924 C2_L_OLTEQ == pleaf->op))
925 c2_error("String pattern cannot have an arithmetic operator.");
926
927 return offset;
928
929 fail:
930 return -1;
931 }
932
933 /**
934 * Parse a condition with legacy syntax.
935 */
c2_parse_legacy(const char * pattern,int offset,c2_ptr_t * presult)936 static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) {
937 if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' ||
938 !strchr(pattern + offset + 2, ':')) {
939 c2_error("Legacy parser: Invalid format.");
940 }
941
942 // Allocate memory for new leaf
943 auto pleaf = cmalloc(c2_l_t);
944 presult->isbranch = false;
945 presult->l = pleaf;
946 memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
947 pleaf->type = C2_L_TSTRING;
948 pleaf->op = C2_L_OEQ;
949 pleaf->ptntype = C2_L_PTSTRING;
950
951 // Determine the pattern target
952 #define TGTFILL(pdefid) \
953 (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \
954 pleaf->format = C2_PREDEFS[pdefid].format)
955 switch (pattern[offset]) {
956 case 'n': TGTFILL(C2_L_PNAME); break;
957 case 'i': TGTFILL(C2_L_PCLASSI); break;
958 case 'g': TGTFILL(C2_L_PCLASSG); break;
959 case 'r': TGTFILL(C2_L_PROLE); break;
960 default: c2_error("Target \"%c\" invalid.\n", pattern[offset]);
961 }
962 #undef TGTFILL
963
964 offset += 2;
965
966 // Determine the match type
967 switch (pattern[offset]) {
968 case 'e': pleaf->match = C2_L_MEXACT; break;
969 case 'a': pleaf->match = C2_L_MCONTAINS; break;
970 case 's': pleaf->match = C2_L_MSTART; break;
971 case 'w': pleaf->match = C2_L_MWILDCARD; break;
972 case 'p': pleaf->match = C2_L_MPCRE; break;
973 default: c2_error("Type \"%c\" invalid.\n", pattern[offset]);
974 }
975 ++offset;
976
977 // Determine the pattern flags
978 while (':' != pattern[offset]) {
979 switch (pattern[offset]) {
980 case 'i': pleaf->match_ignorecase = true; break;
981 default: c2_error("Flag \"%c\" invalid.", pattern[offset]);
982 }
983 ++offset;
984 }
985 ++offset;
986
987 // Copy the pattern
988 pleaf->ptnstr = strdup(pattern + offset);
989
990 return offset;
991
992 fail:
993 return -1;
994 }
995
996 #undef c2_error
997
998 /**
999 * Do postprocessing on a condition leaf.
1000 */
c2_l_postprocess(session_t * ps,c2_l_t * pleaf)1001 static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
1002 // Give a pattern type to a leaf with exists operator, if needed
1003 if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) {
1004 pleaf->ptntype = (C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING : C2_L_PTINT);
1005 }
1006
1007 // Get target atom if it's not a predefined one
1008 if (pleaf->predef == C2_L_PUNDEFINED) {
1009 pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt);
1010 if (!pleaf->tgtatom) {
1011 log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
1012 return false;
1013 }
1014 }
1015
1016 // Insert target Atom into atom track list
1017 if (pleaf->tgtatom) {
1018 bool found = false;
1019 for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
1020 if (pleaf->tgtatom == platom->atom) {
1021 found = true;
1022 break;
1023 }
1024 }
1025 if (!found) {
1026 auto pnew = cmalloc(latom_t);
1027 pnew->next = ps->track_atom_lst;
1028 pnew->atom = pleaf->tgtatom;
1029 ps->track_atom_lst = pnew;
1030 }
1031 }
1032
1033 // Warn about lower case characters in target name
1034 if (pleaf->predef == C2_L_PUNDEFINED) {
1035 for (const char *pc = pleaf->tgt; *pc; ++pc) {
1036 if (islower(*pc)) {
1037 log_warn("Lowercase character in target name \"%s\".",
1038 pleaf->tgt);
1039 break;
1040 }
1041 }
1042 }
1043
1044 // PCRE patterns
1045 if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) {
1046 #ifdef CONFIG_REGEX_PCRE
1047 const char *error = NULL;
1048 int erroffset = 0;
1049 int options = 0;
1050
1051 // Ignore case flag
1052 if (pleaf->match_ignorecase)
1053 options |= PCRE_CASELESS;
1054
1055 // Compile PCRE expression
1056 pleaf->regex_pcre =
1057 pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL);
1058 if (!pleaf->regex_pcre) {
1059 log_error("Pattern \"%s\": PCRE regular expression parsing "
1060 "failed on "
1061 "offset %d: %s",
1062 pleaf->ptnstr, erroffset, error);
1063 return false;
1064 }
1065 #ifdef CONFIG_REGEX_PCRE_JIT
1066 pleaf->regex_pcre_extra =
1067 pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error);
1068 if (!pleaf->regex_pcre_extra) {
1069 printf("Pattern \"%s\": PCRE regular expression study failed: %s",
1070 pleaf->ptnstr, error);
1071 }
1072 #endif
1073
1074 // Free the target string
1075 // free(pleaf->tgt);
1076 // pleaf->tgt = NULL;
1077 #else
1078 log_error("PCRE regular expression support not compiled in.");
1079 return false;
1080 #endif
1081 }
1082
1083 return true;
1084 }
1085
c2_tree_postprocess(session_t * ps,c2_ptr_t node)1086 static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) {
1087 if (!node.isbranch) {
1088 return c2_l_postprocess(ps, node.l);
1089 }
1090 if (!c2_tree_postprocess(ps, node.b->opr1))
1091 return false;
1092 return c2_tree_postprocess(ps, node.b->opr2);
1093 }
1094
c2_list_postprocess(session_t * ps,c2_lptr_t * list)1095 bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) {
1096 c2_lptr_t *head = list;
1097 while (head) {
1098 if (!c2_tree_postprocess(ps, head->ptr))
1099 return false;
1100 head = head->next;
1101 }
1102 return true;
1103 }
1104 /**
1105 * Free a condition tree.
1106 */
c2_free(c2_ptr_t p)1107 static void c2_free(c2_ptr_t p) {
1108 // For a branch element
1109 if (p.isbranch) {
1110 c2_b_t *const pbranch = p.b;
1111
1112 if (!pbranch)
1113 return;
1114
1115 c2_free(pbranch->opr1);
1116 c2_free(pbranch->opr2);
1117 free(pbranch);
1118 }
1119 // For a leaf element
1120 else {
1121 c2_l_t *const pleaf = p.l;
1122
1123 if (!pleaf)
1124 return;
1125
1126 free(pleaf->tgt);
1127 free(pleaf->ptnstr);
1128 #ifdef CONFIG_REGEX_PCRE
1129 pcre_free(pleaf->regex_pcre);
1130 LPCRE_FREE_STUDY(pleaf->regex_pcre_extra);
1131 #endif
1132 free(pleaf);
1133 }
1134 }
1135
1136 /**
1137 * Free a condition tree in c2_lptr_t.
1138 */
c2_free_lptr(c2_lptr_t * lp)1139 c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) {
1140 if (!lp)
1141 return NULL;
1142
1143 c2_lptr_t *pnext = lp->next;
1144 c2_free(lp->ptr);
1145 free(lp);
1146
1147 return pnext;
1148 }
1149
1150 /**
1151 * Get a string representation of a rule target.
1152 */
c2h_dump_str_tgt(const c2_l_t * pleaf)1153 static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) {
1154 if (pleaf->predef != C2_L_PUNDEFINED) {
1155 return C2_PREDEFS[pleaf->predef].name;
1156 } else {
1157 return pleaf->tgt;
1158 }
1159 }
1160
1161 /**
1162 * Get a string representation of a target.
1163 */
c2h_dump_str_type(const c2_l_t * pleaf)1164 static const char *c2h_dump_str_type(const c2_l_t *pleaf) {
1165 switch (pleaf->type) {
1166 case C2_L_TWINDOW: return "w";
1167 case C2_L_TDRAWABLE: return "d";
1168 case C2_L_TCARDINAL: return "c";
1169 case C2_L_TSTRING: return "s";
1170 case C2_L_TATOM: return "a";
1171 case C2_L_TUNDEFINED: break;
1172 }
1173
1174 return NULL;
1175 }
1176
1177 /**
1178 * Dump a condition tree.
1179 */
c2_dump(c2_ptr_t p)1180 static void c2_dump(c2_ptr_t p) {
1181 // For a branch
1182 if (p.isbranch) {
1183 const c2_b_t *const pbranch = p.b;
1184
1185 if (!pbranch)
1186 return;
1187
1188 if (pbranch->neg)
1189 putchar('!');
1190
1191 printf("(");
1192 c2_dump(pbranch->opr1);
1193
1194 switch (pbranch->op) {
1195 case C2_B_OAND: printf(" && "); break;
1196 case C2_B_OOR: printf(" || "); break;
1197 case C2_B_OXOR: printf(" XOR "); break;
1198 default: assert(0); break;
1199 }
1200
1201 c2_dump(pbranch->opr2);
1202 printf(") ");
1203 }
1204 // For a leaf
1205 else {
1206 const c2_l_t *const pleaf = p.l;
1207
1208 if (!pleaf)
1209 return;
1210
1211 if (C2_L_OEXISTS == pleaf->op && pleaf->neg)
1212 putchar('!');
1213
1214 // Print target name, type, and format
1215 {
1216 printf("%s", c2h_dump_str_tgt(pleaf));
1217 if (pleaf->tgt_onframe)
1218 putchar('@');
1219 if (pleaf->index >= 0)
1220 printf("[%d]", pleaf->index);
1221 printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf));
1222 }
1223
1224 // Print operator
1225 putchar(' ');
1226
1227 if (C2_L_OEXISTS != pleaf->op && pleaf->neg)
1228 putchar('!');
1229
1230 switch (pleaf->match) {
1231 case C2_L_MEXACT: break;
1232 case C2_L_MCONTAINS: putchar('*'); break;
1233 case C2_L_MSTART: putchar('^'); break;
1234 case C2_L_MPCRE: putchar('~'); break;
1235 case C2_L_MWILDCARD: putchar('%'); break;
1236 }
1237
1238 if (pleaf->match_ignorecase)
1239 putchar('?');
1240
1241 switch (pleaf->op) {
1242 case C2_L_OEXISTS: break;
1243 case C2_L_OEQ: fputs("=", stdout); break;
1244 case C2_L_OGT: fputs(">", stdout); break;
1245 case C2_L_OGTEQ: fputs(">=", stdout); break;
1246 case C2_L_OLT: fputs("<", stdout); break;
1247 case C2_L_OLTEQ: fputs("<=", stdout); break;
1248 }
1249
1250 if (C2_L_OEXISTS == pleaf->op)
1251 return;
1252
1253 // Print pattern
1254 putchar(' ');
1255 switch (pleaf->ptntype) {
1256 case C2_L_PTINT: printf("%ld", pleaf->ptnint); break;
1257 case C2_L_PTSTRING:
1258 // TODO: Escape string before printing out?
1259 printf("\"%s\"", pleaf->ptnstr);
1260 break;
1261 default: assert(0); break;
1262 }
1263 }
1264 }
1265
1266 /**
1267 * Get the type atom of a condition.
1268 */
c2_get_atom_type(const c2_l_t * pleaf)1269 static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) {
1270 switch (pleaf->type) {
1271 case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL;
1272 case C2_L_TWINDOW: return XCB_ATOM_WINDOW;
1273 case C2_L_TSTRING: return XCB_ATOM_STRING;
1274 case C2_L_TATOM: return XCB_ATOM_ATOM;
1275 case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE;
1276 default: assert(0); break;
1277 }
1278 unreachable;
1279 }
1280
1281 /**
1282 * Match a window against a single leaf window condition.
1283 *
1284 * For internal use.
1285 */
c2_match_once_leaf(session_t * ps,const struct managed_win * w,const c2_l_t * pleaf,bool * pres,bool * perr)1286 static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w,
1287 const c2_l_t *pleaf, bool *pres, bool *perr) {
1288 assert(pleaf);
1289
1290 const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id);
1291
1292 // Return if wid is missing
1293 if (pleaf->predef == C2_L_PUNDEFINED && !wid) {
1294 return;
1295 }
1296
1297 const int idx = (pleaf->index < 0 ? 0 : pleaf->index);
1298
1299 switch (pleaf->ptntype) {
1300 // Deal with integer patterns
1301 case C2_L_PTINT: {
1302 long tgt = 0;
1303
1304 // Get the value
1305 // A predefined target
1306 if (pleaf->predef != C2_L_PUNDEFINED) {
1307 *perr = false;
1308 switch (pleaf->predef) {
1309 case C2_L_PID: tgt = wid; break;
1310 case C2_L_PX: tgt = w->g.x; break;
1311 case C2_L_PY: tgt = w->g.y; break;
1312 case C2_L_PX2: tgt = w->g.x + w->widthb; break;
1313 case C2_L_PY2: tgt = w->g.y + w->heightb; break;
1314 case C2_L_PWIDTH: tgt = w->g.width; break;
1315 case C2_L_PHEIGHT: tgt = w->g.height; break;
1316 case C2_L_PWIDTHB: tgt = w->widthb; break;
1317 case C2_L_PHEIGHTB: tgt = w->heightb; break;
1318 case C2_L_PBDW: tgt = w->g.border_width; break;
1319 case C2_L_PFULLSCREEN: tgt = win_is_fullscreen(ps, w); break;
1320 case C2_L_POVREDIR: tgt = w->a.override_redirect; break;
1321 case C2_L_PARGB: tgt = win_has_alpha(w); break;
1322 case C2_L_PFOCUSED: tgt = win_is_focused_raw(ps, w); break;
1323 case C2_L_PWMWIN: tgt = w->wmwin; break;
1324 case C2_L_PBSHAPED: tgt = w->bounding_shaped; break;
1325 case C2_L_PROUNDED: tgt = w->rounded_corners; break;
1326 case C2_L_PCLIENT: tgt = w->client_win; break;
1327 case C2_L_PLEADER: tgt = w->leader; break;
1328 default:
1329 *perr = true;
1330 assert(0);
1331 break;
1332 }
1333 }
1334 // A raw window property
1335 else {
1336 winprop_t prop =
1337 x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L,
1338 c2_get_atom_type(pleaf), pleaf->format);
1339 if (prop.nitems) {
1340 *perr = false;
1341 tgt = winprop_get_int(prop);
1342 }
1343 free_winprop(&prop);
1344 }
1345
1346 if (*perr)
1347 return;
1348
1349 // Do comparison
1350 switch (pleaf->op) {
1351 case C2_L_OEXISTS:
1352 *pres = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true);
1353 break;
1354 case C2_L_OEQ: *pres = (tgt == pleaf->ptnint); break;
1355 case C2_L_OGT: *pres = (tgt > pleaf->ptnint); break;
1356 case C2_L_OGTEQ: *pres = (tgt >= pleaf->ptnint); break;
1357 case C2_L_OLT: *pres = (tgt < pleaf->ptnint); break;
1358 case C2_L_OLTEQ: *pres = (tgt <= pleaf->ptnint); break;
1359 default:
1360 *perr = true;
1361 assert(0);
1362 break;
1363 }
1364 } break;
1365 // String patterns
1366 case C2_L_PTSTRING: {
1367 const char *tgt = NULL;
1368 char *tgt_free = NULL;
1369
1370 // A predefined target
1371 if (pleaf->predef != C2_L_PUNDEFINED) {
1372 switch (pleaf->predef) {
1373 case C2_L_PWINDOWTYPE: tgt = WINTYPES[w->window_type]; break;
1374 case C2_L_PNAME: tgt = w->name; break;
1375 case C2_L_PCLASSG: tgt = w->class_general; break;
1376 case C2_L_PCLASSI: tgt = w->class_instance; break;
1377 case C2_L_PROLE: tgt = w->role; break;
1378 default: assert(0); break;
1379 }
1380 } else if (pleaf->type == C2_L_TATOM) {
1381 // An atom type property, convert it to string
1382 winprop_t prop =
1383 x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, 1L,
1384 c2_get_atom_type(pleaf), pleaf->format);
1385 xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop);
1386 if (atom) {
1387 xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
1388 ps->c, xcb_get_atom_name(ps->c, atom), NULL);
1389 if (reply) {
1390 tgt_free = strndup(
1391 xcb_get_atom_name_name(reply),
1392 (size_t)xcb_get_atom_name_name_length(reply));
1393 free(reply);
1394 }
1395 }
1396 if (tgt_free) {
1397 tgt = tgt_free;
1398 }
1399 free_winprop(&prop);
1400 } else {
1401 // Not an atom type, just fetch the string list
1402 char **strlst = NULL;
1403 int nstr;
1404 if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr) &&
1405 nstr > idx) {
1406 tgt_free = strdup(strlst[idx]);
1407 tgt = tgt_free;
1408 }
1409 if (strlst)
1410 XFreeStringList(strlst);
1411 }
1412
1413 if (tgt) {
1414 *perr = false;
1415 } else {
1416 return;
1417 }
1418
1419 // Actual matching
1420 switch (pleaf->op) {
1421 case C2_L_OEXISTS: *pres = true; break;
1422 case C2_L_OEQ:
1423 switch (pleaf->match) {
1424 case C2_L_MEXACT:
1425 if (pleaf->match_ignorecase)
1426 *pres = !strcasecmp(tgt, pleaf->ptnstr);
1427 else
1428 *pres = !strcmp(tgt, pleaf->ptnstr);
1429 break;
1430 case C2_L_MCONTAINS:
1431 if (pleaf->match_ignorecase)
1432 *pres = strcasestr(tgt, pleaf->ptnstr);
1433 else
1434 *pres = strstr(tgt, pleaf->ptnstr);
1435 break;
1436 case C2_L_MSTART:
1437 if (pleaf->match_ignorecase)
1438 *pres = !strncasecmp(tgt, pleaf->ptnstr,
1439 strlen(pleaf->ptnstr));
1440 else
1441 *pres = !strncmp(tgt, pleaf->ptnstr,
1442 strlen(pleaf->ptnstr));
1443 break;
1444 case C2_L_MWILDCARD: {
1445 int flags = 0;
1446 if (pleaf->match_ignorecase)
1447 flags |= FNM_CASEFOLD;
1448 *pres = !fnmatch(pleaf->ptnstr, tgt, flags);
1449 } break;
1450 case C2_L_MPCRE:
1451 #ifdef CONFIG_REGEX_PCRE
1452 assert(strlen(tgt) <= INT_MAX);
1453 *pres =
1454 (pcre_exec(pleaf->regex_pcre, pleaf->regex_pcre_extra,
1455 tgt, (int)strlen(tgt), 0, 0, NULL, 0) >= 0);
1456 #else
1457 assert(0);
1458 #endif
1459 break;
1460 }
1461 break;
1462 default: *perr = true; assert(0);
1463 }
1464
1465 // Free the string after usage, if necessary
1466 if (tgt_free) {
1467 if (C2_L_TATOM == pleaf->type)
1468 XFree(tgt_free);
1469 else
1470 free(tgt_free);
1471 }
1472 } break;
1473 default: assert(0); break;
1474 }
1475 }
1476
1477 /**
1478 * Match a window against a single window condition.
1479 *
1480 * @return true if matched, false otherwise.
1481 */
c2_match_once(session_t * ps,const struct managed_win * w,const c2_ptr_t cond)1482 static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) {
1483 bool result = false;
1484 bool error = true;
1485
1486 // Handle a branch
1487 if (cond.isbranch) {
1488 const c2_b_t *pb = cond.b;
1489
1490 if (!pb)
1491 return false;
1492
1493 error = false;
1494
1495 switch (pb->op) {
1496 case C2_B_OAND:
1497 result = (c2_match_once(ps, w, pb->opr1) &&
1498 c2_match_once(ps, w, pb->opr2));
1499 break;
1500 case C2_B_OOR:
1501 result = (c2_match_once(ps, w, pb->opr1) ||
1502 c2_match_once(ps, w, pb->opr2));
1503 break;
1504 case C2_B_OXOR:
1505 result = (c2_match_once(ps, w, pb->opr1) !=
1506 c2_match_once(ps, w, pb->opr2));
1507 break;
1508 default: error = true; assert(0);
1509 }
1510
1511 #ifdef DEBUG_WINMATCH
1512 log_trace("(%#010x): branch: result = %d, pattern = ", w->base.id, result);
1513 c2_dump(cond);
1514 putchar('\n');
1515 #endif
1516 }
1517 // Handle a leaf
1518 else {
1519 const c2_l_t *pleaf = cond.l;
1520
1521 if (!pleaf)
1522 return false;
1523
1524 c2_match_once_leaf(ps, w, pleaf, &result, &error);
1525
1526 // For EXISTS operator, no errors are fatal
1527 if (C2_L_OEXISTS == pleaf->op && error) {
1528 result = false;
1529 error = false;
1530 }
1531
1532 #ifdef DEBUG_WINMATCH
1533 log_trace("(%#010x): leaf: result = %d, error = %d, "
1534 "client = %#010x, pattern = ",
1535 w->base.id, result, error, w->client_win);
1536 c2_dump(cond);
1537 putchar('\n');
1538 #endif
1539 }
1540
1541 // Postprocess the result
1542 if (error)
1543 result = false;
1544
1545 if (cond.isbranch ? cond.b->neg : cond.l->neg)
1546 result = !result;
1547
1548 return result;
1549 }
1550
1551 /**
1552 * Match a window against a condition linked list.
1553 *
1554 * @param cache a place to cache the last matched condition
1555 * @param pdata a place to return the data
1556 * @return true if matched, false otherwise.
1557 */
c2_match(session_t * ps,const struct managed_win * w,const c2_lptr_t * condlst,void ** pdata)1558 bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
1559 void **pdata) {
1560 // Then go through the whole linked list
1561 for (; condlst; condlst = condlst->next) {
1562 if (c2_match_once(ps, w, condlst->ptr)) {
1563 if (pdata)
1564 *pdata = condlst->data;
1565 return true;
1566 }
1567 }
1568
1569 return false;
1570 }
1571