mirror of
https://github.com/yshui/picom.git
synced 2024-11-11 13:51:02 -05:00
c23dbe61f7
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>
1938 lines
50 KiB
C
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;
|
|
}
|