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