From 679bfe3cab740f06b38f606d599304f515b9de4d Mon Sep 17 00:00:00 2001 From: Richard Grenville Date: Mon, 28 Jan 2013 21:39:38 +0800 Subject: [PATCH] Feature #16: Advanced window matching - Add advanced window matching system, capable of matching against arbitrary window properties as well as a series of internal properties, with 4 additional operators (>, <, >=, <=) useful for integer targets, and support of logical operators. The old matching system is removed, but compatibility with the format is retained. - As the new matching system is pretty complicated, and I have no past experience in writing a parser, it's pretty possible that bugs are present. It also has inferior performance, but I hope it doesn't matter on modern CPUs. - It's possible to disable matching system at compile time with NO_C2=1 now. - Add ps->o.config_file to track which config file we have actually read. Queryable via D-Bus. - Parse -d in first pass in get_cfg() as c2 needs to query X to get atoms during condition parsing. - Fix a bug in wid_get_prop_adv() that 0 == rformat is not handled correctly. - Fix incompatibility with FreeBSD sed in dbus-examples/cdbus-driver.sh . - Add recipe to generate .clang_complete in Makefile, used by Vim clang_complete plugin. - Add DEBUG_C2 for debugging condition string parsing. DEBUG_WINMATCH is still used for match debugging. - Rename win_on_wdata_change() to win_on_factor_change(). - Extra malloc() failure checks. Add const to matching cache members in session_t. Code clean-up. Documentation update. --- c2.c | 1296 +++++++++++++++++++++++++++++++++++++++++++++++++++++ c2.h | 326 ++++++++++++++ common.h | 58 +-- compton.c | 396 ++++------------ compton.h | 62 +-- dbus.c | 1 + 6 files changed, 1756 insertions(+), 383 deletions(-) create mode 100644 c2.c create mode 100644 c2.h diff --git a/c2.c b/c2.c new file mode 100644 index 000000000..5bbb4e3a8 --- /dev/null +++ b/c2.c @@ -0,0 +1,1296 @@ +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * See LICENSE for more information. + * + */ + +#include "c2.h" + +/** + * Parse a condition string. + */ +c2_lptr_t * +c2_parse(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) { + 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(ps, pattern, 0, &result); + else + offset = c2_parse_grp(ps, pattern, 0, &result, 0); + + if (offset < 0) { + c2_freep(&result); + return NULL; + } + + // Insert to pcondlst + { + const static c2_lptr_t lptr_def = C2_LPTR_INIT; + c2_lptr_t *plptr = malloc(sizeof(c2_lptr_t)); + if (!plptr) + printf_errfq(1, "(): Failed to allocate memory for new condition linked" + " list element."); + memcpy(plptr, &lptr_def, sizeof(c2_lptr_t)); + plptr->ptr = result; + if (pcondlst) { + plptr->next = *pcondlst; + *pcondlst = plptr; + } + +#ifdef DEBUG_C2 + printf_dbgf("(\"%s\"): ", pattern); + c2_dump(plptr->ptr); +#endif + + return plptr; + } +} + +#undef c2_error +#define c2_error(format, ...) do { \ + printf_err("Pattern \"%s\" pos %d: " format, pattern, offset, \ + ## __VA_ARGS__); \ + return -1; \ +} while(0) + +#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(session_t *ps, 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'); + +#undef c2_error +#define c2_error(format, ...) do { \ + printf_err("Pattern \"%s\" pos %d: " format, pattern, offset, \ + ## __VA_ARGS__); \ + goto c2_parse_grp_fail; \ +} while(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(ps, pattern, offset + 1, pele, level + 1)) < 0) + goto c2_parse_grp_fail; + } + // Otherwise it's a leaf + else { + if ((offset = c2_parse_target(ps, pattern, offset, pele)) < 0) + goto c2_parse_grp_fail; + + assert(!pele->isbranch && !c2_ptr_isempty(*pele)); + + if ((offset = c2_parse_op(pattern, offset, pele)) < 0) + goto c2_parse_grp_fail; + + if ((offset = c2_parse_pattern(ps, pattern, offset, pele)) < 0) + goto c2_parse_grp_fail; + + if (!c2_l_postprocess(ps, pele->l)) + goto c2_parse_grp_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; + +c2_parse_grp_fail: + c2_freep(&eles[0]); + c2_freep(&eles[1]); + + return -1; +} + +#undef c2_error +#define c2_error(format, ...) do { \ + printf_err("Pattern \"%s\" pos %d: " format, pattern, offset, \ + ## __VA_ARGS__); \ + return -1; \ +} while(0) + +/** + * Parse the target part of a rule. + */ +static int +c2_parse_target(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult) { + // Initialize leaf + presult->isbranch = false; + presult->l = malloc(sizeof(c2_l_t)); + if (!presult->l) + c2_error("Failed to allocate memory for new leaf."); + + 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 = mstrncpy(&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 = mstrcpy(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) { + printf_errf("(): Warning: Type specified for a default target will be ignored."); + } + else { + if (pleaf->type && type != pleaf->type) + printf_errf("(): Warning: 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) + printf_errf("(): Warning: Format \"%d\" specified on a default target will be ignored.", format); + else if (C2_L_TSTRING == pleaf->type) + printf_errf("(): Warning: Format \"%d\" specified on a string target will be ignored.", format); + else { + if (pleaf->format && pleaf->format != format) + printf_err("Warning: 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; +} + +/** + * 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; +} + +/** + * Parse the pattern part of a leaf. + */ +static int +c2_parse_pattern(session_t *ps, 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. + char *tptnstr = malloc((strlen(pattern + offset) + 1) * sizeof(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 = mstrncpy(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 = mstrcpy(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; +} + +/** + * Parse a condition with legacy syntax. + */ +static int +c2_parse_legacy(session_t *ps, 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 + c2_l_t *pleaf = malloc(sizeof(c2_l_t)); + if (!pleaf) + printf_errfq(1, "(): Failed to allocate memory for new leaf."); + 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 = mstrcpy(pattern + offset); + + if (!c2_l_postprocess(ps, pleaf)) + return -1; + + return offset; +} + +#undef c2_error +#define c2_error(format, ...) { \ + printf_err(format, ## __VA_ARGS__); \ + return false; } + +/** + * 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) + c2_error("Failed to get atom for target \"%s\".", pleaf->tgt); + } + + // 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) { + latom_t *pnew = malloc(sizeof(latom_t)); + if (!pnew) + printf_errfq(1, "(): Failed to allocate memory for new track atom."); + 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_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)) { + printf_errf("(): Warning: 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) + c2_error("Pattern \"%s\": PCRE regular expression parsing failed on " + "offset %d: %s", pleaf->ptnstr, erroffset, error); +#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 + c2_error("PCRE regular expression support not compiled in."); +#endif + } + + 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_raw(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_raw(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_raw(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 XA_CARDINAL; + case C2_L_TWINDOW: + return XA_WINDOW; + case C2_L_TSTRING: + return XA_STRING; + case C2_L_TATOM: + return XA_ATOM; + case C2_L_TDRAWABLE: + return XA_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 Window 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_POVREDIR: tgt = w->a.override_redirect; break; + case C2_L_PFOCUSED: tgt = w->focused_real; break; + case C2_L_PWMWIN: tgt = w->wmwin; 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) { + tgt_free = XGetAtomName(ps->dpy, atom); + } + 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 = mstrcpy(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) + XFree(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 + printf_dbgf("(%#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 + printf_dbgf("(%#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 + * @return true if matched, false otherwise. + */ +bool +c2_match(session_t *ps, win *w, const c2_lptr_t *condlst, + const c2_lptr_t **cache) { + // Check if the cached entry matches firstly + if (cache && *cache && c2_match_once(ps, w, (*cache)->ptr)) + 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; + return true; + } + } + + return false; +} + diff --git a/c2.h b/c2.h new file mode 100644 index 000000000..000c68448 --- /dev/null +++ b/c2.h @@ -0,0 +1,326 @@ +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * See LICENSE for more information. + * + */ + +#include "common.h" + +#include +#include + +// libpcre +#ifdef CONFIG_REGEX_PCRE +#include + +// For compatiblity with 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(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult, int level); + +static int +c2_parse_target(session_t *ps, 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(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult); + +static int +c2_parse_legacy(session_t *ps, const char *pattern, int offset, c2_ptr_t *presult); + +static bool +c2_l_postprocess(session_t *ps, c2_l_t *pleaf); + +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 +c2_dump_raw(c2_ptr_t p); + +/** + * Wrapper of c2_dump_raw(). + */ +static inline void +c2_dump(c2_ptr_t p) { + c2_dump_raw(p); + printf("\n"); + fflush(stdout); +} + +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); + diff --git a/common.h b/common.h index 004d9553b..bc057aa8d 100644 --- a/common.h +++ b/common.h @@ -76,20 +76,6 @@ #include #include -// libpcre -#ifdef CONFIG_REGEX_PCRE -#include - -// For compatiblity with @@ -257,18 +243,6 @@ enum wincond_type { #define CONDF_IGNORECASE 0x0001 -typedef struct _wincond { - enum wincond_target target; - enum wincond_type type; - char *pattern; -#ifdef CONFIG_REGEX_PCRE - pcre *regex_pcre; - pcre_extra *regex_pcre_extra; -#endif - int16_t flags; - struct _wincond *next; -} wincond_t; - /// VSync modes. typedef enum { VSYNC_NONE, @@ -297,13 +271,13 @@ struct _timeout_t; struct _win; -#ifdef CONFIG_C2 typedef struct _c2_lptr c2_lptr_t; -#endif /// Structure representing all options. typedef struct { // === General === + /// The configuration file we used. + char *config_file; /// The display name we used. NULL means we are using the value of the /// DISPLAY environment variable. char *display; @@ -350,7 +324,7 @@ typedef struct { double shadow_opacity; bool clear_shadow; /// Shadow blacklist. A linked list of conditions. - wincond_t *shadow_blacklist; + c2_lptr_t *shadow_blacklist; /// Whether bounding-shaped window should be ignored. bool shadow_ignore_shaped; /// Whether to respect _COMPTON_SHADOW. @@ -368,7 +342,7 @@ typedef struct { /// Whether to disable fading on window open/close. bool no_fading_openclose; /// Fading blacklist. A linked list of conditions. - wincond_t *fade_blacklist; + c2_lptr_t *fade_blacklist; // === Opacity === /// Default opacity for specific window types @@ -404,7 +378,7 @@ typedef struct { /// based on window opacity. bool inactive_dim_fixed; /// Conditions of windows to have inverted colors. - wincond_t *invert_color_list; + c2_lptr_t *invert_color_list; // === Focus related === /// Consider windows of specific types to be always focused. @@ -412,7 +386,7 @@ typedef struct { /// Whether to use EWMH _NET_ACTIVE_WINDOW to find active window. bool use_ewmh_active_win; /// A list of windows always to be considered focused. - wincond_t *focus_blacklist; + c2_lptr_t *focus_blacklist; /// Whether to do window grouping with WM_TRANSIENT_FOR. bool detect_transient; /// Whether to do window grouping with WM_CLIENT_LEADER. @@ -736,10 +710,10 @@ typedef struct _win { char *class_general; /// WM_WINDOW_ROLE value of the window. char *role; - wincond_t *cache_sblst; - wincond_t *cache_fblst; - wincond_t *cache_fcblst; - wincond_t *cache_ivclst; + const c2_lptr_t *cache_sblst; + const c2_lptr_t *cache_fblst; + const c2_lptr_t *cache_fcblst; + const c2_lptr_t *cache_ivclst; // Opacity-related members /// Current window opacity. @@ -1056,10 +1030,13 @@ print_timestamp(session_t *ps) { /** * Allocate the space and copy a string. */ -static inline char * __attribute__((const)) +static inline char * mstrcpy(const char *src) { char *str = malloc(sizeof(char) * (strlen(src) + 1)); + if (!str) + printf_errfq(1, "(): Failed to allocate memory."); + strcpy(str, src); return str; @@ -1068,10 +1045,13 @@ mstrcpy(const char *src) { /** * Allocate the space and copy a string. */ -static inline char * __attribute__((const)) +static inline char * mstrncpy(const char *src, unsigned len) { char *str = malloc(sizeof(char) * (len + 1)); + if (!str) + printf_errfq(1, "(): Failed to allocate memory."); + strncpy(str, src, len); str[len] = '\0'; @@ -1479,7 +1459,7 @@ win_set_invert_color_force(session_t *ps, win *w, switch_t val); ///@{ c2_lptr_t * -c2_parse(session_t *ps, c2_lptr_t **pcondlst, char *pattern); +c2_parse(session_t *ps, c2_lptr_t **pcondlst, const char *pattern); c2_lptr_t * c2_free_lptr(c2_lptr_t *lp); diff --git a/compton.c b/compton.c index 814bf4a3b..cbb5e7b61 100644 --- a/compton.c +++ b/compton.c @@ -567,7 +567,7 @@ wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset, if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length, False, rtype, &type, &format, &nitems, &after, &data) && nitems && (AnyPropertyType == type || type == rtype) - && (!format || format == rformat) + && (!rformat || format == rformat) && (8 == format || 16 == format || 32 == format)) { return (winprop_t) { .data.p8 = data, @@ -629,244 +629,20 @@ win_rounded_corners(session_t *ps, win *w) { XFree(rects); } -/** - * Match a window against a single window condition. - * - * @return true if matched, false otherwise. - */ -static bool -win_match_once(win *w, const wincond_t *cond) { - const char *target; - bool matched = false; - -#ifdef DEBUG_WINMATCH - printf("win_match_once(%#010lx \"%s\"): cond = %p", w->id, w->name, - cond); -#endif - - if (InputOnly == w->a.class) { -#ifdef DEBUG_WINMATCH - printf(": InputOnly\n"); -#endif - return false; - } - - // Determine the target - target = NULL; - switch (cond->target) { - case CONDTGT_NAME: - target = w->name; - break; - case CONDTGT_CLASSI: - target = w->class_instance; - break; - case CONDTGT_CLASSG: - target = w->class_general; - break; - case CONDTGT_ROLE: - target = w->role; - break; - } - - if (!target) { -#ifdef DEBUG_WINMATCH - printf(": Target not found\n"); -#endif - return false; - } - - // Determine pattern type and match - switch (cond->type) { - case CONDTP_EXACT: - if (cond->flags & CONDF_IGNORECASE) - matched = !strcasecmp(target, cond->pattern); - else - matched = !strcmp(target, cond->pattern); - break; - case CONDTP_ANYWHERE: - if (cond->flags & CONDF_IGNORECASE) - matched = strcasestr(target, cond->pattern); - else - matched = strstr(target, cond->pattern); - break; - case CONDTP_FROMSTART: - if (cond->flags & CONDF_IGNORECASE) - matched = !strncasecmp(target, cond->pattern, - strlen(cond->pattern)); - else - matched = !strncmp(target, cond->pattern, - strlen(cond->pattern)); - break; - case CONDTP_WILDCARD: - { - int flags = 0; - if (cond->flags & CONDF_IGNORECASE) - flags = FNM_CASEFOLD; - matched = !fnmatch(cond->pattern, target, flags); - } - break; - case CONDTP_REGEX_PCRE: -#ifdef CONFIG_REGEX_PCRE - matched = (pcre_exec(cond->regex_pcre, cond->regex_pcre_extra, - target, strlen(target), 0, 0, NULL, 0) >= 0); -#endif - break; - } - -#ifdef DEBUG_WINMATCH - printf(", matched = %d\n", matched); -#endif - - return matched; -} - -/** - * Match a window against a condition linked list. - * - * @param cache a place to cache the last matched condition - * @return true if matched, false otherwise. - */ -static bool -win_match(win *w, wincond_t *condlst, wincond_t **cache) { - // Check if the cached entry matches firstly - if (cache && *cache && win_match_once(w, *cache)) - return true; - - // Then go through the whole linked list - for (; condlst; condlst = condlst->next) { - if (win_match_once(w, condlst)) { - if (cache) - *cache = condlst; - return true; - } - } - - return false; -} - /** * Add a pattern to a condition linked list. */ static bool -condlst_add(wincond_t **pcondlst, const char *pattern) { +condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) { if (!pattern) return false; - unsigned plen = strlen(pattern); - wincond_t *cond; - const char *pos; - - if (plen < 4 || ':' != pattern[1] || !strchr(pattern + 2, ':')) { - printf("Pattern \"%s\": Format invalid.\n", pattern); - return false; - } - - // Allocate memory for the new condition - cond = malloc(sizeof(wincond_t)); - - // Determine the pattern target - switch (pattern[0]) { - case 'n': - cond->target = CONDTGT_NAME; - break; - case 'i': - cond->target = CONDTGT_CLASSI; - break; - case 'g': - cond->target = CONDTGT_CLASSG; - break; - case 'r': - cond->target = CONDTGT_ROLE; - break; - default: - printf("Pattern \"%s\": Target \"%c\" invalid.\n", - pattern, pattern[0]); - free(cond); - return false; - } - - // Determine the pattern type - switch (pattern[2]) { - case 'e': - cond->type = CONDTP_EXACT; - break; - case 'a': - cond->type = CONDTP_ANYWHERE; - break; - case 's': - cond->type = CONDTP_FROMSTART; - break; - case 'w': - cond->type = CONDTP_WILDCARD; - break; -#ifdef CONFIG_REGEX_PCRE - case 'p': - cond->type = CONDTP_REGEX_PCRE; - break; -#endif - default: - printf("Pattern \"%s\": Type \"%c\" invalid.\n", - pattern, pattern[2]); - free(cond); - return false; - } - - // Determine the pattern flags - pos = &pattern[3]; - cond->flags = 0; - while (':' != *pos) { - switch (*pos) { - case 'i': - cond->flags |= CONDF_IGNORECASE; - break; - default: - printf("Pattern \"%s\": Flag \"%c\" invalid.\n", - pattern, *pos); - break; - } - ++pos; - } - - // Copy the pattern - ++pos; - cond->pattern = NULL; -#ifdef CONFIG_REGEX_PCRE - cond->regex_pcre = NULL; - cond->regex_pcre_extra = NULL; -#endif - if (CONDTP_REGEX_PCRE == cond->type) { -#ifdef CONFIG_REGEX_PCRE - const char *error = NULL; - int erroffset = 0; - int options = 0; - - if (cond->flags & CONDF_IGNORECASE) - options |= PCRE_CASELESS; - - cond->regex_pcre = pcre_compile(pos, options, &error, &erroffset, - NULL); - if (!cond->regex_pcre) { - printf("Pattern \"%s\": PCRE regular expression parsing failed on " - "offset %d: %s\n", pattern, erroffset, error); - free(cond); - return false; - } -#ifdef CONFIG_REGEX_PCRE_JIT - cond->regex_pcre_extra = pcre_study(cond->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); - if (!cond->regex_pcre_extra) { - printf("Pattern \"%s\": PCRE regular expression study failed: %s", - pattern, error); - } -#endif +#ifdef CONFIG_C2 + if (!c2_parse(ps, pcondlst, pattern)) + exit(1); +#else + printf_errfq(1, "(): Condition support not compiled in."); #endif - } - else { - cond->pattern = mstrcpy(pos); - } - - // Insert it into the linked list - cond->next = *pcondlst; - *pcondlst = cond; return true; } @@ -2313,7 +2089,7 @@ win_determine_shadow(session_t *ps, win *w) { w->shadow = (UNSET == w->shadow_force ? (ps->o.wintype_shadow[w->window_type] - && !win_match(w, ps->o.shadow_blacklist, &w->cache_sblst) + && !win_match(ps, w, ps->o.shadow_blacklist, &w->cache_sblst) && !(ps->o.shadow_ignore_shaped && w->bounding_shaped && !w->rounded_corners) && !(ps->o.respect_prop_shadow && 0 == w->prop_shadow)) @@ -2347,7 +2123,7 @@ win_determine_invert_color(session_t *ps, win *w) { if (UNSET != w->invert_color_force) w->invert_color = w->invert_color_force; else - w->invert_color = win_match(w, ps->o.invert_color_list, &w->cache_ivclst); + w->invert_color = win_match(ps, w, ps->o.invert_color_list, &w->cache_ivclst); if (w->invert_color != invert_color_old) add_damage_win(ps, w); @@ -2361,13 +2137,15 @@ win_on_wtype_change(session_t *ps, win *w) { win_determine_shadow(ps, w); win_determine_fade(ps, w); win_update_focused(ps, w); + if (ps->o.invert_color_list) + win_determine_invert_color(ps, w); } /** * Function to be called on window data changes. */ static void -win_on_wdata_change(session_t *ps, win *w) { +win_on_factor_change(session_t *ps, win *w) { if (ps->o.shadow_blacklist) win_determine_shadow(ps, w); if (ps->o.fade_blacklist) @@ -2481,9 +2259,11 @@ win_mark_client(session_t *ps, win *w, Window client) { win_get_name(ps, w); win_get_class(ps, w); win_get_role(ps, w); - win_on_wdata_change(ps, w); } + // Update everything related to conditions + win_on_factor_change(ps, w); + // Update window focus state win_update_focused(ps, w); } @@ -3048,7 +2828,7 @@ win_update_focused(session_t *ps, win *w) { || (ps->o.mark_wmwin_focused && w->wmwin) || (ps->o.mark_ovredir_focused && w->id == w->client_win && !w->wmwin) - || win_match(w, ps->o.focus_blacklist, &w->cache_fcblst)) + || win_match(ps, w, ps->o.focus_blacklist, &w->cache_fcblst)) w->focused = true; // If window grouping detection is enabled, mark the window active if @@ -3102,6 +2882,9 @@ win_set_focused(session_t *ps, win *w, bool focused) { else { win_update_focused(ps, w); } + + // Update everything related to conditions + win_on_factor_change(ps, w); } } /** @@ -3153,6 +2936,9 @@ win_set_leader(session_t *ps, win *w, Window nleader) { else { win_update_focused(ps, w); } + + // Update everything related to conditions + win_on_factor_change(ps, w); } } @@ -3746,7 +3532,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) { win *w = find_toplevel(ps, ev->window); if (w && 1 == win_get_name(ps, w)) { - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3755,7 +3541,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { win *w = find_toplevel(ps, ev->window); if (w) { win_get_class(ps, w); - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3763,7 +3549,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { if (ps->o.track_wdata && ps->atom_role == ev->atom) { win *w = find_toplevel(ps, ev->window); if (w && 1 == win_get_role(ps, w)) { - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); } } @@ -3790,7 +3576,7 @@ ev_property_notify(session_t *ps, XPropertyEvent *ev) { if (!w) w = find_toplevel(ps, ev->window); if (w) - win_on_wdata_change(ps, w); + win_on_factor_change(ps, w); break; } } @@ -4095,24 +3881,7 @@ usage(void) { " inverted color. Resource-hogging, and is not well tested.\n" "--dbus\n" " Enable remote control via D-Bus. See the D-BUS API section in the\n" - " man page for more details.\n" - "\n" - "Format of a condition:\n" - "\n" - " condition = :[]:\n" - "\n" - " is one of \"n\" (window name), \"i\" (window class\n" - " instance), \"g\" (window general class), and \"r\"\n" - " (window role).\n" - "\n" - " is one of \"e\" (exact match), \"a\" (match anywhere),\n" - " \"s\" (match from start), \"w\" (wildcard), and \"p\" (PCRE\n" - " regular expressions, if compiled with the support).\n" - "\n" - " could be a series of flags. Currently the only defined\n" - " flag is \"i\" (ignore case).\n" - "\n" - " is the actual pattern string.\n"; + " man page for more details.\n"; fputs(usage_text , stderr); exit(1); @@ -4270,8 +4039,6 @@ open_config_file(char *cpath, char **ppath) { f = fopen(path, "r"); if (f && ppath) *ppath = path; - else - free(path); return f; } @@ -4356,7 +4123,7 @@ parse_vsync(session_t *ps, const char *optarg) { * Parse a condition list in configuration file. */ static void -parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, +parse_cfg_condlst(session_t *ps, const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) { config_setting_t *setting = config_lookup(pcfg, name); if (setting) { @@ -4364,12 +4131,12 @@ parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, if (config_setting_is_array(setting)) { int i = config_setting_length(setting); while (i--) { - condlst_add(pcondlst, config_setting_get_string_elem(setting, i)); + condlst_add(ps, pcondlst, config_setting_get_string_elem(setting, i)); } } // Treat it as a single pattern if it's a string else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { - condlst_add(pcondlst, config_setting_get_string(setting)); + condlst_add(ps, pcondlst, config_setting_get_string(setting)); } } } @@ -4378,7 +4145,7 @@ parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, * Parse a configuration file from default location. */ static void -parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { +parse_config(session_t *ps, struct options_tmp *pcfgtmp) { char *path = NULL; FILE *f; config_t cfg; @@ -4386,10 +4153,14 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { double dval = 0.0; const char *sval = NULL; - f = open_config_file(cpath, &path); + f = open_config_file(ps->o.config_file, &path); if (!f) { - if (cpath) - printf_errfq(1, "(): Failed to read the specified configuration file."); + if (ps->o.config_file) { + printf_errfq(1, "(): Failed to read configuration file \"%s\".", + ps->o.config_file); + free(ps->o.config_file); + ps->o.config_file = NULL; + } return; } @@ -4417,7 +4188,10 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { } config_set_auto_convert(&cfg, 1); - free(path); + if (path != ps->o.config_file) { + free(ps->o.config_file); + ps->o.config_file = path; + } // Get options from the configuration file. We don't do range checking // right now. It will be done later @@ -4512,11 +4286,11 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { lcfg_lookup_bool(&cfg, "detect-client-leader", &ps->o.detect_client_leader); // --shadow-exclude - parse_cfg_condlst(&cfg, &ps->o.shadow_blacklist, "shadow-exclude"); + parse_cfg_condlst(ps, &cfg, &ps->o.shadow_blacklist, "shadow-exclude"); // --focus-exclude - parse_cfg_condlst(&cfg, &ps->o.focus_blacklist, "focus-exclude"); + parse_cfg_condlst(ps, &cfg, &ps->o.focus_blacklist, "focus-exclude"); // --invert-color-include - parse_cfg_condlst(&cfg, &ps->o.invert_color_list, "invert-color-include"); + parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include"); // --blur-background lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background); // --blur-background-frame @@ -4554,7 +4328,7 @@ parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp) { * Process arguments and configuration files. */ static void -get_cfg(session_t *ps, int argc, char *const *argv) { +get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { const static char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hscnfFCaSzGb"; const static struct option longopts[] = { { "help", no_argument, NULL, 'h' }, @@ -4595,14 +4369,33 @@ get_cfg(session_t *ps, int argc, char *const *argv) { { NULL, 0, NULL, 0 }, }; + int o = 0, longopt_idx = -1, i = 0; + + if (first_pass) { + // Pre-parse the commandline arguments to check for --config and invalid + // switches + // Must reset optind to 0 here in case we reread the commandline + // arguments + optind = 1; + while (-1 != + (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { + if (256 == o) + ps->o.config_file = mstrcpy(optarg); + else if ('d' == o) + ps->o.display = mstrcpy(optarg); + else if ('?' == o || ':' == o) + usage(); + } + + return; + } + struct options_tmp cfgtmp = { .no_dock_shadow = false, .no_dnd_shadow = false, .menu_opacity = 1.0, }; bool shadow_enable = false, fading_enable = false; - int o, longopt_idx, i; - char *config_file = NULL; char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL)); for (i = 0; i < NUM_WINTYPES; ++i) { @@ -4611,21 +4404,8 @@ get_cfg(session_t *ps, int argc, char *const *argv) { ps->o.wintype_opacity[i] = 1.0; } - // Pre-parse the commandline arguments to check for --config and invalid - // switches - // Must reset optind to 0 here in case we reread the commandline - // arguments - optind = 1; - while (-1 != - (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { - if (256 == o) - config_file = mstrcpy(optarg); - else if ('?' == o || ':' == o) - usage(); - } - #ifdef CONFIG_LIBCONFIG - parse_config(ps, config_file, &cfgtmp); + parse_config(ps, &cfgtmp); #endif // Parse commandline arguments. Range checking will be done later. @@ -4643,7 +4423,6 @@ get_cfg(session_t *ps, int argc, char *const *argv) { usage(); break; case 'd': - ps->o.display = mstrcpy(optarg); break; case 'D': ps->o.fade_delta = atoi(optarg); @@ -4732,7 +4511,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 263: // --shadow-exclude - condlst_add(&ps->o.shadow_blacklist, optarg); + condlst_add(ps, &ps->o.shadow_blacklist, optarg); break; case 264: // --mark-ovredir-focused @@ -4796,7 +4575,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 279: // --focus-exclude - condlst_add(&ps->o.focus_blacklist, optarg); + condlst_add(ps, &ps->o.focus_blacklist, optarg); break; case 280: // --inactive-dim-fixed @@ -4832,7 +4611,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { break; case 288: // --invert-color-include - condlst_add(&ps->o.invert_color_list, optarg); + condlst_add(ps, &ps->o.invert_color_list, optarg); break; default: usage(); @@ -4884,11 +4663,6 @@ get_cfg(session_t *ps, int argc, char *const *argv) { ps->o.track_focus = true; } - // Determine whether we need to track window name and class - if (ps->o.shadow_blacklist || ps->o.fade_blacklist - || ps->o.focus_blacklist || ps->o.invert_color_list) - ps->o.track_wdata = true; - // Determine whether we track window grouping if (ps->o.detect_transient || ps->o.detect_client_leader) { ps->o.track_leader = true; @@ -5702,7 +5476,8 @@ session_init(session_t *ps_old, int argc, char **argv) { ps->o.wintype_focus[WINTYPE_NORMAL] = false; ps->o.wintype_focus[WINTYPE_UTILITY] = false; - get_cfg(ps, argc, argv); + // First pass + get_cfg(ps, argc, argv, true); // Inherit old Display if possible, primarily for resource leak checking if (ps_old && ps_old->dpy) @@ -5712,11 +5487,13 @@ session_init(session_t *ps_old, int argc, char **argv) { if (!ps->dpy) { ps->dpy = XOpenDisplay(ps->o.display); if (!ps->dpy) { - fprintf(stderr, "Can't open display\n"); - exit(1); + printf_errfq(1, "(): Can't open display."); } } + // Second pass + get_cfg(ps, argc, argv, false); + XSetErrorHandler(error); if (ps->o.synchronize) { XSynchronize(ps->dpy, 1); @@ -5973,11 +5750,24 @@ session_destroy(session_t *ps) { ps->alpha_picts = NULL; } +#ifdef CONFIG_C2 // Free blacklists free_wincondlst(&ps->o.shadow_blacklist); free_wincondlst(&ps->o.fade_blacklist); free_wincondlst(&ps->o.focus_blacklist); free_wincondlst(&ps->o.invert_color_list); +#endif + + // Free tracked atom list + { + latom_t *next = NULL; + for (latom_t *this = ps->track_atom_lst; this; this = next) { + next = this->next; + free(this); + } + + ps->track_atom_lst = NULL; + } // Free ignore linked list { @@ -6025,6 +5815,7 @@ session_destroy(session_t *ps) { free(ps->gaussian_map); free(ps->o.display); free(ps->o.logpath); + free(ps->o.config_file); free(ps->pfds_read); free(ps->pfds_write); free(ps->pfds_except); @@ -6164,11 +5955,6 @@ main(int argc, char **argv) { printf_errf("Failed to create new session."); return 1; } -#ifdef DEBUG_C2 - // c2_parse(ps_g, NULL, "name ~= \"master\""); - // c2_parse(ps_g, NULL, "n:e:Notification"); - c2_parse(ps_g, NULL, "(WM_NAME:16s = 'Notification' || class_g = 'fox') && class_g = 'fox'"); -#endif session_run(ps_g); ps_old = ps_g; session_destroy(ps_g); diff --git a/compton.h b/compton.h index 7670424e9..75d517ec8 100644 --- a/compton.h +++ b/compton.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #ifdef CONFIG_VSYNC_DRM @@ -165,37 +164,16 @@ free_damage(session_t *ps, Damage *p) { } } +#ifdef CONFIG_C2 /** - * Destroy a wincond_t. + * Destroy a condition list. */ -inline static void -free_wincond(wincond_t *cond) { - if (cond->pattern) - free(cond->pattern); -#ifdef CONFIG_REGEX_PCRE - if (cond->regex_pcre_extra) - LPCRE_FREE_STUDY(cond->regex_pcre_extra); - if (cond->regex_pcre) - pcre_free(cond->regex_pcre); -#endif - free(cond); -} - -/** - * Destroy a linked list of wincond_t. - */ -inline static void -free_wincondlst(wincond_t **cond_lst) { - wincond_t *next = NULL; - - for (wincond_t *cond = *cond_lst; cond; cond = next) { - next = cond->next; - - free_wincond(cond); - } - - *cond_lst = NULL; +static inline void +free_wincondlst(c2_lptr_t **pcondlst) { + while ((*pcondlst = c2_free_lptr(*pcondlst))) + continue; } +#endif /** * Destroy all resources in a struct _win. @@ -396,14 +374,20 @@ win_is_fullscreen(session_t *ps, const win *w) { static void win_rounded_corners(session_t *ps, win *w); -static bool -win_match_once(win *w, const wincond_t *cond); - -static bool -win_match(win *w, wincond_t *condlst, wincond_t * *cache); +/** + * Wrapper of c2_match(). + */ +static inline bool +win_match(session_t *ps, win *w, c2_lptr_t *condlst, const c2_lptr_t **cache) { +#ifdef CONFIG_C2 + return c2_match(ps, w, condlst, cache); +#else + return false; +#endif +} static bool -condlst_add(wincond_t **pcondlst, const char *pattern); +condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern); static long determine_evmask(session_t *ps, Window wid, win_evmode_t mode); @@ -594,7 +578,7 @@ static void win_on_wtype_change(session_t *ps, win *w); static void -win_on_wdata_change(session_t *ps, win *w); +win_on_factor_change(session_t *ps, win *w); static void win_upd_run(session_t *ps, win *w, win_upd_t *pupd); @@ -858,15 +842,15 @@ static FILE * open_config_file(char *cpath, char **path); static void -parse_cfg_condlst(const config_t *pcfg, wincond_t **pcondlst, +parse_cfg_condlst(session_t *ps, const config_t *pcfg, c2_lptr_t **pcondlst, const char *name); static void -parse_config(session_t *ps, char *cpath, struct options_tmp *pcfgtmp); +parse_config(session_t *ps, struct options_tmp *pcfgtmp); #endif static void -get_cfg(session_t *ps, int argc, char *const *argv); +get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass); static void init_atoms(session_t *ps); diff --git a/dbus.c b/dbus.c index a86ed174d..44f1369d7 100644 --- a/dbus.c +++ b/dbus.c @@ -859,6 +859,7 @@ cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { return true; } + cdbus_m_opts_get_do(config_file, cdbus_reply_string); cdbus_m_opts_get_do(mark_wmwin_focused, cdbus_reply_bool); cdbus_m_opts_get_do(mark_ovredir_focused, cdbus_reply_bool); cdbus_m_opts_get_do(fork_after_register, cdbus_reply_bool);