Files
picom/src/c2.c
Yuxuan Shui 1e0deea57f Improve the include situation
Slightly clean up header inclusion with the help of clang's module system.
It's better for files to include the things you need directly.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2019-01-18 23:30:44 +00:00

1666 lines
42 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 <fnmatch.h>
#include <ctype.h>
// libpcre
#ifdef CONFIG_REGEX_PCRE
#include <pcre.h>
// For compatiblity with <libpcre-8.20
#ifndef PCRE_STUDY_JIT_COMPILE
#define PCRE_STUDY_JIT_COMPILE 0
#define LPCRE_FREE_STUDY(extra) pcre_free(extra)
#else
#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra)
#endif
#endif
#include "common.h"
#include "win.h"
#include "c2.h"
#include "config.h"
#include "string_utils.h"
#include "utils.h"
#include "log.h"
#pragma GCC diagnostic error "-Wunused-parameter"
#define C2_MAX_LEVELS 10
typedef struct _c2_b c2_b_t;
typedef struct _c2_l c2_l_t;
/// Pointer to a condition tree.
typedef struct {
bool isbranch : 1;
union {
c2_b_t *b;
c2_l_t *l;
};
} c2_ptr_t;
/// Initializer for c2_ptr_t.
#define C2_PTR_INIT { \
.isbranch = false, \
.l = NULL, \
}
static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
/// Operator of a branch element.
typedef enum {
C2_B_OUNDEFINED,
C2_B_OAND,
C2_B_OOR,
C2_B_OXOR,
} c2_b_op_t;
/// Structure for branch element in a window condition
struct _c2_b {
bool neg : 1;
c2_b_op_t op;
c2_ptr_t opr1;
c2_ptr_t opr2;
};
/// Initializer for c2_b_t.
#define C2_B_INIT { \
.neg = false, \
.op = C2_B_OUNDEFINED, \
.opr1 = C2_PTR_INIT, \
.opr2 = C2_PTR_INIT, \
}
/// Structure for leaf element in a window condition
struct _c2_l {
bool neg : 1;
enum {
C2_L_OEXISTS,
C2_L_OEQ,
C2_L_OGT,
C2_L_OGTEQ,
C2_L_OLT,
C2_L_OLTEQ,
} op : 3;
enum {
C2_L_MEXACT,
C2_L_MSTART,
C2_L_MCONTAINS,
C2_L_MWILDCARD,
C2_L_MPCRE,
} match : 3;
bool match_ignorecase : 1;
char *tgt;
Atom tgtatom;
bool tgt_onframe;
int index;
enum {
C2_L_PUNDEFINED,
C2_L_PID,
C2_L_PX,
C2_L_PY,
C2_L_PX2,
C2_L_PY2,
C2_L_PWIDTH,
C2_L_PHEIGHT,
C2_L_PWIDTHB,
C2_L_PHEIGHTB,
C2_L_PBDW,
C2_L_PFULLSCREEN,
C2_L_POVREDIR,
C2_L_PARGB,
C2_L_PFOCUSED,
C2_L_PWMWIN,
C2_L_PBSHAPED,
C2_L_PROUNDED,
C2_L_PCLIENT,
C2_L_PWINDOWTYPE,
C2_L_PLEADER,
C2_L_PNAME,
C2_L_PCLASSG,
C2_L_PCLASSI,
C2_L_PROLE,
} predef;
enum c2_l_type {
C2_L_TUNDEFINED,
C2_L_TSTRING,
C2_L_TCARDINAL,
C2_L_TWINDOW,
C2_L_TATOM,
C2_L_TDRAWABLE,
} type;
int format;
enum {
C2_L_PTUNDEFINED,
C2_L_PTSTRING,
C2_L_PTINT,
} ptntype;
char *ptnstr;
long ptnint;
#ifdef CONFIG_REGEX_PCRE
pcre *regex_pcre;
pcre_extra *regex_pcre_extra;
#endif
};
/// Initializer for c2_l_t.
#define C2_L_INIT { \
.neg = false, \
.op = C2_L_OEXISTS, \
.match = C2_L_MEXACT, \
.match_ignorecase = false, \
.tgt = NULL, \
.tgtatom = 0, \
.tgt_onframe = false, \
.predef = C2_L_PUNDEFINED, \
.index = -1, \
.type = C2_L_TUNDEFINED, \
.format = 0, \
.ptntype = C2_L_PTUNDEFINED, \
.ptnstr = NULL, \
.ptnint = 0, \
}
static const c2_l_t leaf_def = C2_L_INIT;
/// Linked list type of conditions.
struct _c2_lptr {
c2_ptr_t ptr;
void *data;
struct _c2_lptr *next;
};
/// Initializer for c2_lptr_t.
#define C2_LPTR_INIT { \
.ptr = C2_PTR_INIT, \
.data = NULL, \
.next = NULL, \
}
/// Structure representing a predefined target.
typedef struct {
const char *name;
enum c2_l_type type;
int format;
} c2_predef_t;
// Predefined targets.
static const c2_predef_t C2_PREDEFS[] = {
[C2_L_PID ] = { "id" , C2_L_TCARDINAL , 0 },
[C2_L_PX ] = { "x" , C2_L_TCARDINAL , 0 },
[C2_L_PY ] = { "y" , C2_L_TCARDINAL , 0 },
[C2_L_PX2 ] = { "x2" , C2_L_TCARDINAL , 0 },
[C2_L_PY2 ] = { "y2" , C2_L_TCARDINAL , 0 },
[C2_L_PWIDTH ] = { "width" , C2_L_TCARDINAL , 0 },
[C2_L_PHEIGHT ] = { "height" , C2_L_TCARDINAL , 0 },
[C2_L_PWIDTHB ] = { "widthb" , C2_L_TCARDINAL , 0 },
[C2_L_PHEIGHTB ] = { "heightb" , C2_L_TCARDINAL , 0 },
[C2_L_PBDW ] = { "border_width" , C2_L_TCARDINAL , 0 },
[C2_L_PFULLSCREEN ] = { "fullscreen" , C2_L_TCARDINAL , 0 },
[C2_L_POVREDIR ] = { "override_redirect" , C2_L_TCARDINAL , 0 },
[C2_L_PARGB ] = { "argb" , C2_L_TCARDINAL , 0 },
[C2_L_PFOCUSED ] = { "focused" , C2_L_TCARDINAL , 0 },
[C2_L_PWMWIN ] = { "wmwin" , C2_L_TCARDINAL , 0 },
[C2_L_PBSHAPED ] = { "bounding_shaped" , C2_L_TCARDINAL , 0 },
[C2_L_PROUNDED ] = { "rounded_corners" , C2_L_TCARDINAL , 0 },
[C2_L_PCLIENT ] = { "client" , C2_L_TWINDOW , 0 },
[C2_L_PWINDOWTYPE ] = { "window_type" , C2_L_TSTRING , 0 },
[C2_L_PLEADER ] = { "leader" , C2_L_TWINDOW , 0 },
[C2_L_PNAME ] = { "name" , C2_L_TSTRING , 0 },
[C2_L_PCLASSG ] = { "class_g" , C2_L_TSTRING , 0 },
[C2_L_PCLASSI ] = { "class_i" , C2_L_TSTRING , 0 },
[C2_L_PROLE ] = { "role" , C2_L_TSTRING , 0 },
};
/**
* 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(c) || '_' == c)
return 1;
else
return 0;
}
/**
* Return whether a c2_ptr_t is empty.
*/
static inline 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 = cmalloc(c2_b_t)
};
p.b->opr1 = p1;
p.b->opr2 = p2;
p.b->op = op;
return p;
}
/**
* Get the precedence value of a condition branch operator.
*/
static inline int
c2h_b_opp(c2_b_op_t op) {
switch (op) {
case C2_B_OAND: return 2;
case C2_B_OOR: return 1;
case C2_B_OXOR: return 1;
default: break;
}
assert(0);
return 0;
}
/**
* Compare precedence of two condition branch operators.
*
* Associativity is left-to-right, forever.
*
* @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
* negative number otherwise
*/
static inline int
c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
return c2h_b_opp(op1) - c2h_b_opp(op2);
}
static int
c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level);
static int
c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult);
static int
c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult);
static void
c2_free(c2_ptr_t p);
/**
* Wrapper of c2_free().
*/
static inline void
c2_freep(c2_ptr_t *pp) {
if (pp) {
c2_free(*pp);
c2_ptr_reset(pp);
}
}
static const char *
c2h_dump_str_tgt(const c2_l_t *pleaf);
static const char *
c2h_dump_str_type(const c2_l_t *pleaf);
static void attr_unused
c2_dump(c2_ptr_t p);
static Atom
c2_get_atom_type(const c2_l_t *pleaf);
static bool
c2_match_once(session_t *ps, 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);
#endif
return plptr;
}
}
#define c2_error(format, ...) do { \
log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \
goto fail; \
} while(0)
// TODO Not a very good macro
#define C2H_SKIP_SPACES() { while (isspace(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(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
unsigned tgtlen = 0;
for (; pattern[offset]
&& (isalnum(pattern[offset]) || '_' == pattern[offset]); ++offset) {
++tgtlen;
}
if (!tgtlen)
c2_error("Empty target.");
pleaf->tgt = strndup(&pattern[offset - tgtlen], tgtlen);
// Check for predefined targets
for (unsigned i = 1; i < sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0]); ++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;
}
}
// Alias for predefined targets
if (!pleaf->predef) {
#define TGTFILL(pdefid) \
(pleaf->predef = pdefid, \
pleaf->type = C2_PREDEFS[pdefid].type, \
pleaf->format = C2_PREDEFS[pdefid].format)
// if (!strcmp("WM_NAME", tgt) || !strcmp("_NET_WM_NAME", tgt))
// TGTFILL(C2_L_PNAME);
#undef TGTFILL
// Alias for custom properties
#define TGTFILL(target, type, format) \
(pleaf->target = strdup(target), \
pleaf->type = type, \
pleaf->format = format)
// if (!strcmp("SOME_ALIAS"))
// TGTFILL("ALIAS_TEXT", C2_L_TSTRING, 32);
#undef TGTFILL
}
C2H_SKIP_SPACES();
// Parse target-on-frame flag
if ('@' == pattern[offset]) {
pleaf->tgt_onframe = true;
++offset;
C2H_SKIP_SPACES();
}
// Parse index
if ('[' == pattern[offset]) {
offset++;
C2H_SKIP_SPACES();
int index = -1;
char *endptr = NULL;
index = strtol(pattern + offset, &endptr, 0);
if (!endptr || pattern + offset == endptr)
c2_error("No index number found after bracket.");
if (index < 0)
c2_error("Index number invalid.");
if (pleaf->predef)
c2_error("Predefined targets can't have index.");
pleaf->index = index;
offset = 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;
int format = 0;
{
char *endptr = NULL;
format = strtol(pattern + offset, &endptr, 0);
assert(endptr);
if ((hasformat = (endptr && endptr != pattern + offset)))
offset = 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) {
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)
log_warn("Format \"%d\" specified on a default target will be ignored.", format);
else if (C2_L_TSTRING == pleaf->type)
log_warn("Format \"%d\" 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 = 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;
// Check for boolean patterns
if (!strcmp_wd("true", &pattern[offset])) {
pleaf->ptntype = C2_L_PTINT;
pleaf->ptnint = true;
offset += strlen("true");
}
else if (!strcmp_wd("false", &pattern[offset])) {
pleaf->ptntype = C2_L_PTINT;
pleaf->ptnint = false;
offset += strlen("false");
}
// Check for integer patterns
else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0),
pattern + offset != endptr) {
pleaf->ptntype = C2_L_PTINT;
offset = endptr - pattern;
// Make sure we are stopping at the end of a word
if (isalnum(pattern[offset]))
c2_error("Trailing characters after a numeric pattern.");
}
// Check for string patterns
else {
bool raw = false;
char delim = '\0';
// String flags
if ('r' == tolower(pattern[offset])) {
raw = true;
++offset;
C2H_SKIP_SPACES();
}
// Check for delimiters
if ('\"' == pattern[offset] || '\'' == pattern[offset]) {
pleaf->ptntype = C2_L_PTSTRING;
delim = pattern[offset];
++offset;
}
if (C2_L_PTSTRING != pleaf->ptntype)
c2_error("Invalid pattern type.");
// Parse the string now
// We can't determine the length of the pattern, so we use the length
// to the end of the pattern string -- currently escape sequences
// cannot be converted to a string longer than itself.
auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char);
char *ptptnstr = tptnstr;
pleaf->ptnstr = tptnstr;
for (; pattern[offset] && delim != pattern[offset]; ++offset) {
// Handle escape sequences if it's not a raw string
if ('\\' == pattern[offset] && !raw) {
switch(pattern[++offset]) {
case '\\': *(ptptnstr++) = '\\'; break;
case '\'': *(ptptnstr++) = '\''; break;
case '\"': *(ptptnstr++) = '\"'; break;
case 'a': *(ptptnstr++) = '\a'; break;
case 'b': *(ptptnstr++) = '\b'; break;
case 'f': *(ptptnstr++) = '\f'; break;
case 'n': *(ptptnstr++) = '\n'; break;
case 'r': *(ptptnstr++) = '\r'; break;
case 't': *(ptptnstr++) = '\t'; break;
case 'v': *(ptptnstr++) = '\v'; break;
case 'o':
case 'x':
{
char *tstr = strndup(pattern + offset + 1, 2);
char *pstr = NULL;
long val = strtol(tstr, &pstr,
('o' == pattern[offset] ? 8: 16));
free(tstr);
if (pstr != &tstr[2] || val <= 0)
c2_error("Invalid octal/hex escape sequence.");
assert(val < 256 && val >= 0);
*(ptptnstr++) = 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) {
unsigned plen = strlen(pattern + offset);
if (plen < 4 || ':' != pattern[offset + 1]
|| !strchr(pattern + offset + 2, ':'))
c2_error("Legacy parser: Invalid format.");
// Allocate memory for new leaf
auto pleaf = cmalloc(c2_l_t);
presult->isbranch = false;
presult->l = pleaf;
memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
pleaf->type = C2_L_TSTRING;
pleaf->op = C2_L_OEQ;
pleaf->ptntype = C2_L_PTSTRING;
// Determine the pattern target
#define TGTFILL(pdefid) \
(pleaf->predef = pdefid, \
pleaf->type = C2_PREDEFS[pdefid].type, \
pleaf->format = C2_PREDEFS[pdefid].format)
switch (pattern[offset]) {
case 'n': TGTFILL(C2_L_PNAME); break;
case 'i': TGTFILL(C2_L_PCLASSI); break;
case 'g': TGTFILL(C2_L_PCLASSG); break;
case 'r': TGTFILL(C2_L_PROLE); break;
default: c2_error("Target \"%c\" invalid.\n", pattern[offset]);
}
#undef TGTFILL
offset += 2;
// Determine the match type
switch (pattern[offset]) {
case 'e': pleaf->match = C2_L_MEXACT; break;
case 'a': pleaf->match = C2_L_MCONTAINS; break;
case 's': pleaf->match = C2_L_MSTART; break;
case 'w': pleaf->match = C2_L_MWILDCARD; break;
case 'p': pleaf->match = C2_L_MPCRE; break;
default: c2_error("Type \"%c\" invalid.\n", pattern[offset]);
}
++offset;
// Determine the pattern flags
while (':' != pattern[offset]) {
switch (pattern[offset]) {
case 'i': pleaf->match_ignorecase = true; break;
default: c2_error("Flag \"%c\" invalid.", pattern[offset]);
}
++offset;
}
++offset;
// Copy the pattern
pleaf->ptnstr = strdup(pattern + offset);
return offset;
fail:
return -1;
}
#undef c2_error
/**
* Do postprocessing on a condition leaf.
*/
static bool
c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
// Give a pattern type to a leaf with exists operator, if needed
if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) {
pleaf->ptntype =
(C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING: C2_L_PTINT);
}
// Get target atom if it's not a predefined one
if (!pleaf->predef) {
pleaf->tgtatom = get_atom(ps, pleaf->tgt);
if (!pleaf->tgtatom) {
log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
return false;
}
}
// Insert target Atom into atom track list
if (pleaf->tgtatom) {
bool found = false;
for (latom_t *platom = ps->track_atom_lst; platom;
platom = platom->next) {
if (pleaf->tgtatom == platom->atom) {
found = true;
break;
}
}
if (!found) {
auto pnew = cmalloc(latom_t);
pnew->next = ps->track_atom_lst;
pnew->atom = pleaf->tgtatom;
ps->track_atom_lst = pnew;
}
}
// Enable specific tracking options in compton if needed by the condition
// TODO: Add track_leader
if (pleaf->predef) {
switch (pleaf->predef) {
case C2_L_PFOCUSED: ps->o.track_focus = true; break;
// case C2_L_PROUNDED: ps->o.detect_rounded_corners = true; break;
case C2_L_PNAME:
case C2_L_PCLASSG:
case C2_L_PCLASSI:
case C2_L_PROLE: ps->o.track_wdata = true; break;
default: break;
}
}
// Warn about lower case characters in target name
if (!pleaf->predef) {
for (const char *pc = pleaf->tgt; *pc; ++pc) {
if (islower(*pc)) {
log_warn("Lowercase character in target name \"%s\".", pleaf->tgt);
break;
}
}
}
// PCRE patterns
if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) {
#ifdef CONFIG_REGEX_PCRE
const char *error = NULL;
int erroffset = 0;
int options = 0;
// Ignore case flag
if (pleaf->match_ignorecase)
options |= PCRE_CASELESS;
// Compile PCRE expression
pleaf->regex_pcre = pcre_compile(pleaf->ptnstr, options,
&error, &erroffset, NULL);
if (!pleaf->regex_pcre) {
log_error("Pattern \"%s\": PCRE regular expression parsing failed on "
"offset %d: %s", pleaf->ptnstr, erroffset, error);
return false;
}
#ifdef CONFIG_REGEX_PCRE_JIT
pleaf->regex_pcre_extra = pcre_study(pleaf->regex_pcre,
PCRE_STUDY_JIT_COMPILE, &error);
if (!pleaf->regex_pcre_extra) {
printf("Pattern \"%s\": PCRE regular expression study failed: %s",
pleaf->ptnstr, error);
}
#endif
// Free the target string
// free(pleaf->tgt);
// pleaf->tgt = NULL;
#else
log_error("PCRE regular expression support not compiled in.");
return false;
#endif
}
return true;
}
static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) {
if (!node.isbranch) {
return c2_l_postprocess(ps, node.l);
}
if (!c2_tree_postprocess(ps, node.b->opr1))
return false;
return c2_tree_postprocess(ps, node.b->opr2);
}
bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) {
c2_lptr_t *head = list;
while (head) {
if (!c2_tree_postprocess(ps, head->ptr))
return false;
head = head->next;
}
return true;
}
/**
* Free a condition tree.
*/
static void
c2_free(c2_ptr_t p) {
// For a branch element
if (p.isbranch) {
c2_b_t * const pbranch = p.b;
if (!pbranch)
return;
c2_free(pbranch->opr1);
c2_free(pbranch->opr2);
free(pbranch);
}
// For a leaf element
else {
c2_l_t * const pleaf = p.l;
if (!pleaf)
return;
free(pleaf->tgt);
free(pleaf->ptnstr);
#ifdef CONFIG_REGEX_PCRE
pcre_free(pleaf->regex_pcre);
LPCRE_FREE_STUDY(pleaf->regex_pcre_extra);
#endif
free(pleaf);
}
}
/**
* Free a condition tree in c2_lptr_t.
*/
c2_lptr_t *
c2_free_lptr(c2_lptr_t *lp) {
if (!lp)
return NULL;
c2_lptr_t *pnext = lp->next;
c2_free(lp->ptr);
free(lp);
return pnext;
}
/**
* Get a string representation of a rule target.
*/
static const char *
c2h_dump_str_tgt(const c2_l_t *pleaf) {
if (pleaf->predef)
return C2_PREDEFS[pleaf->predef].name;
else
return pleaf->tgt;
}
/**
* Get a string representation of a target.
*/
static const char *
c2h_dump_str_type(const c2_l_t *pleaf) {
switch (pleaf->type) {
case C2_L_TWINDOW: return "w";
case C2_L_TDRAWABLE: return "d";
case C2_L_TCARDINAL: return "c";
case C2_L_TSTRING: return "s";
case C2_L_TATOM: return "a";
case C2_L_TUNDEFINED: break;
}
return NULL;
}
/**
* Dump a condition tree.
*/
static void
c2_dump(c2_ptr_t p) {
// For a branch
if (p.isbranch) {
const c2_b_t * const pbranch = p.b;
if (!pbranch)
return;
if (pbranch->neg)
putchar('!');
printf("(");
c2_dump(pbranch->opr1);
switch (pbranch->op) {
case C2_B_OAND: printf(" && "); break;
case C2_B_OOR: printf(" || "); break;
case C2_B_OXOR: printf(" XOR "); break;
default: assert(0); break;
}
c2_dump(pbranch->opr2);
printf(")");
}
// For a leaf
else {
const c2_l_t * const pleaf = p.l;
if (!pleaf)
return;
if (C2_L_OEXISTS == pleaf->op && pleaf->neg)
putchar('!');
// Print target name, type, and format
{
printf("%s", c2h_dump_str_tgt(pleaf));
if (pleaf->tgt_onframe)
putchar('@');
if (pleaf->index >= 0)
printf("[%d]", pleaf->index);
printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf));
}
// Print operator
putchar(' ');
if (C2_L_OEXISTS != pleaf->op && pleaf->neg)
putchar('!');
switch (pleaf->match) {
case C2_L_MEXACT: break;
case C2_L_MCONTAINS: putchar('*'); break;
case C2_L_MSTART: putchar('^'); break;
case C2_L_MPCRE: putchar('~'); break;
case C2_L_MWILDCARD: putchar('%'); break;
}
if (pleaf->match_ignorecase)
putchar('?');
switch (pleaf->op) {
case C2_L_OEXISTS: break;
case C2_L_OEQ: fputs("=", stdout); break;
case C2_L_OGT: fputs(">", stdout); break;
case C2_L_OGTEQ: fputs(">=", stdout); break;
case C2_L_OLT: fputs("<", stdout); break;
case C2_L_OLTEQ: fputs("<=", stdout); break;
}
if (C2_L_OEXISTS == pleaf->op)
return;
// Print pattern
putchar(' ');
switch (pleaf->ptntype) {
case C2_L_PTINT:
printf("%ld", pleaf->ptnint);
break;
case C2_L_PTSTRING:
// TODO: Escape string before printing out?
printf("\"%s\"", pleaf->ptnstr);
break;
default:
assert(0);
break;
}
}
}
/**
* Get the type atom of a condition.
*/
static Atom
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;
}
assert(0);
return AnyPropertyType;
}
/**
* Match a window against a single leaf window condition.
*
* For internal use.
*/
static inline void
c2_match_once_leaf(session_t *ps, 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->id);
// Return if wid is missing
if (!pleaf->predef && !wid)
return;
const int idx = (pleaf->index < 0 ? 0: pleaf->index);
switch (pleaf->ptntype) {
// Deal with integer patterns
case C2_L_PTINT:
{
long tgt = 0;
// Get the value
// A predefined target
if (pleaf->predef) {
*perr = false;
switch (pleaf->predef) {
case C2_L_PID: tgt = wid; break;
case C2_L_PX: tgt = w->g.x; break;
case C2_L_PY: tgt = w->g.y; break;
case C2_L_PX2: tgt = w->g.x + w->widthb; break;
case C2_L_PY2: tgt = w->g.y + w->heightb; break;
case C2_L_PWIDTH: tgt = w->g.width; break;
case C2_L_PHEIGHT: tgt = w->g.height; break;
case C2_L_PWIDTHB: tgt = w->widthb; break;
case C2_L_PHEIGHTB: tgt = w->heightb; break;
case C2_L_PBDW: tgt = w->g.border_width; break;
case C2_L_PFULLSCREEN: tgt = win_is_fullscreen(ps, w); break;
case C2_L_POVREDIR: tgt = w->a.override_redirect; break;
case C2_L_PARGB: tgt = win_has_alpha(w); break;
case C2_L_PFOCUSED: tgt = win_is_focused_real(ps, w); break;
case C2_L_PWMWIN: tgt = w->wmwin; break;
case C2_L_PBSHAPED: tgt = w->bounding_shaped; break;
case C2_L_PROUNDED: tgt = w->rounded_corners; break;
case C2_L_PCLIENT: tgt = w->client_win; break;
case C2_L_PLEADER: tgt = w->leader; break;
default: *perr = true; assert(0); break;
}
}
// A raw window property
else {
winprop_t prop = wid_get_prop_adv(ps, wid, pleaf->tgtatom,
idx, 1L, c2_get_atom_type(pleaf), pleaf->format);
if (prop.nitems) {
*perr = false;
tgt = winprop_get_int(prop);
}
free_winprop(&prop);
}
if (*perr)
return;
// Do comparison
switch (pleaf->op) {
case C2_L_OEXISTS:
*pres = (pleaf->predef ? tgt: true);
break;
case C2_L_OEQ: *pres = (tgt == pleaf->ptnint); break;
case C2_L_OGT: *pres = (tgt > pleaf->ptnint); break;
case C2_L_OGTEQ: *pres = (tgt >= pleaf->ptnint); break;
case C2_L_OLT: *pres = (tgt < pleaf->ptnint); break;
case C2_L_OLTEQ: *pres = (tgt <= pleaf->ptnint); break;
default: *perr = true; assert(0); break;
}
}
break;
// String patterns
case C2_L_PTSTRING:
{
const char *tgt = NULL;
char *tgt_free = NULL;
// A predefined target
if (pleaf->predef) {
switch (pleaf->predef) {
case C2_L_PWINDOWTYPE: tgt = WINTYPES[w->window_type];
break;
case C2_L_PNAME: tgt = w->name; break;
case C2_L_PCLASSG: tgt = w->class_general; break;
case C2_L_PCLASSI: tgt = w->class_instance; break;
case C2_L_PROLE: tgt = w->role; break;
default: assert(0); break;
}
}
// If it's an atom type property, convert atom to string
else if (C2_L_TATOM == pleaf->type) {
winprop_t prop = wid_get_prop_adv(ps, wid, pleaf->tgtatom,
idx, 1L, c2_get_atom_type(pleaf), pleaf->format);
Atom atom = winprop_get_int(prop);
if (atom) {
xcb_get_atom_name_reply_t *reply =
xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, atom), NULL);
if (reply) {
tgt_free = strndup(
xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply));
free(reply);
}
}
if (tgt_free) {
tgt = tgt_free;
}
free_winprop(&prop);
}
// Otherwise, just fetch the string list
else {
char **strlst = NULL;
int nstr;
if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst,
&nstr) && nstr > idx) {
tgt_free = strdup(strlst[idx]);
tgt = tgt_free;
}
if (strlst)
XFreeStringList(strlst);
}
if (tgt) {
*perr = false;
}
else {
return;
}
// Actual matching
switch (pleaf->op) {
case C2_L_OEXISTS:
*pres = true;
break;
case C2_L_OEQ:
switch (pleaf->match) {
case C2_L_MEXACT:
if (pleaf->match_ignorecase)
*pres = !strcasecmp(tgt, pleaf->ptnstr);
else
*pres = !strcmp(tgt, pleaf->ptnstr);
break;
case C2_L_MCONTAINS:
if (pleaf->match_ignorecase)
*pres = strcasestr(tgt, pleaf->ptnstr);
else
*pres = strstr(tgt, pleaf->ptnstr);
break;
case C2_L_MSTART:
if (pleaf->match_ignorecase)
*pres = !strncasecmp(tgt, pleaf->ptnstr,
strlen(pleaf->ptnstr));
else
*pres = !strncmp(tgt, pleaf->ptnstr,
strlen(pleaf->ptnstr));
break;
case C2_L_MWILDCARD:
{
int flags = 0;
if (pleaf->match_ignorecase)
flags |= FNM_CASEFOLD;
*pres = !fnmatch(pleaf->ptnstr, tgt, flags);
}
break;
case C2_L_MPCRE:
#ifdef CONFIG_REGEX_PCRE
*pres = (pcre_exec(pleaf->regex_pcre,
pleaf->regex_pcre_extra,
tgt, strlen(tgt), 0, 0, NULL, 0) >= 0);
#else
assert(0);
#endif
break;
}
break;
default:
*perr = true;
assert(0);
}
// Free the string after usage, if necessary
if (tgt_free) {
if (C2_L_TATOM == pleaf->type)
cxfree(tgt_free);
else
free(tgt_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, win *w, const c2_ptr_t cond) {
bool result = false;
bool error = true;
// Handle a branch
if (cond.isbranch) {
const c2_b_t *pb = cond.b;
if (!pb)
return false;
error = false;
switch (pb->op) {
case C2_B_OAND:
result = (c2_match_once(ps, w, pb->opr1)
&& c2_match_once(ps, w, pb->opr2));
break;
case C2_B_OOR:
result = (c2_match_once(ps, w, pb->opr1)
|| c2_match_once(ps, w, pb->opr2));
break;
case C2_B_OXOR:
result = (c2_match_once(ps, w, pb->opr1)
!= c2_match_once(ps, w, pb->opr2));
break;
default:
error = true;
assert(0);
}
#ifdef DEBUG_WINMATCH
log_trace("(%#010lx): branch: result = %d, pattern = ", w->id, result);
c2_dump(cond);
#endif
}
// Handle a leaf
else {
const c2_l_t *pleaf = cond.l;
if (!pleaf)
return false;
c2_match_once_leaf(ps, w, pleaf, &result, &error);
// For EXISTS operator, no errors are fatal
if (C2_L_OEXISTS == pleaf->op && error) {
result = false;
error = false;
}
#ifdef DEBUG_WINMATCH
log_trace("(%#010lx): leaf: result = %d, error = %d, "
"client = %#010lx, pattern = ",
w->id, result, error, w->client_win);
c2_dump(cond);
#endif
}
// Postprocess the result
if (error)
result = false;
if (cond.isbranch ? cond.b->neg: cond.l->neg)
result = !result;
return result;
}
/**
* Match a window against a condition linked list.
*
* @param cache a place to cache the last matched condition
* @param pdata a place to return the data
* @return true if matched, false otherwise.
*/
bool
c2_match(session_t *ps, win *w, const c2_lptr_t *condlst,
const c2_lptr_t **cache, void **pdata) {
assert(w->a.map_state == XCB_MAP_STATE_VIEWABLE);
// Check if the cached entry matches firstly
if (cache && *cache && c2_match_once(ps, w, (*cache)->ptr)) {
if (pdata)
*pdata = (*cache)->data;
return true;
}
// Then go through the whole linked list
for (; condlst; condlst = condlst->next) {
if (c2_match_once(ps, w, condlst->ptr)) {
if (cache)
*cache = condlst;
if (pdata)
*pdata = condlst->data;
return true;
}
}
return false;
}