aboutsummaryrefslogtreecommitdiff
path: root/src/c2.c
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
committerallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
commita93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch)
tree77f8152222655657472a70e0bfa413a0495dd555 /src/c2.c
parentreset (diff)
downloadcompfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz
compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip
Fixed broken files/code and other errors
Diffstat (limited to 'src/c2.c')
-rw-r--r--src/c2.c1674
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;
+}