picom/src/c2.c

1916 lines
50 KiB
C
Raw Normal View History

// 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 <assert.h>
#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, }
2018-08-22 14:26:08 +00:00
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,
C2_L_OFALSE, // Always false, for annulled conditions
} 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
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, \
.format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \
}
2018-08-22 14:26:08 +00:00
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.
2018-08-22 14:26:08 +00:00
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;
}
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);
/**
* 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:0s = \"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]:0c", 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:0s = \"xterm\" && class_g:0s *= \"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->format, 32);
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]:32a *= \"_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[*]:32a *= \"_NET_WM_STATE_HIDDEN\"", len);
c2_list_free(&cond, NULL);
cond = c2_parse(NULL, "_NET_WM_STATE = '_NET_WM_STATE_HIDDEN'", NULL);
TEST_EQUAL(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) {
// 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;
2021-10-10 13:27:29 +00:00
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;
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();
}
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.");
}
*(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(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)) {
char buf[256];
size_t len = c2_condition_to_str(
(c2_ptr_t){.isbranch = false, .l = pleaf}, buf,
sizeof(buf));
len = min2(sizeof(buf), len);
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",
(int)len, buf);
pleaf->op = C2_L_OFALSE;
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('!');
}
if (pleaf->op == C2_L_OFALSE) {
push_str("(annulled)");
return offset;
}
// 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(':');
sprintf(number, "%d", pleaf->format);
push_str(number);
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_OFALSE: unreachable();
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('"');
push_str(pleaf->ptnstr);
push_char('"');
break;
default: assert(0); break;
}
}
#undef push_char
#undef push_str
return offset;
}
/**
* 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();
}
static bool
c2_match_once_leaf_int(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) {
long long *targets = NULL;
long long *targets_free = NULL;
size_t ntargets = 0;
const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id);
// Get the value
// A predefined target
long long predef_target = 0;
if (leaf->predef != C2_L_PUNDEFINED) {
switch (leaf->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:
log_error("Unknown predefined target %d.", leaf->predef);
assert(false);
return false;
}
ntargets = 1;
targets = &predef_target;
} else {
// A raw window property
int word_count = 1;
int offset = leaf->index;
if (leaf->index < 0) {
// index < 0 means match any index
// Get length of property in 32-bit multiples
auto prop_info = x_get_prop_info(&ps->c, wid, leaf->tgtatom);
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
offset = 0;
}
winprop_t prop =
x_get_prop_with_offset(&ps->c, wid, leaf->tgtatom, offset, word_count,
c2_get_atom_type(leaf), leaf->format);
ntargets = (leaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
if (ntargets > 0) {
targets = targets_free = ccalloc(ntargets, long long);
for (size_t i = 0; i < ntargets; ++i) {
targets[i] = winprop_get_int(prop, i);
}
}
free_winprop(&prop);
}
// Do comparison
bool matches = false;
for (size_t i = 0; i < ntargets; ++i) {
long long tgt = targets[i];
switch (leaf->op) {
case C2_L_OEXISTS:
matches = (leaf->predef != C2_L_PUNDEFINED ? tgt : true);
break;
case C2_L_OEQ: matches = (tgt == leaf->ptnint); break;
case C2_L_OGT: matches = (tgt > leaf->ptnint); break;
case C2_L_OGTEQ: matches = (tgt >= leaf->ptnint); break;
case C2_L_OLT: matches = (tgt < leaf->ptnint); break;
case C2_L_OLTEQ: matches = (tgt <= leaf->ptnint); break;
default: log_error("Unknown operator %d.", leaf->op); assert(false);
}
if (matches) {
break;
}
}
// Free property values after usage, if necessary
if (targets_free) {
free(targets_free);
}
return matches;
}
static bool c2_string_op(const c2_l_t *leaf, const char *target) {
if (leaf->op == C2_L_OEXISTS) {
return true;
}
if (leaf->op != C2_L_OEQ) {
log_error("Unsupported operator %d for string comparison.", leaf->op);
assert(leaf->op == C2_L_OEQ);
return false;
}
if (leaf->match == C2_L_MPCRE) {
#ifdef CONFIG_REGEX_PCRE
assert(strlen(target) <= INT_MAX);
assert(leaf->regex_pcre);
return (pcre2_match(leaf->regex_pcre, (PCRE2_SPTR)target, strlen(target),
0, 0, leaf->regex_pcre_match, NULL) > 0);
#else
log_error("PCRE regular expression support not compiled in.");
assert(leaf->match != C2_L_MPCRE);
return false;
#endif
}
if (leaf->match_ignorecase) {
switch (leaf->match) {
case C2_L_MEXACT: return !strcasecmp(target, leaf->ptnstr);
case C2_L_MCONTAINS: return strcasestr(target, leaf->ptnstr);
case C2_L_MSTART:
return !strncasecmp(target, leaf->ptnstr, strlen(leaf->ptnstr));
case C2_L_MWILDCARD: return !fnmatch(leaf->ptnstr, target, FNM_CASEFOLD);
default: unreachable();
}
} else {
switch (leaf->match) {
case C2_L_MEXACT: return !strcmp(target, leaf->ptnstr);
case C2_L_MCONTAINS: return strstr(target, leaf->ptnstr);
case C2_L_MSTART:
return !strncmp(target, leaf->ptnstr, strlen(leaf->ptnstr));
case C2_L_MWILDCARD: return !fnmatch(leaf->ptnstr, target, 0);
default: unreachable();
}
}
unreachable();
}
static bool
c2_match_once_leaf_string(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) {
const char **targets = NULL;
const char **targets_free = NULL;
const char **targets_free_inner = NULL;
size_t ntargets = 0;
const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id);
// A predefined target
const char *predef_target = NULL;
if (leaf->predef != C2_L_PUNDEFINED) {
switch (leaf->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;
} else if (leaf->type == C2_L_TATOM) {
// An atom type property, convert it to string
int word_count = 1;
int offset = leaf->index;
if (leaf->index < 0) {
// index < 0 means match any index
// Get length of property in 32-bit multiples
auto prop_info = x_get_prop_info(&ps->c, wid, leaf->tgtatom);
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
offset = 0;
}
winprop_t prop =
x_get_prop_with_offset(&ps->c, wid, leaf->tgtatom, offset, word_count,
c2_get_atom_type(leaf), leaf->format);
ntargets = (leaf->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);
} else {
// Not an atom type, just fetch the string list
char **strlst = NULL;
int nstr = 0;
int index = max2(0, leaf->index);
if (wid_get_text_prop(&ps->c, ps->atoms, wid, leaf->tgtatom, &strlst, &nstr)) {
if (leaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
ntargets = to_u32_checked(nstr);
targets = (const char **)strlst;
} else if (nstr > index) {
ntargets = 1;
targets = (const char **)strlst + index;
}
}
if (strlst) {
targets_free = (const char **)strlst;
}
}
bool matches = false;
if (ntargets == 0) {
goto fail;
}
for (size_t i = 0; i < ntargets; ++i) {
if (!targets[i]) {
goto fail;
}
}
// Actual matching
for (size_t i = 0; i < ntargets; ++i) {
if (c2_string_op(leaf, targets[i])) {
matches = true;
break;
}
}
fail:
// 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);
}
return matches;
}
/**
* Match a window against a single leaf window condition.
*
* For internal use.
*/
static inline bool
c2_match_once_leaf(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) {
assert(leaf);
const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id);
// Return if wid is missing
if (leaf->predef == C2_L_PUNDEFINED && !wid) {
log_error("Window ID missing.");
assert(false);
return false;
}
if (leaf->op == C2_L_OFALSE) {
return leaf->neg;
}
switch (leaf->ptntype) {
// Deal with integer patterns
case C2_L_PTINT: return c2_match_once_leaf_int(ps, w, leaf);
// String patterns
case C2_L_PTSTRING: return c2_match_once_leaf_string(ps, w, leaf);
default:
log_error("Unknown pattern type %d.", leaf->ptntype);
assert(false);
return false;
}
}
/**
* 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;
char condition_str[1024];
if (cond.isbranch) {
// Handle a branch (and/or/xor operation)
const c2_b_t *pb = cond.b;
if (!pb) {
return 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:
log_error("Unknown boolean operator %d.", pb->op);
assert(false);
return false;
}
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
size_t len =
c2_condition_to_str(cond, condition_str, sizeof(condition_str));
len = min2(len, sizeof(condition_str));
log_debug("(%#010x): branch: result = %d, pattern = %.*s",
w->base.id, result, (int)len, condition_str);
}
} else {
// A leaf
const c2_l_t *pleaf = cond.l;
if (!pleaf) {
return false;
}
result = c2_match_once_leaf(ps, w, pleaf);
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
size_t len =
c2_condition_to_str(cond, condition_str, sizeof(condition_str));
len = min2(len, sizeof(condition_str));
log_debug("(%#010x): leaf: result = %d, client = %#010x, "
"pattern = %.*s",
w->base.id, result, w->client_win, (int)len, condition_str);
}
}
// Postprocess the result
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;
}