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