diff options
| author | allusive-dev <[email protected]> | 2023-09-19 17:47:33 +1000 |
|---|---|---|
| committer | allusive-dev <[email protected]> | 2023-09-19 17:47:33 +1000 |
| commit | a93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch) | |
| tree | 77f8152222655657472a70e0bfa413a0495dd555 /src/c2.c | |
| parent | reset (diff) | |
| download | compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip | |
Fixed broken files/code and other errors
Diffstat (limited to 'src/c2.c')
| -rw-r--r-- | src/c2.c | 1674 |
1 files changed, 1674 insertions, 0 deletions
diff --git a/src/c2.c b/src/c2.c new file mode 100644 index 0000000..3500f7b --- /dev/null +++ b/src/c2.c @@ -0,0 +1,1674 @@ +// SPDX-License-Identifier: MIT + +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * See LICENSE-mit for more information. + * + */ + +#include <ctype.h> +#include <fnmatch.h> +#include <stdio.h> +#include <string.h> + +// libpcre +#ifdef CONFIG_REGEX_PCRE +#include <pcre.h> + +// For compatibility with <libpcre-8.20 +#ifndef PCRE_STUDY_JIT_COMPILE +#define PCRE_STUDY_JIT_COMPILE 0 +#define LPCRE_FREE_STUDY(extra) pcre_free(extra) +#else +#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra) +#endif + +#endif + +#include <X11/Xlib.h> +#include <xcb/xcb.h> + +#include "atom.h" +#include "common.h" +#include "compiler.h" +#include "config.h" +#include "log.h" +#include "string_utils.h" +#include "utils.h" +#include "win.h" +#include "x.h" + +#include "c2.h" + +#pragma GCC diagnostic error "-Wunused-parameter" + +#define C2_MAX_LEVELS 10 + +typedef struct _c2_b c2_b_t; +typedef struct _c2_l c2_l_t; + +/// Pointer to a condition tree. +typedef struct { + bool isbranch : 1; + union { + c2_b_t *b; + c2_l_t *l; + }; +} c2_ptr_t; + +/// Initializer for c2_ptr_t. +#define C2_PTR_INIT \ + { .isbranch = false, .l = NULL, } + +static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT; + +/// Operator of a branch element. +typedef enum { + C2_B_OUNDEFINED, + C2_B_OAND, + C2_B_OOR, + C2_B_OXOR, +} c2_b_op_t; + +/// Structure for branch element in a window condition +struct _c2_b { + bool neg : 1; + c2_b_op_t op; + c2_ptr_t opr1; + c2_ptr_t opr2; +}; + +/// Initializer for c2_b_t. +#define C2_B_INIT \ + { .neg = false, .op = C2_B_OUNDEFINED, .opr1 = C2_PTR_INIT, .opr2 = C2_PTR_INIT, } + +/// Structure for leaf element in a window condition +struct _c2_l { + bool neg : 1; + enum { C2_L_OEXISTS, + C2_L_OEQ, + C2_L_OGT, + C2_L_OGTEQ, + C2_L_OLT, + C2_L_OLTEQ, + } op : 3; + enum { C2_L_MEXACT, + C2_L_MSTART, + C2_L_MCONTAINS, + C2_L_MWILDCARD, + C2_L_MPCRE, + } match : 3; + bool match_ignorecase : 1; + char *tgt; + xcb_atom_t tgtatom; + bool tgt_onframe; + int index; + enum { C2_L_PUNDEFINED = -1, + C2_L_PID = 0, + C2_L_PX, + C2_L_PY, + C2_L_PX2, + C2_L_PY2, + C2_L_PWIDTH, + C2_L_PHEIGHT, + C2_L_PWIDTHB, + C2_L_PHEIGHTB, + C2_L_PBDW, + C2_L_PFULLSCREEN, + C2_L_POVREDIR, + C2_L_PARGB, + C2_L_PFOCUSED, + C2_L_PWMWIN, + C2_L_PBSHAPED, + C2_L_PROUNDED, + C2_L_PCLIENT, + C2_L_PWINDOWTYPE, + C2_L_PLEADER, + C2_L_PNAME, + C2_L_PCLASSG, + C2_L_PCLASSI, + C2_L_PROLE, + } predef; + enum c2_l_type { + C2_L_TUNDEFINED, + C2_L_TSTRING, + C2_L_TCARDINAL, + C2_L_TWINDOW, + C2_L_TATOM, + C2_L_TDRAWABLE, + } type; + int format; + enum { C2_L_PTUNDEFINED, + C2_L_PTSTRING, + C2_L_PTINT, + } ptntype; + char *ptnstr; + long ptnint; +#ifdef CONFIG_REGEX_PCRE + pcre *regex_pcre; + pcre_extra *regex_pcre_extra; +#endif +}; + +/// Initializer for c2_l_t. +#define C2_L_INIT \ + { \ + .neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT, \ + .match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \ + .predef = C2_L_PUNDEFINED, .index = 0, .type = C2_L_TUNDEFINED, \ + .format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \ + } + +static const c2_l_t leaf_def = C2_L_INIT; + +/// Linked list type of conditions. +struct _c2_lptr { + c2_ptr_t ptr; + void *data; + struct _c2_lptr *next; +}; + +/// Initializer for c2_lptr_t. +#define C2_LPTR_INIT \ + { .ptr = C2_PTR_INIT, .data = NULL, .next = NULL, } + +/// Structure representing a predefined target. +typedef struct { + const char *name; + enum c2_l_type type; + int format; +} c2_predef_t; + +// Predefined targets. +static const c2_predef_t C2_PREDEFS[] = { + [C2_L_PID] = {"id", C2_L_TCARDINAL, 0}, + [C2_L_PX] = {"x", C2_L_TCARDINAL, 0}, + [C2_L_PY] = {"y", C2_L_TCARDINAL, 0}, + [C2_L_PX2] = {"x2", C2_L_TCARDINAL, 0}, + [C2_L_PY2] = {"y2", C2_L_TCARDINAL, 0}, + [C2_L_PWIDTH] = {"width", C2_L_TCARDINAL, 0}, + [C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL, 0}, + [C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL, 0}, + [C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL, 0}, + [C2_L_PBDW] = {"border_width", C2_L_TCARDINAL, 0}, + [C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL, 0}, + [C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL, 0}, + [C2_L_PARGB] = {"argb", C2_L_TCARDINAL, 0}, + [C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL, 0}, + [C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL, 0}, + [C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL, 0}, + [C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL, 0}, + [C2_L_PCLIENT] = {"client", C2_L_TWINDOW, 0}, + [C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING, 0}, + [C2_L_PLEADER] = {"leader", C2_L_TWINDOW, 0}, + [C2_L_PNAME] = {"name", C2_L_TSTRING, 0}, + [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0}, + [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0}, + [C2_L_PROLE] = {"role", C2_L_TSTRING, 0}, +}; + +/** + * Get the numeric property value from a win_prop_t. + */ +static inline long winprop_get_int(winprop_t prop, size_t index) { + long tgt = 0; + + if (!prop.nitems || index >= prop.nitems) { + return 0; + } + + switch (prop.format) { + case 8: tgt = *(prop.p8 + index); break; + case 16: tgt = *(prop.p16 + index); break; + case 32: tgt = *(prop.p32 + index); break; + default: assert(0); break; + } + + return tgt; +} + +/** + * Compare next word in a string with another string. + */ +static inline int strcmp_wd(const char *needle, const char *src) { + int ret = mstrncmp(needle, src); + if (ret) + return ret; + + char c = src[strlen(needle)]; + if (isalnum((unsigned char)c) || '_' == c) + return 1; + else + return 0; +} + +/** + * Return whether a c2_ptr_t is empty. + */ +static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) { + return !(p.isbranch ? (bool)p.b : (bool)p.l); +} + +/** + * Reset a c2_ptr_t. + */ +static inline void c2_ptr_reset(c2_ptr_t *pp) { + if (pp) + memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t)); +} + +/** + * Combine two condition trees. + */ +static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) { + c2_ptr_t p = {.isbranch = true, .b = NULL}; + p.b = cmalloc(c2_b_t); + + p.b->neg = false; + p.b->op = op; + p.b->opr1 = p1; + p.b->opr2 = p2; + + return p; +} + +/** + * Get the precedence value of a condition branch operator. + */ +static inline int c2h_b_opp(c2_b_op_t op) { + switch (op) { + case C2_B_OAND: return 2; + case C2_B_OOR: return 1; + case C2_B_OXOR: return 1; + default: break; + } + + assert(0); + return 0; +} + +/** + * Compare precedence of two condition branch operators. + * + * Associativity is left-to-right, forever. + * + * @return positive number if op1 > op2, 0 if op1 == op2 in precedence, + * negative number otherwise + */ +static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) { + return c2h_b_opp(op1) - c2h_b_opp(op2); +} + +static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level); + +static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult); + +static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult); + +static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult); + +static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult); + +static void c2_free(c2_ptr_t p); + +/** + * Wrapper of c2_free(). + */ +static inline void c2_freep(c2_ptr_t *pp) { + if (pp) { + c2_free(*pp); + c2_ptr_reset(pp); + } +} + +static const char *c2h_dump_str_tgt(const c2_l_t *pleaf); + +static const char *c2h_dump_str_type(const c2_l_t *pleaf); + +static void attr_unused c2_dump(c2_ptr_t p); + +static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf); + +static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond); + +/** + * Parse a condition string. + */ +c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { + if (!pattern) + return NULL; + + // Parse the pattern + c2_ptr_t result = C2_PTR_INIT; + int offset = -1; + + if (strlen(pattern) >= 2 && ':' == pattern[1]) + offset = c2_parse_legacy(pattern, 0, &result); + else + offset = c2_parse_grp(pattern, 0, &result, 0); + + if (offset < 0) { + c2_freep(&result); + return NULL; + } + + // Insert to pcondlst + { + static const c2_lptr_t lptr_def = C2_LPTR_INIT; + auto plptr = cmalloc(c2_lptr_t); + memcpy(plptr, &lptr_def, sizeof(c2_lptr_t)); + plptr->ptr = result; + plptr->data = data; + if (pcondlst) { + plptr->next = *pcondlst; + *pcondlst = plptr; + } + +#ifdef DEBUG_C2 + log_trace("(\"%s\"): ", pattern); + c2_dump(plptr->ptr); + putchar('\n'); +#endif + + return plptr; + } +} + +#define c2_error(format, ...) \ + do { \ + log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \ + goto fail; \ + } while (0) + +// TODO(yshui) Not a very good macro, should probably be a function +#define C2H_SKIP_SPACES() \ + { \ + while (isspace((unsigned char)pattern[offset])) \ + ++offset; \ + } + +/** + * Parse a group in condition string. + * + * @return offset of next character in string + */ +static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) { + // Check for recursion levels + if (level > C2_MAX_LEVELS) + c2_error("Exceeded maximum recursion levels."); + + if (!pattern) + return -1; + + // Expected end character + const char endchar = (offset ? ')' : '\0'); + + // We use a system that a maximum of 2 elements are kept. When we find + // the third element, we combine the elements according to operator + // precedence. This design limits operators to have at most two-levels + // of precedence and fixed left-to-right associativity. + + // For storing branch operators. ops[0] is actually unused + c2_b_op_t ops[3] = {}; + // For storing elements + c2_ptr_t eles[2] = {C2_PTR_INIT, C2_PTR_INIT}; + // Index of next free element slot in eles + int elei = 0; + // Pointer to the position of next element + c2_ptr_t *pele = eles; + // Negation flag of next operator + bool neg = false; + // Whether we are expecting an element immediately, is true at first, or + // after encountering a logical operator + bool next_expected = true; + + // Parse the pattern character-by-character + for (; pattern[offset]; ++offset) { + assert(elei <= 2); + + // Jump over spaces + if (isspace((unsigned char)pattern[offset])) + continue; + + // Handle end of group + if (')' == pattern[offset]) + break; + + // Handle "!" + if ('!' == pattern[offset]) { + if (!next_expected) + c2_error("Unexpected \"!\"."); + + neg = !neg; + continue; + } + + // Handle AND and OR + if ('&' == pattern[offset] || '|' == pattern[offset]) { + if (next_expected) + c2_error("Unexpected logical operator."); + + next_expected = true; + if (!mstrncmp("&&", pattern + offset)) { + ops[elei] = C2_B_OAND; + ++offset; + } else if (!mstrncmp("||", pattern + offset)) { + ops[elei] = C2_B_OOR; + ++offset; + } else + c2_error("Illegal logical operator."); + + continue; + } + + // Parsing an element + if (!next_expected) + c2_error("Unexpected expression."); + + assert(!elei || ops[elei]); + + // If we are out of space + if (2 == elei) { + --elei; + // If the first operator has higher or equal precedence, combine + // the first two elements + if (c2h_b_opcmp(ops[1], ops[2]) >= 0) { + eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]); + c2_ptr_reset(&eles[1]); + pele = &eles[elei]; + ops[1] = ops[2]; + } + // Otherwise, combine the second and the incoming one + else { + eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL); + assert(eles[1].isbranch); + pele = &eles[1].b->opr2; + } + // The last operator always needs to be reset + ops[2] = C2_B_OUNDEFINED; + } + + // It's a subgroup if it starts with '(' + if ('(' == pattern[offset]) { + if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) + goto fail; + } + // Otherwise it's a leaf + else { + if ((offset = c2_parse_target(pattern, offset, pele)) < 0) + goto fail; + + assert(!pele->isbranch && !c2_ptr_isempty(*pele)); + + if ((offset = c2_parse_op(pattern, offset, pele)) < 0) + goto fail; + + if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) + goto fail; + } + // Decrement offset -- we will increment it in loop update + --offset; + + // Apply negation + if (neg) { + neg = false; + if (pele->isbranch) + pele->b->neg = !pele->b->neg; + else + pele->l->neg = !pele->l->neg; + } + + next_expected = false; + ++elei; + pele = &eles[elei]; + } + + // Wrong end character? + if (pattern[offset] && !endchar) + c2_error("Expected end of string but found '%c'.", pattern[offset]); + if (!pattern[offset] && endchar) + c2_error("Expected '%c' but found end of string.", endchar); + + // Handle end of group + if (!elei) { + c2_error("Empty group."); + } else if (next_expected) { + c2_error("Missing rule before end of group."); + } else if (elei > 1) { + assert(2 == elei); + assert(ops[1]); + eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]); + c2_ptr_reset(&eles[1]); + } + + *presult = eles[0]; + + if (')' == pattern[offset]) + ++offset; + + return offset; + +fail: + c2_freep(&eles[0]); + c2_freep(&eles[1]); + + return -1; +} + +/** + * Parse the target part of a rule. + */ +static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { + // Initialize leaf + presult->isbranch = false; + presult->l = cmalloc(c2_l_t); + + c2_l_t *const pleaf = presult->l; + memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); + + // Parse negation marks + while ('!' == pattern[offset]) { + pleaf->neg = !pleaf->neg; + ++offset; + C2H_SKIP_SPACES(); + } + + // Copy target name out + int tgtlen = 0; + for (; pattern[offset] && + (isalnum((unsigned char)pattern[offset]) || '_' == pattern[offset]); + ++offset) { + ++tgtlen; + } + if (!tgtlen) { + c2_error("Empty target."); + } + pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen); + + // Check for predefined targets + static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0])); + for (int i = 0; i < npredefs; ++i) { + if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) { + pleaf->predef = i; + pleaf->type = C2_PREDEFS[i].type; + pleaf->format = C2_PREDEFS[i].format; + break; + } + } + + C2H_SKIP_SPACES(); + + // Parse target-on-frame flag + if ('@' == pattern[offset]) { + pleaf->tgt_onframe = true; + ++offset; + C2H_SKIP_SPACES(); + } + + // Parse index + if ('[' == pattern[offset]) { + if (pleaf->predef != C2_L_PUNDEFINED) { + c2_error("Predefined targets can't have index."); + } + + offset++; + + C2H_SKIP_SPACES(); + + long index = -1; + const char *endptr = NULL; + + if ('*' == pattern[offset]) { + index = -1; + endptr = pattern + offset + 1; + } else { + index = strtol(pattern + offset, (char **)&endptr, 0); + if (index < 0) { + c2_error("Index number invalid."); + } + } + + if (!endptr || pattern + offset == endptr) { + c2_error("No index number found after bracket."); + } + + pleaf->index = to_int_checked(index); + offset = to_int_checked(endptr - pattern); + + C2H_SKIP_SPACES(); + + if (pattern[offset] != ']') { + c2_error("Index end marker not found."); + } + + ++offset; + + C2H_SKIP_SPACES(); + } + + // Parse target type and format + if (':' == pattern[offset]) { + ++offset; + C2H_SKIP_SPACES(); + + // Look for format + bool hasformat = false; + long format = 0; + { + char *endptr = NULL; + format = strtol(pattern + offset, &endptr, 0); + assert(endptr); + if ((hasformat = (endptr && endptr != pattern + offset))) { + offset = to_int_checked(endptr - pattern); + } + C2H_SKIP_SPACES(); + } + + // Look for type + enum c2_l_type type = C2_L_TUNDEFINED; + switch (pattern[offset]) { + case 'w': type = C2_L_TWINDOW; break; + case 'd': type = C2_L_TDRAWABLE; break; + case 'c': type = C2_L_TCARDINAL; break; + case 's': type = C2_L_TSTRING; break; + case 'a': type = C2_L_TATOM; break; + default: c2_error("Invalid type character."); + } + + if (type) { + if (pleaf->predef != C2_L_PUNDEFINED) { + log_warn("Type specified for a default target " + "will be ignored."); + } else { + if (pleaf->type && type != pleaf->type) { + log_warn("Default type overridden on " + "target."); + } + pleaf->type = type; + } + } + + offset++; + C2H_SKIP_SPACES(); + + // Default format + if (!pleaf->format) { + switch (pleaf->type) { + case C2_L_TWINDOW: + case C2_L_TDRAWABLE: + case C2_L_TATOM: pleaf->format = 32; break; + case C2_L_TSTRING: pleaf->format = 8; break; + default: break; + } + } + + // Write format + if (hasformat) { + if (pleaf->predef != C2_L_PUNDEFINED) { + log_warn("Format \"%ld\" specified on a default target " + "will be ignored.", + format); + } else if (pleaf->type == C2_L_TSTRING) { + log_warn("Format \"%ld\" specified on a string target " + "will be ignored.", + format); + } else { + if (pleaf->format && pleaf->format != format) { + log_warn("Default format %d overridden on " + "target.", + pleaf->format); + } + pleaf->format = to_int_checked(format); + } + } + } + + if (!pleaf->type) { + c2_error("Target type cannot be determined."); + } + + // if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type) + // c2_error("Target format cannot be determined."); + + if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) { + c2_error("Invalid format."); + } + + return offset; + +fail: + return -1; +} + +/** + * Parse the operator part of a leaf. + */ +static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) { + c2_l_t *const pleaf = presult->l; + + // Parse negation marks + C2H_SKIP_SPACES(); + while ('!' == pattern[offset]) { + pleaf->neg = !pleaf->neg; + ++offset; + C2H_SKIP_SPACES(); + } + + // Parse qualifiers + if ('*' == pattern[offset] || '^' == pattern[offset] || '%' == pattern[offset] || + '~' == pattern[offset]) { + switch (pattern[offset]) { + case '*': pleaf->match = C2_L_MCONTAINS; break; + case '^': pleaf->match = C2_L_MSTART; break; + case '%': pleaf->match = C2_L_MWILDCARD; break; + case '~': pleaf->match = C2_L_MPCRE; break; + default: assert(0); + } + ++offset; + C2H_SKIP_SPACES(); + } + + // Parse flags + while ('?' == pattern[offset]) { + pleaf->match_ignorecase = true; + ++offset; + C2H_SKIP_SPACES(); + } + + // Parse operator + while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) { + if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) + pleaf->op = C2_L_OGTEQ; + else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) + pleaf->op = C2_L_OLTEQ; + else if (pleaf->op) { + c2_error("Duplicate operator."); + } else { + switch (pattern[offset]) { + case '=': pleaf->op = C2_L_OEQ; break; + case '>': pleaf->op = C2_L_OGT; break; + case '<': pleaf->op = C2_L_OLT; break; + default: assert(0); + } + } + ++offset; + C2H_SKIP_SPACES(); + } + + // Check for problems + if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) + c2_error("Exists/greater-than/less-than operators cannot have a " + "qualifier."); + + return offset; + +fail: + return -1; +} + +/** + * Parse the pattern part of a leaf. + */ +static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) { + c2_l_t *const pleaf = presult->l; + + // Exists operator cannot have pattern + if (!pleaf->op) { + return offset; + } + + C2H_SKIP_SPACES(); + + char *endptr = NULL; + if (!strcmp_wd("true", &pattern[offset])) { + pleaf->ptntype = C2_L_PTINT; + pleaf->ptnint = true; + offset += 4; // length of "true"; + } else if (!strcmp_wd("false", &pattern[offset])) { + pleaf->ptntype = C2_L_PTINT; + pleaf->ptnint = false; + offset += 5; // length of "false"; + } else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0), + pattern + offset != endptr) { + pleaf->ptntype = C2_L_PTINT; + offset = to_int_checked(endptr - pattern); + // Make sure we are stopping at the end of a word + if (isalnum((unsigned char)pattern[offset])) { + c2_error("Trailing characters after a numeric pattern."); + } + } else { + // Parse string patterns + bool raw = false; + char delim = '\0'; + + // String flags + if (tolower((unsigned char)pattern[offset]) == 'r') { + raw = true; + ++offset; + C2H_SKIP_SPACES(); + } + + // Check for delimiters + if (pattern[offset] == '\"' || pattern[offset] == '\'') { + pleaf->ptntype = C2_L_PTSTRING; + delim = pattern[offset]; + ++offset; + } + + if (pleaf->ptntype != C2_L_PTSTRING) { + c2_error("Invalid pattern type."); + } + + // Parse the string now + // We can't determine the length of the pattern, so we use the length + // to the end of the pattern string -- currently escape sequences + // cannot be converted to a string longer than itself. + auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char); + char *ptptnstr = tptnstr; + pleaf->ptnstr = tptnstr; + for (; pattern[offset] && delim != pattern[offset]; ++offset) { + // Handle escape sequences if it's not a raw string + if ('\\' == pattern[offset] && !raw) { + switch (pattern[++offset]) { + case '\\': *(ptptnstr++) = '\\'; break; + case '\'': *(ptptnstr++) = '\''; break; + case '\"': *(ptptnstr++) = '\"'; break; + case 'a': *(ptptnstr++) = '\a'; break; + case 'b': *(ptptnstr++) = '\b'; break; + case 'f': *(ptptnstr++) = '\f'; break; + case 'n': *(ptptnstr++) = '\n'; break; + case 'r': *(ptptnstr++) = '\r'; break; + case 't': *(ptptnstr++) = '\t'; break; + case 'v': *(ptptnstr++) = '\v'; break; + case 'o': + case 'x': { + char *tstr = strndup(pattern + offset + 1, 2); + char *pstr = NULL; + long val = strtol( + tstr, &pstr, ('o' == pattern[offset] ? 8 : 16)); + free(tstr); + if (pstr != &tstr[2] || val <= 0) + c2_error("Invalid octal/hex escape " + "sequence."); + *(ptptnstr++) = to_char_checked(val); + offset += 2; + break; + } + default: c2_error("Invalid escape sequence."); + } + } else { + *(ptptnstr++) = pattern[offset]; + } + } + if (!pattern[offset]) + c2_error("Premature end of pattern string."); + ++offset; + *ptptnstr = '\0'; + pleaf->ptnstr = strdup(tptnstr); + free(tptnstr); + } + + C2H_SKIP_SPACES(); + + if (!pleaf->ptntype) + c2_error("Invalid pattern type."); + + // Check if the type is correct + if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) && + C2_L_PTSTRING == pleaf->ptntype) || + ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type || + C2_L_TDRAWABLE == pleaf->type) && + C2_L_PTINT == pleaf->ptntype))) + c2_error("Pattern type incompatible with target type."); + + if (C2_L_PTINT == pleaf->ptntype && pleaf->match) + c2_error("Integer/boolean pattern cannot have operator qualifiers."); + + if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) + c2_error("Integer/boolean pattern cannot have flags."); + + if (C2_L_PTSTRING == pleaf->ptntype && + (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op || + C2_L_OLTEQ == pleaf->op)) + c2_error("String pattern cannot have an arithmetic operator."); + + return offset; + +fail: + return -1; +} + +/** + * Parse a condition with legacy syntax. + */ +static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { + if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' || + !strchr(pattern + offset + 2, ':')) { + c2_error("Legacy parser: Invalid format."); + } + + // Allocate memory for new leaf + auto pleaf = cmalloc(c2_l_t); + presult->isbranch = false; + presult->l = pleaf; + memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); + pleaf->type = C2_L_TSTRING; + pleaf->op = C2_L_OEQ; + pleaf->ptntype = C2_L_PTSTRING; + + // Determine the pattern target +#define TGTFILL(pdefid) \ + (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \ + pleaf->format = C2_PREDEFS[pdefid].format) + switch (pattern[offset]) { + case 'n': TGTFILL(C2_L_PNAME); break; + case 'i': TGTFILL(C2_L_PCLASSI); break; + case 'g': TGTFILL(C2_L_PCLASSG); break; + case 'r': TGTFILL(C2_L_PROLE); break; + default: c2_error("Target \"%c\" invalid.\n", pattern[offset]); + } +#undef TGTFILL + + offset += 2; + + // Determine the match type + switch (pattern[offset]) { + case 'e': pleaf->match = C2_L_MEXACT; break; + case 'a': pleaf->match = C2_L_MCONTAINS; break; + case 's': pleaf->match = C2_L_MSTART; break; + case 'w': pleaf->match = C2_L_MWILDCARD; break; + case 'p': pleaf->match = C2_L_MPCRE; break; + default: c2_error("Type \"%c\" invalid.\n", pattern[offset]); + } + ++offset; + + // Determine the pattern flags + while (':' != pattern[offset]) { + switch (pattern[offset]) { + case 'i': pleaf->match_ignorecase = true; break; + default: c2_error("Flag \"%c\" invalid.", pattern[offset]); + } + ++offset; + } + ++offset; + + // Copy the pattern + pleaf->ptnstr = strdup(pattern + offset); + + return offset; + +fail: + return -1; +} + +#undef c2_error + +/** + * Do postprocessing on a condition leaf. + */ +static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { + // Give a pattern type to a leaf with exists operator, if needed + if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) { + pleaf->ptntype = (C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING : C2_L_PTINT); + } + + // Get target atom if it's not a predefined one + if (pleaf->predef == C2_L_PUNDEFINED) { + pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt); + if (!pleaf->tgtatom) { + log_error("Failed to get atom for target \"%s\".", pleaf->tgt); + return false; + } + } + + // Insert target Atom into atom track list + if (pleaf->tgtatom) { + bool found = false; + for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) { + if (pleaf->tgtatom == platom->atom) { + found = true; + break; + } + } + if (!found) { + auto pnew = cmalloc(latom_t); + pnew->next = ps->track_atom_lst; + pnew->atom = pleaf->tgtatom; + ps->track_atom_lst = pnew; + } + } + + // Warn about lower case characters in target name + if (pleaf->predef == C2_L_PUNDEFINED) { + for (const char *pc = pleaf->tgt; *pc; ++pc) { + if (islower((unsigned char)*pc)) { + log_warn("Lowercase character in target name \"%s\".", + pleaf->tgt); + break; + } + } + } + + // PCRE patterns + if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) { +#ifdef CONFIG_REGEX_PCRE + const char *error = NULL; + int erroffset = 0; + int options = 0; + + // Ignore case flag + if (pleaf->match_ignorecase) + options |= PCRE_CASELESS; + + // Compile PCRE expression + pleaf->regex_pcre = + pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL); + if (!pleaf->regex_pcre) { + log_error("Pattern \"%s\": PCRE regular expression parsing " + "failed on " + "offset %d: %s", + pleaf->ptnstr, erroffset, error); + return false; + } +#ifdef CONFIG_REGEX_PCRE_JIT + pleaf->regex_pcre_extra = + pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); + if (!pleaf->regex_pcre_extra) { + printf("Pattern \"%s\": PCRE regular expression study failed: %s", + pleaf->ptnstr, error); + } +#endif + + // Free the target string + // free(pleaf->tgt); + // pleaf->tgt = NULL; +#else + log_error("PCRE regular expression support not compiled in."); + return false; +#endif + } + + return true; +} + +static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) { + if (!node.isbranch) { + return c2_l_postprocess(ps, node.l); + } + if (!c2_tree_postprocess(ps, node.b->opr1)) + return false; + return c2_tree_postprocess(ps, node.b->opr2); +} + +bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) { + c2_lptr_t *head = list; + while (head) { + if (!c2_tree_postprocess(ps, head->ptr)) + return false; + head = head->next; + } + return true; +} +/** + * Free a condition tree. + */ +static void c2_free(c2_ptr_t p) { + // For a branch element + if (p.isbranch) { + c2_b_t *const pbranch = p.b; + + if (!pbranch) + return; + + c2_free(pbranch->opr1); + c2_free(pbranch->opr2); + free(pbranch); + } + // For a leaf element + else { + c2_l_t *const pleaf = p.l; + + if (!pleaf) + return; + + free(pleaf->tgt); + free(pleaf->ptnstr); +#ifdef CONFIG_REGEX_PCRE + pcre_free(pleaf->regex_pcre); + LPCRE_FREE_STUDY(pleaf->regex_pcre_extra); +#endif + free(pleaf); + } +} + +/** + * Free a condition tree in c2_lptr_t. + */ +c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) { + if (!lp) + return NULL; + + c2_lptr_t *pnext = lp->next; + c2_free(lp->ptr); + free(lp); + + return pnext; +} + +/** + * Get a string representation of a rule target. + */ +static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { + if (pleaf->predef != C2_L_PUNDEFINED) { + return C2_PREDEFS[pleaf->predef].name; + } else { + return pleaf->tgt; + } +} + +/** + * Get a string representation of a target. + */ +static const char *c2h_dump_str_type(const c2_l_t *pleaf) { + switch (pleaf->type) { + case C2_L_TWINDOW: return "w"; + case C2_L_TDRAWABLE: return "d"; + case C2_L_TCARDINAL: return "c"; + case C2_L_TSTRING: return "s"; + case C2_L_TATOM: return "a"; + case C2_L_TUNDEFINED: break; + } + + return NULL; +} + +/** + * Dump a condition tree. + */ +static void c2_dump(c2_ptr_t p) { + // For a branch + if (p.isbranch) { + const c2_b_t *const pbranch = p.b; + + if (!pbranch) { + return; + } + + if (pbranch->neg) { + putchar('!'); + } + + printf("("); + c2_dump(pbranch->opr1); + + switch (pbranch->op) { + case C2_B_OAND: printf(" && "); break; + case C2_B_OOR: printf(" || "); break; + case C2_B_OXOR: printf(" XOR "); break; + default: assert(0); break; + } + + c2_dump(pbranch->opr2); + printf(") "); + } + // For a leaf + else { + const c2_l_t *const pleaf = p.l; + + if (!pleaf) { + return; + } + + if (C2_L_OEXISTS == pleaf->op && pleaf->neg) { + putchar('!'); + } + + // Print target name, type, and format + { + printf("%s", c2h_dump_str_tgt(pleaf)); + if (pleaf->tgt_onframe) { + putchar('@'); + } + if (pleaf->predef == C2_L_PUNDEFINED) { + if (pleaf->index < 0) { + printf("[*]"); + } else { + printf("[%d]", pleaf->index); + } + } + printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf)); + } + + // Print operator + putchar(' '); + + if (C2_L_OEXISTS != pleaf->op && pleaf->neg) { + putchar('!'); + } + + switch (pleaf->match) { + case C2_L_MEXACT: break; + case C2_L_MCONTAINS: putchar('*'); break; + case C2_L_MSTART: putchar('^'); break; + case C2_L_MPCRE: putchar('~'); break; + case C2_L_MWILDCARD: putchar('%'); break; + } + + if (pleaf->match_ignorecase) { + putchar('?'); + } + + switch (pleaf->op) { + case C2_L_OEXISTS: break; + case C2_L_OEQ: fputs("=", stdout); break; + case C2_L_OGT: fputs(">", stdout); break; + case C2_L_OGTEQ: fputs(">=", stdout); break; + case C2_L_OLT: fputs("<", stdout); break; + case C2_L_OLTEQ: fputs("<=", stdout); break; + } + + if (C2_L_OEXISTS == pleaf->op) { + return; + } + + // Print pattern + putchar(' '); + switch (pleaf->ptntype) { + case C2_L_PTINT: printf("%ld", pleaf->ptnint); break; + case C2_L_PTSTRING: + // TODO(yshui) Escape string before printing out? + printf("\"%s\"", pleaf->ptnstr); + break; + default: assert(0); break; + } + } +} + +/** + * Get the type atom of a condition. + */ +static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { + switch (pleaf->type) { + case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL; + case C2_L_TWINDOW: return XCB_ATOM_WINDOW; + case C2_L_TSTRING: return XCB_ATOM_STRING; + case C2_L_TATOM: return XCB_ATOM_ATOM; + case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE; + default: assert(0); break; + } + unreachable; +} + +/** + * Match a window against a single leaf window condition. + * + * For internal use. + */ +static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w, + const c2_l_t *pleaf, bool *pres, bool *perr) { + assert(pleaf); + + const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id); + + // Return if wid is missing + if (pleaf->predef == C2_L_PUNDEFINED && !wid) { + return; + } + + const int idx = (pleaf->index < 0 ? 0 : pleaf->index); + + switch (pleaf->ptntype) { + // Deal with integer patterns + case C2_L_PTINT: { + long *targets = NULL; + long *targets_free = NULL; + size_t ntargets = 0; + + // Get the value + // A predefined target + long predef_target = 0; + if (pleaf->predef != C2_L_PUNDEFINED) { + *perr = false; + switch (pleaf->predef) { + case C2_L_PID: predef_target = wid; break; + case C2_L_PX: predef_target = w->g.x; break; + case C2_L_PY: predef_target = w->g.y; break; + case C2_L_PX2: predef_target = w->g.x + w->widthb; break; + case C2_L_PY2: predef_target = w->g.y + w->heightb; break; + case C2_L_PWIDTH: predef_target = w->g.width; break; + case C2_L_PHEIGHT: predef_target = w->g.height; break; + case C2_L_PWIDTHB: predef_target = w->widthb; break; + case C2_L_PHEIGHTB: predef_target = w->heightb; break; + case C2_L_PBDW: predef_target = w->g.border_width; break; + case C2_L_PFULLSCREEN: + predef_target = win_is_fullscreen(ps, w); + break; + case C2_L_POVREDIR: predef_target = w->a.override_redirect; break; + case C2_L_PARGB: predef_target = win_has_alpha(w); break; + case C2_L_PFOCUSED: + predef_target = win_is_focused_raw(ps, w); + break; + case C2_L_PWMWIN: predef_target = w->wmwin; break; + case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break; + case C2_L_PROUNDED: predef_target = w->rounded_corners; break; + case C2_L_PCLIENT: predef_target = w->client_win; break; + case C2_L_PLEADER: predef_target = w->leader; break; + default: + *perr = true; + assert(0); + break; + } + ntargets = 1; + targets = &predef_target; + } + // A raw window property + else { + int word_count = 1; + if (pleaf->index < 0) { + // Get length of property in 32-bit multiples + auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); + word_count = to_int_checked((prop_info.length + 4 - 1) / 4); + } + winprop_t prop = x_get_prop_with_offset( + ps->c, wid, pleaf->tgtatom, idx, word_count, + c2_get_atom_type(pleaf), pleaf->format); + + ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); + if (ntargets > 0) { + targets = targets_free = ccalloc(ntargets, long); + *perr = false; + for (size_t i = 0; i < ntargets; ++i) { + targets[i] = winprop_get_int(prop, i); + } + } + free_winprop(&prop); + } + + if (*perr) { + goto fail_int; + } + + // Do comparison + bool res = false; + for (size_t i = 0; i < ntargets; ++i) { + long tgt = targets[i]; + switch (pleaf->op) { + case C2_L_OEXISTS: + res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); + break; + case C2_L_OEQ: res = (tgt == pleaf->ptnint); break; + case C2_L_OGT: res = (tgt > pleaf->ptnint); break; + case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break; + case C2_L_OLT: res = (tgt < pleaf->ptnint); break; + case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break; + default: *perr = true; assert(0); + } + if (res) { + break; + } + } + *pres = res; + + fail_int: + // Free property values after usage, if necessary + if (targets_free) { + free(targets_free); + } + } break; + // String patterns + case C2_L_PTSTRING: { + const char **targets = NULL; + const char **targets_free = NULL; + const char **targets_free_inner = NULL; + size_t ntargets = 0; + + // A predefined target + const char *predef_target = NULL; + if (pleaf->predef != C2_L_PUNDEFINED) { + switch (pleaf->predef) { + case C2_L_PWINDOWTYPE: + predef_target = WINTYPES[w->window_type]; + break; + case C2_L_PNAME: predef_target = w->name; break; + case C2_L_PCLASSG: predef_target = w->class_general; break; + case C2_L_PCLASSI: predef_target = w->class_instance; break; + case C2_L_PROLE: predef_target = w->role; break; + default: assert(0); break; + } + ntargets = 1; + targets = &predef_target; + } + // An atom type property, convert it to string + else if (pleaf->type == C2_L_TATOM) { + int word_count = 1; + if (pleaf->index < 0) { + // Get length of property in 32-bit multiples + auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); + word_count = to_int_checked((prop_info.length + 4 - 1) / 4); + } + winprop_t prop = x_get_prop_with_offset( + ps->c, wid, pleaf->tgtatom, idx, word_count, + c2_get_atom_type(pleaf), pleaf->format); + + ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); + targets = targets_free = (const char **)ccalloc(2 * ntargets, char *); + targets_free_inner = targets + ntargets; + + for (size_t i = 0; i < ntargets; ++i) { + xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i); + if (atom) { + xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( + ps->c, xcb_get_atom_name(ps->c, atom), NULL); + if (reply) { + targets[i] = targets_free_inner[i] = strndup( + xcb_get_atom_name_name(reply), + (size_t)xcb_get_atom_name_name_length(reply)); + free(reply); + } + } + } + free_winprop(&prop); + } + // Not an atom type, just fetch the string list + else { + char **strlst = NULL; + int nstr = 0; + if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) { + if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) { + ntargets = to_u32_checked(nstr); + targets = (const char **)strlst; + } else if (nstr > idx) { + ntargets = 1; + targets = (const char **)strlst + idx; + } + } + if (strlst) { + targets_free = (const char **)strlst; + } + } + + if (ntargets == 0) { + goto fail_str; + } + for (size_t i = 0; i < ntargets; ++i) { + if (!targets[i]) { + goto fail_str; + } + } + *perr = false; + + // Actual matching + bool res = false; + for (size_t i = 0; i < ntargets; ++i) { + const char *tgt = targets[i]; + switch (pleaf->op) { + case C2_L_OEXISTS: res = true; break; + case C2_L_OEQ: + switch (pleaf->match) { + case C2_L_MEXACT: + if (pleaf->match_ignorecase) { + res = !strcasecmp(tgt, pleaf->ptnstr); + } else { + res = !strcmp(tgt, pleaf->ptnstr); + } + break; + case C2_L_MCONTAINS: + if (pleaf->match_ignorecase) { + res = strcasestr(tgt, pleaf->ptnstr); + } else { + res = strstr(tgt, pleaf->ptnstr); + } + break; + case C2_L_MSTART: + if (pleaf->match_ignorecase) { + res = !strncasecmp(tgt, pleaf->ptnstr, + strlen(pleaf->ptnstr)); + } else { + res = !strncmp(tgt, pleaf->ptnstr, + strlen(pleaf->ptnstr)); + } + break; + case C2_L_MWILDCARD: { + int flags = 0; + if (pleaf->match_ignorecase) { + flags |= FNM_CASEFOLD; + } + res = !fnmatch(pleaf->ptnstr, tgt, flags); + } break; + case C2_L_MPCRE: +#ifdef CONFIG_REGEX_PCRE + assert(strlen(tgt) <= INT_MAX); + res = (pcre_exec(pleaf->regex_pcre, + pleaf->regex_pcre_extra, tgt, + (int)strlen(tgt), 0, 0, NULL, 0) >= 0); +#else + assert(0); +#endif + break; + } + break; + default: *perr = true; assert(0); + } + if (res) { + break; + } + } + *pres = res; + + fail_str: + // Free the string after usage, if necessary + if (targets_free_inner) { + for (size_t i = 0; i < ntargets; ++i) { + if (targets_free_inner[i]) { + free((void *)targets_free_inner[i]); + } + } + } + // Free property values after usage, if necessary + if (targets_free) { + free(targets_free); + } + } break; + default: assert(0); break; + } +} + +/** + * Match a window against a single window condition. + * + * @return true if matched, false otherwise. + */ +static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) { + bool result = false; + bool error = true; + + // Handle a branch + if (cond.isbranch) { + const c2_b_t *pb = cond.b; + + if (!pb) + return false; + + error = false; + + switch (pb->op) { + case C2_B_OAND: + result = (c2_match_once(ps, w, pb->opr1) && + c2_match_once(ps, w, pb->opr2)); + break; + case C2_B_OOR: + result = (c2_match_once(ps, w, pb->opr1) || + c2_match_once(ps, w, pb->opr2)); + break; + case C2_B_OXOR: + result = (c2_match_once(ps, w, pb->opr1) != + c2_match_once(ps, w, pb->opr2)); + break; + default: error = true; assert(0); + } + +#ifdef DEBUG_WINMATCH + log_trace("(%#010x): branch: result = %d, pattern = ", w->base.id, result); + c2_dump(cond); + putchar('\n'); +#endif + } + // Handle a leaf + else { + const c2_l_t *pleaf = cond.l; + + if (!pleaf) + return false; + + c2_match_once_leaf(ps, w, pleaf, &result, &error); + + // For EXISTS operator, no errors are fatal + if (C2_L_OEXISTS == pleaf->op && error) { + result = false; + error = false; + } + +#ifdef DEBUG_WINMATCH + log_trace("(%#010x): leaf: result = %d, error = %d, " + "client = %#010x, pattern = ", + w->base.id, result, error, w->client_win); + c2_dump(cond); + putchar('\n'); +#endif + } + + // Postprocess the result + if (error) + result = false; + + if (cond.isbranch ? cond.b->neg : cond.l->neg) + result = !result; + + return result; +} + +/** + * Match a window against a condition linked list. + * + * @param cache a place to cache the last matched condition + * @param pdata a place to return the data + * @return true if matched, false otherwise. + */ +bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, + void **pdata) { + assert(ps->server_grabbed); + // Then go through the whole linked list + for (; condlst; condlst = condlst->next) { + if (c2_match_once(ps, w, condlst->ptr)) { + if (pdata) + *pdata = condlst->data; + return true; + } + } + + return false; +} |