1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-11 13:51:02 -05:00
picom/src/c2.c
Yuxuan Shui c23dbe61f7
c2: add a libfuzzer fuzzer for c2
Add fix some simple bugs discovered by the fuzzer. Two cases of missing
input validation (we have assertions, but assertion failures are not
user friendly). And one case of jump over variable initialization with
goto.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-17 16:48:58 +00:00

1938 lines
50 KiB
C

// 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>
#include <uthash.h>
// libpcre
#ifdef CONFIG_REGEX_PCRE
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#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 "test.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;
struct c2_tracked_property_key {
xcb_atom_t property;
bool is_on_frame;
char padding[3];
};
static_assert(sizeof(struct c2_tracked_property_key) == 8, "Padding bytes in "
"c2_tracked_property_key");
struct c2_tracked_property {
UT_hash_handle hh;
struct c2_tracked_property_key key;
bool is_string;
};
struct c2_state {
struct c2_tracked_property *tracked_properties;
struct atom *atoms;
};
/// 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 = 0,
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;
enum {
C2_L_PTUNDEFINED,
C2_L_PTSTRING,
C2_L_PTINT,
} ptntype;
char *ptnstr;
long ptnint;
#ifdef CONFIG_REGEX_PCRE
pcre2_code *regex_pcre;
pcre2_match_data *regex_pcre_match;
#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, \
.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;
} c2_predef_t;
// Predefined targets.
static const c2_predef_t C2_PREDEFS[] = {
[C2_L_PID] = {"id", C2_L_TCARDINAL},
[C2_L_PX] = {"x", C2_L_TCARDINAL},
[C2_L_PY] = {"y", C2_L_TCARDINAL},
[C2_L_PX2] = {"x2", C2_L_TCARDINAL},
[C2_L_PY2] = {"y2", C2_L_TCARDINAL},
[C2_L_PWIDTH] = {"width", C2_L_TCARDINAL},
[C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL},
[C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL},
[C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL},
[C2_L_PBDW] = {"border_width", C2_L_TCARDINAL},
[C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL},
[C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL},
[C2_L_PARGB] = {"argb", C2_L_TCARDINAL},
[C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL},
[C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL},
[C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL},
[C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL},
[C2_L_PCLIENT] = {"client", C2_L_TWINDOW},
[C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING},
[C2_L_PLEADER] = {"leader", C2_L_TWINDOW},
[C2_L_PNAME] = {"name", C2_L_TSTRING},
[C2_L_PCLASSG] = {"class_g", C2_L_TSTRING},
[C2_L_PCLASSI] = {"class_i", C2_L_TSTRING},
[C2_L_PROLE] = {"role", C2_L_TSTRING},
};
/**
* 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;
}
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);
static size_t c2_condition_to_str(c2_ptr_t p, char *output, size_t len);
static const char *c2_condition_to_str2(c2_ptr_t ptr);
/**
* 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 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;
}
}
TEST_CASE(c2_parse) {
log_init_tls();
char str[1024];
c2_lptr_t *cond = c2_parse(NULL, "name = \"xterm\"", NULL);
TEST_NOTEQUAL(cond, NULL);
TEST_TRUE(!cond->ptr.isbranch);
TEST_NOTEQUAL(cond->ptr.l, NULL);
TEST_EQUAL(cond->ptr.l->op, C2_L_OEQ);
TEST_EQUAL(cond->ptr.l->ptntype, C2_L_PTSTRING);
TEST_STREQUAL(cond->ptr.l->ptnstr, "xterm");
size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "name:s = \"xterm\"", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "_GTK_FRAME_EXTENTS@:c", NULL);
TEST_NOTEQUAL(cond, NULL);
TEST_TRUE(!cond->ptr.isbranch);
TEST_NOTEQUAL(cond->ptr.l, NULL);
TEST_EQUAL(cond->ptr.l->op, C2_L_OEXISTS);
TEST_EQUAL(cond->ptr.l->match, C2_L_MEXACT);
TEST_EQUAL(cond->ptr.l->predef, C2_L_PUNDEFINED);
TEST_EQUAL(cond->ptr.l->type, C2_L_TCARDINAL);
TEST_TRUE(cond->ptr.l->tgt_onframe);
TEST_NOTEQUAL(cond->ptr.l->tgt, NULL);
TEST_STREQUAL(cond->ptr.l->tgt, "_GTK_FRAME_EXTENTS");
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "_GTK_FRAME_EXTENTS@[0]:c", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "name = \"xterm\" && class_g *= \"XTerm\"", NULL);
TEST_NOTEQUAL(cond, NULL);
TEST_TRUE(cond->ptr.isbranch);
TEST_NOTEQUAL(cond->ptr.b, NULL);
TEST_EQUAL(cond->ptr.b->op, C2_B_OAND);
TEST_NOTEQUAL(cond->ptr.b->opr1.l, NULL);
TEST_NOTEQUAL(cond->ptr.b->opr2.l, NULL);
TEST_EQUAL(cond->ptr.b->opr1.l->op, C2_L_OEQ);
TEST_EQUAL(cond->ptr.b->opr1.l->match, C2_L_MEXACT);
TEST_EQUAL(cond->ptr.b->opr1.l->ptntype, C2_L_PTSTRING);
TEST_EQUAL(cond->ptr.b->opr2.l->op, C2_L_OEQ);
TEST_EQUAL(cond->ptr.b->opr2.l->match, C2_L_MCONTAINS);
TEST_EQUAL(cond->ptr.b->opr2.l->ptntype, C2_L_PTSTRING);
TEST_STREQUAL(cond->ptr.b->opr1.l->tgt, "name");
TEST_EQUAL(cond->ptr.b->opr1.l->predef, C2_L_PNAME);
TEST_STREQUAL(cond->ptr.b->opr2.l->tgt, "class_g");
TEST_EQUAL(cond->ptr.b->opr2.l->predef, C2_L_PCLASSG);
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "(name:s = \"xterm\" && class_g:s *= \"XTerm\")", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "_NET_WM_STATE[1]:32a *='_NET_WM_STATE_HIDDEN'", NULL);
TEST_EQUAL(cond->ptr.l->index, 1);
TEST_EQUAL(cond->ptr.l->type, C2_L_TATOM);
TEST_STREQUAL(cond->ptr.l->tgt, "_NET_WM_STATE");
TEST_STREQUAL(cond->ptr.l->ptnstr, "_NET_WM_STATE_HIDDEN");
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "_NET_WM_STATE[1]:a *= \"_NET_WM_STATE_HIDDEN\"", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "_NET_WM_STATE[*]:32a*='_NET_WM_STATE_HIDDEN'", NULL);
TEST_EQUAL(cond->ptr.l->index, -1);
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "_NET_WM_STATE[*]:a *= \"_NET_WM_STATE_HIDDEN\"", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "!class_i:0s", NULL);
TEST_NOTEQUAL(cond, NULL);
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "!class_i:s", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "_NET_WM_STATE = '_NET_WM_STATE_HIDDEN'", NULL);
TEST_EQUAL(cond, NULL);
cond = c2_parse(NULL, "1A:\n1111111111111ar1", NULL);
TEST_EQUAL(cond, NULL);
cond = c2_parse(NULL, "N [4444444444444: \n", NULL);
TEST_EQUAL(cond, NULL);
cond = c2_parse(NULL, " x:a=\"b\377\\xCCCCC", NULL);
TEST_EQUAL(cond, NULL);
cond = c2_parse(NULL, "!!!!!!!((((((!(((((,", NULL);
TEST_EQUAL(cond, NULL);
const char *rule = "(((role:s = \"\\\\tg^\\n\\n[\\t\" && role:s ~?= \"\") && "
"role:s ~?= \"\\n\\n\\n\\b\\n^\\n*0bon\") && role:s ~?= "
"\"\\n\\n\\x8a\\b\\n^\\n*0\\n[\\n[\\n\\n\\b\\n\")";
cond = c2_parse(NULL, rule, NULL);
TEST_NOTEQUAL(cond, NULL);
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, rule, len);
c2_list_free(&cond, NULL);
}
#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) {
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;
// Check for recursion levels
if (level > C2_MAX_LEVELS) {
c2_error("Exceeded maximum recursion levels.");
}
// 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] || '.' == 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;
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.");
}
if (index > INT_MAX) {
c2_error("Index %ld too large.", index);
}
pleaf->index = (int)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();
// Write format
if (hasformat) {
log_warn("Format \"%ld\" specified on target \"%s\" will be "
"ignored.",
format, pleaf->tgt);
if (format && format != 8 && format != 16 && format != 32) {
c2_error("Invalid format %ld.", format);
}
}
}
if (!pleaf->type) {
c2_error("Target type cannot be determined.");
}
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();
}
if (raw == true) {
log_warn("Raw string patterns has been deprecated. pos %d", offset);
}
// 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': {
scoped_charp tstr = strndup(pattern + offset + 1, 2);
char *pstr = NULL;
long val = strtol(
tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
if (pstr != &tstr[2] || val <= 0) {
c2_error("Invalid octal/hex escape "
"sequence.");
}
if (val > 255) {
c2_error("Octal/hex escape sequence out "
"of ASCII range.");
}
if (val > 127) {
// Manual sign extension
val -= 256;
}
*(ptptnstr++) = (char)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;
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(struct c2_state *state, xcb_connection_t *c, 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(state->atoms, pleaf->tgt, c);
if (!pleaf->tgtatom) {
log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
return false;
}
}
// Insert target atom into tracked property name list
if (pleaf->tgtatom) {
struct c2_tracked_property *property;
struct c2_tracked_property_key key = {
.property = pleaf->tgtatom,
.is_on_frame = pleaf->tgt_onframe,
};
HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), property);
if (property == NULL) {
property = cmalloc(struct c2_tracked_property);
property->key = key;
HASH_ADD_KEYPTR(hh, state->tracked_properties, &property->key,
sizeof(property->key), property);
property->is_string = pleaf->type == C2_L_TSTRING;
} else {
if (property->is_string != (pleaf->type == C2_L_TSTRING)) {
log_error("Type mismatch for property \"%s\", %s a "
"string, now %s. Offending rule is: %s, it "
"will be disabled.",
pleaf->tgt, property->is_string ? "was" : "wasn't",
pleaf->type == C2_L_TSTRING ? "is" : "isn't",
c2_condition_to_str2(
(c2_ptr_t){.isbranch = false, .l = pleaf}));
return false;
}
}
}
// 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
int errorcode = 0;
PCRE2_SIZE erroffset = 0;
unsigned int options = 0;
// Ignore case flag
if (pleaf->match_ignorecase) {
options |= PCRE2_CASELESS;
}
// Compile PCRE expression
pleaf->regex_pcre =
pcre2_compile((PCRE2_SPTR)pleaf->ptnstr, PCRE2_ZERO_TERMINATED,
options, &errorcode, &erroffset, NULL);
if (pleaf->regex_pcre == NULL) {
PCRE2_UCHAR buffer[256];
pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
log_error("Pattern \"%s\": PCRE regular expression "
"parsing "
"failed on "
"offset %zu: %s",
pleaf->ptnstr, erroffset, buffer);
return false;
}
pleaf->regex_pcre_match =
pcre2_match_data_create_from_pattern(pleaf->regex_pcre, NULL);
#else
log_error("PCRE regular expression support not compiled in.");
return false;
#endif
}
return true;
}
static bool c2_tree_postprocess(struct c2_state *state, xcb_connection_t *c, c2_ptr_t node) {
if (!node.isbranch) {
return c2_l_postprocess(state, c, node.l);
}
return c2_tree_postprocess(state, c, node.b->opr1) &&
c2_tree_postprocess(state, c, node.b->opr2);
}
bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, c2_lptr_t *list) {
c2_lptr_t *head = list;
while (head) {
if (!c2_tree_postprocess(state, c, 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
pcre2_code_free(pleaf->regex_pcre);
pcre2_match_data_free(pleaf->regex_pcre_match);
#endif
free(pleaf);
}
}
/**
* Free a condition tree in c2_lptr_t.
*/
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
if (!lp) {
return NULL;
}
c2_lptr_t *pnext = lp->next;
if (f) {
f(lp->data);
}
lp->data = NULL;
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;
}
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 to string. Return the number of characters that
* would have been written if the buffer had been large enough, excluding
* the null terminator.
* null terminator will not be written to the output.
*/
static size_t c2_condition_to_str(c2_ptr_t p, char *output, size_t len) {
#define push_char(c) \
if (offset < len) \
output[offset] = (c); \
offset++
#define push_str(str) \
do { \
if (offset < len) { \
size_t slen = strlen(str); \
if (offset + slen > len) \
slen = len - offset; \
memcpy(output + offset, str, slen); \
} \
offset += strlen(str); \
} while (false)
size_t offset = 0;
if (p.isbranch) {
// Branch, i.e. logical operators &&, ||, XOR
const c2_b_t *const pbranch = p.b;
if (!pbranch) {
return 0;
}
if (pbranch->neg) {
push_char('!');
}
push_char('(');
if (len > offset) {
offset += c2_condition_to_str(pbranch->opr1, output + offset,
len - offset);
} else {
offset += c2_condition_to_str(pbranch->opr1, NULL, 0);
}
switch (pbranch->op) {
case C2_B_OAND: push_str(" && "); break;
case C2_B_OOR: push_str(" || "); break;
case C2_B_OXOR: push_str(" XOR "); break;
default: assert(0); break;
}
if (len > offset) {
offset += c2_condition_to_str(pbranch->opr2, output + offset,
len - offset);
} else {
offset += c2_condition_to_str(pbranch->opr2, NULL, 0);
}
push_str(")");
} else {
// Leaf node
const c2_l_t *const pleaf = p.l;
char number[128];
if (!pleaf) {
return 0;
}
if (C2_L_OEXISTS == pleaf->op && pleaf->neg) {
push_char('!');
}
// Print target name, type, and format
const char *target_str = c2h_dump_str_tgt(pleaf);
push_str(target_str);
if (pleaf->tgt_onframe) {
push_char('@');
}
if (pleaf->predef == C2_L_PUNDEFINED) {
if (pleaf->index < 0) {
push_str("[*]");
} else {
sprintf(number, "[%d]", pleaf->index);
push_str(number);
}
}
const char *type_str = c2h_dump_str_type(pleaf);
push_char(':');
push_str(type_str);
if (C2_L_OEXISTS == pleaf->op) {
return offset;
}
// Print operator
push_char(' ');
if (C2_L_OEXISTS != pleaf->op && pleaf->neg) {
push_char('!');
}
switch (pleaf->match) {
case C2_L_MEXACT: break;
case C2_L_MCONTAINS: push_char('*'); break;
case C2_L_MSTART: push_char('^'); break;
case C2_L_MPCRE: push_char('~'); break;
case C2_L_MWILDCARD: push_char('%'); break;
}
if (pleaf->match_ignorecase) {
push_char('?');
}
switch (pleaf->op) {
case C2_L_OEXISTS: break;
case C2_L_OEQ: push_str("="); break;
case C2_L_OGT: push_str(">"); break;
case C2_L_OGTEQ: push_str(">="); break;
case C2_L_OLT: push_str("<"); break;
case C2_L_OLTEQ: push_str("<="); break;
}
// Print pattern
push_char(' ');
switch (pleaf->ptntype) {
case C2_L_PTINT:
sprintf(number, "%ld", pleaf->ptnint);
push_str(number);
break;
case C2_L_PTSTRING:
// TODO(yshui) Escape string before printing out?
push_char('"');
for (int i = 0; pleaf->ptnstr[i]; i++) {
switch (pleaf->ptnstr[i]) {
case '\\': push_str("\\\\"); break;
case '"': push_str("\\\""); break;
case '\a': push_str("\\a"); break;
case '\b': push_str("\\b"); break;
case '\f': push_str("\\f"); break;
case '\r': push_str("\\r"); break;
case '\v': push_str("\\v"); break;
case '\t': push_str("\\t"); break;
case '\n': push_str("\\n"); break;
default:
if (isprint(pleaf->ptnstr[i])) {
push_char(pleaf->ptnstr[i]);
} else {
sprintf(number, "\\x%02x",
(unsigned char)pleaf->ptnstr[i]);
push_str(number);
}
break;
}
}
push_char('"');
break;
default: assert(0); break;
}
}
#undef push_char
#undef push_str
return offset;
}
/// Wrapper of c2_condition_to_str which uses an internal static buffer, and
/// returns a nul terminated string. The returned string is only valid until the
/// next call to this function, and should not be freed.
static const char *c2_condition_to_str2(c2_ptr_t ptr) {
static thread_local char buf[4096];
auto len = c2_condition_to_str(ptr, buf, sizeof(buf));
if (len >= sizeof(buf)) {
// Resulting string is too long, clobber the last character with a nul.
buf[sizeof(buf) - 1] = '\0';
} else {
buf[len] = '\0';
}
return buf;
}
/**
* 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 long *targets = NULL;
long long *targets_free = NULL;
size_t ntargets = 0;
// Get the value
// A predefined target
long 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 = w->is_fullscreen; 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(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), 0);
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
if (ntargets > 0) {
targets = targets_free = ccalloc(ntargets, long 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 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), 0);
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.c, xcb_get_atom_name(ps->c.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->c, ps->atoms, 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);
assert(pleaf->regex_pcre);
res = (pcre2_match(pleaf->regex_pcre, (PCRE2_SPTR)tgt,
strlen(tgt), 0, 0,
pleaf->regex_pcre_match, NULL) > 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);
}
log_debug("(%#010x): branch: result = %d, pattern = %s", w->base.id,
result, c2_condition_to_str2(cond));
} else {
// A leaf
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;
}
log_debug("(%#010x): leaf: result = %d, client = %#010x, "
"pattern = %s",
w->base.id, result, w->client_win, c2_condition_to_str2(cond));
}
// 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;
}
/// Iterate over all conditions in a condition linked list. Call the callback for each of
/// the conditions. If the callback returns true, the iteration stops early.
///
/// Returns whether the iteration was stopped early.
bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) {
for (auto i = condlist; i; i = i->next) {
if (cb(i, data)) {
return true;
}
}
return false;
}
/// Return user data stored in a condition.
void *c2_list_get_data(const c2_lptr_t *condlist) {
return condlist->data;
}
struct c2_state *c2_state_new(struct atom *atoms) {
auto ret = ccalloc(1, struct c2_state);
ret->atoms = atoms;
return ret;
}
void c2_state_free(struct c2_state *state) {
struct c2_tracked_property *property, *tmp;
HASH_ITER(hh, state->tracked_properties, property, tmp) {
HASH_DEL(state->tracked_properties, property);
free(property);
}
free(state);
}
bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property) {
struct c2_tracked_property *p;
struct c2_tracked_property_key key = {
.property = property,
.is_on_frame = true,
};
HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p);
if (p != NULL) {
return true;
}
key.is_on_frame = false;
HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p);
return p != NULL;
}