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