diff options
author | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2014-03-31 23:22:00 -0500 |
---|---|---|
committer | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2014-03-31 23:22:51 -0500 |
commit | 0f7f449bfc2da30521c39332ded5d6f72e239a16 (patch) | |
tree | 39157da56af257e49aa9ef9c61b609e343d2df83 | |
parent | 24e34ad9892c4c5bf774bd0c32e369be52954936 (diff) | |
parent | fb41c5018bbe1808176636ac6c51e2b927cda8f2 (diff) | |
download | tdebase-0f7f449bfc2da30521c39332ded5d6f72e239a16.tar.gz tdebase-0f7f449bfc2da30521c39332ded5d6f72e239a16.zip |
Merge working compton-tde branch
-rw-r--r-- | twin/compton-tde/c2.c | 1315 | ||||
-rw-r--r-- | twin/compton-tde/c2.h | 350 | ||||
-rw-r--r-- | twin/compton-tde/common.h | 2413 | ||||
-rw-r--r-- | twin/compton-tde/compton.c | 7854 | ||||
-rw-r--r-- | twin/compton-tde/compton.h | 1321 | ||||
-rw-r--r-- | twin/compton-tde/dbus.c | 1195 | ||||
-rw-r--r-- | twin/compton-tde/dbus.h | 252 | ||||
-rw-r--r-- | twin/compton-tde/man/compton-tde-trans.1.html | 897 | ||||
-rw-r--r-- | twin/compton-tde/man/compton-tde.1.html | 1603 | ||||
-rw-r--r-- | twin/compton-tde/man/compton-trans.1 | 201 | ||||
-rw-r--r-- | twin/compton-tde/man/compton.1 | 904 | ||||
-rw-r--r-- | twin/compton-tde/opengl.c | 1741 | ||||
-rw-r--r-- | twin/compton-tde/opengl.h | 145 |
13 files changed, 20191 insertions, 0 deletions
diff --git a/twin/compton-tde/c2.c b/twin/compton-tde/c2.c new file mode 100644 index 000000000..de221c01d --- /dev/null +++ b/twin/compton-tde/c2.c @@ -0,0 +1,1315 @@ +/* + * 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_parsed(session_t *ps, 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(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; + plptr->data = data; + 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_PX: tgt = w->a.x; break; + case C2_L_PY: tgt = w->a.y; break; + case C2_L_PX2: tgt = w->a.x + w->widthb; break; + case C2_L_PY2: tgt = w->a.y + w->heightb; break; + case C2_L_PWIDTH: tgt = w->a.width; break; + case C2_L_PHEIGHT: tgt = w->a.height; break; + case C2_L_PWIDTHB: tgt = w->widthb; break; + case C2_L_PHEIGHTB: tgt = w->heightb; break; + case C2_L_PBDW: tgt = w->a.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 = (WMODE_ARGB == w->mode); break; + case C2_L_PFOCUSED: tgt = win_is_focused_real(ps, w); 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) + 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 + 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 + * @param pdata a place to return the data + * @return true if matched, false otherwise. + */ +bool +c2_matchd(session_t *ps, win *w, const c2_lptr_t *condlst, + const c2_lptr_t **cache, void **pdata) { + // 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; +} + diff --git a/twin/compton-tde/c2.h b/twin/compton-tde/c2.h new file mode 100644 index 000000000..129a5e739 --- /dev/null +++ b/twin/compton-tde/c2.h @@ -0,0 +1,350 @@ +/* + * 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 <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 + +#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, \ +} + +const static 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_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, \ +} + +const static 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. +const static 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_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 }, +}; + +#define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1)) + +/** + * 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 = malloc(sizeof(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(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/twin/compton-tde/common.h b/twin/compton-tde/common.h new file mode 100644 index 000000000..275b7b671 --- /dev/null +++ b/twin/compton-tde/common.h @@ -0,0 +1,2413 @@ +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net> + * See LICENSE for more information. + * + */ + +#ifndef COMPTON_COMMON_H +#define COMPTON_COMMON_H + +// === Options === + +// Debug options, enable them using -D in CFLAGS +// #define DEBUG_REPAINT 1 +// #define DEBUG_EVENTS 1 +// #define DEBUG_RESTACK 1 +// #define DEBUG_WINTYPE 1 +// #define DEBUG_CLIENTWIN 1 +// #define DEBUG_WINDATA 1 +// #define DEBUG_WINMATCH 1 +// #define DEBUG_REDIR 1 +// #define DEBUG_ALLOC_REG 1 +// #define DEBUG_FRAME 1 +// #define DEBUG_LEADER 1 +// #define DEBUG_C2 1 +// #define DEBUG_GLX 1 +// #define DEBUG_GLX_GLSL 1 +// #define DEBUG_GLX_ERR 1 +// #define DEBUG_GLX_MARK 1 +// #define DEBUG_GLX_PAINTREG 1 +// #define MONITOR_REPAINT 1 + +// Whether to enable PCRE regular expression support in blacklists, enabled +// by default +// #define CONFIG_REGEX_PCRE 1 +// Whether to enable JIT support of libpcre. This may cause problems on PaX +// kernels. +// #define CONFIG_REGEX_PCRE_JIT 1 +// Whether to enable parsing of configuration files using libconfig. +// #define CONFIG_LIBCONFIG 1 +// Whether we are using a legacy version of libconfig (1.3.x). +// #define CONFIG_LIBCONFIG_LEGACY 1 +// Whether to enable DRM VSync support +// #define CONFIG_VSYNC_DRM 1 +// Whether to enable OpenGL support +// #define CONFIG_VSYNC_OPENGL 1 +// Whether to enable GLX GLSL support +// #define CONFIG_VSYNC_OPENGL_GLSL 1 +// Whether to enable GLX FBO support +// #define CONFIG_VSYNC_OPENGL_FBO 1 +// Whether to enable DBus support with libdbus. +// #define CONFIG_DBUS 1 +// Whether to enable condition support. +// #define CONFIG_C2 1 +// Whether to enable X Sync support. +// #define CONFIG_XSYNC 1 +// Whether to enable GLX Sync support. +// #define CONFIG_GLX_XSYNC 1 + +// TDE specific options +// #define USE_ENV_HOME 1 +#define WRITE_PID_FILE 1 +#define _TDE_COMP_MGR_VERSION_ 3.00 + +#if !defined(CONFIG_C2) && defined(DEBUG_C2) +#error Cannot enable c2 debugging without c2 support. +#endif + +#if (!defined(CONFIG_XSYNC) || !defined(CONFIG_VSYNC_OPENGL)) && defined(CONFIG_GLX_SYNC) +#error Cannot enable GL sync without X Sync / OpenGL support. +#endif + +#ifndef COMPTON_VERSION +#define COMPTON_VERSION "unknown" +#endif + +// === Includes === + +// For some special functions +#define _GNU_SOURCE + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <sys/poll.h> +#include <assert.h> +#include <time.h> +#include <ctype.h> +#include <sys/time.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xcomposite.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/shape.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/Xdbe.h> +#ifdef CONFIG_XSYNC +#include <X11/extensions/sync.h> +#endif + +#ifdef CONFIG_XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +// Workarounds for missing definitions in very old versions of X headers, +// thanks to consolers for reporting +#ifndef PictOpDifference +#define PictOpDifference 0x39 +#endif + +// libconfig +#ifdef CONFIG_LIBCONFIG +#include <libgen.h> +#include <libconfig.h> +#endif + +// libdbus +#ifdef CONFIG_DBUS +#include <dbus/dbus.h> +#endif + +// libGL +#ifdef CONFIG_VSYNC_OPENGL +#if defined(CONFIG_VSYNC_OPENGL_GLSL) || defined(CONFIG_VSYNC_OPENGL_FBO) +#define GL_GLEXT_PROTOTYPES +#endif + +#include <GL/glx.h> + +// Workarounds for missing definitions in some broken GL drivers, thanks to +// douglasp and consolers for reporting +#ifndef GL_TEXTURE_RECTANGLE +#define GL_TEXTURE_RECTANGLE 0x84F5 +#endif + +#ifndef GLX_BACK_BUFFER_AGE_EXT +#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 +#endif + +#endif + +// === Macros === + +#define MSTR_(s) #s +#define MSTR(s) MSTR_(s) + +/// @brief Wrapper for gcc branch prediction builtin, for likely branch. +#define likely(x) __builtin_expect(!!(x), 1) + +/// @brief Wrapper for gcc branch prediction builtin, for unlikely branch. +#define unlikely(x) __builtin_expect(!!(x), 0) + +/// Print out an error message. +#define printf_err(format, ...) \ + fprintf(stderr, format "\n", ## __VA_ARGS__) + +/// Print out an error message with function name. +#define printf_errf(format, ...) \ + printf_err("%s" format, __func__, ## __VA_ARGS__) + +/// Print out an error message with function name, and quit with a +/// specific exit code. +#define printf_errfq(code, format, ...) { \ + printf_err("%s" format, __func__, ## __VA_ARGS__); \ + exit(code); \ +} + +/// Print out a debug message. +#define printf_dbg(format, ...) \ + printf(format, ## __VA_ARGS__); \ + fflush(stdout) + +/// Print out a debug message with function name. +#define printf_dbgf(format, ...) \ + printf_dbg("%s" format, __func__, ## __VA_ARGS__) + +// Use #s here to prevent macro expansion +/// Macro used for shortening some debugging code. +#define CASESTRRET(s) case s: return #s + +// === Constants === +#if !(COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2) +#error libXcomposite version unsupported +#endif + +/// @brief Length of generic buffers. +#define BUF_LEN 80 + +#define ROUNDED_PERCENT 0.05 +#define ROUNDED_PIXELS 10 + +#define OPAQUE 0xffffffff +#define REGISTER_PROP "_NET_WM_CM_S" + +#define TIME_MS_MAX LONG_MAX +#define FADE_DELTA_TOLERANCE 0.2 +#define SWOPTI_TOLERANCE 3000 +#define TIMEOUT_RUN_TOLERANCE 0.05 +#define WIN_GET_LEADER_MAX_RECURSION 20 + +#define SEC_WRAP (15L * 24L * 60L * 60L) + +#define NS_PER_SEC 1000000000L +#define US_PER_SEC 1000000L +#define MS_PER_SEC 1000 + +#define XRFILTER_CONVOLUTION "convolution" +#define XRFILTER_GUASSIAN "gaussian" +#define XRFILTER_BINOMIAL "binomial" + +/// @brief Maximum OpenGL FBConfig depth. +#define OPENGL_MAX_DEPTH 32 + +/// @brief Maximum OpenGL buffer age. +#define CGLX_MAX_BUFFER_AGE 5 + +/// @brief Maximum passes for blur. +#define MAX_BLUR_PASS 5 + +// Window flags + +// Window size is changed +#define WFLAG_SIZE_CHANGE 0x0001 +// Window size/position is changed +#define WFLAG_POS_CHANGE 0x0002 +// Window opacity / dim state changed +#define WFLAG_OPCT_CHANGE 0x0004 + +// === Types === + +typedef uint32_t opacity_t; +typedef long time_ms_t; + +typedef enum { + WINTYPE_UNKNOWN, + WINTYPE_DESKTOP, + WINTYPE_DOCK, + WINTYPE_TOOLBAR, + WINTYPE_MENU, + WINTYPE_UTILITY, + WINTYPE_SPLASH, + WINTYPE_DIALOG, + WINTYPE_NORMAL, + WINTYPE_DROPDOWN_MENU, + WINTYPE_POPUP_MENU, + WINTYPE_TOOLTIP, + WINTYPE_NOTIFY, + WINTYPE_COMBO, + WINTYPE_DND, + NUM_WINTYPES +} wintype_t; + +/// Enumeration type to represent switches. +typedef enum { + OFF, // false + ON, // true + UNSET +} switch_t; + +/// Structure representing a X geometry. +typedef struct { + int wid; + int hei; + int x; + int y; +} geometry_t; + +/// Enumeration type of window painting mode. +typedef enum { + WMODE_TRANS, + WMODE_SOLID, + WMODE_ARGB +} winmode_t; + +/// Structure representing needed window updates. +typedef struct { + bool shadow : 1; + bool fade : 1; + bool focus : 1; + bool invert_color : 1; +} win_upd_t; + +/// Structure representing Window property value. +typedef struct { + // All pointers have the same length, right? + // I wanted to use anonymous union but it's a GNU extension... + union { + unsigned char *p8; + short *p16; + long *p32; + } data; + unsigned long nitems; + Atom type; + int format; +} winprop_t; + +typedef struct _ignore { + struct _ignore *next; + unsigned long sequence; +} ignore_t; + +enum wincond_target { + CONDTGT_NAME, + CONDTGT_CLASSI, + CONDTGT_CLASSG, + CONDTGT_ROLE, +}; + +enum wincond_type { + CONDTP_EXACT, + CONDTP_ANYWHERE, + CONDTP_FROMSTART, + CONDTP_WILDCARD, + CONDTP_REGEX_PCRE, +}; + +#define CONDF_IGNORECASE 0x0001 + +/// VSync modes. +typedef enum { + VSYNC_NONE, + VSYNC_DRM, + VSYNC_OPENGL, + VSYNC_OPENGL_OML, + VSYNC_OPENGL_SWC, + VSYNC_OPENGL_MSWC, + NUM_VSYNC, +} vsync_t; + +/// @brief Possible backends of compton. +enum backend { + BKEND_XRENDER, + BKEND_GLX, + BKEND_XR_GLX_HYBRID, + NUM_BKEND, +}; + +/// @brief Possible swap methods. +enum { + SWAPM_BUFFER_AGE = -1, + SWAPM_UNDEFINED = 0, + SWAPM_COPY = 1, + SWAPM_EXCHANGE = 2, +}; + +typedef struct _glx_texture glx_texture_t; + +#ifdef CONFIG_VSYNC_OPENGL +#ifdef DEBUG_GLX_DEBUG_CONTEXT +typedef GLXContext (*f_glXCreateContextAttribsARB) (Display *dpy, + GLXFBConfig config, GLXContext share_context, Bool direct, + const int *attrib_list); +typedef void (*GLDEBUGPROC) (GLenum source, GLenum type, + GLuint id, GLenum severity, GLsizei length, const GLchar* message, + GLvoid* userParam); +typedef void (*f_DebugMessageCallback) (GLDEBUGPROC, void *userParam); +#endif + +typedef int (*f_WaitVideoSync) (int, int, unsigned *); +typedef int (*f_GetVideoSync) (unsigned *); + +typedef Bool (*f_GetSyncValuesOML) (Display* dpy, GLXDrawable drawable, int64_t* ust, int64_t* msc, int64_t* sbc); +typedef Bool (*f_WaitForMscOML) (Display* dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t* ust, int64_t* msc, int64_t* sbc); + +typedef int (*f_SwapIntervalSGI) (int interval); +typedef int (*f_SwapIntervalMESA) (unsigned int interval); + +typedef void (*f_BindTexImageEXT) (Display *display, GLXDrawable drawable, int buffer, const int *attrib_list); +typedef void (*f_ReleaseTexImageEXT) (Display *display, GLXDrawable drawable, int buffer); + +typedef void (*f_CopySubBuffer) (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height); + +#ifdef CONFIG_GLX_SYNC +// Looks like duplicate typedef of the same type is safe? +typedef int64_t GLint64; +typedef uint64_t GLuint64; +typedef struct __GLsync *GLsync; + +#ifndef GL_SYNC_FLUSH_COMMANDS_BIT +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#endif + +#ifndef GL_TIMEOUT_IGNORED +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#endif + +#ifndef GL_ALREADY_SIGNALED +#define GL_ALREADY_SIGNALED 0x911A +#endif + +#ifndef GL_TIMEOUT_EXPIRED +#define GL_TIMEOUT_EXPIRED 0x911B +#endif + +#ifndef GL_CONDITION_SATISFIED +#define GL_CONDITION_SATISFIED 0x911C +#endif + +#ifndef GL_WAIT_FAILED +#define GL_WAIT_FAILED 0x911D +#endif + +typedef GLsync (*f_FenceSync) (GLenum condition, GLbitfield flags); +typedef GLboolean (*f_IsSync) (GLsync sync); +typedef void (*f_DeleteSync) (GLsync sync); +typedef GLenum (*f_ClientWaitSync) (GLsync sync, GLbitfield flags, + GLuint64 timeout); +typedef void (*f_WaitSync) (GLsync sync, GLbitfield flags, + GLuint64 timeout); +typedef GLsync (*f_ImportSyncEXT) (GLenum external_sync_type, + GLintptr external_sync, GLbitfield flags); +#endif + +#ifdef DEBUG_GLX_MARK +typedef void (*f_StringMarkerGREMEDY) (GLsizei len, const void *string); +typedef void (*f_FrameTerminatorGREMEDY) (void); +#endif + +/// @brief Wrapper of a GLX FBConfig. +typedef struct { + GLXFBConfig cfg; + GLint texture_fmt; + GLint texture_tgts; + bool y_inverted; +} glx_fbconfig_t; + +/// @brief Wrapper of a binded GLX texture. +struct _glx_texture { + GLuint texture; + GLXPixmap glpixmap; + Pixmap pixmap; + GLenum target; + unsigned width; + unsigned height; + unsigned depth; + bool y_inverted; +}; +#endif + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +typedef struct { + /// Fragment shader for blur. + GLuint frag_shader; + /// GLSL program for blur. + GLuint prog; + /// Location of uniform "offset_x" in blur GLSL program. + GLint unifm_offset_x; + /// Location of uniform "offset_y" in blur GLSL program. + GLint unifm_offset_y; + /// Location of uniform "factor_center" in blur GLSL program. + GLint unifm_factor_center; +} glx_blur_pass_t; + +typedef struct { + /// Framebuffer used for blurring. + GLuint fbo; + /// Textures used for blurring. + GLuint textures[2]; + /// Width of the textures. + int width; + /// Height of the textures. + int height; +} glx_blur_cache_t; +#endif + +typedef struct { + Pixmap pixmap; + Picture pict; + glx_texture_t *ptex; +} paint_t; + +#define PAINT_INIT { .pixmap = None, .pict = None } + +typedef struct { + int size; + double *data; +} conv; + +/// Linked list type of atoms. +typedef struct _latom { + Atom atom; + struct _latom *next; +} latom_t; + +/// A representation of raw region data +typedef struct { + XRectangle *rects; + int nrects; +} reg_data_t; + +#define REG_DATA_INIT { NULL, 0 } + +struct _timeout_t; + +struct _win; + +typedef struct _c2_lptr c2_lptr_t; + +/// Structure representing all options. +typedef struct _options_t { + // === General === + /// The configuration file we used. + char *config_file; + /// Path to write PID to. + char *write_pid_path; + /// The display name we used. NULL means we are using the value of the + /// <code>DISPLAY</code> environment variable. + char *display; + /// Safe representation of display name. + char *display_repr; + /// The backend in use. + enum backend backend; + /// Whether to sync X drawing to avoid certain delay issues with + /// GLX backend. + bool xrender_sync; + /// Whether to sync X drawing with X Sync fence. + bool xrender_sync_fence; + /// Whether to avoid using stencil buffer under GLX backend. Might be + /// unsafe. + bool glx_no_stencil; + /// Whether to copy unmodified regions from front buffer. + bool glx_copy_from_front; + /// Whether to use glXCopySubBufferMESA() to update screen. + bool glx_use_copysubbuffermesa; + /// Whether to avoid rebinding pixmap on window damage. + bool glx_no_rebind_pixmap; + /// GLX swap method we assume OpenGL uses. + int glx_swap_method; + /// Whether to use GL_EXT_gpu_shader4 to (hopefully) accelerates blurring. + bool glx_use_gpushader4; + /// Whether to try to detect WM windows and mark them as focused. + bool mark_wmwin_focused; + /// Whether to mark override-redirect windows as focused. + bool mark_ovredir_focused; + /// Whether to fork to background. + bool fork_after_register; + /// Whether to detect rounded corners. + bool detect_rounded_corners; + /// Whether to paint on X Composite overlay window instead of root + /// window. + bool paint_on_overlay; + /// Resize damage for a specific number of pixels. + int resize_damage; + /// Whether to unredirect all windows if a full-screen opaque window + /// is detected. + bool unredir_if_possible; + /// List of conditions of windows to ignore as a full-screen window + /// when determining if a window could be unredirected. + c2_lptr_t *unredir_if_possible_blacklist; + /// Delay before unredirecting screen. + time_ms_t unredir_if_possible_delay; + /// Forced redirection setting through D-Bus. + switch_t redirected_force; + /// Whether to stop painting. Controlled through D-Bus. + switch_t stoppaint_force; + /// Whether to enable D-Bus support. + bool dbus; + /// Path to log file. + char *logpath; + /// Number of cycles to paint in benchmark mode. 0 for disabled. + int benchmark; + /// Window to constantly repaint in benchmark mode. 0 for full-screen. + Window benchmark_wid; + /// A list of conditions of windows not to paint. + c2_lptr_t *paint_blacklist; + /// Whether to work under synchronized mode for debugging. + bool synchronize; + + // === VSync & software optimization === + /// User-specified refresh rate. + int refresh_rate; + /// Whether to enable refresh-rate-based software optimization. + bool sw_opti; + /// VSync method to use; + vsync_t vsync; + /// Whether to enable double buffer. + bool dbe; + /// Whether to do VSync aggressively. + bool vsync_aggressive; + /// Whether to use glFinish() instead of glFlush() for (possibly) better + /// VSync yet probably higher CPU usage. + bool vsync_use_glfinish; + + // === Shadow === + /// Enable/disable shadow for specific window types. + bool wintype_shadow[NUM_WINTYPES]; + /// Red, green and blue tone of the shadow. + double shadow_red, shadow_green, shadow_blue; + int shadow_radius; + int shadow_offset_x, shadow_offset_y; + double shadow_opacity; + bool clear_shadow; + /// Geometry of a region in which shadow is not painted on. + geometry_t shadow_exclude_reg_geom; + /// Shadow blacklist. A linked list of conditions. + c2_lptr_t *shadow_blacklist; + /// Whether bounding-shaped window should be ignored. + bool shadow_ignore_shaped; + /// Whether to respect _TDE_WM_WINDOW_SHADOW. + bool respect_prop_shadow; + /// Whether to crop shadow to the very Xinerama screen. + bool xinerama_shadow_crop; + + // === Fading === + /// Enable/disable fading for specific window types. + bool wintype_fade[NUM_WINTYPES]; + /// How much to fade in in a single fading step. + opacity_t fade_in_step; + /// How much to fade out in a single fading step. + opacity_t fade_out_step; + /// Fading time delta. In milliseconds. + time_ms_t fade_delta; + /// Whether to disable fading on window open/close. + bool no_fading_openclose; + /// Whether to disable fading on opacity change + bool no_fading_opacitychange; + /// Fading blacklist. A linked list of conditions. + c2_lptr_t *fade_blacklist; + + // === Opacity === + /// Default opacity for specific window types + double wintype_opacity[NUM_WINTYPES]; + /// Default opacity for inactive windows. + /// 32-bit integer with the format of _NET_WM_OPACITY. 0 stands for + /// not enabled, default. + opacity_t inactive_opacity; + /// Default opacity for inactive windows. + opacity_t active_opacity; + /// Whether inactive_opacity overrides the opacity set by window + /// attributes. + bool inactive_opacity_override; + /// Frame opacity. Relative to window opacity, also affects shadow + /// opacity. + double frame_opacity; + /// Whether to detect _NET_WM_OPACITY on client windows. Used on window + /// managers that don't pass _NET_WM_OPACITY to frame windows. + bool detect_client_opacity; + /// Step for pregenerating alpha pictures. 0.01 - 1.0. + double alpha_step; + + // === Other window processing === + /// Whether to blur background of semi-transparent / ARGB windows. + bool blur_background; + /// Whether to blur background when the window frame is not opaque. + /// Implies blur_background. + bool blur_background_frame; + /// Whether to use fixed blur strength instead of adjusting according + /// to window opacity. + bool blur_background_fixed; + /// Background blur blacklist. A linked list of conditions. + c2_lptr_t *blur_background_blacklist; + /// Blur convolution kernel. + XFixed *blur_kerns[MAX_BLUR_PASS]; + /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable. + double inactive_dim; + /// Whether to use fixed inactive dim opacity, instead of deciding + /// based on window opacity. + bool inactive_dim_fixed; + /// Conditions of windows to have inverted colors. + c2_lptr_t *invert_color_list; + /// Rules to change window opacity. + c2_lptr_t *opacity_rules; + + // === Focus related === + /// Consider windows of specific types to be always focused. + bool wintype_focus[NUM_WINTYPES]; + /// 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. + c2_lptr_t *focus_blacklist; + /// Whether to do window grouping with <code>WM_TRANSIENT_FOR</code>. + bool detect_transient; + /// Whether to do window grouping with <code>WM_CLIENT_LEADER</code>. + bool detect_client_leader; + + // === Calculated === + /// Whether compton needs to track focus changes. + bool track_focus; + /// Whether compton needs to track window name and class. + bool track_wdata; + /// Whether compton needs to track window leaders. + bool track_leader; +} options_t; + +/// Structure containing all necessary data for a compton session. +typedef struct _session_t { + // === Display related === + /// Display in use. + Display *dpy; + /// Default screen. + int scr; + /// Default visual. + Visual *vis; + /// Default depth. + int depth; + /// Root window. + Window root; + /// Height of root window. + int root_height; + /// Width of root window. + int root_width; + // Damage of root window. + // Damage root_damage; + /// X Composite overlay window. Used if <code>--paint-on-overlay</code>. + Window overlay; + /// Whether the root tile is filled by compton. + bool root_tile_fill; + /// Picture of the root window background. + paint_t root_tile_paint; + /// A region of the size of the screen. + XserverRegion screen_reg; + /// Picture of root window. Destination of painting in no-DBE painting + /// mode. + Picture root_picture; + /// A Picture acting as the painting target. + Picture tgt_picture; + /// Temporary buffer to paint to before sending to display. + paint_t tgt_buffer; +#ifdef CONFIG_XSYNC + XSyncFence tgt_buffer_fence; +#endif + /// DBE back buffer for root window. Used in DBE painting mode. + XdbeBackBuffer root_dbe; + /// Window ID of the window we register as a symbol. + Window reg_win; + + // === Operation related === + /// Program options. + options_t o; + /// File descriptors to check for reading. + fd_set *pfds_read; + /// File descriptors to check for writing. + fd_set *pfds_write; + /// File descriptors to check for exceptions. + fd_set *pfds_except; + /// Largest file descriptor in fd_set-s above. + int nfds_max; + /// Linked list of all timeouts. + struct _timeout_t *tmout_lst; + /// Timeout for delayed unredirection. + struct _timeout_t *tmout_unredir; + /// Whether we have hit unredirection timeout. + bool tmout_unredir_hit; + /// Whether we have received an event in this cycle. + bool ev_received; + /// Whether the program is idling. I.e. no fading, no potential window + /// changes. + bool idling; + /// Program start time. + struct timeval time_start; + /// The region needs to painted on next paint. + XserverRegion all_damage; + /// The region damaged on the last paint. + XserverRegion all_damage_last[CGLX_MAX_BUFFER_AGE]; + /// Whether all windows are currently redirected. + bool redirected; + /// Pre-generated alpha pictures. + Picture *alpha_picts; + /// Whether all reg_ignore of windows should expire in this paint. + bool reg_ignore_expire; + /// Time of last fading. In milliseconds. + time_ms_t fade_time; + /// Head pointer of the error ignore linked list. + ignore_t *ignore_head; + /// Pointer to the <code>next</code> member of tail element of the error + /// ignore linked list. + ignore_t **ignore_tail; +#ifdef CONFIG_VSYNC_OPENGL + /// Current GLX Z value. + int glx_z; +#endif + // Cached blur convolution kernels. + XFixed *blur_kerns_cache[MAX_BLUR_PASS]; + /// Reset program after next paint. + bool reset; + + // === Expose event related === + /// Pointer to an array of <code>XRectangle</code>-s of exposed region. + XRectangle *expose_rects; + /// Number of <code>XRectangle</code>-s in <code>expose_rects</code>. + int size_expose; + /// Index of the next free slot in <code>expose_rects</code>. + int n_expose; + + // === Window related === + /// Linked list of all windows. + struct _win *list; + /// Pointer to <code>win</code> of current active window. Used by + /// EWMH <code>_NET_ACTIVE_WINDOW</code> focus detection. In theory, + /// it's more reliable to store the window ID directly here, just in + /// case the WM does something extraordinary, but caching the pointer + /// means another layer of complexity. + struct _win *active_win; + /// Window ID of leader window of currently active window. Used for + /// subsidiary window detection. + Window active_leader; + + // === Shadow/dimming related === + /// 1x1 black Picture. + Picture black_picture; + /// 1x1 Picture of the shadow color. + Picture cshadow_picture; + /// 1x1 white Picture. + Picture white_picture; + /// Gaussian map of shadow. + conv *gaussian_map; + // for shadow precomputation + /// Shadow depth on one side. + int cgsize; + /// Pre-computed color table for corners of shadow. + unsigned char *shadow_corner; + /// Pre-computed color table for a side of shadow. + unsigned char *shadow_top; + /// A region in which shadow is not painted on. + XserverRegion shadow_exclude_reg; + + // === Software-optimization-related === + /// Currently used refresh rate. + short refresh_rate; + /// Interval between refresh in nanoseconds. + long refresh_intv; + /// Nanosecond offset of the first painting. + long paint_tm_offset; + +#ifdef CONFIG_VSYNC_DRM + // === DRM VSync related === + /// File descriptor of DRI device file. Used for DRM VSync. + int drm_fd; +#endif + +#ifdef CONFIG_VSYNC_OPENGL + // === OpenGL related === + /// GLX context. + GLXContext glx_context; + /// Whether we have GL_ARB_texture_non_power_of_two. + bool glx_has_texture_non_power_of_two; + /// Pointer to glXGetVideoSyncSGI function. + f_GetVideoSync glXGetVideoSyncSGI; + /// Pointer to glXWaitVideoSyncSGI function. + f_WaitVideoSync glXWaitVideoSyncSGI; + /// Pointer to glXGetSyncValuesOML function. + f_GetSyncValuesOML glXGetSyncValuesOML; + /// Pointer to glXWaitForMscOML function. + f_WaitForMscOML glXWaitForMscOML; + /// Pointer to glXSwapIntervalSGI function. + f_SwapIntervalSGI glXSwapIntervalProc; + /// Pointer to glXSwapIntervalMESA function. + f_SwapIntervalMESA glXSwapIntervalMESAProc; + /// Pointer to glXBindTexImageEXT function. + f_BindTexImageEXT glXBindTexImageProc; + /// Pointer to glXReleaseTexImageEXT function. + f_ReleaseTexImageEXT glXReleaseTexImageProc; + /// Pointer to glXCopySubBufferMESA function. + f_CopySubBuffer glXCopySubBufferProc; +#ifdef CONFIG_GLX_SYNC + /// Pointer to the glFenceSync() function. + f_FenceSync glFenceSyncProc; + /// Pointer to the glIsSync() function. + f_IsSync glIsSyncProc; + /// Pointer to the glDeleteSync() function. + f_DeleteSync glDeleteSyncProc; + /// Pointer to the glClientWaitSync() function. + f_ClientWaitSync glClientWaitSyncProc; + /// Pointer to the glWaitSync() function. + f_WaitSync glWaitSyncProc; + /// Pointer to the glImportSyncEXT() function. + f_ImportSyncEXT glImportSyncEXT; +#endif +#ifdef DEBUG_GLX_MARK + /// Pointer to StringMarkerGREMEDY function. + f_StringMarkerGREMEDY glStringMarkerGREMEDY; + /// Pointer to FrameTerminatorGREMEDY function. + f_FrameTerminatorGREMEDY glFrameTerminatorGREMEDY; +#endif + /// FBConfig-s for GLX pixmap of different depths. + glx_fbconfig_t *glx_fbconfigs[OPENGL_MAX_DEPTH + 1]; +#ifdef CONFIG_VSYNC_OPENGL_GLSL + glx_blur_pass_t glx_blur_passes[MAX_BLUR_PASS]; +#endif +#endif + + // === X extension related === + /// Event base number for X Fixes extension. + int xfixes_event; + /// Error base number for X Fixes extension. + int xfixes_error; + /// Event base number for X Damage extension. + int damage_event; + /// Error base number for X Damage extension. + int damage_error; + /// Event base number for X Render extension. + int render_event; + /// Error base number for X Render extension. + int render_error; + /// Event base number for X Composite extension. + int composite_event; + /// Error base number for X Composite extension. + int composite_error; + /// Major opcode for X Composite extension. + int composite_opcode; + /// Whether X Composite NameWindowPixmap is available. Aka if X + /// Composite version >= 0.2. + bool has_name_pixmap; + /// Whether X Shape extension exists. + bool shape_exists; + /// Event base number for X Shape extension. + int shape_event; + /// Error base number for X Shape extension. + int shape_error; + /// Whether X RandR extension exists. + bool randr_exists; + /// Event base number for X RandR extension. + int randr_event; + /// Error base number for X RandR extension. + int randr_error; +#ifdef CONFIG_VSYNC_OPENGL + /// Whether X GLX extension exists. + bool glx_exists; + /// Event base number for X GLX extension. + int glx_event; + /// Error base number for X GLX extension. + int glx_error; +#endif + /// Whether X DBE extension exists. + bool dbe_exists; +#ifdef CONFIG_XINERAMA + /// Whether X Xinerama extension exists. + bool xinerama_exists; + /// Xinerama screen info. + XineramaScreenInfo *xinerama_scrs; + /// Xinerama screen regions. + XserverRegion *xinerama_scr_regs; + /// Number of Xinerama screens. + int xinerama_nscrs; +#endif +#ifdef CONFIG_XSYNC + /// Whether X Sync extension exists. + bool xsync_exists; + /// Event base number for X Sync extension. + int xsync_event; + /// Error base number for X Sync extension. + int xsync_error; +#endif + /// Whether X Render convolution filter exists. + bool xrfilter_convolution_exists; + + // === Atoms === + /// Atom of property <code>_NET_WM_OPACITY</code>. + Atom atom_opacity; + /// Atom of <code>_NET_FRAME_EXTENTS</code>. + Atom atom_frame_extents; + /// Property atom to identify top-level frame window. Currently + /// <code>WM_STATE</code>. + Atom atom_client; + /// Atom of property <code>WM_NAME</code>. + Atom atom_name; + /// Atom of property <code>_NET_WM_NAME</code>. + Atom atom_name_ewmh; + /// Atom of property <code>WM_CLASS</code>. + Atom atom_class; + /// Atom of property <code>WM_WINDOW_ROLE</code>. + Atom atom_role; + /// Atom of property <code>WM_TRANSIENT_FOR</code>. + Atom atom_transient; + /// Atom of property <code>WM_CLIENT_LEADER</code>. + Atom atom_client_leader; + /// Atom of property <code>_NET_ACTIVE_WINDOW</code>. + Atom atom_ewmh_active_win; + /// Atom of property <code>_TDE_WM_WINDOW_SHADOW</code>. + Atom atom_compton_shadow; + /// Atom of property <code>_NET_WM_WINDOW_TYPE</code>. + Atom atom_win_type; + /// Atom of property <code>_KDE_TRANSPARENT_TO_BLACK</code>. + Atom atom_win_type_tde_transparent_to_black; + /// Atom of property <code>_KDE_TRANSPARENT_TO_DESKTOP</code>. + Atom atom_win_type_tde_transparent_to_desktop; + /// Array of atoms of all possible window types. + Atom atoms_wintypes[NUM_WINTYPES]; + /// Linked list of additional atoms to track. + latom_t *track_atom_lst; + +#ifdef CONFIG_DBUS + // === DBus related === + // DBus connection. + DBusConnection *dbus_conn; + // DBus service name. + char *dbus_service; +#endif +} session_t; + +/// Structure representing a top-level window compton manages. +typedef struct _win { + /// Pointer to the next structure in the linked list. + struct _win *next; + /// Pointer to the next higher window to paint. + struct _win *prev_trans; + + // Core members + /// ID of the top-level frame window. + Window id; + /// Window attributes. + XWindowAttributes a; +#ifdef CONFIG_XINERAMA + /// Xinerama screen this window is on. + int xinerama_scr; +#endif + /// Window visual pict format; + XRenderPictFormat *pictfmt; + /// Window painting mode. + winmode_t mode; + /// Whether the window has been damaged at least once. + bool damaged; +#ifdef CONFIG_XSYNC + /// X Sync fence of drawable. + XSyncFence fence; +#endif + /// Whether the window was damaged after last paint. + bool pixmap_damaged; + /// Damage of the window. + Damage damage; + /// Paint info of the window. + paint_t paint; + /// Bounding shape of the window. + XserverRegion border_size; + /// Region of the whole window, shadow region included. + XserverRegion extents; + /// Window flags. Definitions above. + int_fast16_t flags; + /// Whether there's a pending <code>ConfigureNotify</code> happening + /// when the window is unmapped. + bool need_configure; + /// Queued <code>ConfigureNotify</code> when the window is unmapped. + XConfigureEvent queue_configure; + /// Region to be ignored when painting. Basically the region where + /// higher opaque windows will paint upon. Depends on window frame + /// opacity state, window geometry, window mapped/unmapped state, + /// window mode, of this and all higher windows. + XserverRegion reg_ignore; + /// Cached width/height of the window including border. + int widthb, heightb; + /// Whether the window has been destroyed. + bool destroyed; + /// Whether the window is bounding-shaped. + bool bounding_shaped; + /// Whether the window just have rounded corners. + bool rounded_corners; + /// Whether this window is to be painted. + bool to_paint; + /// Whether the window is painting excluded. + bool paint_excluded; + /// Whether the window is unredirect-if-possible excluded. + bool unredir_if_possible_excluded; + /// Whether this window is in open/close state. + bool in_openclose; + + // Client window related members + /// ID of the top-level client window of the window. + Window client_win; + /// Type of the window. + wintype_t window_type; + /// Whether it looks like a WM window. We consider a window WM window if + /// it does not have a decedent with WM_STATE and it is not override- + /// redirected itself. + bool wmwin; + /// Leader window ID of the window. + Window leader; + /// Cached topmost window ID of the window. + Window cache_leader; + + // Focus-related members + /// Whether the window is to be considered focused. + bool focused; + /// Override value of window focus state. Set by D-Bus method calls. + switch_t focused_force; + + // Blacklist related members + /// Name of the window. + char *name; + /// Window instance class of the window. + char *class_instance; + /// Window general class of the window. + char *class_general; + /// <code>WM_WINDOW_ROLE</code> value of the window. + char *role; + const c2_lptr_t *cache_sblst; + const c2_lptr_t *cache_fblst; + const c2_lptr_t *cache_fcblst; + const c2_lptr_t *cache_ivclst; + const c2_lptr_t *cache_bbblst; + const c2_lptr_t *cache_oparule; + const c2_lptr_t *cache_pblst; + const c2_lptr_t *cache_uipblst; + + // Opacity-related members + /// Current window opacity. + opacity_t opacity; + /// Target window opacity. + opacity_t opacity_tgt; + /// Cached value of opacity window attribute. + opacity_t opacity_prop; + /// Cached value of opacity window attribute on client window. For + /// broken window managers not transferring client window's + /// _NET_WM_OPACITY value + opacity_t opacity_prop_client; + /// Last window opacity value we set. + opacity_t opacity_set; + + // Fading-related members + /// Do not fade if it's false. Change on window type change. + /// Used by fading blacklist in the future. + bool fade; + /// Override value of window fade state. Set by D-Bus method calls. + switch_t fade_force; + /// Callback to be called after fading completed. + void (*fade_callback) (session_t *ps, struct _win *w); + + // Frame-opacity-related members + /// Current window frame opacity. Affected by window opacity. + double frame_opacity; + /// Frame widths. Determined by client window attributes. + unsigned int left_width, right_width, top_width, bottom_width; + + // Shadow-related members + /// Whether a window has shadow. Calculated. + bool shadow; + /// Override value of window shadow state. Set by D-Bus method calls. + switch_t shadow_force; + /// Opacity of the shadow. Affected by window opacity and frame opacity. + double shadow_opacity; + /// X offset of shadow. Affected by commandline argument. + int shadow_dx; + /// Y offset of shadow. Affected by commandline argument. + int shadow_dy; + /// Width of shadow. Affected by window size and commandline argument. + int shadow_width; + /// Height of shadow. Affected by window size and commandline argument. + int shadow_height; + /// Relative size of shadow. + int shadow_size; + /// Picture to render shadow. Affected by window size. + paint_t shadow_paint; + /// The value of _TDE_WM_WINDOW_SHADOW attribute of the window. Below 0 for + /// none. + long prop_shadow; + + // Dim-related members + /// Whether the window is to be dimmed. + bool dim; + + /// Whether to invert window color. + bool invert_color; + /// Override value of window color inversion state. Set by D-Bus method + /// calls. + switch_t invert_color_force; + + /// Whether to blur window background. + bool blur_background; + + /// Whether to show black background + bool show_black_background; + + /// Whether to show desktop background + bool show_root_tile; + +#ifdef CONFIG_VSYNC_OPENGL_GLSL + /// Textures and FBO background blur use. + glx_blur_cache_t glx_blur_cache; +#endif +} win; + +/// Temporary structure used for communication between +/// <code>get_cfg()</code> and <code>parse_config()</code>. +struct options_tmp { + bool no_dock_shadow; + bool no_dnd_shadow; + double menu_opacity; +}; + +/// Structure for a recorded timeout. +typedef struct _timeout_t { + bool enabled; + void *data; + bool (*callback)(session_t *ps, struct _timeout_t *ptmout); + time_ms_t interval; + time_ms_t firstrun; + time_ms_t lastrun; + struct _timeout_t *next; +} timeout_t; + +/// Enumeration for window event hints. +typedef enum { + WIN_EVMODE_UNKNOWN, + WIN_EVMODE_FRAME, + WIN_EVMODE_CLIENT +} win_evmode_t; + +extern const char * const WINTYPES[NUM_WINTYPES]; +extern const char * const VSYNC_STRS[NUM_VSYNC + 1]; +extern const char * const BACKEND_STRS[NUM_BKEND + 1]; +extern session_t *ps_g; + +// == Debugging code == +static inline void +print_timestamp(session_t *ps); + +#ifdef DEBUG_ALLOC_REG + +#include <execinfo.h> +#define BACKTRACE_SIZE 5 + +/** + * Print current backtrace, excluding the first two items. + * + * Stolen from glibc manual. + */ +static inline void +print_backtrace(void) { + void *array[BACKTRACE_SIZE]; + size_t size; + char **strings; + + size = backtrace(array, BACKTRACE_SIZE); + strings = backtrace_symbols(array, size); + + for (size_t i = 2; i < size; i++) + printf ("%s\n", strings[i]); + + free(strings); +} + +/** + * Wrapper of <code>XFixesCreateRegion</code>, for debugging. + */ +static inline XserverRegion +XFixesCreateRegion_(Display *dpy, XRectangle *p, int n, + const char *func, int line) { + XserverRegion reg = XFixesCreateRegion(dpy, p, n); + print_timestamp(ps_g); + printf("%#010lx: XFixesCreateRegion() in %s():%d\n", reg, func, line); + print_backtrace(); + fflush(stdout); + return reg; +} + +/** + * Wrapper of <code>XFixesDestroyRegion</code>, for debugging. + */ +static inline void +XFixesDestroyRegion_(Display *dpy, XserverRegion reg, + const char *func, int line) { + XFixesDestroyRegion(dpy, reg); + print_timestamp(ps_g); + printf("%#010lx: XFixesDestroyRegion() in %s():%d\n", reg, func, line); + fflush(stdout); +} + +#define XFixesCreateRegion(dpy, p, n) XFixesCreateRegion_(dpy, p, n, __func__, __LINE__) +#define XFixesDestroyRegion(dpy, reg) XFixesDestroyRegion_(dpy, reg, __func__, __LINE__) +#endif + +// === Functions === + +/** + * @brief Quit if the passed-in pointer is empty. + */ +static inline void * +allocchk_(const char *func_name, void *ptr) { + if (!ptr) { + printf_err("%s(): Failed to allocate memory.", func_name); + exit(1); + } + return ptr; +} + +/// @brief Wrapper of allocchk_(). +#define allocchk(ptr) allocchk_(__func__, ptr) + +/// @brief Wrapper of malloc(). +#define cmalloc(nmemb, type) ((type *) allocchk(malloc((nmemb) * sizeof(type)))) + +/// @brief Wrapper of calloc(). +#define ccalloc(nmemb, type) ((type *) allocchk(calloc((nmemb), sizeof(type)))) + +/// @brief Wrapper of ealloc(). +#define crealloc(ptr, nmemb, type) ((type *) allocchk(realloc((ptr), (nmemb) * sizeof(type)))) + +/** + * Return whether a struct timeval value is empty. + */ +static inline bool +timeval_isempty(struct timeval *ptv) { + if (!ptv) + return false; + + return ptv->tv_sec <= 0 && ptv->tv_usec <= 0; +} + +/** + * Compare a struct timeval with a time in milliseconds. + * + * @return > 0 if ptv > ms, 0 if ptv == 0, -1 if ptv < ms + */ +static inline int +timeval_ms_cmp(struct timeval *ptv, time_ms_t ms) { + assert(ptv); + + // We use those if statement instead of a - expression because of possible + // truncation problem from long to int. + { + long sec = ms / MS_PER_SEC; + if (ptv->tv_sec > sec) + return 1; + if (ptv->tv_sec < sec) + return -1; + } + + { + long usec = ms % MS_PER_SEC * (US_PER_SEC / MS_PER_SEC); + if (ptv->tv_usec > usec) + return 1; + if (ptv->tv_usec < usec) + return -1; + } + + return 0; +} + +/** + * Subtracting two struct timeval values. + * + * Taken from glibc manual. + * + * Subtract the `struct timeval' values X and Y, + * storing the result in RESULT. + * Return 1 if the difference is negative, otherwise 0. + */ +static inline int +timeval_subtract(struct timeval *result, + struct timeval *x, + struct timeval *y) { + /* Perform the carry for the later subtraction by updating y. */ + if (x->tv_usec < y->tv_usec) { + long nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; + y->tv_usec -= 1000000 * nsec; + y->tv_sec += nsec; + } + + if (x->tv_usec - y->tv_usec > 1000000) { + long nsec = (x->tv_usec - y->tv_usec) / 1000000; + y->tv_usec += 1000000 * nsec; + y->tv_sec -= nsec; + } + + /* Compute the time remaining to wait. + tv_usec is certainly positive. */ + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_usec = x->tv_usec - y->tv_usec; + + /* Return 1 if result is negative. */ + return x->tv_sec < y->tv_sec; +} + +/** + * Subtracting two struct timespec values. + * + * Taken from glibc manual. + * + * Subtract the `struct timespec' values X and Y, + * storing the result in RESULT. + * Return 1 if the difference is negative, otherwise 0. + */ +static inline int +timespec_subtract(struct timespec *result, + struct timespec *x, + struct timespec *y) { + /* Perform the carry for the later subtraction by updating y. */ + if (x->tv_nsec < y->tv_nsec) { + long nsec = (y->tv_nsec - x->tv_nsec) / NS_PER_SEC + 1; + y->tv_nsec -= NS_PER_SEC * nsec; + y->tv_sec += nsec; + } + + if (x->tv_nsec - y->tv_nsec > NS_PER_SEC) { + long nsec = (x->tv_nsec - y->tv_nsec) / NS_PER_SEC; + y->tv_nsec += NS_PER_SEC * nsec; + y->tv_sec -= nsec; + } + + /* Compute the time remaining to wait. + tv_nsec is certainly positive. */ + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_nsec = x->tv_nsec - y->tv_nsec; + + /* Return 1 if result is negative. */ + return x->tv_sec < y->tv_sec; +} + +/** + * Get current time in struct timeval. + */ +static inline struct timeval __attribute__((const)) +get_time_timeval(void) { + struct timeval tv = { 0, 0 }; + + gettimeofday(&tv, NULL); + + // Return a time of all 0 if the call fails + return tv; +} + +/** + * Get current time in struct timespec. + * + * Note its starting time is unspecified. + */ +static inline struct timespec __attribute__((const)) +get_time_timespec(void) { + struct timespec tm = { 0, 0 }; + + clock_gettime(CLOCK_MONOTONIC, &tm); + + // Return a time of all 0 if the call fails + return tm; +} + + +/** + * Print time passed since program starts execution. + * + * Used for debugging. + */ +static inline void +print_timestamp(session_t *ps) { + struct timeval tm, diff; + + if (gettimeofday(&tm, NULL)) return; + + timeval_subtract(&diff, &tm, &ps->time_start); + printf("[ %5ld.%02ld ] ", diff.tv_sec, diff.tv_usec / 10000); +} + +/** + * Allocate the space and copy a string. + */ +static inline char * +mstrcpy(const char *src) { + char *str = cmalloc(strlen(src) + 1, char); + + strcpy(str, src); + + return str; +} + +/** + * Allocate the space and copy a string. + */ +static inline char * +mstrncpy(const char *src, unsigned len) { + char *str = cmalloc(len + 1, char); + + strncpy(str, src, len); + str[len] = '\0'; + + return str; +} + +/** + * Allocate the space and join two strings. + */ +static inline char * +mstrjoin(const char *src1, const char *src2) { + char *str = cmalloc(strlen(src1) + strlen(src2) + 1, char); + + strcpy(str, src1); + strcat(str, src2); + + return str; +} + +/** + * Allocate the space and join two strings; + */ +static inline char * +mstrjoin3(const char *src1, const char *src2, const char *src3) { + char *str = cmalloc(strlen(src1) + strlen(src2) + + strlen(src3) + 1, char); + + strcpy(str, src1); + strcat(str, src2); + strcat(str, src3); + + return str; +} + +/** + * Concatenate a string on heap with another string. + */ +static inline void +mstrextend(char **psrc1, const char *src2) { + *psrc1 = crealloc(*psrc1, (*psrc1 ? strlen(*psrc1): 0) + strlen(src2) + 1, + char); + + strcat(*psrc1, src2); +} + +/** + * Normalize an int value to a specific range. + * + * @param i int value to normalize + * @param min minimal value + * @param max maximum value + * @return normalized value + */ +static inline int __attribute__((const)) +normalize_i_range(int i, int min, int max) { + if (i > max) return max; + if (i < min) return min; + return i; +} + +/** + * Select the larger integer of two. + */ +static inline int __attribute__((const)) +max_i(int a, int b) { + return (a > b ? a : b); +} + +/** + * Select the smaller integer of two. + */ +static inline int __attribute__((const)) +min_i(int a, int b) { + return (a > b ? b : a); +} + +/** + * Select the larger long integer of two. + */ +static inline long __attribute__((const)) +max_l(long a, long b) { + return (a > b ? a : b); +} + +/** + * Select the smaller long integer of two. + */ +static inline long __attribute__((const)) +min_l(long a, long b) { + return (a > b ? b : a); +} + +/** + * Normalize a double value to a specific range. + * + * @param d double value to normalize + * @param min minimal value + * @param max maximum value + * @return normalized value + */ +static inline double __attribute__((const)) +normalize_d_range(double d, double min, double max) { + if (d > max) return max; + if (d < min) return min; + return d; +} + +/** + * Normalize a double value to 0.\ 0 - 1.\ 0. + * + * @param d double value to normalize + * @return normalized value + */ +static inline double __attribute__((const)) +normalize_d(double d) { + return normalize_d_range(d, 0.0, 1.0); +} + +/** + * Parse a VSync option argument. + */ +static inline bool +parse_vsync(session_t *ps, const char *str) { + for (vsync_t i = 0; VSYNC_STRS[i]; ++i) + if (!strcasecmp(str, VSYNC_STRS[i])) { + ps->o.vsync = i; + return true; + } + + printf_errf("(\"%s\"): Invalid vsync argument.", str); + return false; +} + +/** + * Parse a backend option argument. + */ +static inline bool +parse_backend(session_t *ps, const char *str) { + for (enum backend i = 0; BACKEND_STRS[i]; ++i) + if (!strcasecmp(str, BACKEND_STRS[i])) { + ps->o.backend = i; + return true; + } + // Keep compatibility with an old revision containing a spelling mistake... + if (!strcasecmp(str, "xr_glx_hybird")) { + ps->o.backend = BKEND_XR_GLX_HYBRID; + return true; + } + // cju wants to use dashes + if (!strcasecmp(str, "xr-glx-hybrid")) { + ps->o.backend = BKEND_XR_GLX_HYBRID; + return true; + } + printf_errf("(\"%s\"): Invalid backend argument.", str); + return false; +} + +/** + * Parse a glx_swap_method option argument. + */ +static inline bool +parse_glx_swap_method(session_t *ps, const char *str) { + // Parse alias + if (!strcmp("undefined", str)) { + ps->o.glx_swap_method = 0; + return true; + } + + if (!strcmp("copy", str)) { + ps->o.glx_swap_method = 1; + return true; + } + + if (!strcmp("exchange", str)) { + ps->o.glx_swap_method = 2; + return true; + } + + if (!strcmp("buffer-age", str)) { + ps->o.glx_swap_method = -1; + return true; + } + + // Parse number + { + char *pc = NULL; + int age = strtol(str, &pc, 0); + if (!pc || str == pc) { + printf_errf("(\"%s\"): Invalid number.", str); + return false; + } + + for (; *pc; ++pc) + if (!isspace(*pc)) { + printf_errf("(\"%s\"): Trailing characters.", str); + return false; + } + + if (age > CGLX_MAX_BUFFER_AGE + 1 || age < -1) { + printf_errf("(\"%s\"): Number too large / too small.", str); + return false; + } + + ps->o.glx_swap_method = age; + } + + return true; +} + +timeout_t * +timeout_insert(session_t *ps, time_ms_t interval, + bool (*callback)(session_t *ps, timeout_t *ptmout), void *data); + +void +timeout_invoke(session_t *ps, timeout_t *ptmout); + +bool +timeout_drop(session_t *ps, timeout_t *prm); + +void +timeout_reset(session_t *ps, timeout_t *ptmout); + +/** + * Add a file descriptor to a select() fd_set. + */ +static inline bool +fds_insert_select(fd_set **ppfds, int fd) { + assert(fd <= FD_SETSIZE); + + if (!*ppfds) { + if ((*ppfds = malloc(sizeof(fd_set)))) { + FD_ZERO(*ppfds); + } + else { + fprintf(stderr, "Failed to allocate memory for select() fdset.\n"); + exit(1); + } + } + + FD_SET(fd, *ppfds); + + return true; +} + +/** + * Add a new file descriptor to wait for. + */ +static inline bool +fds_insert(session_t *ps, int fd, short events) { + bool result = true; + + ps->nfds_max = max_i(fd + 1, ps->nfds_max); + + if (POLLIN & events) + result = fds_insert_select(&ps->pfds_read, fd) && result; + if (POLLOUT & events) + result = fds_insert_select(&ps->pfds_write, fd) && result; + if (POLLPRI & events) + result = fds_insert_select(&ps->pfds_except, fd) && result; + + return result; +} + +/** + * Delete a file descriptor to wait for. + */ +static inline void +fds_drop(session_t *ps, int fd, short events) { + // Drop fd from respective fd_set-s + if (POLLIN & events && ps->pfds_read) + FD_CLR(fd, ps->pfds_read); + if (POLLOUT & events && ps->pfds_write) + FD_CLR(fd, ps->pfds_write); + if (POLLPRI & events && ps->pfds_except) + FD_CLR(fd, ps->pfds_except); +} + +#define CPY_FDS(key) \ + fd_set * key = NULL; \ + if (ps->key) { \ + key = malloc(sizeof(fd_set)); \ + memcpy(key, ps->key, sizeof(fd_set)); \ + if (!key) { \ + fprintf(stderr, "Failed to allocate memory for copying select() fdset.\n"); \ + exit(1); \ + } \ + } \ + +/** + * Poll for changes. + * + * poll() is much better than select(), but ppoll() does not exist on + * *BSD. + */ +static inline int +fds_poll(session_t *ps, struct timeval *ptv) { + // Copy fds + CPY_FDS(pfds_read); + CPY_FDS(pfds_write); + CPY_FDS(pfds_except); + + int ret = select(ps->nfds_max, pfds_read, pfds_write, pfds_except, ptv); + + free(pfds_read); + free(pfds_write); + free(pfds_except); + + return ret; +} +#undef CPY_FDS + +/** + * Wrapper of XFree() for convenience. + * + * Because a NULL pointer cannot be passed to XFree(), its man page says. + */ +static inline void +cxfree(void *data) { + if (data) + XFree(data); +} + +/** + * Wrapper of XInternAtom() for convenience. + */ +static inline Atom +get_atom(session_t *ps, const char *atom_name) { + return XInternAtom(ps->dpy, atom_name, False); +} + +/** + * Return the painting target window. + */ +static inline Window +get_tgt_window(session_t *ps) { + return ps->o.paint_on_overlay ? ps->overlay: ps->root; +} + +/** + * Find a window from window id in window linked list of the session. + */ +static inline win * +find_win(session_t *ps, Window id) { + if (!id) + return NULL; + + win *w; + + for (w = ps->list; w; w = w->next) { + if (w->id == id && !w->destroyed) + return w; + } + + return 0; +} + +/** + * Find out the WM frame of a client window using existing data. + * + * @param id window ID + * @return struct _win object of the found window, NULL if not found + */ +static inline win * +find_toplevel(session_t *ps, Window id) { + if (!id) + return NULL; + + for (win *w = ps->list; w; w = w->next) { + if (w->client_win == id && !w->destroyed) + return w; + } + + return NULL; +} + + +/** + * Check if current backend uses XRender for rendering. + */ +static inline bool +bkend_use_xrender(session_t *ps) { + return BKEND_XRENDER == ps->o.backend + || BKEND_XR_GLX_HYBRID == ps->o.backend; +} + +/** + * Check if current backend uses GLX. + */ +static inline bool +bkend_use_glx(session_t *ps) { + return BKEND_GLX == ps->o.backend + || BKEND_XR_GLX_HYBRID == ps->o.backend; +} + +/** + * Check if a window is really focused. + */ +static inline bool +win_is_focused_real(session_t *ps, const win *w) { + return IsViewable == w->a.map_state && ps->active_win == w; +} + +/** + * Find out the currently focused window. + * + * @return struct _win object of the found window, NULL if not found + */ +static inline win * +find_focused(session_t *ps) { + if (!ps->o.track_focus) return NULL; + + if (ps->active_win && win_is_focused_real(ps, ps->active_win)) + return ps->active_win; + return NULL; +} + +/** + * Copies a region. + */ +static inline XserverRegion +copy_region(const session_t *ps, XserverRegion oldregion) { + if (!oldregion) + return None; + + XserverRegion region = XFixesCreateRegion(ps->dpy, NULL, 0); + + XFixesCopyRegion(ps->dpy, region, oldregion); + + return region; +} + +/** + * Destroy a <code>XserverRegion</code>. + */ +static inline void +free_region(session_t *ps, XserverRegion *p) { + if (*p) { + XFixesDestroyRegion(ps->dpy, *p); + *p = None; + } +} + +/** + * Free all regions in ps->all_damage_last . + */ +static inline void +free_all_damage_last(session_t *ps) { + for (int i = 0; i < CGLX_MAX_BUFFER_AGE; ++i) + free_region(ps, &ps->all_damage_last[i]); +} + +#ifdef CONFIG_XSYNC +/** + * Free a XSync fence. + */ +static inline void +free_fence(session_t *ps, XSyncFence *pfence) { + if (*pfence) + XSyncDestroyFence(ps->dpy, *pfence); + *pfence = None; +} +#else +#define free_fence(ps, pfence) ((void) 0) +#endif + +/** + * Crop a rectangle by another rectangle. + * + * psrc and pdst cannot be the same. + */ +static inline void +rect_crop(XRectangle *pdst, const XRectangle *psrc, const XRectangle *pbound) { + assert(psrc != pdst); + pdst->x = max_i(psrc->x, pbound->x); + pdst->y = max_i(psrc->y, pbound->y); + pdst->width = max_i(0, min_i(psrc->x + psrc->width, pbound->x + pbound->width) - pdst->x); + pdst->height = max_i(0, min_i(psrc->y + psrc->height, pbound->y + pbound->height) - pdst->y); +} + +/** + * Check if a rectangle includes the whole screen. + */ +static inline bool +rect_is_fullscreen(session_t *ps, int x, int y, unsigned wid, unsigned hei) { + return (x <= 0 && y <= 0 + && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); +} + +/** + * Check if a window is a fullscreen window. + * + * It's not using w->border_size for performance measures. + */ +static inline bool +win_is_fullscreen(session_t *ps, const win *w) { + return rect_is_fullscreen(ps, w->a.x, w->a.y, w->widthb, w->heightb) + && !w->bounding_shaped; +} + +/** + * Determine if a window has a specific property. + * + * @param ps current session + * @param w window to check + * @param atom atom of property to check + * @return 1 if it has the attribute, 0 otherwise + */ +static inline bool +wid_has_prop(const session_t *ps, Window w, Atom atom) { + Atom type = None; + int format; + unsigned long nitems, after; + unsigned char *data; + + if (Success == XGetWindowProperty(ps->dpy, w, atom, 0, 0, False, + AnyPropertyType, &type, &format, &nitems, &after, &data)) { + cxfree(data); + if (type) return true; + } + + return false; +} + +winprop_t +wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset, + long length, Atom rtype, int rformat); + +/** + * Wrapper of wid_get_prop_adv(). + */ +static inline winprop_t +wid_get_prop(const session_t *ps, Window wid, Atom atom, long length, + Atom rtype, int rformat) { + return wid_get_prop_adv(ps, wid, atom, 0L, length, rtype, rformat); +} + +/** + * Get the numeric property value from a win_prop_t. + */ +static inline long +winprop_get_int(winprop_t prop) { + long tgt = 0; + + if (!prop.nitems) + return 0; + + switch (prop.format) { + case 8: tgt = *(prop.data.p8); break; + case 16: tgt = *(prop.data.p16); break; + case 32: tgt = *(prop.data.p32); break; + default: assert(0); + break; + } + + return tgt; +} + +bool +wid_get_text_prop(session_t *ps, Window wid, Atom prop, + char ***pstrlst, int *pnstr); + +/** + * Free a <code>winprop_t</code>. + * + * @param pprop pointer to the <code>winprop_t</code> to free. + */ +static inline void +free_winprop(winprop_t *pprop) { + // Empty the whole structure to avoid possible issues + if (pprop->data.p8) { + cxfree(pprop->data.p8); + pprop->data.p8 = NULL; + } + pprop->nitems = 0; +} + +void +force_repaint(session_t *ps); + +bool +vsync_init(session_t *ps); + +void +vsync_deinit(session_t *ps); + +#ifdef CONFIG_VSYNC_OPENGL +/** @name GLX + */ +///@{ + +#ifdef CONFIG_GLX_SYNC +void +xr_glx_sync(session_t *ps, Drawable d, XSyncFence *pfence); +#endif + +bool +glx_init(session_t *ps, bool need_render); + +void +glx_destroy(session_t *ps); + +void +glx_on_root_change(session_t *ps); + +bool +glx_init_blur(session_t *ps); + +bool +glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, Pixmap pixmap, + unsigned width, unsigned height, unsigned depth); + +void +glx_release_pixmap(session_t *ps, glx_texture_t *ptex); + +void +glx_paint_pre(session_t *ps, XserverRegion *preg); + +/** + * Check if a texture is binded, or is binded to the given pixmap. + */ +static inline bool +glx_tex_binded(const glx_texture_t *ptex, Pixmap pixmap) { + return ptex && ptex->glpixmap && ptex->texture + && (!pixmap || pixmap == ptex->pixmap); +} + +void +glx_set_clip(session_t *ps, XserverRegion reg, const reg_data_t *pcache_reg); + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +bool +glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, + GLfloat factor_center, + XserverRegion reg_tgt, const reg_data_t *pcache_reg, + glx_blur_cache_t *pbc); +#endif + +bool +glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, + GLfloat factor, XserverRegion reg_tgt, const reg_data_t *pcache_reg); + +bool +glx_render(session_t *ps, const glx_texture_t *ptex, + int x, int y, int dx, int dy, int width, int height, int z, + double opacity, bool neg, + XserverRegion reg_tgt, const reg_data_t *pcache_reg); + +bool +glx_render_specified_color(session_t *ps, int color, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg); + +void +glx_swap_copysubbuffermesa(session_t *ps, XserverRegion reg); + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +GLuint +glx_create_shader(GLenum shader_type, const char *shader_str); + +GLuint +glx_create_program(const GLuint * const shaders, int nshaders); +#endif + +/** + * Free a GLX texture. + */ +static inline void +free_texture_r(session_t *ps, GLuint *ptexture) { + if (*ptexture) { + assert(ps->glx_context); + glDeleteTextures(1, ptexture); + *ptexture = 0; + } +} + +/** + * Free a GLX Framebuffer object. + */ +static inline void +free_glx_fbo(session_t *ps, GLuint *pfbo) { +#ifdef CONFIG_VSYNC_OPENGL_FBO + if (*pfbo) { + glDeleteFramebuffers(1, pfbo); + *pfbo = 0; + } +#endif + assert(!*pfbo); +} + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +/** + * Free data in glx_blur_cache_t on resize. + */ +static inline void +free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { + free_texture_r(ps, &pbc->textures[0]); + free_texture_r(ps, &pbc->textures[1]); + pbc->width = 0; + pbc->height = 0; +} + +/** + * Free a glx_blur_cache_t + */ +static inline void +free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { + free_glx_fbo(ps, &pbc->fbo); + free_glx_bc_resize(ps, pbc); +} +#endif +#endif + +/** + * Free a glx_texture_t. + */ +static inline void +free_texture(session_t *ps, glx_texture_t **pptex) { + glx_texture_t *ptex = *pptex; + + // Quit if there's nothing + if (!ptex) + return; + +#ifdef CONFIG_VSYNC_OPENGL + glx_release_pixmap(ps, ptex); + + free_texture_r(ps, &ptex->texture); + + // Free structure itself + free(ptex); + *pptex = NULL; +#endif + assert(!*pptex); +} + +/** + * Add a OpenGL debugging marker. + */ +static inline void +glx_mark_(session_t *ps, const char *func, XID xid, bool start) { +#ifdef DEBUG_GLX_MARK + if (bkend_use_glx(ps) && ps->glStringMarkerGREMEDY) { + if (!func) func = "(unknown)"; + const char *postfix = (start ? " (start)": " (end)"); + char *str = malloc((strlen(func) + 12 + 2 + + strlen(postfix) + 5) * sizeof(char)); + strcpy(str, func); + sprintf(str + strlen(str), "(%#010lx)%s", xid, postfix); + ps->glStringMarkerGREMEDY(strlen(str), str); + free(str); + } +#endif +} + +#define glx_mark(ps, xid, start) glx_mark_(ps, __func__, xid, start) + +/** + * Add a OpenGL debugging marker. + */ +static inline void +glx_mark_frame(session_t *ps) { +#ifdef DEBUG_GLX_MARK + if (bkend_use_glx(ps) && ps->glFrameTerminatorGREMEDY) + ps->glFrameTerminatorGREMEDY(); +#endif +} + +///@} + +#ifdef CONFIG_XSYNC +#define xr_sync(ps, d, pfence) xr_sync_(ps, d, pfence) +#else +#define xr_sync(ps, d, pfence) xr_sync_(ps, d) +#endif + +/** + * Synchronizes a X Render drawable to ensure all pending painting requests + * are completed. + */ +static inline void +xr_sync_(session_t *ps, Drawable d +#ifdef CONFIG_XSYNC + , XSyncFence *pfence +#endif + ) { + if (!ps->o.xrender_sync) + return; + + XSync(ps->dpy, False); +#ifdef CONFIG_XSYNC + if (ps->o.xrender_sync_fence && ps->xsync_exists) { + // TODO: If everybody just follows the rules stated in X Sync prototype, + // we need only one fence per screen, but let's stay a bit cautious right + // now + XSyncFence tmp_fence = None; + if (!pfence) + pfence = &tmp_fence; + assert(pfence); + if (!*pfence) + *pfence = XSyncCreateFence(ps->dpy, d, False); + if (*pfence) { + Bool triggered = False; + /* if (XSyncQueryFence(ps->dpy, *pfence, &triggered) && triggered) + XSyncResetFence(ps->dpy, *pfence); */ + // The fence may fail to be created (e.g. because of died drawable) + assert(!XSyncQueryFence(ps->dpy, *pfence, &triggered) || !triggered); + XSyncTriggerFence(ps->dpy, *pfence); + XSyncAwaitFence(ps->dpy, pfence, 1); + assert(!XSyncQueryFence(ps->dpy, *pfence, &triggered) || triggered); + } + else { + printf_errf("(%#010lx): Failed to create X Sync fence.", d); + } + free_fence(ps, &tmp_fence); + if (*pfence) + XSyncResetFence(ps->dpy, *pfence); + } +#endif +#ifdef CONFIG_GLX_SYNC + xr_glx_sync(ps, d, pfence); +#endif +} + +/** @name DBus handling + */ +///@{ +#ifdef CONFIG_DBUS +/** @name DBus handling + */ +///@{ +bool +cdbus_init(session_t *ps); + +void +cdbus_destroy(session_t *ps); + +void +cdbus_loop(session_t *ps); + +void +cdbus_ev_win_added(session_t *ps, win *w); + +void +cdbus_ev_win_destroyed(session_t *ps, win *w); + +void +cdbus_ev_win_mapped(session_t *ps, win *w); + +void +cdbus_ev_win_unmapped(session_t *ps, win *w); + +void +cdbus_ev_win_focusout(session_t *ps, win *w); + +void +cdbus_ev_win_focusin(session_t *ps, win *w); +//!@} + +/** @name DBus hooks + */ +///@{ +void +win_set_shadow_force(session_t *ps, win *w, switch_t val); + +void +win_set_fade_force(session_t *ps, win *w, switch_t val); + +void +win_set_focused_force(session_t *ps, win *w, switch_t val); + +void +win_set_invert_color_force(session_t *ps, win *w, switch_t val); + +void +opts_init_track_focus(session_t *ps); + +void +opts_set_no_fading_openclose(session_t *ps, bool newval); + +void +opts_set_no_fading_opacitychange(session_t *ps, bool newval); +//!@} +#endif + +#ifdef CONFIG_C2 +/** @name c2 + */ +///@{ + +c2_lptr_t * +c2_parsed(session_t *ps, c2_lptr_t **pcondlst, const char *pattern, + void *data); + +#define c2_parse(ps, pcondlst, pattern) c2_parsed((ps), (pcondlst), (pattern), NULL) + +c2_lptr_t * +c2_free_lptr(c2_lptr_t *lp); + +bool +c2_matchd(session_t *ps, win *w, const c2_lptr_t *condlst, + const c2_lptr_t **cache, void **pdata); + +#define c2_match(ps, w, condlst, cache) c2_matchd((ps), (w), (condlst), \ + (cache), NULL) +#endif + +///@} + +#endif + +/** + * @brief Dump raw bytes in HEX format. + * + * @param data pointer to raw data + * @param len length of data + */ +static inline void +hexdump(const char *data, int len) { + static const int BYTE_PER_LN = 16; + + if (len <= 0) + return; + + // Print header + printf("%10s:", "Offset"); + for (int i = 0; i < BYTE_PER_LN; ++i) + printf(" %2d", i); + putchar('\n'); + + // Dump content + for (int offset = 0; offset < len; ++offset) { + if (!(offset % BYTE_PER_LN)) + printf("0x%08x:", offset); + + printf(" %02hhx", data[offset]); + + if ((BYTE_PER_LN - 1) == offset % BYTE_PER_LN) + putchar('\n'); + } + if (len % BYTE_PER_LN) + putchar('\n'); + + fflush(stdout); +} + diff --git a/twin/compton-tde/compton.c b/twin/compton-tde/compton.c new file mode 100644 index 000000000..123703a19 --- /dev/null +++ b/twin/compton-tde/compton.c @@ -0,0 +1,7854 @@ +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net> + * See LICENSE for more information. + * + */ + +#include "compton.h" +#include <ctype.h> + +// === Global constants === + +/// Name strings for window types. +const char * const WINTYPES[NUM_WINTYPES] = { + "unknown", + "desktop", + "dock", + "toolbar", + "menu", + "utility", + "splash", + "dialog", + "normal", + "dropdown_menu", + "popup_menu", + "tooltip", + "notify", + "combo", + "dnd", +}; + +/// Names of VSync modes. +const char * const VSYNC_STRS[NUM_VSYNC + 1] = { + "none", // VSYNC_NONE + "drm", // VSYNC_DRM + "opengl", // VSYNC_OPENGL + "opengl-oml", // VSYNC_OPENGL_OML + "opengl-swc", // VSYNC_OPENGL_SWC + "opengl-mswc", // VSYNC_OPENGL_MSWC + NULL +}; + +/// Names of backends. +const char * const BACKEND_STRS[NUM_BKEND + 1] = { + "xrender", // BKEND_XRENDER + "glx", // BKEND_GLX + "xr_glx_hybrid",// BKEND_XR_GLX_HYBRID + NULL +}; + +/// Function pointers to init VSync modes. +static bool (* const (VSYNC_FUNCS_INIT[NUM_VSYNC]))(session_t *ps) = { + [VSYNC_DRM ] = vsync_drm_init, + [VSYNC_OPENGL ] = vsync_opengl_init, + [VSYNC_OPENGL_OML ] = vsync_opengl_oml_init, + [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_init, + [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_init, +}; + +/// Function pointers to wait for VSync. +static int (* const (VSYNC_FUNCS_WAIT[NUM_VSYNC]))(session_t *ps) = { +#ifdef CONFIG_VSYNC_DRM + [VSYNC_DRM ] = vsync_drm_wait, +#endif +#ifdef CONFIG_VSYNC_OPENGL + [VSYNC_OPENGL ] = vsync_opengl_wait, + [VSYNC_OPENGL_OML ] = vsync_opengl_oml_wait, +#endif +}; + +/// Function pointers to deinitialize VSync. +static void (* const (VSYNC_FUNCS_DEINIT[NUM_VSYNC]))(session_t *ps) = { +#ifdef CONFIG_VSYNC_OPENGL + [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_deinit, + [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_deinit, +#endif +}; + +/// Names of root window properties that could point to a pixmap of +/// background. +const static char *background_props_str[] = { + "_XROOTPMAP_ID", + "_XSETROOT_ID", + 0, +}; + +// === Global variables === + +/// Pointer to current session, as a global variable. Only used by +/// <code>error()</code> and <code>reset_enable()</code>, which could not +/// have a pointer to current session passed in. +session_t *ps_g = NULL; + +// === Execution control === + +struct sigaction usr_action; +sigset_t block_mask; + +int my_exit_code = 3; + +void write_pid_file(pid_t pid) +{ +#ifdef WRITE_PID_FILE +#ifdef USE_ENV_HOME + const char *home = getenv("HOME"); +#else + const char *home; + struct passwd *p; + p = getpwuid(getuid()); + if (p) + home = p->pw_dir; + else + home = getenv("HOME"); +#endif + const char *filename; + const char *configfile = "/.compton-tde.pid"; + int n = strlen(home)+strlen(configfile)+1; + filename = (char*)malloc(n*sizeof(char)); + memset(filename,0,n); + strcat(filename, home); + strcat(filename, configfile); + + printf("writing '%s' as pidfile\n\n", filename); + + /* now that we did all that by way of introduction...write the file! */ + FILE *pFile; + char buffer[255]; + sprintf(buffer, "%d", pid); + pFile = fopen(filename, "w"); + if (pFile) { + fwrite(buffer,1,strlen(buffer), pFile); + fclose(pFile); + } + + free(filename); + filename = NULL; +#endif +} + +void delete_pid_file() +{ +#ifdef WRITE_PID_FILE +#ifdef USE_ENV_HOME + const char *home = getenv("HOME"); +#else + const char *home; + struct passwd *p; + p = getpwuid(getuid()); + if (p) + home = p->pw_dir; + else + home = getenv("HOME"); +#endif + const char *filename; + const char *configfile = "/.compton-tde.pid"; + int n = strlen(home)+strlen(configfile)+1; + filename = (char*)malloc(n*sizeof(char)); + memset(filename,0,n); + strcat(filename, home); + strcat(filename, configfile); + + printf("deleting '%s' as pidfile\n\n", filename); + + /* now that we did all that by way of introduction...delete the file! */ + unlink(filename); + + free(filename); + filename = NULL; +#endif + +#if WORK_AROUND_FGLRX + if ((my_exit_code == 3) && (restartOnSigterm)) { + printf("compton-tde lost connection to X server, restarting...\n"); fflush(stdout); + sleep(1); + char me[2048]; + int chars = readlink("/proc/self/exe", me, sizeof(me)); + me[chars] = 0; + me[2047] = 0; + execl(me, basename(me), (char*)NULL); + } +#endif +} + +void handle_siguser (int sig) +{ + int uidnum; + if (sig == SIGTERM) { + my_exit_code=0; + delete_pid_file(); + exit(0); + } + if (sig == SIGUSR1) { + char newuid[1024]; +#ifndef NDEBUG + printf("Enter the new user ID:\n"); fflush(stdout); +#endif + char *eof; + newuid[0] = '\0'; + newuid[sizeof(newuid)-1] = '\0'; + eof = fgets(newuid, sizeof(newuid), stdin); + uidnum = atoi(newuid); +#ifndef NDEBUG + printf("Setting compton-tde process uid to %d...\n", uidnum); fflush(stdout); +#endif + + my_exit_code=4; + delete_pid_file(); + my_exit_code=3; + setuid(uidnum); + write_pid_file(getpid()); + + } + else { + uidnum = getuid(); + } + if ((sig == SIGUSR1) || (sig == SIGUSR2)) { + get_cfg(ps_g, 0, 0, false); /* reload the configuration file */ + + /* set background/shadow picture using the new settings */ + ps_g->cshadow_picture = solid_picture(ps_g, true, 1, ps_g->o.shadow_red, ps_g->o.shadow_green, ps_g->o.shadow_blue); + + /* regenerate shadows using the new settings */ + init_alpha_picts(ps_g); + init_filters(ps_g); + } +} + +// === Fading === + +/** + * Get the time left before next fading point. + * + * In milliseconds. + */ +static int +fade_timeout(session_t *ps) { + int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time; + + diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2); + + return diff; +} + +/** + * Run fading on a window. + * + * @param steps steps of fading + */ +static void +run_fade(session_t *ps, win *w, unsigned steps) { + // If we have reached target opacity, return + if (w->opacity == w->opacity_tgt) { + return; + } + + if (!w->fade) + w->opacity = w->opacity_tgt; + else if (steps) { + // Use double below because opacity_t will probably overflow during + // calculations + if (w->opacity < w->opacity_tgt) + w->opacity = normalize_d_range( + (double) w->opacity + (double) ps->o.fade_in_step * steps, + 0.0, w->opacity_tgt); + else + w->opacity = normalize_d_range( + (double) w->opacity - (double) ps->o.fade_out_step * steps, + w->opacity_tgt, OPAQUE); + } + + if (w->opacity != w->opacity_tgt) { + ps->idling = false; + } +} + +/** + * Set fade callback of a window, and possibly execute the previous + * callback. + * + * @param exec_callback whether the previous callback is to be executed + */ +static void +set_fade_callback(session_t *ps, win *w, + void (*callback) (session_t *ps, win *w), bool exec_callback) { + void (*old_callback) (session_t *ps, win *w) = w->fade_callback; + + w->fade_callback = callback; + // Must be the last line as the callback could destroy w! + if (exec_callback && old_callback) { + old_callback(ps, w); + // Although currently no callback function affects window state on + // next paint, it could, in the future + ps->idling = false; + } +} + +// === Shadows === + +static double __attribute__((const)) +gaussian(double r, double x, double y) { + return ((1 / (sqrt(2 * M_PI * r))) * + exp((- (x * x + y * y)) / (2 * r * r))); +} + +static conv * +make_gaussian_map(double r) { + conv *c; + int size = ((int) ceil((r * 3)) + 1) & ~1; + int center = size / 2; + int x, y; + double t; + double g; + + c = malloc(sizeof(conv) + size * size * sizeof(double)); + c->size = size; + c->data = (double *) (c + 1); + t = 0.0; + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + g = gaussian(r, x - center, y - center); + t += g; + c->data[y * size + x] = g; + } + } + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + c->data[y * size + x] /= t; + } + } + + return c; +} + +/* + * A picture will help + * + * -center 0 width width+center + * -center +-----+-------------------+-----+ + * | | | | + * | | | | + * 0 +-----+-------------------+-----+ + * | | | | + * | | | | + * | | | | + * height +-----+-------------------+-----+ + * | | | | + * height+ | | | | + * center +-----+-------------------+-----+ + */ + +static unsigned char +sum_gaussian(conv *map, double opacity, + int x, int y, int width, int height) { + int fx, fy; + double *g_data; + double *g_line = map->data; + int g_size = map->size; + int center = g_size / 2; + int fx_start, fx_end; + int fy_start, fy_end; + double v; + + /* + * Compute set of filter values which are "in range", + * that's the set with: + * 0 <= x + (fx-center) && x + (fx-center) < width && + * 0 <= y + (fy-center) && y + (fy-center) < height + * + * 0 <= x + (fx - center) x + fx - center < width + * center - x <= fx fx < width + center - x + */ + + fx_start = center - x; + if (fx_start < 0) fx_start = 0; + fx_end = width + center - x; + if (fx_end > g_size) fx_end = g_size; + + fy_start = center - y; + if (fy_start < 0) fy_start = 0; + fy_end = height + center - y; + if (fy_end > g_size) fy_end = g_size; + + g_line = g_line + fy_start * g_size + fx_start; + + v = 0; + + for (fy = fy_start; fy < fy_end; fy++) { + g_data = g_line; + g_line += g_size; + + for (fx = fx_start; fx < fx_end; fx++) { + v += *g_data++; + } + } + + if (v > 1) v = 1; + + return ((unsigned char) (v * opacity * 255.0)); +} + +/* precompute shadow corners and sides + to save time for large windows */ + +static void +presum_gaussian(session_t *ps, conv *map) { + int center = map->size / 2; + int opacity, x, y; + + ps->cgsize = map->size; + + if (ps->shadow_corner) + free(ps->shadow_corner); + if (ps->shadow_top) + free(ps->shadow_top); + + ps->shadow_corner = malloc((ps->cgsize + 1) * (ps->cgsize + 1) * 26); + ps->shadow_top = malloc((ps->cgsize + 1) * 26); + + for (x = 0; x <= ps->cgsize; x++) { + ps->shadow_top[25 * (ps->cgsize + 1) + x] = + sum_gaussian(map, 1, x - center, center, ps->cgsize * 2, ps->cgsize * 2); + + for (opacity = 0; opacity < 25; opacity++) { + ps->shadow_top[opacity * (ps->cgsize + 1) + x] = + ps->shadow_top[25 * (ps->cgsize + 1) + x] * opacity / 25; + } + + for (y = 0; y <= x; y++) { + ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x] + = sum_gaussian(map, 1, x - center, y - center, ps->cgsize * 2, ps->cgsize * 2); + ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + x * (ps->cgsize + 1) + y] + = ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x]; + + for (opacity = 0; opacity < 25; opacity++) { + ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1) + + y * (ps->cgsize + 1) + x] + = ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1) + + x * (ps->cgsize + 1) + y] + = ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + + y * (ps->cgsize + 1) + x] * opacity / 25; + } + } + } +} + +static XImage * +make_shadow(session_t *ps, double opacity, + int width, int height) { + XImage *ximage; + unsigned char *data; + int ylimit, xlimit; + int swidth = width + ps->cgsize; + int sheight = height + ps->cgsize; + int center = ps->cgsize / 2; + int x, y; + unsigned char d; + int x_diff; + int opacity_int = (int)(opacity * 25); + + data = malloc(swidth * sheight * sizeof(unsigned char)); + if (!data) return 0; + + ximage = XCreateImage(ps->dpy, ps->vis, 8, + ZPixmap, 0, (char *) data, swidth, sheight, 8, swidth * sizeof(char)); + + if (!ximage) { + free(data); + return 0; + } + + /* + * Build the gaussian in sections + */ + + /* + * center (fill the complete data array) + */ + + // If clear_shadow is enabled and the border & corner shadow (which + // later will be filled) could entirely cover the area of the shadow + // that will be displayed, do not bother filling other pixels. If it + // can't, we must fill the other pixels here. + /* if (!(clear_shadow && ps->o.shadow_offset_x <= 0 && ps->o.shadow_offset_x >= -ps->cgsize + && ps->o.shadow_offset_y <= 0 && ps->o.shadow_offset_y >= -ps->cgsize)) { */ + if (ps->cgsize > 0) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + ps->cgsize]; + } else { + d = sum_gaussian(ps->gaussian_map, + opacity, center, center, width, height); + } + memset(data, d, sheight * swidth); + // } + + /* + * corners + */ + + ylimit = ps->cgsize; + if (ylimit > sheight / 2) ylimit = (sheight + 1) / 2; + + xlimit = ps->cgsize; + if (xlimit > swidth / 2) xlimit = (swidth + 1) / 2; + + for (y = 0; y < ylimit; y++) { + for (x = 0; x < xlimit; x++) { + if (xlimit == ps->cgsize && ylimit == ps->cgsize) { + d = ps->shadow_corner[opacity_int * (ps->cgsize + 1) * (ps->cgsize + 1) + + y * (ps->cgsize + 1) + x]; + } else { + d = sum_gaussian(ps->gaussian_map, + opacity, x - center, y - center, width, height); + } + data[y * swidth + x] = d; + data[(sheight - y - 1) * swidth + x] = d; + data[(sheight - y - 1) * swidth + (swidth - x - 1)] = d; + data[y * swidth + (swidth - x - 1)] = d; + } + } + + /* + * top/bottom + */ + + x_diff = swidth - (ps->cgsize * 2); + if (x_diff > 0 && ylimit > 0) { + for (y = 0; y < ylimit; y++) { + if (ylimit == ps->cgsize) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + y]; + } else { + d = sum_gaussian(ps->gaussian_map, + opacity, center, y - center, width, height); + } + memset(&data[y * swidth + ps->cgsize], d, x_diff); + memset(&data[(sheight - y - 1) * swidth + ps->cgsize], d, x_diff); + } + } + + /* + * sides + */ + + for (x = 0; x < xlimit; x++) { + if (xlimit == ps->cgsize) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + x]; + } else { + d = sum_gaussian(ps->gaussian_map, + opacity, x - center, center, width, height); + } + for (y = ps->cgsize; y < sheight - ps->cgsize; y++) { + data[y * swidth + x] = d; + data[y * swidth + (swidth - x - 1)] = d; + } + } + + /* + if (clear_shadow) { + // Clear the region in the shadow that the window would cover based + // on shadow_offset_{x,y} user provides + int xstart = normalize_i_range(- (int) ps->o.shadow_offset_x, 0, swidth); + int xrange = normalize_i_range(width - (int) ps->o.shadow_offset_x, + 0, swidth) - xstart; + int ystart = normalize_i_range(- (int) ps->o.shadow_offset_y, 0, sheight); + int yend = normalize_i_range(height - (int) ps->o.shadow_offset_y, + 0, sheight); + int y; + + for (y = ystart; y < yend; y++) { + memset(&data[y * swidth + xstart], 0, xrange); + } + } + */ + + return ximage; +} + +/** + * Generate shadow <code>Picture</code> for a window. + */ +static bool +win_build_shadow(session_t *ps, win *w, double opacity) { + const int width = w->widthb; + const int height = w->heightb; + + XImage *shadow_image = NULL; + Pixmap shadow_pixmap = None, shadow_pixmap_argb = None; + Picture shadow_picture = None, shadow_picture_argb = None; + GC gc = None; + + shadow_image = make_shadow(ps, opacity, width, height); + if (!shadow_image) + return None; + + shadow_pixmap = XCreatePixmap(ps->dpy, ps->root, + shadow_image->width, shadow_image->height, 8); + shadow_pixmap_argb = XCreatePixmap(ps->dpy, ps->root, + shadow_image->width, shadow_image->height, 32); + + if (!shadow_pixmap || !shadow_pixmap_argb) + goto shadow_picture_err; + + shadow_picture = XRenderCreatePicture(ps->dpy, shadow_pixmap, + XRenderFindStandardFormat(ps->dpy, PictStandardA8), 0, 0); + shadow_picture_argb = XRenderCreatePicture(ps->dpy, shadow_pixmap_argb, + XRenderFindStandardFormat(ps->dpy, PictStandardARGB32), 0, 0); + if (!shadow_picture || !shadow_picture_argb) + goto shadow_picture_err; + + gc = XCreateGC(ps->dpy, shadow_pixmap, 0, 0); + if (!gc) + goto shadow_picture_err; + + XPutImage(ps->dpy, shadow_pixmap, gc, shadow_image, 0, 0, 0, 0, + shadow_image->width, shadow_image->height); + XRenderComposite(ps->dpy, PictOpSrc, ps->cshadow_picture, shadow_picture, + shadow_picture_argb, 0, 0, 0, 0, 0, 0, + shadow_image->width, shadow_image->height); + + w->shadow_paint.pixmap = shadow_pixmap_argb; + w->shadow_paint.pict = shadow_picture_argb; + + // Sync it once and only once + xr_sync(ps, w->shadow_paint.pixmap, NULL); + + bool success = paint_bind_tex(ps, &w->shadow_paint, shadow_image->width, shadow_image->height, 32, true); + + XFreeGC(ps->dpy, gc); + XDestroyImage(shadow_image); + XFreePixmap(ps->dpy, shadow_pixmap); + XRenderFreePicture(ps->dpy, shadow_picture); + + return success; + +shadow_picture_err: + if (shadow_image) + XDestroyImage(shadow_image); + if (shadow_pixmap) + XFreePixmap(ps->dpy, shadow_pixmap); + if (shadow_pixmap_argb) + XFreePixmap(ps->dpy, shadow_pixmap_argb); + if (shadow_picture) + XRenderFreePicture(ps->dpy, shadow_picture); + if (shadow_picture_argb) + XRenderFreePicture(ps->dpy, shadow_picture_argb); + if (gc) + XFreeGC(ps->dpy, gc); + + return false; +} + +/** + * Generate a 1x1 <code>Picture</code> of a particular color. + */ +static Picture +solid_picture(session_t *ps, bool argb, double a, + double r, double g, double b) { + Pixmap pixmap; + Picture picture; + XRenderPictureAttributes pa; + XRenderColor c; + + pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, argb ? 32 : 8); + + if (!pixmap) return None; + + pa.repeat = True; + picture = XRenderCreatePicture(ps->dpy, pixmap, + XRenderFindStandardFormat(ps->dpy, argb + ? PictStandardARGB32 : PictStandardA8), + CPRepeat, + &pa); + + if (!picture) { + XFreePixmap(ps->dpy, pixmap); + return None; + } + + c.alpha = a * 0xffff; + c.red = r * 0xffff; + c.green = g * 0xffff; + c.blue = b * 0xffff; + + XRenderFillRectangle(ps->dpy, PictOpSrc, picture, &c, 0, 0, 1, 1); + XFreePixmap(ps->dpy, pixmap); + + return picture; +} + +// === Error handling === + +static void +discard_ignore(session_t *ps, unsigned long sequence) { + while (ps->ignore_head) { + if ((long) (sequence - ps->ignore_head->sequence) > 0) { + ignore_t *next = ps->ignore_head->next; + free(ps->ignore_head); + ps->ignore_head = next; + if (!ps->ignore_head) { + ps->ignore_tail = &ps->ignore_head; + } + } else { + break; + } + } +} + +static void +set_ignore(session_t *ps, unsigned long sequence) { + ignore_t *i = malloc(sizeof(ignore_t)); + if (!i) return; + + i->sequence = sequence; + i->next = 0; + *ps->ignore_tail = i; + ps->ignore_tail = &i->next; +} + +static int +should_ignore(session_t *ps, unsigned long sequence) { + discard_ignore(ps, sequence); + return ps->ignore_head && ps->ignore_head->sequence == sequence; +} + +// === Windows === + +/** + * Get a specific attribute of a window. + * + * Returns a blank structure if the returned type and format does not + * match the requested type and format. + * + * @param ps current session + * @param w window + * @param atom atom of attribute to fetch + * @param length length to read + * @param rtype atom of the requested type + * @param rformat requested format + * @return a <code>winprop_t</code> structure containing the attribute + * and number of items. A blank one on failure. + */ +winprop_t +wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset, + long length, Atom rtype, int rformat) { + Atom type = None; + int format = 0; + unsigned long nitems = 0, after = 0; + unsigned char *data = NULL; + + if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length, + False, rtype, &type, &format, &nitems, &after, &data) + && nitems && (AnyPropertyType == type || type == rtype) + && (!rformat || format == rformat) + && (8 == format || 16 == format || 32 == format)) { + return (winprop_t) { + .data.p8 = data, + .nitems = nitems, + .type = type, + .format = format, + }; + } + + cxfree(data); + + return (winprop_t) { + .data.p8 = NULL, + .nitems = 0, + .type = AnyPropertyType, + .format = 0 + }; +} + +/** + * Check if a window has rounded corners. + */ +static void +win_rounded_corners(session_t *ps, win *w) { + if (!w->bounding_shaped) + return; + + // Fetch its bounding region + if (!w->border_size) + w->border_size = border_size(ps, w, true); + + // Quit if border_size() returns None + if (!w->border_size) + return; + + // Determine the minimum width/height of a rectangle that could mark + // a window as having rounded corners + unsigned short minwidth = max_i(w->widthb * (1 - ROUNDED_PERCENT), + w->widthb - ROUNDED_PIXELS); + unsigned short minheight = max_i(w->heightb * (1 - ROUNDED_PERCENT), + w->heightb - ROUNDED_PIXELS); + + // Get the rectangles in the bounding region + int nrects = 0, i; + XRectangle *rects = XFixesFetchRegion(ps->dpy, w->border_size, &nrects); + if (!rects) + return; + + // Look for a rectangle large enough for this window be considered + // having rounded corners + for (i = 0; i < nrects; ++i) + if (rects[i].width >= minwidth && rects[i].height >= minheight) { + w->rounded_corners = true; + cxfree(rects); + return; + } + + w->rounded_corners = false; + cxfree(rects); +} + +/** + * Add a pattern to a condition linked list. + */ +static bool +condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) { + if (!pattern) + return false; + +#ifdef CONFIG_C2 + if (!c2_parse(ps, pcondlst, pattern)) + exit(1); +#else + printf_errfq(1, "(): Condition support not compiled in."); +#endif + + return true; +} + +/** + * Determine the event mask for a window. + */ +static long +determine_evmask(session_t *ps, Window wid, win_evmode_t mode) { + long evmask = NoEventMask; + win *w = NULL; + + // Check if it's a mapped frame window + if (WIN_EVMODE_FRAME == mode + || ((w = find_win(ps, wid)) && IsViewable == w->a.map_state)) { + evmask |= PropertyChangeMask; + if (ps->o.track_focus && !ps->o.use_ewmh_active_win) + evmask |= FocusChangeMask; + } + + // Check if it's a mapped client window + if (WIN_EVMODE_CLIENT == mode + || ((w = find_toplevel(ps, wid)) && IsViewable == w->a.map_state)) { + if (ps->o.frame_opacity || ps->o.track_wdata || ps->track_atom_lst + || ps->o.detect_client_opacity) + evmask |= PropertyChangeMask; + } + + return evmask; +} + +/** + * Find out the WM frame of a client window by querying X. + * + * @param ps current session + * @param wid window ID + * @return struct _win object of the found window, NULL if not found + */ +static win * +find_toplevel2(session_t *ps, Window wid) { + win *w = NULL; + + // We traverse through its ancestors to find out the frame + while (wid && wid != ps->root && !(w = find_win(ps, wid))) { + Window troot; + Window parent; + Window *tchildren; + unsigned tnchildren; + + // XQueryTree probably fails if you run compton when X is somehow + // initializing (like add it in .xinitrc). In this case + // just leave it alone. + if (!XQueryTree(ps->dpy, wid, &troot, &parent, &tchildren, + &tnchildren)) { + parent = 0; + break; + } + + cxfree(tchildren); + + wid = parent; + } + + return w; +} + +/** + * Recheck currently focused window and set its <code>w->focused</code> + * to true. + * + * @param ps current session + * @return struct _win of currently focused window, NULL if not found + */ +static win * +recheck_focus(session_t *ps) { + // Use EWMH _NET_ACTIVE_WINDOW if enabled + if (ps->o.use_ewmh_active_win) { + update_ewmh_active_win(ps); + return ps->active_win; + } + + // Determine the currently focused window so we can apply appropriate + // opacity on it + Window wid = 0; + int revert_to; + + XGetInputFocus(ps->dpy, &wid, &revert_to); + + win *w = find_win_all(ps, wid); + +#ifdef DEBUG_EVENTS + print_timestamp(ps); + printf_dbgf("(): %#010lx (%#010lx \"%s\") focused.\n", wid, + (w ? w->id: None), (w ? w->name: NULL)); +#endif + + // And we set the focus state here + if (w) { + win_set_focused(ps, w, true); + return w; + } + + return NULL; +} + +static Bool +determine_window_transparent_to_black(const session_t *ps, Window w); + +static Bool +determine_window_transparent_to_desktop(const session_t *ps, Window w); + +static bool +get_root_tile(session_t *ps) { + /* + if (ps->o.paint_on_overlay) { + return ps->root_picture; + } */ + + assert(!ps->root_tile_paint.pixmap); + ps->root_tile_fill = false; + + bool fill = false; + Pixmap pixmap = None; + + // Get the values of background attributes + for (int p = 0; background_props_str[p]; p++) { + winprop_t prop = wid_get_prop(ps, ps->root, + get_atom(ps, background_props_str[p]), + 1L, XA_PIXMAP, 32); + if (prop.nitems) { + pixmap = *prop.data.p32; + fill = false; + free_winprop(&prop); + break; + } + free_winprop(&prop); + } + + // Make sure the pixmap we got is valid + if (pixmap && !validate_pixmap(ps, pixmap)) + pixmap = None; + + // Create a pixmap if there isn't any + if (!pixmap) { + pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, ps->depth); + fill = true; + } + + // Create Picture + { + XRenderPictureAttributes pa = { + .repeat = True, + }; + ps->root_tile_paint.pict = XRenderCreatePicture( + ps->dpy, pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis), + CPRepeat, &pa); + } + + // Fill pixmap if needed + if (fill) { + XRenderColor c; + + c.red = c.green = c.blue = 0x8080; + c.alpha = 0xffff; + XRenderFillRectangle(ps->dpy, PictOpSrc, ps->root_tile_paint.pict, &c, 0, 0, 1, 1); + } + + ps->root_tile_fill = fill; + ps->root_tile_paint.pixmap = pixmap; +#ifdef CONFIG_VSYNC_OPENGL + if (BKEND_GLX == ps->o.backend) + return glx_bind_pixmap(ps, &ps->root_tile_paint.ptex, ps->root_tile_paint.pixmap, 0, 0, 0); +#endif + + return true; +} + +/** + * Paint root window content. + */ +static void +paint_root(session_t *ps, XserverRegion reg_paint) { + if (!ps->root_tile_paint.pixmap) + get_root_tile(ps); + + win_render(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, + NULL, ps->root_tile_paint.pict); +} + +/** + * Get a rectangular region a window occupies, excluding shadow. + */ +static XserverRegion +win_get_region(session_t *ps, win *w, bool use_offset) { + XRectangle r; + + r.x = (use_offset ? w->a.x: 0); + r.y = (use_offset ? w->a.y: 0); + r.width = w->widthb; + r.height = w->heightb; + + return XFixesCreateRegion(ps->dpy, &r, 1); +} + +/** + * Get a rectangular region a window occupies, excluding frame and shadow. + */ +static XserverRegion +win_get_region_noframe(session_t *ps, win *w, bool use_offset) { + XRectangle r; + + r.x = (use_offset ? w->a.x: 0) + w->a.border_width + w->left_width; + r.y = (use_offset ? w->a.y: 0) + w->a.border_width + w->top_width; + r.width = max_i(w->a.width - w->left_width - w->right_width, 0); + r.height = max_i(w->a.height - w->top_width - w->bottom_width, 0); + + if (r.width > 0 && r.height > 0) + return XFixesCreateRegion(ps->dpy, &r, 1); + else + return XFixesCreateRegion(ps->dpy, NULL, 0); +} + +/** + * Get a rectangular region a window (and possibly its shadow) occupies. + * + * Note w->shadow and shadow geometry must be correct before calling this + * function. + */ +static XserverRegion +win_extents(session_t *ps, win *w) { + XRectangle r; + + r.x = w->a.x; + r.y = w->a.y; + r.width = w->widthb; + r.height = w->heightb; + + if (w->shadow) { + XRectangle sr; + + sr.x = w->a.x + w->shadow_dx; + sr.y = w->a.y + w->shadow_dy; + sr.width = w->shadow_width; + sr.height = w->shadow_height; + + if (sr.x < r.x) { + r.width = (r.x + r.width) - sr.x; + r.x = sr.x; + } + + if (sr.y < r.y) { + r.height = (r.y + r.height) - sr.y; + r.y = sr.y; + } + + if (sr.x + sr.width > r.x + r.width) { + r.width = sr.x + sr.width - r.x; + } + + if (sr.y + sr.height > r.y + r.height) { + r.height = sr.y + sr.height - r.y; + } + } + + return XFixesCreateRegion(ps->dpy, &r, 1); +} + +/** + * Retrieve the bounding shape of a window. + */ +static XserverRegion +border_size(session_t *ps, win *w, bool use_offset) { + // Start with the window rectangular region + XserverRegion fin = win_get_region(ps, w, use_offset); + + // Only request for a bounding region if the window is shaped + if (w->bounding_shaped) { + /* + * if window doesn't exist anymore, this will generate an error + * as well as not generate a region. Perhaps a better XFixes + * architecture would be to have a request that copies instead + * of creates, that way you'd just end up with an empty region + * instead of an invalid XID. + */ + + XserverRegion border = XFixesCreateRegionFromWindow( + ps->dpy, w->id, WindowRegionBounding); + + if (!border) + return fin; + + if (use_offset) { + // Translate the region to the correct place + XFixesTranslateRegion(ps->dpy, border, + w->a.x + w->a.border_width, + w->a.y + w->a.border_width); + } + + // Intersect the bounding region we got with the window rectangle, to + // make sure the bounding region is not bigger than the window + // rectangle + XFixesIntersectRegion(ps->dpy, fin, fin, border); + XFixesDestroyRegion(ps->dpy, border); + } + + return fin; +} + +/** + * Look for the client window of a particular window. + */ +static Window +find_client_win(session_t *ps, Window w) { + if (wid_has_prop(ps, w, ps->atom_client)) { + return w; + } + + Window *children; + unsigned int nchildren; + unsigned int i; + Window ret = 0; + + if (!wid_get_children(ps, w, &children, &nchildren)) { + return 0; + } + + for (i = 0; i < nchildren; ++i) { + if ((ret = find_client_win(ps, children[i]))) + break; + } + + cxfree(children); + + return ret; +} + +/** + * Retrieve frame extents from a window. + */ +static void +get_frame_extents(session_t *ps, win *w, Window client) { + w->left_width = 0; + w->right_width = 0; + w->top_width = 0; + w->bottom_width = 0; + + winprop_t prop = wid_get_prop(ps, client, ps->atom_frame_extents, + 4L, XA_CARDINAL, 32); + + if (4 == prop.nitems) { + const long * const extents = prop.data.p32; + w->left_width = extents[0]; + w->right_width = extents[1]; + w->top_width = extents[2]; + w->bottom_width = extents[3]; + + if (ps->o.frame_opacity) + update_reg_ignore_expire(ps, w); + } + +#ifdef DEBUG_FRAME + printf_dbgf("(%#010lx): %d, %d, %d, %d\n", w->id, + w->left_width, w->right_width, w->top_width, w->bottom_width); +#endif + + free_winprop(&prop); +} + +/** + * Get alpha <code>Picture</code> for an opacity in <code>double</code>. + */ +static inline Picture +get_alpha_pict_d(session_t *ps, double o) { + assert((round(normalize_d(o) / ps->o.alpha_step)) <= round(1.0 / ps->o.alpha_step)); + return ps->alpha_picts[(int) (round(normalize_d(o) + / ps->o.alpha_step))]; +} + +/** + * Get alpha <code>Picture</code> for an opacity in + * <code>opacity_t</code>. + */ +static inline Picture +get_alpha_pict_o(session_t *ps, opacity_t o) { + return get_alpha_pict_d(ps, (double) o / OPAQUE); +} + +static win * +paint_preprocess(session_t *ps, win *list) { + win *t = NULL, *next = NULL; + + // Fading step calculation + time_ms_t steps = 0L; + if (ps->fade_time) { + steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta; + } + // Reset fade_time if unset, or there appears to be a time disorder + if (!ps->fade_time || steps < 0L) { + ps->fade_time = get_time_ms(); + steps = 0L; + } + ps->fade_time += steps * ps->o.fade_delta; + + XserverRegion last_reg_ignore = None; + + bool unredir_possible = false; + // Trace whether it's the highest window to paint + bool is_highest = true; + for (win *w = list; w; w = next) { + bool to_paint = true; + const winmode_t mode_old = w->mode; + + // In case calling the fade callback function destroys this window + next = w->next; + opacity_t opacity_old = w->opacity; + + // Data expiration + { + // Remove built shadow if needed + if (w->flags & WFLAG_SIZE_CHANGE) + free_paint(ps, &w->shadow_paint); + + // Destroy reg_ignore on all windows if they should expire + if (ps->reg_ignore_expire) + free_region(ps, &w->reg_ignore); + } + + // Update window opacity target and dim state if asked + if (WFLAG_OPCT_CHANGE & w->flags) { + calc_opacity(ps, w); + calc_dim(ps, w); + } + + // Run fading + run_fade(ps, w, steps); + + // Opacity will not change, from now on. + + // Give up if it's not damaged or invisible, or it's unmapped and its + // pixmap is gone (for example due to a ConfigureNotify), or when it's + // excluded + if (!w->damaged + || w->a.x + w->a.width < 1 || w->a.y + w->a.height < 1 + || w->a.x >= ps->root_width || w->a.y >= ps->root_height + || ((IsUnmapped == w->a.map_state || w->destroyed) && !w->paint.pixmap) + || get_alpha_pict_o(ps, w->opacity) == ps->alpha_picts[0] + || w->paint_excluded) + to_paint = false; + + // to_paint will never change afterward + + // Determine mode as early as possible + if (to_paint && (!w->to_paint || w->opacity != opacity_old)) + win_determine_mode(ps, w); + + if (to_paint) { + // Fetch bounding region + if (!w->border_size) + w->border_size = border_size(ps, w, true); + + // Fetch window extents + if (!w->extents) + w->extents = win_extents(ps, w); + + // Calculate frame_opacity + { + double frame_opacity_old = w->frame_opacity; + + if (ps->o.frame_opacity && 1.0 != ps->o.frame_opacity + && win_has_frame(w)) + w->frame_opacity = get_opacity_percent(w) * + ps->o.frame_opacity; + else + w->frame_opacity = 0.0; + + // Destroy all reg_ignore above when frame opaque state changes on + // SOLID mode + if (w->to_paint && WMODE_SOLID == mode_old + && (0.0 == frame_opacity_old) != (0.0 == w->frame_opacity)) + ps->reg_ignore_expire = true; + } + + // Calculate shadow opacity + if (w->frame_opacity) + w->shadow_opacity = ps->o.shadow_opacity * w->frame_opacity; + else + w->shadow_opacity = ps->o.shadow_opacity * get_opacity_percent(w); + } + + // Add window to damaged area if its painting status changes + // or opacity changes + if (to_paint != w->to_paint || w->opacity != opacity_old) + add_damage_win(ps, w); + + // Destroy all reg_ignore above when window mode changes + if ((to_paint && WMODE_SOLID == w->mode) + != (w->to_paint && WMODE_SOLID == mode_old)) + ps->reg_ignore_expire = true; + + if (to_paint) { + // Generate ignore region for painting to reduce GPU load + if (ps->reg_ignore_expire || !w->to_paint) { + free_region(ps, &w->reg_ignore); + + // If the window is solid, we add the window region to the + // ignored region + if (WMODE_SOLID == w->mode) { + if (!w->frame_opacity) { + if (w->border_size) + w->reg_ignore = copy_region(ps, w->border_size); + else + w->reg_ignore = win_get_region(ps, w, true); + } + else { + w->reg_ignore = win_get_region_noframe(ps, w, true); + if (w->border_size) + XFixesIntersectRegion(ps->dpy, w->reg_ignore, w->reg_ignore, + w->border_size); + } + + if (last_reg_ignore) + XFixesUnionRegion(ps->dpy, w->reg_ignore, w->reg_ignore, + last_reg_ignore); + } + // Otherwise we copy the last region over + else if (last_reg_ignore) + w->reg_ignore = copy_region(ps, last_reg_ignore); + else + w->reg_ignore = None; + } + + last_reg_ignore = w->reg_ignore; + + // (Un)redirect screen + // We could definitely unredirect the screen when there's no window to + // paint, but this is typically unnecessary, may cause flickering when + // fading is enabled, and could create inconsistency when the wallpaper + // is not correctly set. + if (ps->o.unredir_if_possible && is_highest && to_paint) { + is_highest = false; + if (WMODE_SOLID == w->mode + && (!w->frame_opacity || !win_has_frame(w)) + && win_is_fullscreen(ps, w) + && !w->unredir_if_possible_excluded) + unredir_possible = true; + } + + // Reset flags + w->flags = 0; + } + + // Avoid setting w->to_paint if w is to be freed + bool destroyed = (w->opacity_tgt == w->opacity && w->destroyed); + + if (to_paint) { + w->prev_trans = t; + t = w; + } + else { + assert(w->destroyed == (w->fade_callback == destroy_callback)); + check_fade_fin(ps, w); + } + + if (!destroyed) + w->to_paint = to_paint; + } + + + // If possible, unredirect all windows and stop painting + if (UNSET != ps->o.redirected_force) + unredir_possible = !ps->o.redirected_force; + + // If there's no window to paint, and the screen isn't redirected, + // don't redirect it. + if (ps->o.unredir_if_possible && is_highest && !ps->redirected) + unredir_possible = true; + if (unredir_possible) { + if (ps->redirected) { + if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit) + redir_stop(ps); + else if (!ps->tmout_unredir->enabled) { + timeout_reset(ps, ps->tmout_unredir); + ps->tmout_unredir->enabled = true; + } + } + } + else { + ps->tmout_unredir->enabled = false; + redir_start(ps); + } + + return t; +} + +/** + * Paint the shadow of a window. + */ +static inline void +win_paint_shadow(session_t *ps, win *w, + XserverRegion reg_paint, const reg_data_t *pcache_reg) { + if (!paint_isvalid(ps, &w->shadow_paint)) { + printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id); + return; + } + + render(ps, 0, 0, w->a.x + w->shadow_dx, w->a.y + w->shadow_dy, + w->shadow_width, w->shadow_height, w->shadow_opacity, true, false, + w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, pcache_reg); +} + +/** + * Create an picture. + */ +static inline Picture +xr_build_picture(session_t *ps, int wid, int hei, + XRenderPictFormat *pictfmt) { + if (!pictfmt) + pictfmt = XRenderFindVisualFormat(ps->dpy, ps->vis); + + int depth = pictfmt->depth; + + Pixmap tmp_pixmap = XCreatePixmap(ps->dpy, ps->root, wid, hei, depth); + if (!tmp_pixmap) + return None; + + Picture tmp_picture = XRenderCreatePicture(ps->dpy, tmp_pixmap, + pictfmt, 0, 0); + free_pixmap(ps, &tmp_pixmap); + + return tmp_picture; +} + +/** + * @brief Blur an area on a buffer. + * + * @param ps current session + * @param tgt_buffer a buffer as both source and destination + * @param x x pos + * @param y y pos + * @param wid width + * @param hei height + * @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at + * least one kernel + * @param reg_clip a clipping region to be applied on intermediate buffers + * + * @return true if successful, false otherwise + */ +static bool +xr_blur_dst(session_t *ps, Picture tgt_buffer, + int x, int y, int wid, int hei, XFixed **blur_kerns, + XserverRegion reg_clip) { + assert(blur_kerns[0]); + + // Directly copying from tgt_buffer to it does not work, so we create a + // Picture in the middle. + Picture tmp_picture = xr_build_picture(ps, wid, hei, NULL); + + if (!tmp_picture) { + printf_errf("(): Failed to build intermediate Picture."); + return false; + } + + if (reg_clip && tmp_picture) + XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0); + + Picture src_pict = tgt_buffer, dst_pict = tmp_picture; + for (int i = 0; blur_kerns[i]; ++i) { + assert(i < MAX_BLUR_PASS - 1); + XFixed *convolution_blur = blur_kerns[i]; + int kwid = XFixedToDouble(convolution_blur[0]), + khei = XFixedToDouble(convolution_blur[1]); + bool rd_from_tgt = (tgt_buffer == src_pict); + + // Copy from source picture to destination. The filter must + // be applied on source picture, to get the nearby pixels outside the + // window. + XRenderSetPictureFilter(ps->dpy, src_pict, XRFILTER_CONVOLUTION, + convolution_blur, kwid * khei + 2); + XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, dst_pict, + (rd_from_tgt ? x: 0), (rd_from_tgt ? y: 0), 0, 0, + (rd_from_tgt ? 0: x), (rd_from_tgt ? 0: y), wid, hei); + xrfilter_reset(ps, src_pict); + + { + XserverRegion tmp = src_pict; + src_pict = dst_pict; + dst_pict = tmp; + } + } + + if (src_pict != tgt_buffer) + XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, tgt_buffer, + 0, 0, 0, 0, x, y, wid, hei); + + free_picture(ps, &tmp_picture); + + return true; +} + +/** + * Blur the background of a window. + */ +static inline void +win_blur_background(session_t *ps, win *w, Picture tgt_buffer, + XserverRegion reg_paint, const reg_data_t *pcache_reg) { + const int x = w->a.x; + const int y = w->a.y; + const int wid = w->widthb; + const int hei = w->heightb; + + double factor_center = 1.0; + // Adjust blur strength according to window opacity, to make it appear + // better during fading + if (!ps->o.blur_background_fixed) { + double pct = 1.0 - get_opacity_percent(w) * (1.0 - 1.0 / 9.0); + factor_center = pct * 8.0 / (1.1 - pct); + } + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + // Normalize blur kernels + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + XFixed *kern_src = ps->o.blur_kerns[i]; + XFixed *kern_dst = ps->blur_kerns_cache[i]; + assert(i < MAX_BLUR_PASS); + if (!kern_src) { + assert(!kern_dst); + break; + } + + assert(!kern_dst + || (kern_src[0] == kern_dst[0] && kern_src[1] == kern_dst[1])); + + // Skip for fixed factor_center if the cache exists already + if (ps->o.blur_background_fixed && kern_dst) continue; + + int kwid = XFixedToDouble(kern_src[0]), + khei = XFixedToDouble(kern_src[1]); + + // Allocate cache space if needed + if (!kern_dst) { + kern_dst = malloc((kwid * khei + 2) * sizeof(XFixed)); + if (!kern_dst) { + printf_errf("(): Failed to allocate memory for blur kernel."); + return; + } + ps->blur_kerns_cache[i] = kern_dst; + } + + // Modify the factor of the center pixel + kern_src[2 + (khei / 2) * kwid + kwid / 2] = + XDoubleToFixed(factor_center); + + // Copy over + memcpy(kern_dst, kern_src, (kwid * khei + 2) * sizeof(XFixed)); + normalize_conv_kern(kwid, khei, kern_dst + 2); + } + + // Minimize the region we try to blur, if the window itself is not + // opaque, only the frame is. + XserverRegion reg_noframe = None; + if (WMODE_SOLID == w->mode) { + XserverRegion reg_all = border_size(ps, w, false); + reg_noframe = win_get_region_noframe(ps, w, false); + XFixesSubtractRegion(ps->dpy, reg_noframe, reg_all, reg_noframe); + free_region(ps, ®_all); + } + xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, + reg_noframe); + free_region(ps, ®_noframe); + } + break; +#ifdef CONFIG_VSYNC_OPENGL_GLSL + case BKEND_GLX: + // TODO: Handle frame opacity + glx_blur_dst(ps, x, y, wid, hei, ps->glx_z - 0.5, factor_center, + reg_paint, pcache_reg, &w->glx_blur_cache); + break; +#endif + default: + assert(0); + } +} + +static void +render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, + double opacity, bool argb, bool neg, + Picture pict, glx_texture_t *ptex, + XserverRegion reg_paint, const reg_data_t *pcache_reg) { + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + Picture alpha_pict = get_alpha_pict_d(ps, opacity); + if (alpha_pict != ps->alpha_picts[0]) { + int op = ((!argb && !alpha_pict) ? PictOpSrc: PictOpOver); + XRenderComposite(ps->dpy, op, pict, alpha_pict, + ps->tgt_buffer.pict, x, y, 0, 0, dx, dy, wid, hei); + } + break; + } +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + glx_render(ps, ptex, x, y, dx, dy, wid, hei, + ps->glx_z, opacity, neg, reg_paint, pcache_reg); + ps->glx_z += 1; + break; +#endif + default: + assert(0); + } +} + +/** + * Paint a window itself and dim it if asked. + */ +static inline void +win_paint_win(session_t *ps, win *w, XserverRegion reg_paint, + const reg_data_t *pcache_reg) { + glx_mark(ps, w->id, true); + + // Fetch Pixmap + if (!w->paint.pixmap && ps->has_name_pixmap) { + set_ignore_next(ps); + w->paint.pixmap = XCompositeNameWindowPixmap(ps->dpy, w->id); + if (w->paint.pixmap) + free_fence(ps, &w->fence); + } + + Drawable draw = w->paint.pixmap; + if (!draw) + draw = w->id; + + // XRender: Build picture + if (bkend_use_xrender(ps) && !w->paint.pict) { + { + XRenderPictureAttributes pa = { + .subwindow_mode = IncludeInferiors, + }; + + w->paint.pict = XRenderCreatePicture(ps->dpy, draw, w->pictfmt, + CPSubwindowMode, &pa); + } + } + + if (IsViewable == w->a.map_state) + xr_sync(ps, draw, &w->fence); + + // GLX: Build texture + // Let glx_bind_pixmap() determine pixmap size, because if the user + // is resizing windows, the width and height we get may not be up-to-date, + // causing the jittering issue M4he reported in #7. + if (!paint_bind_tex(ps, &w->paint, 0, 0, 0, + (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { + printf_errf("(%#010lx): Failed to bind texture. Expect troubles.", w->id); + } + w->pixmap_damaged = false; + + if (!paint_isvalid(ps, &w->paint)) { + printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id); + return; + } + + const int x = w->a.x; + const int y = w->a.y; + const int wid = w->widthb; + const int hei = w->heightb; + + Picture pict = w->paint.pict; + + // Invert window color, if required + if (bkend_use_xrender(ps) && w->invert_color) { + Picture newpict = xr_build_picture(ps, wid, hei, w->pictfmt); + if (newpict) { + // Apply clipping region to save some CPU + if (reg_paint) { + XserverRegion reg = copy_region(ps, reg_paint); + XFixesTranslateRegion(ps->dpy, reg, -x, -y); + XFixesSetPictureClipRegion(ps->dpy, newpict, 0, 0, reg); + free_region(ps, ®); + } + + XRenderComposite(ps->dpy, PictOpSrc, pict, None, + newpict, 0, 0, 0, 0, 0, 0, wid, hei); + XRenderComposite(ps->dpy, PictOpDifference, ps->white_picture, None, + newpict, 0, 0, 0, 0, 0, 0, wid, hei); + // We use an extra PictOpInReverse operation to get correct pixel + // alpha. There could be a better solution. + if (WMODE_ARGB == w->mode) + XRenderComposite(ps->dpy, PictOpInReverse, pict, None, + newpict, 0, 0, 0, 0, 0, 0, wid, hei); + pict = newpict; + } + } + + const double dopacity = get_opacity_percent(w); + + if (!w->frame_opacity) { + win_render(ps, w, 0, 0, wid, hei, dopacity, reg_paint, pcache_reg, pict); + } + else { + // Painting parameters + const int t = w->a.border_width + w->top_width; + const int l = w->a.border_width + w->left_width; + const int b = w->a.border_width + w->bottom_width; + const int r = w->a.border_width + w->right_width; + +#define COMP_BDR(cx, cy, cwid, chei) \ + win_render(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity, \ + reg_paint, pcache_reg, pict) + + // The following complicated logic is required because some broken + // window managers (I'm talking about you, Openbox!) that makes + // top_width + bottom_width > height in some cases. + + // top + int phei = min_i(t, hei); + if (phei > 0) + COMP_BDR(0, 0, wid, phei); + + if (hei > t) { + phei = min_i(hei - t, b); + + // bottom + if (phei > 0) + COMP_BDR(0, hei - phei, wid, phei); + + phei = hei - t - phei; + if (phei > 0) { + int pwid = min_i(l, wid); + // left + if (pwid > 0) + COMP_BDR(0, t, pwid, phei); + + if (wid > l) { + pwid = min_i(wid - l, r); + + // right + if (pwid > 0) + COMP_BDR(wid - pwid, t, pwid, phei); + + pwid = wid - l - pwid; + if (pwid > 0) { + // body + win_render(ps, w, l, t, pwid, phei, dopacity, reg_paint, pcache_reg, pict); + } + } + } + } + } + +#undef COMP_BDR + + if (pict != w->paint.pict) + free_picture(ps, &pict); + + // Dimming the window if needed + if (w->dim) { + double dim_opacity = ps->o.inactive_dim; + if (!ps->o.inactive_dim_fixed) + dim_opacity *= get_opacity_percent(w); + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + unsigned short cval = 0xffff * dim_opacity; + + // Premultiply color + XRenderColor color = { + .red = 0, .green = 0, .blue = 0, .alpha = cval, + }; + + XRectangle rect = { + .x = x, + .y = y, + .width = wid, + .height = hei, + }; + + XRenderFillRectangles(ps->dpy, PictOpOver, ps->tgt_buffer.pict, + &color, &rect, 1); + } + break; +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + glx_dim_dst(ps, x, y, wid, hei, ps->glx_z - 0.7, dim_opacity, + reg_paint, pcache_reg); + break; +#endif + } + } + + glx_mark(ps, w->id, false); +} + +/** + * Rebuild cached <code>screen_reg</code>. + */ +static void +rebuild_screen_reg(session_t *ps) { + if (ps->screen_reg) + XFixesDestroyRegion(ps->dpy, ps->screen_reg); + ps->screen_reg = get_screen_region(ps); +} + +/** + * Rebuild <code>shadow_exclude_reg</code>. + */ +static void +rebuild_shadow_exclude_reg(session_t *ps) { + free_region(ps, &ps->shadow_exclude_reg); + XRectangle rect = geom_to_rect(ps, &ps->o.shadow_exclude_reg_geom, NULL); + ps->shadow_exclude_reg = rect_to_reg(ps, &rect); +} + +static void +paint_all(session_t *ps, XserverRegion region, XserverRegion region_real, win *t) { + if (!region_real) + region_real = region; + +#ifdef DEBUG_REPAINT + static struct timespec last_paint = { 0 }; +#endif + XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None; + +#ifdef CONFIG_VSYNC_OPENGL + if (bkend_use_glx(ps)) { + glx_paint_pre(ps, ®ion); + } +#endif + + if (!region) { + region_real = region = get_screen_region(ps); + } + else { + // Remove the damaged area out of screen + XFixesIntersectRegion(ps->dpy, region, region, ps->screen_reg); + } + +#ifdef MONITOR_REPAINT + // Note: MONITOR_REPAINT cannot work with DBE right now. + // Picture old_tgt_buffer = ps->tgt_buffer.pict; + ps->tgt_buffer.pict = ps->tgt_picture; +#else + if (!paint_isvalid(ps, &ps->tgt_buffer)) { + // DBE painting mode: Directly paint to a Picture of the back buffer + if (BKEND_XRENDER == ps->o.backend && ps->o.dbe) { + ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy, ps->root_dbe, + XRenderFindVisualFormat(ps->dpy, ps->vis), + 0, 0); + } + // No-DBE painting mode: Paint to an intermediate Picture then paint + // the Picture to root window + else { + if (!ps->tgt_buffer.pixmap) { + free_paint(ps, &ps->tgt_buffer); + ps->tgt_buffer.pixmap = XCreatePixmap(ps->dpy, ps->root, + ps->root_width, ps->root_height, ps->depth); + } + + if (BKEND_GLX != ps->o.backend) + ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy, + ps->tgt_buffer.pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis), + 0, 0); + } + } +#endif + + if (BKEND_XRENDER == ps->o.backend) + XFixesSetPictureClipRegion(ps->dpy, ps->tgt_picture, 0, 0, region_real); + +#ifdef MONITOR_REPAINT + switch (ps->o.backend) { + case BKEND_XRENDER: + XRenderComposite(ps->dpy, PictOpSrc, ps->black_picture, None, + ps->tgt_picture, 0, 0, 0, 0, 0, 0, + ps->root_width, ps->root_height); + break; + case BKEND_GLX: + case BKEND_XR_GLX_HYBRID: + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + break; + } +#endif + + if (t && t->reg_ignore) { + // Calculate the region upon which the root window is to be painted + // based on the ignore region of the lowest window, if available + reg_paint = reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0); + XFixesSubtractRegion(ps->dpy, reg_paint, region, t->reg_ignore); + } + else { + reg_paint = region; + } + + set_tgt_clip(ps, reg_paint, NULL); + paint_root(ps, reg_paint); + + // Create temporary regions for use during painting + if (!reg_tmp) + reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0); + reg_tmp2 = XFixesCreateRegion(ps->dpy, NULL, 0); + + for (win *w = t; w; w = w->prev_trans) { + // Painting shadow + if (w->shadow) { + // Lazy shadow building + if (!paint_isvalid(ps, &w->shadow_paint)) + win_build_shadow(ps, w, 1); + + // Shadow is to be painted based on the ignore region of current + // window + if (w->reg_ignore) { + if (w == t) { + // If it's the first cycle and reg_tmp2 is not ready, calculate + // the paint region here + reg_paint = reg_tmp; + XFixesSubtractRegion(ps->dpy, reg_paint, region, w->reg_ignore); + } + else { + // Otherwise, used the cached region during last cycle + reg_paint = reg_tmp2; + } + XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->extents); + } + else { + reg_paint = reg_tmp; + XFixesIntersectRegion(ps->dpy, reg_paint, region, w->extents); + } + + if (ps->shadow_exclude_reg) + XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint, + ps->shadow_exclude_reg); + + // Might be worthwhile to crop the region to shadow border + { + XRectangle rec_shadow_border = { + .x = w->a.x + w->shadow_dx, + .y = w->a.y + w->shadow_dy, + .width = w->shadow_width, + .height = w->shadow_height + }; + XserverRegion reg_shadow = XFixesCreateRegion(ps->dpy, + &rec_shadow_border, 1); + XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, reg_shadow); + free_region(ps, ®_shadow); + } + + // Clear the shadow here instead of in make_shadow() for saving GPU + // power and handling shaped windows + if (ps->o.clear_shadow && w->border_size) + XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint, w->border_size); + +#ifdef CONFIG_XINERAMA + if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0) + XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, + ps->xinerama_scr_regs[w->xinerama_scr]); +#endif + + // Detect if the region is empty before painting + { + reg_data_t cache_reg = REG_DATA_INIT; + if (region == reg_paint + || !is_region_empty(ps, reg_paint, &cache_reg)) { + set_tgt_clip(ps, reg_paint, &cache_reg); + + win_paint_shadow(ps, w, reg_paint, &cache_reg); + } + free_reg_data(&cache_reg); + } + } + + // Calculate the region based on the reg_ignore of the next (higher) + // window and the bounding region + reg_paint = reg_tmp; + if (w->prev_trans && w->prev_trans->reg_ignore) { + XFixesSubtractRegion(ps->dpy, reg_paint, region, + w->prev_trans->reg_ignore); + // Copy the subtracted region to be used for shadow painting in next + // cycle + XFixesCopyRegion(ps->dpy, reg_tmp2, reg_paint); + + if (w->border_size) + XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->border_size); + } + else { + if (w->border_size) + XFixesIntersectRegion(ps->dpy, reg_paint, region, w->border_size); + else + reg_paint = region; + } + + { + reg_data_t cache_reg = REG_DATA_INIT; + if (!is_region_empty(ps, reg_paint, &cache_reg)) { + set_tgt_clip(ps, reg_paint, &cache_reg); + + /* Here we redraw the background of the transparent window if we want + to do anything special (i.e. anything other than showing the + windows and desktop prestacked behind of the window). + For example, if you want to blur the background or show another + background pixmap entirely here is the place to do it; simply + draw the new background onto ps->tgt_buffer.pict before continuing! */ + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + if (w->show_root_tile) { + XRenderComposite (ps->dpy, PictOpSrc, ps->root_tile_paint.pict, None, ps->tgt_buffer.pict, + w->a.x, w->a.y, w->a.x, w->a.y, + w->a.x, w->a.y, w->widthb, w->heightb); + } + else if (w->show_black_background) { + XRenderComposite (ps->dpy, PictOpSrc, ps->black_picture, None, ps->tgt_buffer.pict, + w->a.x, w->a.y, w->a.x, w->a.y, + w->a.x, w->a.y, w->widthb, w->heightb); + } + break; + } +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + { + if (w->show_root_tile) { + glx_render(ps, ps->root_tile_paint.ptex, w->a.x, w->a.y, w->a.x, w->a.y, w->widthb, w->heightb, + ps->glx_z, 1.0, false, reg_paint, &cache_reg); + } + else if (w->show_black_background) { + glx_render_specified_color(ps, 0, w->a.x, w->a.y, w->widthb, w->heightb, + ps->glx_z, reg_paint, &cache_reg); + } + break; + } +#endif + } + + // Blur window background + if (w->blur_background && (WMODE_SOLID != w->mode + || (ps->o.blur_background_frame && w->frame_opacity))) { + win_blur_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg); + } + + // Painting the window + win_paint_win(ps, w, reg_paint, &cache_reg); + } + free_reg_data(&cache_reg); + } + } + + // Free up all temporary regions + XFixesDestroyRegion(ps->dpy, reg_tmp); + XFixesDestroyRegion(ps->dpy, reg_tmp2); + + // Do this as early as possible + if (!ps->o.dbe) + set_tgt_clip(ps, None, NULL); + + if (ps->o.vsync) { + // Make sure all previous requests are processed to achieve best + // effect + XSync(ps->dpy, False); +#ifdef CONFIG_VSYNC_OPENGL + if (ps->glx_context) { + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + } +#endif + } + + // Wait for VBlank. We could do it aggressively (send the painting + // request and XFlush() on VBlank) or conservatively (send the request + // only on VBlank). + if (!ps->o.vsync_aggressive) + vsync_wait(ps); + + switch (ps->o.backend) { + case BKEND_XRENDER: + // DBE painting mode, only need to swap the buffer + if (ps->o.dbe) { + XdbeSwapInfo swap_info = { + .swap_window = get_tgt_window(ps), + // Is it safe to use XdbeUndefined? + .swap_action = XdbeCopied + }; + XdbeSwapBuffers(ps->dpy, &swap_info, 1); + } + // No-DBE painting mode + else if (ps->tgt_buffer.pict != ps->tgt_picture) { + XRenderComposite( + ps->dpy, PictOpSrc, ps->tgt_buffer.pict, None, + ps->tgt_picture, 0, 0, 0, 0, + 0, 0, ps->root_width, ps->root_height); + } + break; +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_XR_GLX_HYBRID: + XSync(ps->dpy, False); + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + assert(ps->tgt_buffer.pixmap); + xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); + paint_bind_tex_real(ps, &ps->tgt_buffer, + ps->root_width, ps->root_height, ps->depth, + !ps->o.glx_no_rebind_pixmap); + // See #163 + xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, + ps->root_width, ps->root_height, 0, 1.0, false, region_real, NULL); + // No break here! + case BKEND_GLX: + if (ps->o.glx_use_copysubbuffermesa) + glx_swap_copysubbuffermesa(ps, region_real); + else + glXSwapBuffers(ps->dpy, get_tgt_window(ps)); + break; +#endif + default: + assert(0); + } + glx_mark_frame(ps); + + if (ps->o.vsync_aggressive) + vsync_wait(ps); + + XFlush(ps->dpy); + +#ifdef CONFIG_VSYNC_OPENGL + if (ps->glx_context) { + glFlush(); + glXWaitX(); + } +#endif + + XFixesDestroyRegion(ps->dpy, region); + +#ifdef DEBUG_REPAINT + print_timestamp(ps); + struct timespec now = get_time_timespec(); + struct timespec diff = { 0 }; + timespec_subtract(&diff, &now, &last_paint); + printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); + last_paint = now; + printf("paint:"); + for (win *w = t; w; w = w->prev_trans) + printf(" %#010lx", w->id); + putchar('\n'); + fflush(stdout); +#endif + + // Check if fading is finished on all painted windows + { + win *pprev = NULL; + for (win *w = t; w; w = pprev) { + pprev = w->prev_trans; + check_fade_fin(ps, w); + } + } +} + +static void +add_damage(session_t *ps, XserverRegion damage) { + // Ignore damage when screen isn't redirected + if (!ps->redirected) + free_region(ps, &damage); + + if (!damage) return; + if (ps->all_damage) { + XFixesUnionRegion(ps->dpy, ps->all_damage, ps->all_damage, damage); + XFixesDestroyRegion(ps->dpy, damage); + } else { + ps->all_damage = damage; + } +} + +static void +repair_win(session_t *ps, win *w) { + if (IsViewable != w->a.map_state) + return; + + XserverRegion parts; + + if (!w->damaged) { + parts = win_extents(ps, w); + set_ignore_next(ps); + XDamageSubtract(ps->dpy, w->damage, None, None); + } else { + parts = XFixesCreateRegion(ps->dpy, 0, 0); + set_ignore_next(ps); + XDamageSubtract(ps->dpy, w->damage, None, parts); + XFixesTranslateRegion(ps->dpy, parts, + w->a.x + w->a.border_width, + w->a.y + w->a.border_width); + } + + w->damaged = true; + w->pixmap_damaged = true; + + // Why care about damage when screen is unredirected? + // We will force full-screen repaint on redirection. + if (!ps->redirected) return; + + // Remove the part in the damage area that could be ignored + if (!ps->reg_ignore_expire && w->prev_trans && w->prev_trans->reg_ignore) + XFixesSubtractRegion(ps->dpy, parts, parts, w->prev_trans->reg_ignore); + + add_damage(ps, parts); +} + +static wintype_t +wid_get_prop_wintype(session_t *ps, Window wid) { + set_ignore_next(ps); + winprop_t prop = wid_get_prop(ps, wid, ps->atom_win_type, 32L, XA_ATOM, 32); + + for (unsigned i = 0; i < prop.nitems; ++i) { + for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { + if (ps->atoms_wintypes[j] == (Atom) prop.data.p32[i]) { + free_winprop(&prop); + return j; + } + } + } + + free_winprop(&prop); + + return WINTYPE_UNKNOWN; +} + +static void +map_win(session_t *ps, Window id) { + // Unmap overlay window if it got mapped but we are currently not + // in redirected state. + if (ps->overlay && id == ps->overlay && !ps->redirected) { + XUnmapWindow(ps->dpy, ps->overlay); + XFlush(ps->dpy); + } + + win *w = find_win(ps, id); + +#ifdef DEBUG_EVENTS + printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w); +#endif + + // Don't care about window mapping if it's an InputOnly window + // Try avoiding mapping a window twice + if (!w || InputOnly == w->a.class + || IsViewable == w->a.map_state) + return; + + assert(!win_is_focused_real(ps, w)); + + w->a.map_state = IsViewable; + + cxinerama_win_upd_scr(ps, w); + + // Call XSelectInput() before reading properties so that no property + // changes are lost + XSelectInput(ps->dpy, id, determine_evmask(ps, id, WIN_EVMODE_FRAME)); + + // Notify compton when the shape of a window changes + if (ps->shape_exists) { + XShapeSelectInput(ps->dpy, id, ShapeNotifyMask); + } + + // Make sure the XSelectInput() requests are sent + XFlush(ps->dpy); + + /* This needs to be here since we don't get PropertyNotify when unmapped */ + w->opacity = wid_get_opacity_prop(ps, w->id, OPAQUE); + w->show_root_tile = determine_window_transparent_to_desktop(ps, id); + w->show_black_background = determine_window_transparent_to_black(ps, id); + + // Update window mode here to check for ARGB windows + win_determine_mode(ps, w); + + // Detect client window here instead of in add_win() as the client + // window should have been prepared at this point + if (!w->client_win) { + win_recheck_client(ps, w); + } + else { + // Re-mark client window here + win_mark_client(ps, w, w->client_win); + } + + assert(w->client_win); + +#ifdef DEBUG_WINTYPE + printf_dbgf("(%#010lx): type %s\n", w->id, WINTYPES[w->window_type]); +#endif + + // Detect if the window is shaped or has rounded corners + win_update_shape_raw(ps, w); + + // FocusIn/Out may be ignored when the window is unmapped, so we must + // recheck focus here + if (ps->o.track_focus) + recheck_focus(ps); + + // Update window focus state + win_update_focused(ps, w); + + // Update opacity and dim state + win_update_opacity_prop(ps, w); + w->flags |= WFLAG_OPCT_CHANGE; + + // Check for _TDE_WM_WINDOW_SHADOW + if (ps->o.respect_prop_shadow) + win_update_prop_shadow_raw(ps, w); + + // Many things above could affect shadow + win_determine_shadow(ps, w); + + // Set fading state + w->in_openclose = true; + set_fade_callback(ps, w, finish_map_win, true); + win_determine_fade(ps, w); + + win_determine_blur_background(ps, w); + + w->damaged = false; + + /* if any configure events happened while + the window was unmapped, then configure + the window to its correct place */ + if (w->need_configure) { + configure_win(ps, &w->queue_configure); + } + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_mapped(ps, w); + } +#endif +} + +static void +finish_map_win(session_t *ps, win *w) { + w->in_openclose = false; + if (ps->o.no_fading_openclose) { + win_determine_fade(ps, w); + } +} + +static void +finish_unmap_win(session_t *ps, win *w) { + w->damaged = false; + + w->in_openclose = false; + + update_reg_ignore_expire(ps, w); + + if (w->extents != None) { + /* destroys region */ + add_damage(ps, w->extents); + w->extents = None; + } + + free_wpaint(ps, w); + free_region(ps, &w->border_size); + free_paint(ps, &w->shadow_paint); +} + +static void +unmap_callback(session_t *ps, win *w) { + finish_unmap_win(ps, w); +} + +static void +unmap_win(session_t *ps, win *w) { + if (!w || IsUnmapped == w->a.map_state) return; + + // One last synchronization + if (w->paint.pixmap) + xr_sync(ps, w->paint.pixmap, &w->fence); + free_fence(ps, &w->fence); + + // Set focus out + win_set_focused(ps, w, false); + + w->a.map_state = IsUnmapped; + + // Fading out + w->flags |= WFLAG_OPCT_CHANGE; + set_fade_callback(ps, w, unmap_callback, false); + w->in_openclose = true; + win_determine_fade(ps, w); + + // Validate pixmap if we have to do fading + if (w->fade) + win_validate_pixmap(ps, w); + + // don't care about properties anymore + win_ev_stop(ps, w); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_unmapped(ps, w); + } +#endif +} + +static opacity_t +wid_get_opacity_prop(session_t *ps, Window wid, opacity_t def) { + opacity_t val = def; + + winprop_t prop = wid_get_prop(ps, wid, ps->atom_opacity, 1L, + XA_CARDINAL, 32); + + if (prop.nitems) + val = *prop.data.p32; + + free_winprop(&prop); + + return val; +} + +static double +get_opacity_percent(win *w) { + return ((double) w->opacity) / OPAQUE; +} + +static Bool +get_window_transparent_to_desktop(const session_t *ps, Window w) +{ + Atom actual; + int format; + unsigned long n, left; + + unsigned char *data; + int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_desktop, 0L, 1L, False, + XA_ATOM, &actual, &format, + &n, &left, &data); + + if (result == Success && data != None && format == 32 ) + { + Atom a; + a = *(long*)data; + XFree ( (void *) data); + return True; + } + return False; +} + +static Bool +get_window_transparent_to_black(const session_t *ps, Window w) +{ + Atom actual; + int format; + unsigned long n, left; + + unsigned char *data; + int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_black, 0L, 1L, False, + XA_ATOM, &actual, &format, + &n, &left, &data); + + if (result == Success && data != None && format == 32 ) + { + Atom a; + a = *(long*)data; + XFree ( (void *) data); + return True; + } + return False; +} + +static Bool +determine_window_transparent_to_desktop (const session_t *ps, Window w) +{ + Window root_return, parent_return; + Window *children = NULL; + unsigned int nchildren, i; + Bool type; + + type = get_window_transparent_to_desktop (ps, w); + if (type == True) { + return True; + } + + if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children, + &nchildren)) + { + /* XQueryTree failed. */ + if (children) + XFree ((void *)children); + return False; + } + + for (i = 0;i < nchildren;i++) + { + type = determine_window_transparent_to_desktop (ps, children[i]); + if (type == True) + return True; + } + + if (children) + XFree ((void *)children); + + return False; +} + +static Bool +determine_window_transparent_to_black (const session_t *ps, Window w) +{ + Window root_return, parent_return; + Window *children = NULL; + unsigned int nchildren, i; + Bool type; + Bool ret = False; + + type = get_window_transparent_to_black (ps, w); + if (type == True) { + return True; + } + + if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children, + &nchildren)) + { + /* XQueryTree failed. */ + if (children) { + XFree ((void *)children); + } + return False; + } + + for (i = 0;i < nchildren;i++) + { + type = determine_window_transparent_to_black (ps, children[i]); + if (type == True) { + ret = True; + break; + } + } + + if (children) { + XFree ((void *)children); + } + + return ret; +} + +static void +win_determine_mode(session_t *ps, win *w) { + winmode_t mode = WMODE_SOLID; + + if (w->pictfmt && w->pictfmt->type == PictTypeDirect + && w->pictfmt->direct.alphaMask) { + mode = WMODE_ARGB; + } else if (w->opacity != OPAQUE) { + mode = WMODE_TRANS; + } else { + mode = WMODE_SOLID; + } + + w->mode = mode; +} + +/** + * Calculate and set the opacity of a window. + * + * If window is inactive and inactive_opacity_override is set, the + * priority is: (Simulates the old behavior) + * + * inactive_opacity > _NET_WM_WINDOW_OPACITY (if not opaque) + * > window type default opacity + * + * Otherwise: + * + * _NET_WM_WINDOW_OPACITY (if not opaque) + * > window type default opacity (if not opaque) + * > inactive_opacity + * + * @param ps current session + * @param w struct _win object representing the window + */ +static void +calc_opacity(session_t *ps, win *w) { + opacity_t opacity = OPAQUE; + + if (w->destroyed || IsViewable != w->a.map_state) + opacity = 0; + else { + // Try obeying opacity property and window type opacity firstly + if (OPAQUE == (opacity = w->opacity_prop) + && OPAQUE == (opacity = w->opacity_prop_client)) { + opacity = ps->o.wintype_opacity[w->window_type] * OPAQUE; + } + + // Respect inactive_opacity in some cases + if (ps->o.inactive_opacity && false == w->focused + && (OPAQUE == opacity || ps->o.inactive_opacity_override)) { + opacity = ps->o.inactive_opacity; + } + + // Respect active_opacity only when the window is physically focused + if (OPAQUE == opacity && ps->o.active_opacity && win_is_focused_real(ps, w)) + opacity = ps->o.active_opacity; + } + + w->opacity_tgt = opacity; +} + +/** + * Determine whether a window is to be dimmed. + */ +static void +calc_dim(session_t *ps, win *w) { + bool dim; + + // Make sure we do nothing if the window is unmapped / destroyed + if (w->destroyed || IsViewable != w->a.map_state) + return; + + if (ps->o.inactive_dim && !(w->focused)) { + dim = true; + } else { + dim = false; + } + + if (dim != w->dim) { + w->dim = dim; + add_damage_win(ps, w); + } +} + +/** + * Determine if a window should fade on opacity change. + */ +static void +win_determine_fade(session_t *ps, win *w) { + if (UNSET != w->fade_force) + w->fade = w->fade_force; + else if ((ps->o.no_fading_openclose && w->in_openclose) + || win_match(ps, w, ps->o.fade_blacklist, &w->cache_fblst) + || (ps->o.no_fading_opacitychange && (!w->in_openclose))) + w->fade = false; + else + w->fade = ps->o.wintype_fade[w->window_type]; +} + +/** + * Update window-shape. + */ +static void +win_update_shape_raw(session_t *ps, win *w) { + if (ps->shape_exists) { + w->bounding_shaped = wid_bounding_shaped(ps, w->id); + if (w->bounding_shaped && ps->o.detect_rounded_corners) + win_rounded_corners(ps, w); + } +} + +/** + * Update window-shape related information. + */ +static void +win_update_shape(session_t *ps, win *w) { + if (ps->shape_exists) { + // bool bounding_shaped_old = w->bounding_shaped; + + win_update_shape_raw(ps, w); + + // Shadow state could be changed + win_determine_shadow(ps, w); + + /* + // If clear_shadow state on the window possibly changed, destroy the old + // shadow_pict + if (ps->o.clear_shadow && w->bounding_shaped != bounding_shaped_old) + free_paint(ps, &w->shadow_paint); + */ + } +} + +/** + * Reread _TDE_WM_WINDOW_SHADOW property from a window. + * + * The property must be set on the outermost window, usually the WM frame. + */ +static void +win_update_prop_shadow_raw(session_t *ps, win *w) { + winprop_t prop = wid_get_prop(ps, w->id, ps->atom_compton_shadow, 1, + XA_CARDINAL, 32); + + if (!prop.nitems) { + w->prop_shadow = -1; + } + else { + w->prop_shadow = *prop.data.p32; + } + + free_winprop(&prop); +} + +/** + * Reread _TDE_WM_WINDOW_SHADOW property from a window and update related + * things. + */ +static void +win_update_prop_shadow(session_t *ps, win *w) { + long attr_shadow_old = w->prop_shadow; + + win_update_prop_shadow_raw(ps, w); + + if (w->prop_shadow != attr_shadow_old) + win_determine_shadow(ps, w); +} + +/** + * Determine if a window should have shadow, and update things depending + * on shadow state. + */ +static void +win_determine_shadow(session_t *ps, win *w) { + bool shadow_old = w->shadow; + + w->shadow = (UNSET == w->shadow_force ? + (ps->o.wintype_shadow[w->window_type] + && !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)) + : w->shadow_force); + + // Window extents need update on shadow state change + if (w->shadow != shadow_old) { + // Shadow geometry currently doesn't change on shadow state change + // calc_shadow_geometry(ps, w); + if (w->extents) { + // Mark the old extents as damaged if the shadow is removed + if (!w->shadow) + add_damage(ps, w->extents); + else + free_region(ps, &w->extents); + w->extents = win_extents(ps, w); + // Mark the new extents as damaged if the shadow is added + if (w->shadow) + add_damage_win(ps, w); + } + } +} + +/** + * Determine if a window should have color inverted. + */ +static void +win_determine_invert_color(session_t *ps, win *w) { + // Do not change window invert color state when the window is unmapped, + // unless it comes from w->invert_color_force. + if (UNSET == w->invert_color_force && IsViewable != w->a.map_state) + return; + + bool invert_color_old = w->invert_color; + + if (UNSET != w->invert_color_force) + w->invert_color = w->invert_color_force; + else + 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); +} + +/** + * Determine if a window should have background blurred. + */ +static void +win_determine_blur_background(session_t *ps, win *w) { + bool blur_background_old = w->blur_background; + + w->blur_background = ps->o.blur_background + && !win_match(ps, w, ps->o.blur_background_blacklist, &w->cache_bbblst); + + // Only consider window damaged if it's previously painted with background + // blurred + if (w->blur_background != blur_background_old && (WMODE_SOLID != w->mode + || (ps->o.blur_background_frame && w->frame_opacity))) + add_damage_win(ps, w); +} + +/** + * Update window opacity according to opacity rules. + */ +static void +win_update_opacity_rule(session_t *ps, win *w) { + // If long is 32-bit, unfortunately there's no way could we express "unset", + // so we just entirely don't distinguish "unset" and OPAQUE + opacity_t opacity = OPAQUE; + void *val = NULL; + if (c2_matchd(ps, w, ps->o.opacity_rules, &w->cache_oparule, &val)) + opacity = ((double) (long) val) / 100.0 * OPAQUE; + + if (opacity == w->opacity_set) + return; + + if (OPAQUE != opacity) + wid_set_opacity_prop(ps, w->id, opacity); + else if (OPAQUE != w->opacity_set) + wid_rm_opacity_prop(ps, w->id); + w->opacity_set = opacity; +} + +/** + * Function to be called on window type changes. + */ +static void +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); + if (ps->o.opacity_rules) + win_update_opacity_rule(ps, w); +} + +/** + * Function to be called on window data changes. + */ +static void +win_on_factor_change(session_t *ps, win *w) { + if (ps->o.shadow_blacklist) + win_determine_shadow(ps, w); + if (ps->o.fade_blacklist) + win_determine_fade(ps, w); + if (ps->o.invert_color_list) + win_determine_invert_color(ps, w); + if (ps->o.focus_blacklist) + win_update_focused(ps, w); + if (ps->o.blur_background_blacklist) + win_determine_blur_background(ps, w); + if (ps->o.opacity_rules) + win_update_opacity_rule(ps, w); + if (ps->o.paint_blacklist) + w->paint_excluded = win_match(ps, w, ps->o.paint_blacklist, + &w->cache_pblst); + if (ps->o.unredir_if_possible_blacklist) + w->unredir_if_possible_excluded = win_match(ps, w, + ps->o.unredir_if_possible_blacklist, &w->cache_uipblst); +} + +/** + * Process needed window updates. + */ +static void +win_upd_run(session_t *ps, win *w, win_upd_t *pupd) { + if (pupd->shadow) { + win_determine_shadow(ps, w); + pupd->shadow = false; + } + if (pupd->fade) { + win_determine_fade(ps, w); + pupd->fade = false; + } + if (pupd->invert_color) { + win_determine_invert_color(ps, w); + pupd->invert_color = false; + } + if (pupd->focus) { + win_update_focused(ps, w); + pupd->focus = false; + } +} + +/** + * Update cache data in struct _win that depends on window size. + */ +static void +calc_win_size(session_t *ps, win *w) { + w->widthb = w->a.width + w->a.border_width * 2; + w->heightb = w->a.height + w->a.border_width * 2; + calc_shadow_geometry(ps, w); + w->flags |= WFLAG_SIZE_CHANGE; +} + +/** + * Calculate and update geometry of the shadow of a window. + */ +static void +calc_shadow_geometry(session_t *ps, win *w) { + w->shadow_dx = ps->o.shadow_offset_x * w->shadow_size; + w->shadow_dy = ps->o.shadow_offset_y * w->shadow_size; + w->shadow_width = w->widthb + ps->gaussian_map->size; + w->shadow_height = w->heightb + ps->gaussian_map->size; +} + +/** + * Update window type. + */ +static void +win_upd_wintype(session_t *ps, win *w) { + const wintype_t wtype_old = w->window_type; + + // Detect window type here + w->window_type = wid_get_prop_wintype(ps, w->client_win); + + // Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take + // override-redirect windows or windows without WM_TRANSIENT_FOR as + // _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. + if (WINTYPE_UNKNOWN == w->window_type) { + if (w->a.override_redirect + || !wid_has_prop(ps, w->client_win, ps->atom_transient)) + w->window_type = WINTYPE_NORMAL; + else + w->window_type = WINTYPE_DIALOG; + } + + if (w->window_type != wtype_old) + win_on_wtype_change(ps, w); +} + +/** + * Mark a window as the client window of another. + * + * @param ps current session + * @param w struct _win of the parent window + * @param client window ID of the client window + */ +static void +win_mark_client(session_t *ps, win *w, Window client) { + w->client_win = client; + + // If the window isn't mapped yet, stop here, as the function will be + // called in map_win() + if (IsViewable != w->a.map_state) + return; + + XSelectInput(ps->dpy, client, + determine_evmask(ps, client, WIN_EVMODE_CLIENT)); + + // Make sure the XSelectInput() requests are sent + XFlush(ps->dpy); + + win_upd_wintype(ps, w); + + // Get frame widths. The window is in damaged area already. + if (ps->o.frame_opacity) + get_frame_extents(ps, w, client); + + // Get window group + if (ps->o.track_leader) + win_update_leader(ps, w); + + // Get window name and class if we are tracking them + if (ps->o.track_wdata) { + win_get_name(ps, w); + win_get_class(ps, w); + win_get_role(ps, w); + } + + // Update everything related to conditions + win_on_factor_change(ps, w); + + // Update window focus state + win_update_focused(ps, w); +} + +/** + * Unmark current client window of a window. + * + * @param ps current session + * @param w struct _win of the parent window + */ +static void +win_unmark_client(session_t *ps, win *w) { + Window client = w->client_win; + + w->client_win = None; + + // Recheck event mask + XSelectInput(ps->dpy, client, + determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)); +} + +/** + * Recheck client window of a window. + * + * @param ps current session + * @param w struct _win of the parent window + */ +static void +win_recheck_client(session_t *ps, win *w) { + // Initialize wmwin to false + w->wmwin = false; + + // Look for the client window + + // Always recursively look for a window with WM_STATE, as Fluxbox + // sets override-redirect flags on all frame windows. + Window cw = find_client_win(ps, w->id); +#ifdef DEBUG_CLIENTWIN + if (cw) + printf_dbgf("(%#010lx): client %#010lx\n", w->id, cw); +#endif + // Set a window's client window to itself if we couldn't find a + // client window + if (!cw) { + cw = w->id; + w->wmwin = !w->a.override_redirect; +#ifdef DEBUG_CLIENTWIN + printf_dbgf("(%#010lx): client self (%s)\n", w->id, + (w->wmwin ? "wmwin": "override-redirected")); +#endif + } + + // Unmark the old one + if (w->client_win && w->client_win != cw) + win_unmark_client(ps, w); + + // Mark the new one + win_mark_client(ps, w, cw); +} + +static bool +add_win(session_t *ps, Window id, Window prev) { + const static win win_def = { + .next = NULL, + .prev_trans = NULL, + + .id = None, + .a = { }, +#ifdef CONFIG_XINERAMA + .xinerama_scr = -1, +#endif + .pictfmt = NULL, + .mode = WMODE_TRANS, + .damaged = false, + .damage = None, + .pixmap_damaged = false, + .paint = PAINT_INIT, + .border_size = None, + .extents = None, + .flags = 0, + .need_configure = false, + .queue_configure = { }, + .reg_ignore = None, + .widthb = 0, + .heightb = 0, + .destroyed = false, + .bounding_shaped = false, + .rounded_corners = false, + .to_paint = false, + .in_openclose = false, + + .client_win = None, + .window_type = WINTYPE_UNKNOWN, + .wmwin = false, + .leader = None, + .cache_leader = None, + + .focused = false, + .focused_force = UNSET, + + .name = NULL, + .class_instance = NULL, + .class_general = NULL, + .role = NULL, + .cache_sblst = NULL, + .cache_fblst = NULL, + .cache_fcblst = NULL, + .cache_ivclst = NULL, + .cache_bbblst = NULL, + .cache_oparule = NULL, + + .opacity = 0, + .opacity_tgt = 0, + .opacity_prop = OPAQUE, + .opacity_prop_client = OPAQUE, + .opacity_set = OPAQUE, + + .fade = false, + .fade_force = UNSET, + .fade_callback = NULL, + + .frame_opacity = 0.0, + .left_width = 0, + .right_width = 0, + .top_width = 0, + .bottom_width = 0, + + .shadow = false, + .shadow_force = UNSET, + .shadow_opacity = 0.0, + .shadow_dx = 0, + .shadow_dy = 0, + .shadow_width = 0, + .shadow_height = 0, + .shadow_size = 100, + .shadow_paint = PAINT_INIT, + .prop_shadow = -1, + + .dim = false, + + .invert_color = false, + .invert_color_force = UNSET, + + .blur_background = false, + + .show_black_background = false, + .show_root_tile = false, + }; + + // Reject overlay window and already added windows + if (id == ps->overlay || find_win(ps, id)) { + return false; + } + + // Allocate and initialize the new win structure + win *new = malloc(sizeof(win)); + +#ifdef DEBUG_EVENTS + printf_dbgf("(%#010lx): %p\n", id, new); +#endif + + if (!new) { + printf_errf("(%#010lx): Failed to allocate memory for the new window.", id); + return false; + } + + memcpy(new, &win_def, sizeof(win)); + + // Find window insertion point + win **p = NULL; + if (prev) { + for (p = &ps->list; *p; p = &(*p)->next) { + if ((*p)->id == prev && !(*p)->destroyed) + break; + } + } else { + p = &ps->list; + } + + // Fill structure + new->id = id; + + set_ignore_next(ps); + if (!XGetWindowAttributes(ps->dpy, id, &new->a) + || IsUnviewable == new->a.map_state) { + // Failed to get window attributes probably means the window is gone + // already. IsUnviewable means the window is already reparented + // elsewhere. + free(new); + return false; + } + + // Delay window mapping + int map_state = new->a.map_state; + assert(IsViewable == map_state || IsUnmapped == map_state); + new->a.map_state = IsUnmapped; + + if (InputOutput == new->a.class) { + // Get window picture format + new->pictfmt = XRenderFindVisualFormat(ps->dpy, new->a.visual); + + // Create Damage for window + set_ignore_next(ps); + new->damage = XDamageCreate(ps->dpy, id, XDamageReportNonEmpty); + } + + calc_win_size(ps, new); + + new->next = *p; + *p = new; + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_added(ps, new); + } +#endif + + if (IsViewable == map_state) { + map_win(ps, id); + } + + return true; +} + +static void +restack_win(session_t *ps, win *w, Window new_above) { + Window old_above; + + update_reg_ignore_expire(ps, w); + + if (w->next) { + old_above = w->next->id; + } else { + old_above = None; + } + + if (old_above != new_above) { + win **prev = NULL, **prev_old = NULL; + + // unhook + for (prev = &ps->list; *prev; prev = &(*prev)->next) { + if ((*prev) == w) break; + } + + prev_old = prev; + + bool found = false; + + // rehook + for (prev = &ps->list; *prev; prev = &(*prev)->next) { + if ((*prev)->id == new_above && !(*prev)->destroyed) { + found = true; + break; + } + } + + if (new_above && !found) { + printf_errf("(%#010lx, %#010lx): " + "Failed to found new above window.", w->id, new_above); + return; + } + + *prev_old = w->next; + + w->next = *prev; + *prev = w; + +#ifdef DEBUG_RESTACK + { + const char *desc; + char *window_name = NULL; + bool to_free; + win* c = ps->list; + + printf_dbgf("(%#010lx, %#010lx): " + "Window stack modified. Current stack:\n", w->id, new_above); + + for (; c; c = c->next) { + window_name = "(Failed to get title)"; + + to_free = ev_window_name(ps, c->id, &window_name); + + desc = ""; + if (c->destroyed) desc = "(D) "; + printf("%#010lx \"%s\" %s", c->id, window_name, desc); + if (c->next) + printf("-> "); + + if (to_free) { + cxfree(window_name); + window_name = NULL; + } + } + fputs("\n", stdout); + } +#endif + } +} + +static void +configure_win(session_t *ps, XConfigureEvent *ce) { + // On root window changes + if (ce->window == ps->root) { + free_paint(ps, &ps->tgt_buffer); + + ps->root_width = ce->width; + ps->root_height = ce->height; + + rebuild_screen_reg(ps); + rebuild_shadow_exclude_reg(ps); + free_all_damage_last(ps); + +#ifdef CONFIG_VSYNC_OPENGL + if (BKEND_GLX == ps->o.backend) + glx_on_root_change(ps); +#endif + + return; + } + + // Other window changes + win *w = find_win(ps, ce->window); + XserverRegion damage = None; + + if (!w) + return; + + if (w->a.map_state == IsUnmapped) { + /* save the configure event for when the window maps */ + w->need_configure = true; + w->queue_configure = *ce; + restack_win(ps, w, ce->above); + } else { + if (!(w->need_configure)) { + restack_win(ps, w, ce->above); + } + + bool factor_change = false; + + // Windows restack (including window restacks happened when this + // window is not mapped) could mess up all reg_ignore + ps->reg_ignore_expire = true; + + w->need_configure = false; + + damage = XFixesCreateRegion(ps->dpy, 0, 0); + if (w->extents != None) { + XFixesCopyRegion(ps->dpy, damage, w->extents); + } + + // If window geometry did not change, don't free extents here + if (w->a.x != ce->x || w->a.y != ce->y + || w->a.width != ce->width || w->a.height != ce->height + || w->a.border_width != ce->border_width) { + factor_change = true; + free_region(ps, &w->extents); + free_region(ps, &w->border_size); + } + + w->a.x = ce->x; + w->a.y = ce->y; + + if (w->a.width != ce->width || w->a.height != ce->height + || w->a.border_width != ce->border_width) + free_wpaint(ps, w); + + if (w->a.width != ce->width || w->a.height != ce->height + || w->a.border_width != ce->border_width) { + w->a.width = ce->width; + w->a.height = ce->height; + w->a.border_width = ce->border_width; + calc_win_size(ps, w); + + // Rounded corner detection is affected by window size + if (ps->shape_exists && ps->o.shadow_ignore_shaped + && ps->o.detect_rounded_corners && w->bounding_shaped) + win_update_shape(ps, w); + } + + if (damage) { + XserverRegion extents = win_extents(ps, w); + XFixesUnionRegion(ps->dpy, damage, damage, extents); + XFixesDestroyRegion(ps->dpy, extents); + add_damage(ps, damage); + } + + if (factor_change) { + cxinerama_win_upd_scr(ps, w); + win_on_factor_change(ps, w); + } + } + + // override_redirect flag cannot be changed after window creation, as far + // as I know, so there's no point to re-match windows here. + w->a.override_redirect = ce->override_redirect; +} + +static void +circulate_win(session_t *ps, XCirculateEvent *ce) { + win *w = find_win(ps, ce->window); + Window new_above; + + if (!w) return; + + if (ce->place == PlaceOnTop) { + new_above = ps->list->id; + } else { + new_above = None; + } + + restack_win(ps, w, new_above); +} + +static void +finish_destroy_win(session_t *ps, Window id) { + win **prev = NULL, *w = NULL; + +#ifdef DEBUG_EVENTS + printf_dbgf("(%#010lx): Starting...\n", id); +#endif + + for (prev = &ps->list; (w = *prev); prev = &w->next) { + if (w->id == id && w->destroyed) { +#ifdef DEBUG_EVENTS + printf_dbgf("(%#010lx \"%s\"): %p\n", id, w->name, w); +#endif + + finish_unmap_win(ps, w); + *prev = w->next; + + // Clear active_win if it's pointing to the destroyed window + if (w == ps->active_win) + ps->active_win = NULL; + + free_win_res(ps, w); + + // Drop w from all prev_trans to avoid accessing freed memory in + // repair_win() + for (win *w2 = ps->list; w2; w2 = w2->next) + if (w == w2->prev_trans) + w2->prev_trans = NULL; + + free(w); + break; + } + } +} + +static void +destroy_callback(session_t *ps, win *w) { + finish_destroy_win(ps, w->id); +} + +static void +destroy_win(session_t *ps, Window id) { + win *w = find_win(ps, id); + +#ifdef DEBUG_EVENTS + printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w); +#endif + + if (w) { + unmap_win(ps, w); + + w->destroyed = true; + + // Set fading callback + set_fade_callback(ps, w, destroy_callback, false); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_destroyed(ps, w); + } +#endif + } +} + +static inline void +root_damaged(session_t *ps) { + if (ps->root_tile_paint.pixmap) { + XClearArea(ps->dpy, ps->root, 0, 0, 0, 0, true); + // if (ps->root_picture != ps->root_tile) { + free_root_tile(ps); + /* } + if (root_damage) { + XserverRegion parts = XFixesCreateRegion(ps->dpy, 0, 0); + XDamageSubtract(ps->dpy, root_damage, None, parts); + add_damage(ps, parts); + } */ + } + + // Mark screen damaged + force_repaint(ps); +} + +static void +damage_win(session_t *ps, XDamageNotifyEvent *de) { + /* + if (ps->root == de->drawable) { + root_damaged(); + return; + } */ + + win *w = find_win(ps, de->drawable); + + if (!w) return; + + repair_win(ps, w); +} + +/** + * Xlib error handler function. + */ +static int +xerror(Display __attribute__((unused)) *dpy, XErrorEvent *ev) { + session_t * const ps = ps_g; + + int o = 0; + const char *name = "Unknown"; + + if (should_ignore(ps, ev->serial)) { + return 0; + } + + if (ev->request_code == ps->composite_opcode + && ev->minor_code == X_CompositeRedirectSubwindows) { + fprintf(stderr, "Another composite manager is already running\n"); + exit(1); + } + +#define CASESTRRET2(s) case s: name = #s; break + + o = ev->error_code - ps->xfixes_error; + switch (o) { + CASESTRRET2(BadRegion); + } + + o = ev->error_code - ps->damage_error; + switch (o) { + CASESTRRET2(BadDamage); + } + + o = ev->error_code - ps->render_error; + switch (o) { + CASESTRRET2(BadPictFormat); + CASESTRRET2(BadPicture); + CASESTRRET2(BadPictOp); + CASESTRRET2(BadGlyphSet); + CASESTRRET2(BadGlyph); + } + +#ifdef CONFIG_VSYNC_OPENGL + if (ps->glx_exists) { + o = ev->error_code - ps->glx_error; + switch (o) { + CASESTRRET2(GLX_BAD_SCREEN); + CASESTRRET2(GLX_BAD_ATTRIBUTE); + CASESTRRET2(GLX_NO_EXTENSION); + CASESTRRET2(GLX_BAD_VISUAL); + CASESTRRET2(GLX_BAD_CONTEXT); + CASESTRRET2(GLX_BAD_VALUE); + CASESTRRET2(GLX_BAD_ENUM); + } + } +#endif + +#ifdef CONFIG_XSYNC + if (ps->xsync_exists) { + o = ev->error_code - ps->xsync_error; + switch (o) { + CASESTRRET2(XSyncBadCounter); + CASESTRRET2(XSyncBadAlarm); + CASESTRRET2(XSyncBadFence); + } + } +#endif + + switch (ev->error_code) { + CASESTRRET2(BadAccess); + CASESTRRET2(BadAlloc); + CASESTRRET2(BadAtom); + CASESTRRET2(BadColor); + CASESTRRET2(BadCursor); + CASESTRRET2(BadDrawable); + CASESTRRET2(BadFont); + CASESTRRET2(BadGC); + CASESTRRET2(BadIDChoice); + CASESTRRET2(BadImplementation); + CASESTRRET2(BadLength); + CASESTRRET2(BadMatch); + CASESTRRET2(BadName); + CASESTRRET2(BadPixmap); + CASESTRRET2(BadRequest); + CASESTRRET2(BadValue); + CASESTRRET2(BadWindow); + } + +#undef CASESTRRET2 + + print_timestamp(ps); + { + char buf[BUF_LEN] = ""; + XGetErrorText(ps->dpy, ev->error_code, buf, BUF_LEN); + printf("error %d (%s) request %d minor %d serial %lu (\"%s\")\n", + ev->error_code, name, ev->request_code, + ev->minor_code, ev->serial, buf); + } + + return 0; +} + +static void +expose_root(session_t *ps, XRectangle *rects, int nrects) { + free_all_damage_last(ps); + XserverRegion region = XFixesCreateRegion(ps->dpy, rects, nrects); + add_damage(ps, region); +} + +/** + * Get the value of a type-<code>Window</code> property of a window. + * + * @return the value if successful, 0 otherwise + */ +static Window +wid_get_prop_window(session_t *ps, Window wid, Atom aprop) { + // Get the attribute + Window p = None; + winprop_t prop = wid_get_prop(ps, wid, aprop, 1L, XA_WINDOW, 32); + + // Return it + if (prop.nitems) { + p = *prop.data.p32; + } + + free_winprop(&prop); + + return p; +} + +/** + * Update focused state of a window. + */ +static void +win_update_focused(session_t *ps, win *w) { + bool focused_old = w->focused; + + if (UNSET != w->focused_force) { + w->focused = w->focused_force; + } + else { + w->focused = win_is_focused_real(ps, w); + + // Use wintype_focus, and treat WM windows and override-redirected + // windows specially + if (ps->o.wintype_focus[w->window_type] + || (ps->o.mark_wmwin_focused && w->wmwin) + || (ps->o.mark_ovredir_focused + && w->id == w->client_win && !w->wmwin) + || 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 + // its group is + if (ps->o.track_leader && ps->active_leader + && win_get_leader(ps, w) == ps->active_leader) { + w->focused = true; + } + } + + if (w->focused != focused_old) + w->flags |= WFLAG_OPCT_CHANGE; +} + +/** + * Set real focused state of a window. + */ +static void +win_set_focused(session_t *ps, win *w, bool focused) { + // Unmapped windows will have their focused state reset on map + if (IsUnmapped == w->a.map_state) + return; + + if (win_is_focused_real(ps, w) == focused) return; + + if (focused) { + if (ps->active_win) + win_set_focused(ps, ps->active_win, false); + ps->active_win = w; + } + else if (w == ps->active_win) + ps->active_win = NULL; + + assert(win_is_focused_real(ps, w) == focused); + + win_on_focus_change(ps, w); +} + +/** + * Handle window focus change. + */ +static void +win_on_focus_change(session_t *ps, win *w) { + // If window grouping detection is enabled + if (ps->o.track_leader) { + Window leader = win_get_leader(ps, w); + + // If the window gets focused, replace the old active_leader + if (win_is_focused_real(ps, w) && leader != ps->active_leader) { + Window active_leader_old = ps->active_leader; + + ps->active_leader = leader; + + group_update_focused(ps, active_leader_old); + group_update_focused(ps, leader); + } + // If the group get unfocused, remove it from active_leader + else if (!win_is_focused_real(ps, w) && leader && leader == ps->active_leader + && !group_is_focused(ps, leader)) { + ps->active_leader = None; + group_update_focused(ps, leader); + } + + // The window itself must be updated anyway + win_update_focused(ps, w); + } + // Otherwise, only update the window itself + else { + win_update_focused(ps, w); + } + + // Update everything related to conditions + win_on_factor_change(ps, w); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + if (win_is_focused_real(ps, w)) + cdbus_ev_win_focusin(ps, w); + else + cdbus_ev_win_focusout(ps, w); + } +#endif +} + +/** + * Update leader of a window. + */ +static void +win_update_leader(session_t *ps, win *w) { + Window leader = None; + + // Read the leader properties + if (ps->o.detect_transient && !leader) + leader = wid_get_prop_window(ps, w->client_win, ps->atom_transient); + + if (ps->o.detect_client_leader && !leader) + leader = wid_get_prop_window(ps, w->client_win, ps->atom_client_leader); + + win_set_leader(ps, w, leader); + +#ifdef DEBUG_LEADER + printf_dbgf("(%#010lx): client %#010lx, leader %#010lx, cache %#010lx\n", w->id, w->client_win, w->leader, win_get_leader(ps, w)); +#endif +} + +/** + * Set leader of a window. + */ +static void +win_set_leader(session_t *ps, win *w, Window nleader) { + // If the leader changes + if (w->leader != nleader) { + Window cache_leader_old = win_get_leader(ps, w); + + w->leader = nleader; + + // Forcefully do this to deal with the case when a child window + // gets mapped before parent, or when the window is a waypoint + clear_cache_win_leaders(ps); + + // Update the old and new window group and active_leader if the window + // could affect their state. + Window cache_leader = win_get_leader(ps, w); + if (win_is_focused_real(ps, w) && cache_leader_old != cache_leader) { + ps->active_leader = cache_leader; + + group_update_focused(ps, cache_leader_old); + group_update_focused(ps, cache_leader); + } + // Otherwise, at most the window itself is affected + else { + win_update_focused(ps, w); + } + + // Update everything related to conditions + win_on_factor_change(ps, w); + } +} + +/** + * Internal function of win_get_leader(). + */ +static Window +win_get_leader_raw(session_t *ps, win *w, int recursions) { + // Rebuild the cache if needed + if (!w->cache_leader && (w->client_win || w->leader)) { + // Leader defaults to client window + if (!(w->cache_leader = w->leader)) + w->cache_leader = w->client_win; + + // If the leader of this window isn't itself, look for its ancestors + if (w->cache_leader && w->cache_leader != w->client_win) { + win *wp = find_toplevel(ps, w->cache_leader); + if (wp) { + // Dead loop? + if (recursions > WIN_GET_LEADER_MAX_RECURSION) + return None; + + w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1); + } + } + } + + return w->cache_leader; +} + +/** + * Get the value of a text property of a window. + */ +bool +wid_get_text_prop(session_t *ps, Window wid, Atom prop, + char ***pstrlst, int *pnstr) { + XTextProperty text_prop = { NULL, None, 0, 0 }; + + if (!(XGetTextProperty(ps->dpy, wid, &text_prop, prop) && text_prop.value)) + return false; + + if (Success != + XmbTextPropertyToTextList(ps->dpy, &text_prop, pstrlst, pnstr) + || !*pnstr) { + *pnstr = 0; + if (*pstrlst) + XFreeStringList(*pstrlst); + cxfree(text_prop.value); + return false; + } + + cxfree(text_prop.value); + return true; +} + +/** + * Get the name of a window from window ID. + */ +static bool +wid_get_name(session_t *ps, Window wid, char **name) { + XTextProperty text_prop = { NULL, None, 0, 0 }; + char **strlst = NULL; + int nstr = 0; + + if (!(wid_get_text_prop(ps, wid, ps->atom_name_ewmh, &strlst, &nstr))) { +#ifdef DEBUG_WINDATA + printf_dbgf("(%#010lx): _NET_WM_NAME unset, falling back to WM_NAME.\n", wid); +#endif + + if (!(XGetWMName(ps->dpy, wid, &text_prop) && text_prop.value)) { + return false; + } + if (Success != + XmbTextPropertyToTextList(ps->dpy, &text_prop, &strlst, &nstr) + || !nstr || !strlst) { + if (strlst) + XFreeStringList(strlst); + cxfree(text_prop.value); + return false; + } + cxfree(text_prop.value); + } + + *name = mstrcpy(strlst[0]); + + XFreeStringList(strlst); + + return true; +} + +/** + * Get the role of a window from window ID. + */ +static bool +wid_get_role(session_t *ps, Window wid, char **role) { + char **strlst = NULL; + int nstr = 0; + + if (!wid_get_text_prop(ps, wid, ps->atom_role, &strlst, &nstr)) { + return false; + } + + *role = mstrcpy(strlst[0]); + + XFreeStringList(strlst); + + return true; +} + +/** + * Retrieve a string property of a window and update its <code>win</code> + * structure. + */ +static int +win_get_prop_str(session_t *ps, win *w, char **tgt, + bool (*func_wid_get_prop_str)(session_t *ps, Window wid, char **tgt)) { + int ret = -1; + char *prop_old = *tgt; + + // Can't do anything if there's no client window + if (!w->client_win) + return false; + + // Get the property + ret = func_wid_get_prop_str(ps, w->client_win, tgt); + + // Return -1 if func_wid_get_prop_str() failed, 0 if the property + // doesn't change, 1 if it changes + if (!ret) + ret = -1; + else if (prop_old && !strcmp(*tgt, prop_old)) + ret = 0; + else + ret = 1; + + // Keep the old property if there's no new one + if (*tgt != prop_old) + free(prop_old); + + return ret; +} + +/** + * Retrieve the <code>WM_CLASS</code> of a window and update its + * <code>win</code> structure. + */ +static bool +win_get_class(session_t *ps, win *w) { + char **strlst = NULL; + int nstr = 0; + + // Can't do anything if there's no client window + if (!w->client_win) + return false; + + // Free and reset old strings + free(w->class_instance); + free(w->class_general); + w->class_instance = NULL; + w->class_general = NULL; + + // Retrieve the property string list + if (!wid_get_text_prop(ps, w->client_win, ps->atom_class, &strlst, &nstr)) + return false; + + // Copy the strings if successful + w->class_instance = mstrcpy(strlst[0]); + + if (nstr > 1) + w->class_general = mstrcpy(strlst[1]); + + XFreeStringList(strlst); + +#ifdef DEBUG_WINDATA + printf_dbgf("(%#010lx): client = %#010lx, " + "instance = \"%s\", general = \"%s\"\n", + w->id, w->client_win, w->class_instance, w->class_general); +#endif + + return true; +} + +/** + * Force a full-screen repaint. + */ +void +force_repaint(session_t *ps) { + assert(ps->screen_reg); + XserverRegion reg = None; + if (ps->screen_reg && (reg = copy_region(ps, ps->screen_reg))) { + ps->ev_received = true; + add_damage(ps, reg); + } +} + +#ifdef CONFIG_DBUS +/** @name DBus hooks + */ +///@{ + +/** + * Set w->shadow_force of a window. + */ +void +win_set_shadow_force(session_t *ps, win *w, switch_t val) { + if (val != w->shadow_force) { + w->shadow_force = val; + win_determine_shadow(ps, w); + ps->ev_received = true; + } +} + +/** + * Set w->fade_force of a window. + */ +void +win_set_fade_force(session_t *ps, win *w, switch_t val) { + if (val != w->fade_force) { + w->fade_force = val; + win_determine_fade(ps, w); + ps->ev_received = true; + } +} + +/** + * Set w->focused_force of a window. + */ +void +win_set_focused_force(session_t *ps, win *w, switch_t val) { + if (val != w->focused_force) { + w->focused_force = val; + win_update_focused(ps, w); + ps->ev_received = true; + } +} + +/** + * Set w->invert_color_force of a window. + */ +void +win_set_invert_color_force(session_t *ps, win *w, switch_t val) { + if (val != w->invert_color_force) { + w->invert_color_force = val; + win_determine_invert_color(ps, w); + ps->ev_received = true; + } +} + +/** + * Enable focus tracking. + */ +void +opts_init_track_focus(session_t *ps) { + // Already tracking focus + if (ps->o.track_focus) + return; + + ps->o.track_focus = true; + + if (!ps->o.use_ewmh_active_win) { + // Start listening to FocusChange events + for (win *w = ps->list; w; w = w->next) + if (IsViewable == w->a.map_state) + XSelectInput(ps->dpy, w->id, + determine_evmask(ps, w->id, WIN_EVMODE_FRAME)); + } + + // Recheck focus + recheck_focus(ps); +} + +/** + * Set no_fading_openclose option. + */ +void +opts_set_no_fading_openclose(session_t *ps, bool newval) { + if (newval != ps->o.no_fading_openclose) { + ps->o.no_fading_openclose = newval; + for (win *w = ps->list; w; w = w->next) + win_determine_fade(ps, w); + ps->ev_received = true; + } +} + +/** + * Set no_fading_opacitychange option. + */ +void +opts_set_no_fading_opacitychange(session_t *ps, bool newval) { + if (newval != ps->o.no_fading_opacitychange) { + ps->o.no_fading_opacitychange = newval; + for (win *w = ps->list; w; w = w->next) + win_determine_fade(ps, w); + ps->ev_received = true; + } +} + +//!@} +#endif + +#ifdef DEBUG_EVENTS +static int +ev_serial(XEvent *ev) { + if ((ev->type & 0x7f) != KeymapNotify) { + return ev->xany.serial; + } + return NextRequest(ev->xany.display); +} + +static const char * +ev_name(session_t *ps, XEvent *ev) { + static char buf[128]; + switch (ev->type & 0x7f) { + CASESTRRET(FocusIn); + CASESTRRET(FocusOut); + CASESTRRET(CreateNotify); + CASESTRRET(ConfigureNotify); + CASESTRRET(DestroyNotify); + CASESTRRET(MapNotify); + CASESTRRET(UnmapNotify); + CASESTRRET(ReparentNotify); + CASESTRRET(CirculateNotify); + CASESTRRET(Expose); + CASESTRRET(PropertyNotify); + CASESTRRET(ClientMessage); + } + + if (isdamagenotify(ps, ev)) + return "Damage"; + + if (ps->shape_exists && ev->type == ps->shape_event) + return "ShapeNotify"; + +#ifdef CONFIG_XSYNC + if (ps->xsync_exists) { + int o = ev->type - ps->xsync_event; + switch (o) { + CASESTRRET(CounterNotify); + CASESTRRET(AlarmNotify); + } + } +#endif + + sprintf(buf, "Event %d", ev->type); + + return buf; +} + +static Window +ev_window(session_t *ps, XEvent *ev) { + switch (ev->type) { + case FocusIn: + case FocusOut: + return ev->xfocus.window; + case CreateNotify: + return ev->xcreatewindow.window; + case ConfigureNotify: + return ev->xconfigure.window; + case DestroyNotify: + return ev->xdestroywindow.window; + case MapNotify: + return ev->xmap.window; + case UnmapNotify: + return ev->xunmap.window; + case ReparentNotify: + return ev->xreparent.window; + case CirculateNotify: + return ev->xcirculate.window; + case Expose: + return ev->xexpose.window; + case PropertyNotify: + return ev->xproperty.window; + case ClientMessage: + return ev->xclient.window; + default: + if (isdamagenotify(ps, ev)) { + return ((XDamageNotifyEvent *)ev)->drawable; + } + + if (ps->shape_exists && ev->type == ps->shape_event) { + return ((XShapeEvent *) ev)->window; + } + + return 0; + } +} + +static inline const char * +ev_focus_mode_name(XFocusChangeEvent* ev) { + switch (ev->mode) { + CASESTRRET(NotifyNormal); + CASESTRRET(NotifyWhileGrabbed); + CASESTRRET(NotifyGrab); + CASESTRRET(NotifyUngrab); + } + + return "Unknown"; +} + +static inline const char * +ev_focus_detail_name(XFocusChangeEvent* ev) { + switch (ev->detail) { + CASESTRRET(NotifyAncestor); + CASESTRRET(NotifyVirtual); + CASESTRRET(NotifyInferior); + CASESTRRET(NotifyNonlinear); + CASESTRRET(NotifyNonlinearVirtual); + CASESTRRET(NotifyPointer); + CASESTRRET(NotifyPointerRoot); + CASESTRRET(NotifyDetailNone); + } + + return "Unknown"; +} + +static inline void +ev_focus_report(XFocusChangeEvent* ev) { + printf(" { mode: %s, detail: %s }\n", ev_focus_mode_name(ev), + ev_focus_detail_name(ev)); +} + +#endif + +// === Events === + +/** + * Determine whether we should respond to a <code>FocusIn/Out</code> + * event. + */ +inline static bool +ev_focus_accept(XFocusChangeEvent *ev) { + return NotifyNormal == ev->mode || NotifyUngrab == ev->mode; +} + +static inline void +ev_focus_in(session_t *ps, XFocusChangeEvent *ev) { +#ifdef DEBUG_EVENTS + ev_focus_report(ev); +#endif + + recheck_focus(ps); +} + +inline static void +ev_focus_out(session_t *ps, XFocusChangeEvent *ev) { +#ifdef DEBUG_EVENTS + ev_focus_report(ev); +#endif + + recheck_focus(ps); +} + +inline static void +ev_create_notify(session_t *ps, XCreateWindowEvent *ev) { + assert(ev->parent == ps->root); + add_win(ps, ev->window, 0); +} + +inline static void +ev_configure_notify(session_t *ps, XConfigureEvent *ev) { +#ifdef DEBUG_EVENTS + printf(" { send_event: %d, " + " above: %#010lx, " + " override_redirect: %d }\n", + ev->send_event, ev->above, ev->override_redirect); +#endif + configure_win(ps, ev); +} + +inline static void +ev_destroy_notify(session_t *ps, XDestroyWindowEvent *ev) { + destroy_win(ps, ev->window); +} + +inline static void +ev_map_notify(session_t *ps, XMapEvent *ev) { + map_win(ps, ev->window); +} + +inline static void +ev_unmap_notify(session_t *ps, XUnmapEvent *ev) { + win *w = find_win(ps, ev->window); + + if (w) + unmap_win(ps, w); +} + +inline static void +ev_reparent_notify(session_t *ps, XReparentEvent *ev) { +#ifdef DEBUG_EVENTS + printf_dbg(" { new_parent: %#010lx, override_redirect: %d }\n", + ev->parent, ev->override_redirect); +#endif + + if (ev->parent == ps->root) { + add_win(ps, ev->window, 0); + } else { + destroy_win(ps, ev->window); + + // Reset event mask in case something wrong happens + XSelectInput(ps->dpy, ev->window, + determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)); + + // Check if the window is an undetected client window + // Firstly, check if it's a known client window + if (!find_toplevel(ps, ev->window)) { + // If not, look for its frame window + win *w_top = find_toplevel2(ps, ev->parent); + // If found, and the client window has not been determined, or its + // frame may not have a correct client, continue + if (w_top && (!w_top->client_win + || w_top->client_win == w_top->id)) { + // If it has WM_STATE, mark it the client window + if (wid_has_prop(ps, ev->window, ps->atom_client)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); + } + // Otherwise, watch for WM_STATE on it + else { + XSelectInput(ps->dpy, ev->window, + determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) + | PropertyChangeMask); + } + } + } + } +} + +inline static void +ev_circulate_notify(session_t *ps, XCirculateEvent *ev) { + circulate_win(ps, ev); +} + +inline static void +ev_expose(session_t *ps, XExposeEvent *ev) { + if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) { + int more = ev->count + 1; + if (ps->n_expose == ps->size_expose) { + if (ps->expose_rects) { + ps->expose_rects = realloc(ps->expose_rects, + (ps->size_expose + more) * sizeof(XRectangle)); + ps->size_expose += more; + } else { + ps->expose_rects = malloc(more * sizeof(XRectangle)); + ps->size_expose = more; + } + } + + ps->expose_rects[ps->n_expose].x = ev->x; + ps->expose_rects[ps->n_expose].y = ev->y; + ps->expose_rects[ps->n_expose].width = ev->width; + ps->expose_rects[ps->n_expose].height = ev->height; + ps->n_expose++; + + if (ev->count == 0) { + expose_root(ps, ps->expose_rects, ps->n_expose); + ps->n_expose = 0; + } + } +} + +/** + * Update current active window based on EWMH _NET_ACTIVE_WIN. + * + * Does not change anything if we fail to get the attribute or the window + * returned could not be found. + */ +static void +update_ewmh_active_win(session_t *ps) { + // Search for the window + Window wid = wid_get_prop_window(ps, ps->root, ps->atom_ewmh_active_win); + win *w = find_win_all(ps, wid); + + // Mark the window focused. No need to unfocus the previous one. + if (w) win_set_focused(ps, w, true); +} + +inline static void +ev_property_notify(session_t *ps, XPropertyEvent *ev) { +#ifdef DEBUG_EVENTS + { + // Print out changed atom + char *name = XGetAtomName(ps->dpy, ev->atom); + printf_dbg(" { atom = %s }\n", name); + cxfree(name); + } +#endif + + if (ps->root == ev->window) { + if (ps->o.track_focus && ps->o.use_ewmh_active_win + && ps->atom_ewmh_active_win == ev->atom) { + update_ewmh_active_win(ps); + } + else { + // Destroy the root "image" if the wallpaper probably changed + for (int p = 0; background_props_str[p]; p++) { + if (ev->atom == get_atom(ps, background_props_str[p])) { + root_damaged(ps); + break; + } + } + } + + // Unconcerned about any other proprties on root window + return; + } + + // If WM_STATE changes + if (ev->atom == ps->atom_client) { + // Check whether it could be a client window + if (!find_toplevel(ps, ev->window)) { + // Reset event mask anyway + XSelectInput(ps->dpy, ev->window, + determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)); + + win *w_top = find_toplevel2(ps, ev->window); + // Initialize client_win as early as possible + if (w_top && (!w_top->client_win || w_top->client_win == w_top->id) + && wid_has_prop(ps, ev->window, ps->atom_client)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); + } + } + } + + // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but + // there are always some stupid applications. (#144) + if (ev->atom == ps->atom_win_type) { + win *w = NULL; + if ((w = find_toplevel(ps, ev->window))) + win_upd_wintype(ps, w); + } + + // If _NET_WM_OPACITY changes + if (ev->atom == ps->atom_opacity) { + win *w = NULL; + if ((w = find_win(ps, ev->window))) + w->opacity_prop = wid_get_opacity_prop(ps, w->id, OPAQUE); + else if (ps->o.detect_client_opacity + && (w = find_toplevel(ps, ev->window))) + w->opacity_prop_client = wid_get_opacity_prop(ps, w->client_win, + OPAQUE); + if (w) { + w->flags |= WFLAG_OPCT_CHANGE; + } + } + + // If frame extents property changes + if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) { + win *w = find_toplevel(ps, ev->window); + if (w) { + get_frame_extents(ps, w, ev->window); + // If frame extents change, the window needs repaint + add_damage_win(ps, w); + } + } + + // If name changes + if (ps->o.track_wdata + && (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_factor_change(ps, w); + } + } + + // If class changes + if (ps->o.track_wdata && ps->atom_class == ev->atom) { + win *w = find_toplevel(ps, ev->window); + if (w) { + win_get_class(ps, w); + win_on_factor_change(ps, w); + } + } + + // If role changes + 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_factor_change(ps, w); + } + } + + // If _TDE_WM_WINDOW_SHADOW changes + if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) { + win *w = find_win(ps, ev->window); + if (w) + win_update_prop_shadow(ps, w); + } + + // If a leader property changes + if ((ps->o.detect_transient && ps->atom_transient == ev->atom) + || (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) { + win *w = find_toplevel(ps, ev->window); + if (w) { + win_update_leader(ps, w); + } + } + + // Check for other atoms we are tracking + for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) { + if (platom->atom == ev->atom) { + win *w = find_win(ps, ev->window); + if (!w) + w = find_toplevel(ps, ev->window); + if (w) + win_on_factor_change(ps, w); + break; + } + } +} + +inline static void +ev_damage_notify(session_t *ps, XDamageNotifyEvent *ev) { + damage_win(ps, ev); +} + +inline static void +ev_shape_notify(session_t *ps, XShapeEvent *ev) { + win *w = find_win(ps, ev->window); + if (!w || IsUnmapped == w->a.map_state) return; + + /* + * Empty border_size may indicated an + * unmapped/destroyed window, in which case + * seemingly BadRegion errors would be triggered + * if we attempt to rebuild border_size + */ + if (w->border_size) { + // Mark the old border_size as damaged + add_damage(ps, w->border_size); + + w->border_size = border_size(ps, w, true); + + // Mark the new border_size as damaged + add_damage(ps, copy_region(ps, w->border_size)); + } + + // Redo bounding shape detection and rounded corner detection + win_update_shape(ps, w); + + update_reg_ignore_expire(ps, w); +} + +/** + * Handle ScreenChangeNotify events from X RandR extension. + */ +static void +ev_screen_change_notify(session_t *ps, + XRRScreenChangeNotifyEvent __attribute__((unused)) *ev) { + if (ps->o.xinerama_shadow_crop) + cxinerama_upd_scrs(ps); + + if (ps->o.sw_opti && !ps->o.refresh_rate) { + update_refresh_rate(ps); + if (!ps->refresh_rate) { + fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection " + "failed, --sw-opti disabled."); + ps->o.sw_opti = false; + } + } +} + +#if defined(DEBUG_EVENTS) || defined(DEBUG_RESTACK) +/** + * Get a window's name from window ID. + */ +static bool +ev_window_name(session_t *ps, Window wid, char **name) { + bool to_free = false; + + *name = ""; + if (wid) { + *name = "(Failed to get title)"; + if (ps->root == wid) + *name = "(Root window)"; + else if (ps->overlay == wid) + *name = "(Overlay)"; + else { + win *w = find_win(ps, wid); + if (!w) + w = find_toplevel(ps, wid); + + if (w && w->name) + *name = w->name; + else if (!(w && w->client_win + && (to_free = wid_get_name(ps, w->client_win, name)))) + to_free = wid_get_name(ps, wid, name); + } + } + + return to_free; +} +#endif + +static void +ev_handle(session_t *ps, XEvent *ev) { + if ((ev->type & 0x7f) != KeymapNotify) { + discard_ignore(ps, ev->xany.serial); + } + +#ifdef DEBUG_EVENTS + if (!isdamagenotify(ps, ev)) { + Window wid = ev_window(ps, ev); + char *window_name = NULL; + bool to_free = false; + + to_free = ev_window_name(ps, wid, &window_name); + + print_timestamp(ps); + printf("event %10.10s serial %#010x window %#010lx \"%s\"\n", + ev_name(ps, ev), ev_serial(ev), wid, window_name); + + if (to_free) { + cxfree(window_name); + window_name = NULL; + } + } + +#endif + + switch (ev->type) { + case FocusIn: + ev_focus_in(ps, (XFocusChangeEvent *)ev); + break; + case FocusOut: + ev_focus_out(ps, (XFocusChangeEvent *)ev); + break; + case CreateNotify: + ev_create_notify(ps, (XCreateWindowEvent *)ev); + break; + case ConfigureNotify: + ev_configure_notify(ps, (XConfigureEvent *)ev); + break; + case DestroyNotify: + ev_destroy_notify(ps, (XDestroyWindowEvent *)ev); + break; + case MapNotify: + ev_map_notify(ps, (XMapEvent *)ev); + break; + case UnmapNotify: + ev_unmap_notify(ps, (XUnmapEvent *)ev); + break; + case ReparentNotify: + ev_reparent_notify(ps, (XReparentEvent *)ev); + break; + case CirculateNotify: + ev_circulate_notify(ps, (XCirculateEvent *)ev); + break; + case Expose: + ev_expose(ps, (XExposeEvent *)ev); + break; + case PropertyNotify: + ev_property_notify(ps, (XPropertyEvent *)ev); + break; + default: + if (ps->shape_exists && ev->type == ps->shape_event) { + ev_shape_notify(ps, (XShapeEvent *) ev); + break; + } + if (ps->randr_exists && ev->type == (ps->randr_event + RRScreenChangeNotify)) { + ev_screen_change_notify(ps, (XRRScreenChangeNotifyEvent *) ev); + break; + } + if (isdamagenotify(ps, ev)) { + ev_damage_notify(ps, (XDamageNotifyEvent *) ev); + break; + } + } +} + +// === Main === + +/** + * Print usage text and exit. + */ +static void +usage(int ret) { +#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)" +#define WARNING + const static char *usage_text = + "compton (" COMPTON_VERSION ")\n" + "usage: compton [options]\n" + "Options:\n" + "\n" + "-d display\n" + " Which display should be managed.\n" + "-r radius\n" + " The blur radius for shadows. (default 12)\n" + "-o opacity\n" + " The translucency for shadows. (default .75)\n" + "-l left-offset\n" + " The left offset for shadows. (default -15)\n" + "-t top-offset\n" + " The top offset for shadows. (default -15)\n" + "-I fade-in-step\n" + " Opacity change between steps while fading in. (default 0.028)\n" + "-O fade-out-step\n" + " Opacity change between steps while fading out. (default 0.03)\n" + "-D fade-delta-time\n" + " The time between steps in a fade in milliseconds. (default 10)\n" + "-m opacity\n" + " The opacity for menus. (default 1.0)\n" + "-c\n" + " Enabled client-side shadows on windows.\n" + "-C\n" + " Avoid drawing shadows on dock/panel windows.\n" + "-z\n" + " Zero the part of the shadow's mask behind the window (experimental).\n" + "-f\n" + " Fade windows in/out when opening/closing and when opacity\n" + " changes, unless --no-fading-openclose is used.\n" + "-F\n" + " Equals -f. Deprecated.\n" + "-i opacity\n" + " Opacity of inactive windows. (0.1 - 1.0)\n" + "-e opacity\n" + " Opacity of window titlebars and borders. (0.1 - 1.0)\n" + "-G\n" + " Don't draw shadows on DND windows\n" + "-b\n" + " Daemonize process.\n" + "-S\n" + " Enable synchronous operation (for debugging).\n" + "-v\n" + " Print version Number and exit\\n" + "--config path\n" + " Look for configuration file at the path.\n" + "--write-pid-path path\n" + " Write process ID to a file.\n" + "--shadow-red value\n" + " Red color value of shadow (0.0 - 1.0, defaults to 0).\n" + "--shadow-green value\n" + " Green color value of shadow (0.0 - 1.0, defaults to 0).\n" + "--shadow-blue value\n" + " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n" + "--inactive-opacity-override\n" + " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n" + "--inactive-dim value\n" + " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n" + "--active-opacity opacity\n" + " Default opacity for active windows. (0.0 - 1.0)\n" + "--mark-wmwin-focused\n" + " Try to detect WM windows and mark them as active.\n" + "--shadow-exclude condition\n" + " Exclude conditions for shadows.\n" + "--fade-exclude condition\n" + " Exclude conditions for fading.\n" + "--mark-ovredir-focused\n" + " Mark windows that have no WM frame as active.\n" + "--no-fading-openclose\n" + " Do not fade on window open/close.\n" + "--no-fading-opacitychange\n" + " Do not fade on window opacity change.\n" + "--shadow-ignore-shaped\n" + " Do not paint shadows on shaped windows.\n" + "--detect-rounded-corners\n" + " Try to detect windows with rounded corners and don't consider\n" + " them shaped windows.\n" + "--detect-client-opacity\n" + " Detect _NET_WM_OPACITY on client windows, useful for window\n" + " managers not passing _NET_WM_OPACITY of client windows to frame\n" + " windows.\n" + "--refresh-rate val\n" + " Specify refresh rate of the screen. If not specified or 0, compton\n" + " will try detecting this with X RandR extension.\n" + "--vsync vsync-method\n" + " Set VSync method. There are up to 4 VSync methods currently available.\n" + " none = No VSync\n" +#undef WARNING +#ifndef CONFIG_VSYNC_DRM +#define WARNING WARNING_DISABLED +#else +#define WARNING +#endif + " drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n" + " drivers." WARNING "\n" +#undef WARNING +#ifndef CONFIG_VSYNC_OPENGL +#define WARNING WARNING_DISABLED +#else +#define WARNING +#endif + " opengl = Try to VSync with SGI_video_sync OpenGL extension. Only\n" + " work on some drivers." WARNING"\n" + " opengl-oml = Try to VSync with OML_sync_control OpenGL extension.\n" + " Only work on some drivers. Experimental." WARNING"\n" + " opengl-swc = Try to VSync with SGI_swap_control OpenGL extension.\n" + " Only work on some drivers. Works only with GLX backend.\n" + " Does not actually control paint timing, only buffer swap is\n" + " affected, so it doesn't have the effect of --sw-opti unlike\n" + " other methods." WARNING "\n" + " opengl-mswc = Try to VSync with MESA_swap_control OpenGL\n" + " extension. Basically the same as opengl-swc above, except the\n" + " extension we use." WARNING "\n" + "--vsync-aggressive\n" + " Attempt to send painting request before VBlank and do XFlush()\n" + " during VBlank. This switch may be lifted out at any moment.\n" + "--alpha-step val\n" + " X Render backend: Step for pregenerating alpha pictures. \n" + " 0.01 - 1.0. Defaults to 0.03.\n" + "--dbe\n" + " Enable DBE painting mode, intended to use with VSync to\n" + " (hopefully) eliminate tearing.\n" + "--paint-on-overlay\n" + " Painting on X Composite overlay window.\n" + "--sw-opti\n" + " Limit compton to repaint at most once every 1 / refresh_rate\n" + " second to boost performance.\n" + "--use-ewmh-active-win\n" + " Use _NET_WM_ACTIVE_WINDOW on the root window to determine which\n" + " window is focused instead of using FocusIn/Out events.\n" + "--respect-prop-shadow\n" + " Respect _TDE_WM_WINDOW_SHADOW. This a prototype-level feature, which\n" + " you must not rely on.\n" + "--unredir-if-possible\n" + " Unredirect all windows if a full-screen opaque window is\n" + " detected, to maximize performance for full-screen windows.\n" + "--unredir-if-possible-delay ms\n" + " Delay before unredirecting the window, in milliseconds.\n" + " Defaults to 0.\n" + "--unredir-if-possible-exclude condition\n" + " Conditions of windows that shouldn't be considered full-screen\n" + " for unredirecting screen.\n" + "--focus-exclude condition\n" + " Specify a list of conditions of windows that should always be\n" + " considered focused.\n" + "--inactive-dim-fixed\n" + " Use fixed inactive dim value.\n" + "--detect-transient\n" + " Use WM_TRANSIENT_FOR to group windows, and consider windows in\n" + " the same group focused at the same time.\n" + "--detect-client-leader\n" + " Use WM_CLIENT_LEADER to group windows, and consider windows in\n" + " the same group focused at the same time. WM_TRANSIENT_FOR has\n" + " higher priority if --detect-transient is enabled, too.\n" + "--blur-background\n" + " Blur background of semi-transparent / ARGB windows. Bad in\n" + " performance. The switch name may change without prior\n" + " notifications.\n" + "--blur-background-frame\n" + " Blur background of windows when the window frame is not opaque.\n" + " Implies --blur-background. Bad in performance. The switch name\n" + " may change.\n" + "--blur-background-fixed\n" + " Use fixed blur strength instead of adjusting according to window\n" + " opacity.\n" + "--blur-kern matrix\n" + " Specify the blur convolution kernel, with the following format:\n" + " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n" + " The element in the center must not be included, it will be forever\n" + " 1.0 or changing based on opacity, depending on whether you have\n" + " --blur-background-fixed.\n" + " A 7x7 Guassian blur kernel looks like:\n" + " --blur-kern '7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003'\n" + " Up to 4 blur kernels may be specified, separated with semicolon, for\n" + " multi-pass blur.\n" + " May also be one the predefined kernels: 3x3box (default), 5x5box,\n" + " 7x7box, 3x3gaussian, 5x5gaussian, 7x7gaussian, 9x9gaussian,\n" + " 11x11gaussian.\n" + "--blur-background-exclude condition\n" + " Exclude conditions for background blur.\n" + "--resize-damage integer\n" + " Resize damaged region by a specific number of pixels. A positive\n" + " value enlarges it while a negative one shrinks it. Useful for\n" + " fixing the line corruption issues of blur. May or may not\n" + " work with --glx-no-stencil. Shrinking doesn't function correctly.\n" + "--invert-color-include condition\n" + " Specify a list of conditions of windows that should be painted with\n" + " inverted color. Resource-hogging, and is not well tested.\n" + "--opacity-rule opacity:condition\n" + " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n" + " like \'50:name *= \"Firefox\"'. compton-trans is recommended over\n" + " this. Note we do not distinguish 100% and unset, and we don't make\n" + " any guarantee about possible conflicts with other programs that set\n" + " _NET_WM_WINDOW_OPACITY on frame or client windows.\n" + "--shadow-exclude-reg geometry\n" + " Specify a X geometry that describes the region in which shadow\n" + " should not be painted in, such as a dock window region.\n" + " Use --shadow-exclude-reg \'x10+0-0\', for example, if the 10 pixels\n" + " on the bottom of the screen should not have shadows painted on.\n" +#undef WARNING +#ifndef CONFIG_XINERAMA +#define WARNING WARNING_DISABLED +#else +#define WARNING +#endif + "--xinerama-shadow-crop\n" + " Crop shadow of a window fully on a particular Xinerama screen to the\n" + " screen." WARNING "\n" + "--backend backend\n" + " Choose backend. Possible choices are xrender, glx, and\n" + " xr_glx_hybrid" WARNING ".\n" + "--glx-no-stencil\n" + " GLX backend: Avoid using stencil buffer. Might cause issues\n" + " when rendering transparent content. My tests show a 15% performance\n" + " boost.\n" + "--glx-copy-from-front\n" + " GLX backend: Copy unmodified regions from front buffer instead of\n" + " redrawing them all. My tests with nvidia-drivers show a 5% decrease\n" + " in performance when the whole screen is modified, but a 30% increase\n" + " when only 1/4 is. My tests on nouveau show terrible slowdown. Could\n" + " work with --glx-swap-method but not --glx-use-copysubbuffermesa.\n" + "--glx-use-copysubbuffermesa\n" + " GLX backend: Use MESA_copy_sub_buffer to do partial screen update.\n" + " My tests on nouveau shows a 200% performance boost when only 1/4 of\n" + " the screen is updated. May break VSync and is not available on some\n" + " drivers. Overrides --glx-copy-from-front.\n" + "--glx-no-rebind-pixmap\n" + " GLX backend: Avoid rebinding pixmap on window damage. Probably\n" + " could improve performance on rapid window content changes, but is\n" + " known to break things on some drivers.\n" + "--glx-swap-method undefined/copy/exchange/3/4/5/6/buffer-age\n" + " GLX backend: GLX buffer swap method we assume. Could be\n" + " undefined (0), copy (1), exchange (2), 3-6, or buffer-age (-1).\n" + " \"undefined\" is the slowest and the safest, and the default value.\n" + " 1 is fastest, but may fail on some drivers, 2-6 are gradually slower\n" + " but safer (6 is still faster than 0). -1 means auto-detect using\n" + " GLX_EXT_buffer_age, supported by some drivers. Useless with\n" + " --glx-use-copysubbuffermesa.\n" + "--glx-use-gpushader4\n" + " GLX backend: Use GL_EXT_gpu_shader4 for some optimization on blur\n" + " GLSL code. My tests on GTX 670 show no noticeable effect.\n" + "--xrender-sync\n" + " Attempt to synchronize client applications' draw calls with XSync(),\n" + " used on GLX backend to ensure up-to-date window content is painted.\n" +#undef WARNING +#ifndef CONFIG_XSYNC +#define WARNING WARNING_DISABLED +#else +#define WARNING +#endif + "--xrender-sync-fence\n" + " Additionally use X Sync fence to sync clients' draw calls. Needed\n" + " on nvidia-drivers with GLX backend for some users." WARNING "\n" +#undef WARNING +#ifndef CONFIG_DBUS +#define WARNING WARNING_DISABLED +#else +#define WARNING +#endif + "--dbus\n" + " Enable remote control via D-Bus. See the D-BUS API section in the\n" + " man page for more details." WARNING "\n" + "--benchmark cycles\n" + " Benchmark mode. Repeatedly paint until reaching the specified cycles.\n" + "--benchmark-wid window-id\n" + " Specify window ID to repaint in benchmark mode. If omitted or is 0,\n" + " the whole screen is repainted.\n" + ; + FILE *f = (ret ? stderr: stdout); + fputs(usage_text, f); +#undef WARNING +#undef WARNING_DISABLED + + exit(ret); +} + +/** + * Register a window as symbol, and initialize GLX context if wanted. + */ +static bool +register_cm(session_t *ps) { + assert(!ps->reg_win); + + ps->reg_win = XCreateSimpleWindow(ps->dpy, ps->root, 0, 0, 1, 1, 0, + None, None); + + if (!ps->reg_win) { + printf_errf("(): Failed to create window."); + return false; + } + + // Unredirect the window if it's redirected, just in case + if (ps->redirected) + XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual); + + { + XClassHint *h = XAllocClassHint(); + if (h) { + h->res_name = "compton"; + h->res_class = "xcompmgr"; + } + Xutf8SetWMProperties(ps->dpy, ps->reg_win, "xcompmgr", "xcompmgr", + NULL, 0, NULL, NULL, h); + cxfree(h); + } + + // Set _NET_WM_PID + { + long pid = getpid(); + if (!XChangeProperty(ps->dpy, ps->reg_win, + get_atom(ps, "_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &pid, 1)) { + printf_errf("(): Failed to set _NET_WM_PID."); + } + } + + // Set COMPTON_VERSION + if (!wid_set_text_prop(ps, ps->reg_win, get_atom(ps, "COMPTON_VERSION"), COMPTON_VERSION)) { + printf_errf("(): Failed to set COMPTON_VERSION."); + } + + { + unsigned len = strlen(REGISTER_PROP) + 2; + int s = ps->scr; + + while (s >= 10) { + ++len; + s /= 10; + } + + Window w; + Atom a; + static char net_wm_cm[] = "_NET_WM_CM_Sxx"; + + snprintf (net_wm_cm, sizeof (net_wm_cm), "_NET_WM_CM_S%d", ps->scr); + a = XInternAtom (ps->dpy, net_wm_cm, False); + + char *buf = malloc(len); + snprintf(buf, len, REGISTER_PROP "%d", ps->scr); + buf[len - 1] = '\0'; + // setting this causes compton to abort on TDE login + // XSetSelectionOwner(ps->dpy, get_atom(ps, buf), ps->reg_win, 0); + free(buf); + } + + return true; +} + +/** + * Reopen streams for logging. + */ +static bool +ostream_reopen(session_t *ps, const char *path) { + if (!path) + path = ps->o.logpath; + if (!path) + path = "/dev/null"; + + bool success = freopen(path, "a", stdout); + success = freopen(path, "a", stderr) && success; + if (!success) + printf_errfq(1, "(%s): freopen() failed.", path); + + return success; +} + +/** + * Fork program to background and disable all I/O streams. + */ +static inline bool +fork_after(session_t *ps) { + if (getppid() == 1) + return true; + +#ifdef CONFIG_VSYNC_OPENGL + // GLX context must be released and reattached on fork + if (ps->glx_context && !glXMakeCurrent(ps->dpy, None, NULL)) { + printf_errf("(): Failed to detach GLx context."); + return false; + } +#endif + + int pid = fork(); + + if (-1 == pid) { + printf_errf("(): fork() failed."); + return false; + } + + if (pid > 0) _exit(0); + + setsid(); + +#ifdef CONFIG_VSYNC_OPENGL + if (ps->glx_context + && !glXMakeCurrent(ps->dpy, get_tgt_window(ps), ps->glx_context)) { + printf_errf("(): Failed to make GLX context current."); + return false; + } +#endif + + // Mainly to suppress the _FORTIFY_SOURCE warning + bool success = freopen("/dev/null", "r", stdin); + if (!success) { + printf_errf("(): freopen() failed."); + return false; + } + success = ostream_reopen(ps, NULL); + + return success; +} + +/** + * Write PID to a file. + */ +static inline bool +write_pid(session_t *ps) { + if (!ps->o.write_pid_path) + return true; + + FILE *f = fopen(ps->o.write_pid_path, "w"); + if (unlikely(!f)) { + printf_errf("(): Failed to write PID to \"%s\".", ps->o.write_pid_path); + return false; + } + + fprintf(f, "%ld\n", (long) getpid()); + fclose(f); + + return true; +} + +/** + * Parse a long number. + */ +static inline bool +parse_long(const char *s, long *dest) { + const char *endptr = NULL; + long val = strtol(s, (char **) &endptr, 0); + if (!endptr || endptr == s) { + printf_errf("(\"%s\"): Invalid number.", s); + return false; + } + while (isspace(*endptr)) + ++endptr; + if (*endptr) { + printf_errf("(\"%s\"): Trailing characters.", s); + return false; + } + *dest = val; + return true; +} + +/** + * Parse a floating-point number in matrix. + */ +static inline const char * +parse_matrix_readnum(const char *src, double *dest) { + char *pc = NULL; + double val = strtod(src, &pc); + if (!pc || pc == src) { + printf_errf("(\"%s\"): No number found.", src); + return src; + } + + while (*pc && (isspace(*pc) || ',' == *pc)) + ++pc; + + *dest = val; + + return pc; +} + +/** + * Parse a matrix. + */ +static inline XFixed * +parse_matrix(session_t *ps, const char *src, const char **endptr) { + int wid = 0, hei = 0; + const char *pc = NULL; + XFixed *matrix = NULL; + + // Get matrix width and height + { + double val = 0.0; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + wid = val; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + hei = val; + } + + // Validate matrix width and height + if (wid <= 0 || hei <= 0) { + printf_errf("(): Invalid matrix width/height."); + goto parse_matrix_err; + } + if (!(wid % 2 && hei % 2)) { + printf_errf("(): Width/height not odd."); + goto parse_matrix_err; + } + if (wid > 16 || hei > 16) { + printf_errf("(): Matrix width/height too large."); + goto parse_matrix_err; + } + + // Allocate memory + matrix = calloc(wid * hei + 2, sizeof(XFixed)); + if (!matrix) { + printf_errf("(): Failed to allocate memory for matrix."); + goto parse_matrix_err; + } + + // Read elements + { + int skip = hei / 2 * wid + wid / 2; + bool hasneg = false; + for (int i = 0; i < wid * hei; ++i) { + // Ignore the center element + if (i == skip) { + matrix[2 + i] = XDoubleToFixed(0); + continue; + } + double val = 0; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + if (val < 0) hasneg = true; + matrix[2 + i] = XDoubleToFixed(val); + } + if (BKEND_XRENDER == ps->o.backend && hasneg) + printf_errf("(): A convolution kernel with negative values " + "may not work properly under X Render backend."); + } + + // Detect trailing characters + for ( ;*pc && ';' != *pc; ++pc) + if (!isspace(*pc) && ',' != *pc) { + printf_errf("(): Trailing characters in matrix string."); + goto parse_matrix_err; + } + + // Jump over spaces after ';' + if (';' == *pc) { + ++pc; + while (*pc && isspace(*pc)) + ++pc; + } + + // Require an end of string if endptr is not provided, otherwise + // copy end pointer to endptr + if (endptr) + *endptr = pc; + else if (*pc) { + printf_errf("(): Only one matrix expected."); + goto parse_matrix_err; + } + + // Fill in width and height + matrix[0] = XDoubleToFixed(wid); + matrix[1] = XDoubleToFixed(hei); + + return matrix; + +parse_matrix_err: + free(matrix); + return NULL; +} + +/** + * Parse a convolution kernel. + */ +static inline XFixed * +parse_conv_kern(session_t *ps, const char *src, const char **endptr) { + return parse_matrix(ps, src, endptr); +} + +/** + * Parse a list of convolution kernels. + */ +static bool +parse_conv_kern_lst(session_t *ps, const char *src, XFixed **dest, int max) { + static const struct { + const char *name; + const char *kern_str; + } CONV_KERN_PREDEF[] = { + { "3x3box", "3,3,1,1,1,1,1,1,1,1," }, + { "5x5box", "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1," }, + { "7x7box", "7,7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1," }, + { "3x3gaussian", "3,3,0.243117,0.493069,0.243117,0.493069,0.493069,0.243117,0.493069,0.243117," }, + { "5x5gaussian", "5,5,0.003493,0.029143,0.059106,0.029143,0.003493,0.029143,0.243117,0.493069,0.243117,0.029143,0.059106,0.493069,0.493069,0.059106,0.029143,0.243117,0.493069,0.243117,0.029143,0.003493,0.029143,0.059106,0.029143,0.003493," }, + { "7x7gaussian", "7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003," }, + { "9x9gaussian", "9,9,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000012,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000," }, + { "11x11gaussian", "11,11,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000012,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000," }, + }; + for (int i = 0; + i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) + if (!strcmp(CONV_KERN_PREDEF[i].name, src)) + return parse_conv_kern_lst(ps, CONV_KERN_PREDEF[i].kern_str, dest, max); + + int i = 0; + const char *pc = src; + + // Free old kernels + for (i = 0; i < max; ++i) { + free(dest[i]); + dest[i] = NULL; + } + + // Continue parsing until the end of source string + i = 0; + while (pc && *pc && i < max - 1) { + if (!(dest[i++] = parse_conv_kern(ps, pc, &pc))) + return false; + } + + if (*pc) { + printf_errf("(): Too many blur kernels!"); + return false; + } + + return true; +} + +/** + * Parse a X geometry. + */ +static inline bool +parse_geometry(session_t *ps, const char *src, geometry_t *dest) { + geometry_t geom = { .wid = -1, .hei = -1, .x = -1, .y = -1 }; + long val = 0L; + char *endptr = NULL; + +#define T_STRIPSPACE() do { \ + while (*src && isspace(*src)) ++src; \ + if (!*src) goto parse_geometry_end; \ +} while(0) + + T_STRIPSPACE(); + + // Parse width + // Must be base 10, because "0x0..." may appear + if (!('+' == *src || '-' == *src)) { + val = strtol(src, &endptr, 10); + if (endptr && src != endptr) { + geom.wid = val; + assert(geom.wid >= 0); + src = endptr; + } + T_STRIPSPACE(); + } + + // Parse height + if ('x' == *src) { + ++src; + val = strtol(src, &endptr, 10); + if (endptr && src != endptr) { + geom.hei = val; + if (geom.hei < 0) { + printf_errf("(\"%s\"): Invalid height.", src); + return false; + } + src = endptr; + } + T_STRIPSPACE(); + } + + // Parse x + if ('+' == *src || '-' == *src) { + val = strtol(src, &endptr, 10); + if (endptr && src != endptr) { + geom.x = val; + if ('-' == *src && geom.x <= 0) + geom.x -= 2; + src = endptr; + } + T_STRIPSPACE(); + } + + // Parse y + if ('+' == *src || '-' == *src) { + val = strtol(src, &endptr, 10); + if (endptr && src != endptr) { + geom.y = val; + if ('-' == *src && geom.y <= 0) + geom.y -= 2; + src = endptr; + } + T_STRIPSPACE(); + } + + if (*src) { + printf_errf("(\"%s\"): Trailing characters.", src); + return false; + } + +parse_geometry_end: + *dest = geom; + return true; +} + +/** + * Parse a list of opacity rules. + */ +static inline bool +parse_rule_opacity(session_t *ps, const char *src) { + // Find opacity value + char *endptr = NULL; + long val = strtol(src, &endptr, 0); + if (!endptr || endptr == src) { + printf_errf("(\"%s\"): No opacity specified?", src); + return false; + } + if (val > 100 || val < 0) { + printf_errf("(\"%s\"): Opacity %ld invalid.", src, val); + return false; + } + + // Skip over spaces + while (*endptr && isspace(*endptr)) + ++endptr; + if (':' != *endptr) { + printf_errf("(\"%s\"): Opacity terminator not found.", src); + return false; + } + ++endptr; + + // Parse pattern + // I hope 1-100 is acceptable for (void *) + return c2_parsed(ps, &ps->o.opacity_rules, endptr, (void *) val); +} + +#ifdef CONFIG_LIBCONFIG +/** + * Get a file stream of the configuration file to read. + * + * Follows the XDG specification to search for the configuration file. + */ +static FILE * +open_config_file(char *cpath, char **ppath) { + const static char *config_filename = "/compton-tde.conf"; + const static char *config_filename_legacy = "/.compton-tde.conf"; + const static char *config_home_suffix = "/.config"; + const static char *config_system_dir = "/etc/xdg"; + + char *dir = NULL, *home = NULL; + char *path = cpath; + FILE *f = NULL; + + if (path) { + f = fopen(path, "r"); + if (f && ppath) + *ppath = path; + return f; + } + + // Check user configuration file in $XDG_CONFIG_HOME firstly + if (!((dir = getenv("XDG_CONFIG_HOME")) && strlen(dir))) { + if (!((home = getenv("HOME")) && strlen(home))) + return NULL; + + path = mstrjoin3(home, config_home_suffix, config_filename); + } + else + path = mstrjoin(dir, config_filename); + + f = fopen(path, "r"); + + if (f && ppath) + *ppath = path; + else + free(path); + if (f) + return f; + + // Then check user configuration file in $HOME + if ((home = getenv("HOME")) && strlen(home)) { + path = mstrjoin(home, config_filename_legacy); + f = fopen(path, "r"); + if (f && ppath) + *ppath = path; + else + free(path); + if (f) + return f; + } + + // Check system configuration file in $XDG_CONFIG_DIRS at last + if ((dir = getenv("XDG_CONFIG_DIRS")) && strlen(dir)) { + char *part = strtok(dir, ":"); + while (part) { + path = mstrjoin(part, config_filename); + f = fopen(path, "r"); + if (f && ppath) + *ppath = path; + else + free(path); + if (f) + return f; + part = strtok(NULL, ":"); + } + } + else { + path = mstrjoin(config_system_dir, config_filename); + f = fopen(path, "r"); + if (f && ppath) + *ppath = path; + else + free(path); + if (f) + return f; + } + + return NULL; +} + +/** + * Parse a condition list in configuration file. + */ +static inline void +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) { + // Parse an array of options + if (config_setting_is_array(setting)) { + int i = config_setting_length(setting); + while (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(ps, pcondlst, config_setting_get_string(setting)); + } + } +} + +/** + * Parse an opacity rule list in configuration file. + */ +static inline void +parse_cfg_condlst_opct(session_t *ps, const config_t *pcfg, const char *name) { + config_setting_t *setting = config_lookup(pcfg, name); + if (setting) { + // Parse an array of options + if (config_setting_is_array(setting)) { + int i = config_setting_length(setting); + while (i--) + if (!parse_rule_opacity(ps, config_setting_get_string_elem(setting, + i))) + exit(1); + } + // Treat it as a single pattern if it's a string + else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { + parse_rule_opacity(ps, config_setting_get_string(setting)); + } + } +} + +/** + * Parse a configuration file from default location. + */ +static void +parse_config(session_t *ps, struct options_tmp *pcfgtmp) { + char *path = NULL; + FILE *f; + config_t cfg; + int ival = 0; + double dval = 0.0; + // libconfig manages string memory itself, so no need to manually free + // anything + const char *sval = NULL; + + f = open_config_file(ps->o.config_file, &path); + if (!f) { + 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; + } + + config_init(&cfg); +#ifndef CONFIG_LIBCONFIG_LEGACY + { + // dirname() could modify the original string, thus we must pass a + // copy + char *path2 = mstrcpy(path); + char *parent = dirname(path2); + + if (parent) + config_set_include_dir(&cfg, parent); + + free(path2); + } +#endif + + if (CONFIG_FALSE == config_read(&cfg, f)) { + printf("Error when reading configuration file \"%s\", line %d: %s\n", + path, config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + free(path); + return; + } + config_set_auto_convert(&cfg, 1); + + 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 + + // -D (fade_delta) + if (lcfg_lookup_int(&cfg, "fade-delta", &ival)) + ps->o.fade_delta = ival; + // -I (fade_in_step) + if (config_lookup_float(&cfg, "fade-in-step", &dval)) + ps->o.fade_in_step = normalize_d(dval) * OPAQUE; + // -O (fade_out_step) + if (config_lookup_float(&cfg, "fade-out-step", &dval)) + ps->o.fade_out_step = normalize_d(dval) * OPAQUE; + // -r (shadow_radius) + lcfg_lookup_int(&cfg, "shadow-radius", &ps->o.shadow_radius); + // -o (shadow_opacity) + config_lookup_float(&cfg, "shadow-opacity", &ps->o.shadow_opacity); + // -l (shadow_offset_x) + lcfg_lookup_int(&cfg, "shadow-offset-x", &ps->o.shadow_offset_x); + // -t (shadow_offset_y) + lcfg_lookup_int(&cfg, "shadow-offset-y", &ps->o.shadow_offset_y); + // -i (inactive_opacity) + if (config_lookup_float(&cfg, "inactive-opacity", &dval)) + ps->o.inactive_opacity = normalize_d(dval) * OPAQUE; + // --active_opacity + if (config_lookup_float(&cfg, "active-opacity", &dval)) + ps->o.active_opacity = normalize_d(dval) * OPAQUE; + // -e (frame_opacity) + config_lookup_float(&cfg, "frame-opacity", &ps->o.frame_opacity); + // -z (clear_shadow) + lcfg_lookup_bool(&cfg, "clear-shadow", &ps->o.clear_shadow); + // -c (shadow_enable) + if (config_lookup_bool(&cfg, "shadow", &ival) && ival) + wintype_arr_enable(ps->o.wintype_shadow); + // -C (no_dock_shadow) + lcfg_lookup_bool(&cfg, "no-dock-shadow", &pcfgtmp->no_dock_shadow); + // -G (no_dnd_shadow) + lcfg_lookup_bool(&cfg, "no-dnd-shadow", &pcfgtmp->no_dnd_shadow); + // -m (menu_opacity) + config_lookup_float(&cfg, "menu-opacity", &pcfgtmp->menu_opacity); + // -f (fading_enable) + if (config_lookup_bool(&cfg, "fading", &ival) && ival) + wintype_arr_enable(ps->o.wintype_fade); + // --no-fading-open-close + lcfg_lookup_bool(&cfg, "no-fading-openclose", &ps->o.no_fading_openclose); + // --no-fading-opacitychange + lcfg_lookup_bool(&cfg, "no-fading-opacitychange", &ps->o.no_fading_opacitychange); + // --shadow-red + config_lookup_float(&cfg, "shadow-red", &ps->o.shadow_red); + // --shadow-green + config_lookup_float(&cfg, "shadow-green", &ps->o.shadow_green); + // --shadow-blue + config_lookup_float(&cfg, "shadow-blue", &ps->o.shadow_blue); + // --shadow-exclude-reg + if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval) + && !parse_geometry(ps, sval, &ps->o.shadow_exclude_reg_geom)) + exit(1); + // --inactive-opacity-override + lcfg_lookup_bool(&cfg, "inactive-opacity-override", + &ps->o.inactive_opacity_override); + // --inactive-dim + config_lookup_float(&cfg, "inactive-dim", &ps->o.inactive_dim); + // --mark-wmwin-focused + lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &ps->o.mark_wmwin_focused); + // --mark-ovredir-focused + lcfg_lookup_bool(&cfg, "mark-ovredir-focused", + &ps->o.mark_ovredir_focused); + // --shadow-ignore-shaped + lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", + &ps->o.shadow_ignore_shaped); + // --detect-rounded-corners + lcfg_lookup_bool(&cfg, "detect-rounded-corners", + &ps->o.detect_rounded_corners); + // --xinerama-shadow-crop + lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", + &ps->o.xinerama_shadow_crop); + // --detect-client-opacity + lcfg_lookup_bool(&cfg, "detect-client-opacity", + &ps->o.detect_client_opacity); + // --refresh-rate + lcfg_lookup_int(&cfg, "refresh-rate", &ps->o.refresh_rate); + // --vsync + if (config_lookup_string(&cfg, "vsync", &sval) && !parse_vsync(ps, sval)) + exit(1); + // --backend + if (config_lookup_string(&cfg, "backend", &sval) && !parse_backend(ps, sval)) + exit(1); + // --alpha-step + config_lookup_float(&cfg, "alpha-step", &ps->o.alpha_step); + // --dbe + lcfg_lookup_bool(&cfg, "dbe", &ps->o.dbe); + // --paint-on-overlay + lcfg_lookup_bool(&cfg, "paint-on-overlay", &ps->o.paint_on_overlay); + // --sw-opti + lcfg_lookup_bool(&cfg, "sw-opti", &ps->o.sw_opti); + // --use-ewmh-active-win + lcfg_lookup_bool(&cfg, "use-ewmh-active-win", + &ps->o.use_ewmh_active_win); + // --unredir-if-possible + lcfg_lookup_bool(&cfg, "unredir-if-possible", + &ps->o.unredir_if_possible); + // --unredir-if-possible-delay + if (lcfg_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) + ps->o.unredir_if_possible_delay = ival; + // --inactive-dim-fixed + lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &ps->o.inactive_dim_fixed); + // --detect-transient + lcfg_lookup_bool(&cfg, "detect-transient", &ps->o.detect_transient); + // --detect-client-leader + lcfg_lookup_bool(&cfg, "detect-client-leader", + &ps->o.detect_client_leader); + // --shadow-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.shadow_blacklist, "shadow-exclude"); + // --fade-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.fade_blacklist, "fade-exclude"); + // --focus-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.focus_blacklist, "focus-exclude"); + // --invert-color-include + parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include"); + // --blur-background-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.blur_background_blacklist, "blur-background-exclude"); + // --opacity-rule + parse_cfg_condlst_opct(ps, &cfg, "opacity-rule"); + // --unredir-if-possible-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.unredir_if_possible_blacklist, "unredir-if-possible-exclude"); + // --blur-background + lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background); + // --blur-background-frame + lcfg_lookup_bool(&cfg, "blur-background-frame", + &ps->o.blur_background_frame); + // --blur-background-fixed + lcfg_lookup_bool(&cfg, "blur-background-fixed", + &ps->o.blur_background_fixed); + // --blur-kern + if (config_lookup_string(&cfg, "blur-kern", &sval) + && !parse_conv_kern_lst(ps, sval, ps->o.blur_kerns, MAX_BLUR_PASS)) + exit(1); + // --resize-damage + config_lookup_int(&cfg, "resize-damage", &ps->o.resize_damage); + // --glx-no-stencil + lcfg_lookup_bool(&cfg, "glx-no-stencil", &ps->o.glx_no_stencil); + // --glx-copy-from-front + lcfg_lookup_bool(&cfg, "glx-copy-from-front", &ps->o.glx_copy_from_front); + // --glx-use-copysubbuffermesa + lcfg_lookup_bool(&cfg, "glx-use-copysubbuffermesa", &ps->o.glx_use_copysubbuffermesa); + // --glx-no-rebind-pixmap + lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &ps->o.glx_no_rebind_pixmap); + // --glx-swap-method + if (config_lookup_string(&cfg, "glx-swap-method", &sval) + && !parse_glx_swap_method(ps, sval)) + exit(1); + // --glx-use-gpushader4 + lcfg_lookup_bool(&cfg, "glx-use-gpushader4", &ps->o.glx_use_gpushader4); + // --xrender-sync + lcfg_lookup_bool(&cfg, "xrender-sync", &ps->o.xrender_sync); + // --xrender-sync-fence + lcfg_lookup_bool(&cfg, "xrender-sync-fence", &ps->o.xrender_sync_fence); + // Wintype settings + { + wintype_t i; + + for (i = 0; i < NUM_WINTYPES; ++i) { + char *str = mstrjoin("wintypes.", WINTYPES[i]); + config_setting_t *setting = config_lookup(&cfg, str); + free(str); + if (setting) { + if (config_setting_lookup_bool(setting, "shadow", &ival)) + ps->o.wintype_shadow[i] = (bool) ival; + if (config_setting_lookup_bool(setting, "fade", &ival)) + ps->o.wintype_fade[i] = (bool) ival; + if (config_setting_lookup_bool(setting, "focus", &ival)) + ps->o.wintype_focus[i] = (bool) ival; + config_setting_lookup_float(setting, "opacity", + &ps->o.wintype_opacity[i]); + } + } + } + + config_destroy(&cfg); + + // Adjust shadow offsets + ps->o.shadow_offset_x = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_x * ps->o.shadow_radius / 100); + ps->o.shadow_offset_y = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_y * ps->o.shadow_radius / 100); +} +#endif + +/** + * Process arguments and configuration files. + */ +static void +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:hvscnfFCaSzGb"; + const static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "config", required_argument, NULL, 256 }, + { "shadow-radius", required_argument, NULL, 'r' }, + { "shadow-opacity", required_argument, NULL, 'o' }, + { "shadow-offset-x", required_argument, NULL, 'l' }, + { "shadow-offset-y", required_argument, NULL, 't' }, + { "fade-in-step", required_argument, NULL, 'I' }, + { "fade-out-step", required_argument, NULL, 'O' }, + { "menu-opacity", required_argument, NULL, 'm' }, + { "shadow", no_argument, NULL, 'c' }, + { "no-dock-shadow", no_argument, NULL, 'C' }, + { "clear-shadow", no_argument, NULL, 'z' }, + { "fading", no_argument, NULL, 'f' }, + { "inactive-opacity", required_argument, NULL, 'i' }, + { "frame-opacity", required_argument, NULL, 'e' }, + { "no-dnd-shadow", no_argument, NULL, 'G' }, + { "shadow-red", required_argument, NULL, 257 }, + { "shadow-green", required_argument, NULL, 258 }, + { "shadow-blue", required_argument, NULL, 259 }, + { "inactive-opacity-override", no_argument, NULL, 260 }, + { "inactive-dim", required_argument, NULL, 261 }, + { "mark-wmwin-focused", no_argument, NULL, 262 }, + { "shadow-exclude", required_argument, NULL, 263 }, + { "mark-ovredir-focused", no_argument, NULL, 264 }, + { "no-fading-openclose", no_argument, NULL, 265 }, + { "shadow-ignore-shaped", no_argument, NULL, 266 }, + { "detect-rounded-corners", no_argument, NULL, 267 }, + { "detect-client-opacity", no_argument, NULL, 268 }, + { "refresh-rate", required_argument, NULL, 269 }, + { "vsync", required_argument, NULL, 270 }, + { "alpha-step", required_argument, NULL, 271 }, + { "dbe", no_argument, NULL, 272 }, + { "paint-on-overlay", no_argument, NULL, 273 }, + { "sw-opti", no_argument, NULL, 274 }, + { "vsync-aggressive", no_argument, NULL, 275 }, + { "use-ewmh-active-win", no_argument, NULL, 276 }, + { "respect-prop-shadow", no_argument, NULL, 277 }, + { "unredir-if-possible", no_argument, NULL, 278 }, + { "focus-exclude", required_argument, NULL, 279 }, + { "inactive-dim-fixed", no_argument, NULL, 280 }, + { "detect-transient", no_argument, NULL, 281 }, + { "detect-client-leader", no_argument, NULL, 282 }, + { "blur-background", no_argument, NULL, 283 }, + { "blur-background-frame", no_argument, NULL, 284 }, + { "blur-background-fixed", no_argument, NULL, 285 }, + { "dbus", no_argument, NULL, 286 }, + { "logpath", required_argument, NULL, 287 }, + { "invert-color-include", required_argument, NULL, 288 }, + { "opengl", no_argument, NULL, 289 }, + { "backend", required_argument, NULL, 290 }, + { "glx-no-stencil", no_argument, NULL, 291 }, + { "glx-copy-from-front", no_argument, NULL, 292 }, + { "benchmark", required_argument, NULL, 293 }, + { "benchmark-wid", required_argument, NULL, 294 }, + { "glx-use-copysubbuffermesa", no_argument, NULL, 295 }, + { "blur-background-exclude", required_argument, NULL, 296 }, + { "active-opacity", required_argument, NULL, 297 }, + { "glx-no-rebind-pixmap", no_argument, NULL, 298 }, + { "glx-swap-method", required_argument, NULL, 299 }, + { "fade-exclude", required_argument, NULL, 300 }, + { "blur-kern", required_argument, NULL, 301 }, + { "resize-damage", required_argument, NULL, 302 }, + { "glx-use-gpushader4", no_argument, NULL, 303 }, + { "opacity-rule", required_argument, NULL, 304 }, + { "shadow-exclude-reg", required_argument, NULL, 305 }, + { "paint-exclude", required_argument, NULL, 306 }, + { "xinerama-shadow-crop", no_argument, NULL, 307 }, + { "unredir-if-possible-exclude", required_argument, NULL, 308 }, + { "unredir-if-possible-delay", required_argument, NULL, 309 }, + { "write-pid-path", required_argument, NULL, 310 }, + { "vsync-use-glfinish", no_argument, NULL, 311 }, + { "xrender-sync", no_argument, NULL, 312 }, + { "xrender-sync-fence", no_argument, NULL, 313 }, + { "no-fading-opacitychange", no_argument, NULL, 314 }, + // Must terminate with a NULL entry + { 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 ('S' == o) + ps->o.synchronize = true; + else if ('?' == o || ':' == o) + usage(1); + } + + // Check for abundant positional arguments + if (optind < argc) + printf_errfq(1, "(): compton doesn't accept positional arguments."); + + return; + } + + struct options_tmp cfgtmp = { + .no_dock_shadow = false, + .no_dnd_shadow = false, + .menu_opacity = 1.0, + }; + bool shadow_enable = false, fading_enable = false; + char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL)); + + for (i = 0; i < NUM_WINTYPES; ++i) { + ps->o.wintype_fade[i] = false; + ps->o.wintype_shadow[i] = false; + ps->o.wintype_opacity[i] = 1.0; + } + + // Enforce LC_NUMERIC locale "C" here to make sure dots are recognized + // instead of commas in atof(). + setlocale(LC_NUMERIC, "C"); + +#ifdef CONFIG_LIBCONFIG + parse_config(ps, &cfgtmp); +#endif + + // Parse commandline arguments. Range checking will be done later. + + optind = 1; + while (-1 != + (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { + long val = 0; + switch (o) { +#define P_CASEBOOL(idx, option) case idx: ps->o.option = true; break +#define P_CASELONG(idx, option) \ + case idx: \ + if (!parse_long(optarg, &val)) exit(1); \ + ps->o.option = val; \ + break + + // Short options + case 'h': + usage(0); + break; + case 'v': fprintf (stderr, "%s v%-3.2f\n", argv[0], _TDE_COMP_MGR_VERSION_); my_exit_code=0; exit (0); + case 'd': + case 'S': + break; + P_CASELONG('D', fade_delta); + case 'I': + ps->o.fade_in_step = normalize_d(atof(optarg)) * OPAQUE; + break; + case 'O': + ps->o.fade_out_step = normalize_d(atof(optarg)) * OPAQUE; + break; + case 'c': + shadow_enable = true; + break; + case 'C': + cfgtmp.no_dock_shadow = true; + break; + case 'G': + cfgtmp.no_dnd_shadow = true; + break; + case 'm': + cfgtmp.menu_opacity = atof(optarg); + break; + case 'f': + case 'F': + fading_enable = true; + break; + P_CASELONG('r', shadow_radius); + case 'o': + ps->o.shadow_opacity = atof(optarg); + break; + P_CASELONG('l', shadow_offset_x); + P_CASELONG('t', shadow_offset_y); + case 'i': + ps->o.inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE); + break; + case 'e': + ps->o.frame_opacity = atof(optarg); + break; + P_CASEBOOL('z', clear_shadow); + case 'n': + case 'a': + case 's': + printf_errfq(1, "(): -n, -a, and -s have been removed."); + break; + P_CASEBOOL('b', fork_after_register); + // Long options + case 256: + // --config + break; + case 257: + // --shadow-red + ps->o.shadow_red = atof(optarg); + break; + case 258: + // --shadow-green + ps->o.shadow_green = atof(optarg); + break; + case 259: + // --shadow-blue + ps->o.shadow_blue = atof(optarg); + break; + P_CASEBOOL(260, inactive_opacity_override); + case 261: + // --inactive-dim + ps->o.inactive_dim = atof(optarg); + break; + P_CASEBOOL(262, mark_wmwin_focused); + case 263: + // --shadow-exclude + condlst_add(ps, &ps->o.shadow_blacklist, optarg); + break; + P_CASEBOOL(264, mark_ovredir_focused); + P_CASEBOOL(265, no_fading_openclose); + P_CASEBOOL(266, shadow_ignore_shaped); + P_CASEBOOL(267, detect_rounded_corners); + P_CASEBOOL(268, detect_client_opacity); + P_CASELONG(269, refresh_rate); + case 270: + // --vsync + if (!parse_vsync(ps, optarg)) + exit(1); + break; + case 271: + // --alpha-step + ps->o.alpha_step = atof(optarg); + break; + P_CASEBOOL(272, dbe); + P_CASEBOOL(273, paint_on_overlay); + P_CASEBOOL(274, sw_opti); + P_CASEBOOL(275, vsync_aggressive); + P_CASEBOOL(276, use_ewmh_active_win); + P_CASEBOOL(277, respect_prop_shadow); + P_CASEBOOL(278, unredir_if_possible); + case 279: + // --focus-exclude + condlst_add(ps, &ps->o.focus_blacklist, optarg); + break; + P_CASEBOOL(280, inactive_dim_fixed); + P_CASEBOOL(281, detect_transient); + P_CASEBOOL(282, detect_client_leader); + P_CASEBOOL(283, blur_background); + P_CASEBOOL(284, blur_background_frame); + P_CASEBOOL(285, blur_background_fixed); + P_CASEBOOL(286, dbus); + case 287: + // --logpath + ps->o.logpath = mstrcpy(optarg); + break; + case 288: + // --invert-color-include + condlst_add(ps, &ps->o.invert_color_list, optarg); + break; + case 289: + // --opengl + ps->o.backend = BKEND_GLX; + break; + case 290: + // --backend + if (!parse_backend(ps, optarg)) + exit(1); + break; + P_CASEBOOL(291, glx_no_stencil); + P_CASEBOOL(292, glx_copy_from_front); + P_CASELONG(293, benchmark); + case 294: + // --benchmark-wid + ps->o.benchmark_wid = strtol(optarg, NULL, 0); + break; + P_CASEBOOL(295, glx_use_copysubbuffermesa); + case 296: + // --blur-background-exclude + condlst_add(ps, &ps->o.blur_background_blacklist, optarg); + break; + case 297: + // --active-opacity + ps->o.active_opacity = (normalize_d(atof(optarg)) * OPAQUE); + break; + P_CASEBOOL(298, glx_no_rebind_pixmap); + case 299: + // --glx-swap-method + if (!parse_glx_swap_method(ps, optarg)) + exit(1); + break; + case 300: + // --fade-exclude + condlst_add(ps, &ps->o.fade_blacklist, optarg); + break; + case 301: + // --blur-kern + if (!parse_conv_kern_lst(ps, optarg, ps->o.blur_kerns, MAX_BLUR_PASS)) + exit(1); + break; + P_CASELONG(302, resize_damage); + P_CASEBOOL(303, glx_use_gpushader4); + case 304: + // --opacity-rule + if (!parse_rule_opacity(ps, optarg)) + exit(1); + break; + case 305: + // --shadow-exclude-reg + if (!parse_geometry(ps, optarg, &ps->o.shadow_exclude_reg_geom)) + exit(1); + break; + case 306: + // --paint-exclude + condlst_add(ps, &ps->o.paint_blacklist, optarg); + break; + P_CASEBOOL(307, xinerama_shadow_crop); + case 308: + // --unredir-if-possible-exclude + condlst_add(ps, &ps->o.unredir_if_possible_blacklist, optarg); + break; + P_CASELONG(309, unredir_if_possible_delay); + case 310: + // --write-pid-path + ps->o.write_pid_path = mstrcpy(optarg); + break; + P_CASEBOOL(311, vsync_use_glfinish); + P_CASEBOOL(312, xrender_sync); + P_CASEBOOL(313, xrender_sync_fence); + P_CASEBOOL(314, no_fading_opacitychange); + default: + usage(1); + break; +#undef P_CASEBOOL + } + } + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + // Range checking and option assignments + ps->o.fade_delta = max_i(ps->o.fade_delta, 1); + ps->o.shadow_radius = max_i(ps->o.shadow_radius, 1); + ps->o.shadow_red = normalize_d(ps->o.shadow_red); + ps->o.shadow_green = normalize_d(ps->o.shadow_green); + ps->o.shadow_blue = normalize_d(ps->o.shadow_blue); + ps->o.inactive_dim = normalize_d(ps->o.inactive_dim); + ps->o.frame_opacity = normalize_d(ps->o.frame_opacity); + ps->o.shadow_opacity = normalize_d(ps->o.shadow_opacity); + cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity); + ps->o.refresh_rate = normalize_i_range(ps->o.refresh_rate, 0, 300); + ps->o.alpha_step = normalize_d_range(ps->o.alpha_step, 0.01, 1.0); + if (OPAQUE == ps->o.inactive_opacity) { + ps->o.inactive_opacity = 0; + } + if (OPAQUE == ps->o.active_opacity) { + ps->o.active_opacity = 0; + } + if (shadow_enable) + wintype_arr_enable(ps->o.wintype_shadow); + ps->o.wintype_shadow[WINTYPE_DESKTOP] = false; + if (cfgtmp.no_dock_shadow) + ps->o.wintype_shadow[WINTYPE_DOCK] = false; + if (cfgtmp.no_dnd_shadow) + ps->o.wintype_shadow[WINTYPE_DND] = false; + if (fading_enable) + wintype_arr_enable(ps->o.wintype_fade); + if (1.0 != cfgtmp.menu_opacity) { + ps->o.wintype_opacity[WINTYPE_DROPDOWN_MENU] = cfgtmp.menu_opacity; + ps->o.wintype_opacity[WINTYPE_POPUP_MENU] = cfgtmp.menu_opacity; + } + + // --blur-background-frame implies --blur-background + if (ps->o.blur_background_frame) + ps->o.blur_background = true; + + if (ps->o.xrender_sync_fence) + ps->o.xrender_sync = true; + + // Other variables determined by options + + // Determine whether we need to track focus changes + if (ps->o.inactive_opacity || ps->o.active_opacity || ps->o.inactive_dim) { + ps->o.track_focus = true; + } + + // Determine whether we track window grouping + if (ps->o.detect_transient || ps->o.detect_client_leader) { + ps->o.track_leader = true; + } + + // Fill default blur kernel + if (ps->o.blur_background && !ps->o.blur_kerns[0]) { + // Convolution filter parameter (box blur) + // gaussian or binomial filters are definitely superior, yet looks + // like they aren't supported as of xorg-server-1.13.0 + const static XFixed convolution_blur[] = { + // Must convert to XFixed with XDoubleToFixed() + // Matrix size + XDoubleToFixed(3), XDoubleToFixed(3), + // Matrix + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + }; + ps->o.blur_kerns[0] = malloc(sizeof(convolution_blur)); + if (!ps->o.blur_kerns[0]) { + printf_errf("(): Failed to allocate memory for convolution kernel."); + exit(1); + } + memcpy(ps->o.blur_kerns[0], &convolution_blur, sizeof(convolution_blur)); + } + + rebuild_shadow_exclude_reg(ps); + + if (ps->o.resize_damage < 0) + printf_errf("(): Negative --resize-damage does not work correctly."); +} + +/** + * Fetch all required atoms and save them to a session. + */ +static void +init_atoms(session_t *ps) { + ps->atom_opacity = get_atom(ps, "_NET_WM_WINDOW_OPACITY"); + ps->atom_frame_extents = get_atom(ps, "_NET_FRAME_EXTENTS"); + ps->atom_client = get_atom(ps, "WM_STATE"); + ps->atom_name = XA_WM_NAME; + ps->atom_name_ewmh = get_atom(ps, "_NET_WM_NAME"); + ps->atom_class = XA_WM_CLASS; + ps->atom_role = get_atom(ps, "WM_WINDOW_ROLE"); + ps->atom_transient = XA_WM_TRANSIENT_FOR; + ps->atom_client_leader = get_atom(ps, "WM_CLIENT_LEADER"); + ps->atom_ewmh_active_win = get_atom(ps, "_NET_ACTIVE_WINDOW"); + ps->atom_compton_shadow = get_atom(ps, "_TDE_WM_WINDOW_SHADOW"); + + ps->atom_win_type = get_atom(ps, "_NET_WM_WINDOW_TYPE"); + ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0; + ps->atoms_wintypes[WINTYPE_DESKTOP] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_DESKTOP"); + ps->atoms_wintypes[WINTYPE_DOCK] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_DOCK"); + ps->atoms_wintypes[WINTYPE_TOOLBAR] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_TOOLBAR"); + ps->atoms_wintypes[WINTYPE_MENU] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_MENU"); + ps->atoms_wintypes[WINTYPE_UTILITY] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_UTILITY"); + ps->atoms_wintypes[WINTYPE_SPLASH] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_SPLASH"); + ps->atoms_wintypes[WINTYPE_DIALOG] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_DIALOG"); + ps->atoms_wintypes[WINTYPE_NORMAL] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_NORMAL"); + ps->atoms_wintypes[WINTYPE_DROPDOWN_MENU] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + ps->atoms_wintypes[WINTYPE_POPUP_MENU] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_POPUP_MENU"); + ps->atoms_wintypes[WINTYPE_TOOLTIP] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_TOOLTIP"); + ps->atoms_wintypes[WINTYPE_NOTIFY] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_NOTIFICATION"); + ps->atoms_wintypes[WINTYPE_COMBO] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_COMBO"); + ps->atoms_wintypes[WINTYPE_DND] = get_atom(ps, + "_NET_WM_WINDOW_TYPE_DND"); + + ps->atom_win_type_tde_transparent_to_black = get_atom(ps, "_TDE_TRANSPARENT_TO_BLACK"); + ps->atom_win_type_tde_transparent_to_desktop = get_atom(ps, "_TDE_TRANSPARENT_TO_DESKTOP"); +} + +/** + * Update refresh rate info with X Randr extension. + */ +static void +update_refresh_rate(session_t *ps) { + XRRScreenConfiguration* randr_info; + + if (!(randr_info = XRRGetScreenInfo(ps->dpy, ps->root))) + return; + ps->refresh_rate = XRRConfigCurrentRate(randr_info); + + XRRFreeScreenConfigInfo(randr_info); + + if (ps->refresh_rate) + ps->refresh_intv = US_PER_SEC / ps->refresh_rate; + else + ps->refresh_intv = 0; +} + +/** + * Initialize refresh-rated based software optimization. + * + * @return true for success, false otherwise + */ +static bool +swopti_init(session_t *ps) { + // Prepare refresh rate + // Check if user provides one + ps->refresh_rate = ps->o.refresh_rate; + if (ps->refresh_rate) + ps->refresh_intv = US_PER_SEC / ps->refresh_rate; + + // Auto-detect refresh rate otherwise + if (!ps->refresh_rate && ps->randr_exists) { + update_refresh_rate(ps); + } + + // Turn off vsync_sw if we can't get the refresh rate + if (!ps->refresh_rate) + return false; + + return true; +} + +/** + * Modify a struct timeval timeout value to render at a fixed pace. + * + * @param ps current session + * @param[in,out] ptv pointer to the timeout + */ +static void +swopti_handle_timeout(session_t *ps, struct timeval *ptv) { + if (!ptv) + return; + + // Get the microsecond offset of the time when the we reach the timeout + // I don't think a 32-bit long could overflow here. + long offset = (ptv->tv_usec + get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv; + if (offset < 0) + offset += ps->refresh_intv; + + assert(offset >= 0 && offset < ps->refresh_intv); + + // If the target time is sufficiently close to a refresh time, don't add + // an offset, to avoid certain blocking conditions. + if (offset < SWOPTI_TOLERANCE + || offset > ps->refresh_intv - SWOPTI_TOLERANCE) + return; + + // Add an offset so we wait until the next refresh after timeout + ptv->tv_usec += ps->refresh_intv - offset; + if (ptv->tv_usec > US_PER_SEC) { + ptv->tv_usec -= US_PER_SEC; + ++ptv->tv_sec; + } +} + +/** + * Initialize DRM VSync. + * + * @return true for success, false otherwise + */ +static bool +vsync_drm_init(session_t *ps) { +#ifdef CONFIG_VSYNC_DRM + // Should we always open card0? + if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { + printf_errf("(): Failed to open device."); + return false; + } + + if (vsync_drm_wait(ps)) + return false; + + return true; +#else + printf_errf("(): Program not compiled with DRM VSync support."); + return false; +#endif +} + +#ifdef CONFIG_VSYNC_DRM +/** + * Wait for next VSync, DRM method. + * + * Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp + */ +static int +vsync_drm_wait(session_t *ps) { + int ret = -1; + drm_wait_vblank_t vbl; + + vbl.request.type = _DRM_VBLANK_RELATIVE, + vbl.request.sequence = 1; + + do { + ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); + vbl.request.type &= ~_DRM_VBLANK_RELATIVE; + } while (ret && errno == EINTR); + + if (ret) + fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, " + "unimplemented in this drmver?\n"); + + return ret; + +} +#endif + +/** + * Initialize OpenGL VSync. + * + * Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e + * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html + * + * @return true for success, false otherwise + */ +static bool +vsync_opengl_init(session_t *ps) { +#ifdef CONFIG_VSYNC_OPENGL + if (!ensure_glx_context(ps)) + return false; + + // Get video sync functions + if (!ps->glXGetVideoSyncSGI) + ps->glXGetVideoSyncSGI = (f_GetVideoSync) + glXGetProcAddress((const GLubyte *) "glXGetVideoSyncSGI"); + if (!ps->glXWaitVideoSyncSGI) + ps->glXWaitVideoSyncSGI = (f_WaitVideoSync) + glXGetProcAddress((const GLubyte *) "glXWaitVideoSyncSGI"); + if (!ps->glXWaitVideoSyncSGI || !ps->glXGetVideoSyncSGI) { + printf_errf("(): Failed to get glXWait/GetVideoSyncSGI function."); + return false; + } + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_oml_init(session_t *ps) { +#ifdef CONFIG_VSYNC_OPENGL + if (!ensure_glx_context(ps)) + return false; + + // Get video sync functions + if (!ps->glXGetSyncValuesOML) + ps->glXGetSyncValuesOML = (f_GetSyncValuesOML) + glXGetProcAddress ((const GLubyte *) "glXGetSyncValuesOML"); + if (!ps->glXWaitForMscOML) + ps->glXWaitForMscOML = (f_WaitForMscOML) + glXGetProcAddress ((const GLubyte *) "glXWaitForMscOML"); + if (!ps->glXGetSyncValuesOML || !ps->glXWaitForMscOML) { + printf_errf("(): Failed to get OML_sync_control functions."); + return false; + } + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_swc_init(session_t *ps) { +#ifdef CONFIG_VSYNC_OPENGL + if (!ensure_glx_context(ps)) + return false; + + if (!bkend_use_glx(ps)) { + printf_errf("(): I'm afraid glXSwapIntervalSGI wouldn't help if you are " + "not using GLX backend. You could try, nonetheless."); + } + + // Get video sync functions + if (!ps->glXSwapIntervalProc) + ps->glXSwapIntervalProc = (f_SwapIntervalSGI) + glXGetProcAddress ((const GLubyte *) "glXSwapIntervalSGI"); + if (!ps->glXSwapIntervalProc) { + printf_errf("(): Failed to get SGI_swap_control function."); + return false; + } + ps->glXSwapIntervalProc(1); + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_mswc_init(session_t *ps) { +#ifdef CONFIG_VSYNC_OPENGL + if (!ensure_glx_context(ps)) + return false; + + if (!bkend_use_glx(ps)) { + printf_errf("(): I'm afraid glXSwapIntervalMESA wouldn't help if you are " + "not using GLX backend. You could try, nonetheless."); + } + + // Get video sync functions + if (!ps->glXSwapIntervalMESAProc) + ps->glXSwapIntervalMESAProc = (f_SwapIntervalMESA) + glXGetProcAddress ((const GLubyte *) "glXSwapIntervalMESA"); + if (!ps->glXSwapIntervalMESAProc) { + printf_errf("(): Failed to get MESA_swap_control function."); + return false; + } + ps->glXSwapIntervalMESAProc(1); + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +#ifdef CONFIG_VSYNC_OPENGL +/** + * Wait for next VSync, OpenGL method. + */ +static int +vsync_opengl_wait(session_t *ps) { + unsigned vblank_count = 0; + + ps->glXGetVideoSyncSGI(&vblank_count); + ps->glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count); + // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required? + + return 0; +} + +/** + * Wait for next VSync, OpenGL OML method. + * + * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html + */ +static int +vsync_opengl_oml_wait(session_t *ps) { + int64_t ust = 0, msc = 0, sbc = 0; + + ps->glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc); + ps->glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, + &ust, &msc, &sbc); + + return 0; +} + +static void +vsync_opengl_swc_deinit(session_t *ps) { + // The standard says it doesn't accept 0, but in fact it probably does + if (ps->glx_context && ps->glXSwapIntervalProc) + ps->glXSwapIntervalProc(0); +} + +static void +vsync_opengl_mswc_deinit(session_t *ps) { + if (ps->glx_context && ps->glXSwapIntervalMESAProc) + ps->glXSwapIntervalMESAProc(0); +} +#endif + +/** + * Initialize current VSync method. + */ +bool +vsync_init(session_t *ps) { + if (ps->o.vsync && VSYNC_FUNCS_INIT[ps->o.vsync] + && !VSYNC_FUNCS_INIT[ps->o.vsync](ps)) { + ps->o.vsync = VSYNC_NONE; + return false; + } + else + return true; +} + +/** + * Wait for next VSync. + */ +static void +vsync_wait(session_t *ps) { + if (!ps->o.vsync) + return; + + if (VSYNC_FUNCS_WAIT[ps->o.vsync]) + VSYNC_FUNCS_WAIT[ps->o.vsync](ps); +} + +/** + * Deinitialize current VSync method. + */ +void +vsync_deinit(session_t *ps) { + if (ps->o.vsync && VSYNC_FUNCS_DEINIT[ps->o.vsync]) + VSYNC_FUNCS_DEINIT[ps->o.vsync](ps); + ps->o.vsync = VSYNC_NONE; +} + +/** + * Pregenerate alpha pictures. + */ +static void +init_alpha_picts(session_t *ps) { + int i; + int num = round(1.0 / ps->o.alpha_step) + 1; + + ps->alpha_picts = malloc(sizeof(Picture) * num); + + for (i = 0; i < num; ++i) { + double o = i * ps->o.alpha_step; + if ((1.0 - o) > ps->o.alpha_step) + ps->alpha_picts[i] = solid_picture(ps, false, o, 0, 0, 0); + else + ps->alpha_picts[i] = None; + } +} + +/** + * Initialize double buffer. + */ +static bool +init_dbe(session_t *ps) { + if (!(ps->root_dbe = XdbeAllocateBackBufferName(ps->dpy, + (ps->o.paint_on_overlay ? ps->overlay: ps->root), XdbeCopied))) { + printf_errf("(): Failed to create double buffer. Double buffering " + "cannot work."); + return false; + } + + return true; +} + +/** + * Initialize X composite overlay window. + */ +static void +init_overlay(session_t *ps) { + ps->overlay = XCompositeGetOverlayWindow(ps->dpy, ps->root); + if (ps->overlay) { + // Set window region of the overlay window, code stolen from + // compiz-0.8.8 + XserverRegion region = XFixesCreateRegion(ps->dpy, NULL, 0); + XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeBounding, 0, 0, 0); + XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeInput, 0, 0, region); + XFixesDestroyRegion(ps->dpy, region); + + // Listen to Expose events on the overlay + XSelectInput(ps->dpy, ps->overlay, ExposureMask); + + // Retrieve DamageNotify on root window if we are painting on an + // overlay + // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty); + + // Unmap overlay, firstly. But this typically does not work because + // the window isn't created yet. + // XUnmapWindow(ps->dpy, ps->overlay); + // XFlush(ps->dpy); + } + else { + fprintf(stderr, "Cannot get X Composite overlay window. Falling " + "back to painting on root window.\n"); + ps->o.paint_on_overlay = false; + } +} + +/** + * Query needed X Render / OpenGL filters to check for their existence. + */ +static bool +init_filters(session_t *ps) { + // Blur filter + if (ps->o.blur_background || ps->o.blur_background_frame) { + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + // Query filters + XFilters *pf = XRenderQueryFilters(ps->dpy, get_tgt_window(ps)); + if (pf) { + for (int i = 0; i < pf->nfilter; ++i) { + // Convolution filter + if (!strcmp(pf->filter[i], XRFILTER_CONVOLUTION)) + ps->xrfilter_convolution_exists = true; + } + } + cxfree(pf); + + // Turn features off if any required filter is not present + if (!ps->xrfilter_convolution_exists) { + printf_errf("(): X Render convolution filter unsupported by your X server. Background blur is not possible."); + return false; + } + break; + } +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + { + if (!glx_init_blur(ps)) + return false; + } +#endif + } + } + + return true; +} + +/** + * Redirect all windows. + */ +static void +redir_start(session_t *ps) { + if (!ps->redirected) { +#ifdef DEBUG_REDIR + print_timestamp(ps); + printf_dbgf("(): Screen redirected.\n"); +#endif + + // Map overlay window. Done firstly according to this: + // https://bugzilla.gnome.org/show_bug.cgi?id=597014 + if (ps->overlay) + XMapWindow(ps->dpy, ps->overlay); + + XCompositeRedirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual); + + /* + // Unredirect GL context window as this may have an effect on VSync: + // < http://dri.freedesktop.org/wiki/CompositeSwap > + XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual); + if (ps->o.paint_on_overlay && ps->overlay) { + XCompositeUnredirectWindow(ps->dpy, ps->overlay, + CompositeRedirectManual); + } */ + + // Must call XSync() here + XSync(ps->dpy, False); + + ps->redirected = true; + + // Repaint the whole screen + force_repaint(ps); + } +} + +/** + * Get the poll time. + */ +static time_ms_t +timeout_get_poll_time(session_t *ps) { + const time_ms_t now = get_time_ms(); + time_ms_t wait = TIME_MS_MAX; + + // Traverse throught the timeout linked list + for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = ptmout->next) { + if (ptmout->enabled) { + time_ms_t newrun = timeout_get_newrun(ptmout); + if (newrun <= now) { + wait = 0; + break; + } + else { + time_ms_t newwait = newrun - now; + if (newwait < wait) + wait = newwait; + } + } + } + + return wait; +} + +/** + * Insert a new timeout. + */ +timeout_t * +timeout_insert(session_t *ps, time_ms_t interval, + bool (*callback)(session_t *ps, timeout_t *ptmout), void *data) { + const static timeout_t tmout_def = { + .enabled = true, + .data = NULL, + .callback = NULL, + .firstrun = 0L, + .lastrun = 0L, + .interval = 0L, + }; + + const time_ms_t now = get_time_ms(); + timeout_t *ptmout = malloc(sizeof(timeout_t)); + if (!ptmout) + printf_errfq(1, "(): Failed to allocate memory for timeout."); + memcpy(ptmout, &tmout_def, sizeof(timeout_t)); + + ptmout->interval = interval; + ptmout->firstrun = now; + ptmout->lastrun = now; + ptmout->data = data; + ptmout->callback = callback; + ptmout->next = ps->tmout_lst; + ps->tmout_lst = ptmout; + + return ptmout; +} + +/** + * Drop a timeout. + * + * @return true if we have found the timeout and removed it, false + * otherwise + */ +bool +timeout_drop(session_t *ps, timeout_t *prm) { + timeout_t **pplast = &ps->tmout_lst; + + for (timeout_t *ptmout = ps->tmout_lst; ptmout; + pplast = &ptmout->next, ptmout = ptmout->next) { + if (prm == ptmout) { + *pplast = ptmout->next; + free(ptmout); + + return true; + } + } + + return false; +} + +/** + * Clear all timeouts. + */ +static void +timeout_clear(session_t *ps) { + timeout_t *ptmout = ps->tmout_lst; + timeout_t *next = NULL; + while (ptmout) { + next = ptmout->next; + free(ptmout); + ptmout = next; + } +} + +/** + * Run timeouts. + * + * @return true if we have ran a timeout, false otherwise + */ +static bool +timeout_run(session_t *ps) { + const time_ms_t now = get_time_ms(); + bool ret = false; + timeout_t *pnext = NULL; + + for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = pnext) { + pnext = ptmout->next; + if (ptmout->enabled) { + const time_ms_t max = now + + (time_ms_t) (ptmout->interval * TIMEOUT_RUN_TOLERANCE); + time_ms_t newrun = timeout_get_newrun(ptmout); + if (newrun <= max) { + ret = true; + timeout_invoke(ps, ptmout); + } + } + } + + return ret; +} + +/** + * Invoke a timeout. + */ +void +timeout_invoke(session_t *ps, timeout_t *ptmout) { + const time_ms_t now = get_time_ms(); + ptmout->lastrun = now; + // Avoid modifying the timeout structure after running timeout, to + // make it possible to remove timeout in callback + if (ptmout->callback) + ptmout->callback(ps, ptmout); +} + +/** + * Reset a timeout to initial state. + */ +void +timeout_reset(session_t *ps, timeout_t *ptmout) { + ptmout->firstrun = ptmout->lastrun = get_time_ms(); +} + +/** + * Unredirect all windows. + */ +static void +redir_stop(session_t *ps) { + if (ps->redirected) { +#ifdef DEBUG_REDIR + print_timestamp(ps); + printf_dbgf("(): Screen unredirected.\n"); +#endif + // Destroy all Pictures as they expire once windows are unredirected + // If we don't destroy them here, looks like the resources are just + // kept inaccessible somehow + for (win *w = ps->list; w; w = w->next) + free_wpaint(ps, w); + + XCompositeUnredirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual); + // Unmap overlay window + if (ps->overlay) + XUnmapWindow(ps->dpy, ps->overlay); + + // Must call XSync() here + XSync(ps->dpy, False); + + ps->redirected = false; + } +} + +/** + * Unredirection timeout callback. + */ +static bool +tmout_unredir_callback(session_t *ps, timeout_t *tmout) { + ps->tmout_unredir_hit = true; + tmout->enabled = false; + + return true; +} + +/** + * Main loop. + */ +static bool +mainloop(session_t *ps) { + // Don't miss timeouts even when we have a LOT of other events! + timeout_run(ps); + + // Process existing events + // Sometimes poll() returns 1 but no events are actually read, + // causing XNextEvent() to block, I have no idea what's wrong, so we + // check for the number of events here. + if (XEventsQueued(ps->dpy, QueuedAfterReading)) { + XEvent ev = { }; + + XNextEvent(ps->dpy, &ev); + ev_handle(ps, &ev); + ps->ev_received = true; + + return true; + } + +#ifdef CONFIG_DBUS + if (ps->o.dbus) { + cdbus_loop(ps); + } +#endif + + if (ps->reset) + return false; + + // Calculate timeout + struct timeval *ptv = NULL; + { + // Consider ev_received firstly + if (ps->ev_received || ps->o.benchmark) { + ptv = malloc(sizeof(struct timeval)); + ptv->tv_sec = 0L; + ptv->tv_usec = 0L; + } + // Then consider fading timeout + else if (!ps->idling) { + ptv = malloc(sizeof(struct timeval)); + *ptv = ms_to_tv(fade_timeout(ps)); + } + + // Software optimization is to be applied on timeouts that require + // immediate painting only + if (ptv && ps->o.sw_opti) + swopti_handle_timeout(ps, ptv); + + // Don't continue looping for 0 timeout + if (ptv && timeval_isempty(ptv)) { + free(ptv); + return false; + } + + // Now consider the waiting time of other timeouts + time_ms_t tmout_ms = timeout_get_poll_time(ps); + if (tmout_ms < TIME_MS_MAX) { + if (!ptv) { + ptv = malloc(sizeof(struct timeval)); + *ptv = ms_to_tv(tmout_ms); + } + else if (timeval_ms_cmp(ptv, tmout_ms) > 0) { + *ptv = ms_to_tv(tmout_ms); + } + } + + // Don't continue looping for 0 timeout + if (ptv && timeval_isempty(ptv)) { + free(ptv); + return false; + } + } + + // Polling + fds_poll(ps, ptv); + free(ptv); + ptv = NULL; + + return true; +} + +static void +cxinerama_upd_scrs(session_t *ps) { +#ifdef CONFIG_XINERAMA + free_xinerama_info(ps); + + if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists) return; + + if (!XineramaIsActive(ps->dpy)) return; + + ps->xinerama_scrs = XineramaQueryScreens(ps->dpy, &ps->xinerama_nscrs); + + // Just in case the shit hits the fan... + if (!ps->xinerama_nscrs) { + cxfree(ps->xinerama_scrs); + ps->xinerama_scrs = NULL; + return; + } + + ps->xinerama_scr_regs = allocchk(malloc(sizeof(XserverRegion *) + * ps->xinerama_nscrs)); + for (int i = 0; i < ps->xinerama_nscrs; ++i) { + const XineramaScreenInfo * const s = &ps->xinerama_scrs[i]; + XRectangle r = { .x = s->x_org, .y = s->y_org, + .width = s->width, .height = s->height }; + ps->xinerama_scr_regs[i] = XFixesCreateRegion(ps->dpy, &r, 1); + } +#endif +} + +/** + * Initialize a session. + * + * @param ps_old old session, from which the function will take the X + * connection, then free it + * @param argc number of commandline arguments + * @param argv commandline arguments + */ +static session_t * +session_init(session_t *ps_old, int argc, char **argv) { + const static session_t s_def = { + .dpy = NULL, + .scr = 0, + .vis = NULL, + .depth = 0, + .root = None, + .root_height = 0, + .root_width = 0, + // .root_damage = None, + .overlay = None, + .root_tile_fill = false, + .root_tile_paint = PAINT_INIT, + .screen_reg = None, + .tgt_picture = None, + .tgt_buffer = PAINT_INIT, + .root_dbe = None, + .reg_win = None, + .o = { + .config_file = NULL, + .display = NULL, + .backend = BKEND_XRENDER, + .glx_no_stencil = false, + .glx_copy_from_front = false, + .mark_wmwin_focused = false, + .mark_ovredir_focused = false, + .fork_after_register = false, + .synchronize = false, + .detect_rounded_corners = false, + .paint_on_overlay = false, + .resize_damage = 0, + .unredir_if_possible = false, + .unredir_if_possible_blacklist = NULL, + .unredir_if_possible_delay = 0, + .redirected_force = UNSET, + .stoppaint_force = UNSET, + .dbus = false, + .benchmark = 0, + .benchmark_wid = None, + .logpath = NULL, + + .refresh_rate = 0, + .sw_opti = false, + .vsync = VSYNC_NONE, + .dbe = false, + .vsync_aggressive = false, + + .wintype_shadow = { false }, + .shadow_red = 0.0, + .shadow_green = 0.0, + .shadow_blue = 0.0, + .shadow_radius = 12, + .shadow_offset_x = -15, + .shadow_offset_y = -15, + .shadow_opacity = .75, + .clear_shadow = false, + .shadow_blacklist = NULL, + .shadow_ignore_shaped = false, + .respect_prop_shadow = false, + .xinerama_shadow_crop = false, + + .wintype_fade = { false }, + .fade_in_step = 0.028 * OPAQUE, + .fade_out_step = 0.03 * OPAQUE, + .fade_delta = 10, + .no_fading_openclose = false, + .no_fading_opacitychange = false, + .fade_blacklist = NULL, + + .wintype_opacity = { 0.0 }, + .inactive_opacity = 0, + .inactive_opacity_override = false, + .active_opacity = 0, + .frame_opacity = 0.0, + .detect_client_opacity = false, + .alpha_step = 0.03, + + .blur_background = false, + .blur_background_frame = false, + .blur_background_fixed = false, + .blur_background_blacklist = NULL, + .blur_kerns = { NULL }, + .inactive_dim = 0.0, + .inactive_dim_fixed = false, + .invert_color_list = NULL, + .opacity_rules = NULL, + + .wintype_focus = { false }, + .use_ewmh_active_win = false, + .focus_blacklist = NULL, + .detect_transient = false, + .detect_client_leader = false, + + .track_focus = false, + .track_wdata = false, + .track_leader = false, + }, + + .pfds_read = NULL, + .pfds_write = NULL, + .pfds_except = NULL, + .nfds_max = 0, + .tmout_lst = NULL, + + .all_damage = None, + .all_damage_last = { None }, + .time_start = { 0, 0 }, + .redirected = false, + .alpha_picts = NULL, + .reg_ignore_expire = false, + .idling = false, + .fade_time = 0L, + .ignore_head = NULL, + .ignore_tail = NULL, + .reset = false, + + .expose_rects = NULL, + .size_expose = 0, + .n_expose = 0, + + .list = NULL, + .active_win = NULL, + .active_leader = None, + + .black_picture = None, + .cshadow_picture = None, + .white_picture = None, + .gaussian_map = NULL, + .cgsize = 0, + .shadow_corner = NULL, + .shadow_top = NULL, + + .refresh_rate = 0, + .refresh_intv = 0UL, + .paint_tm_offset = 0L, + +#ifdef CONFIG_VSYNC_DRM + .drm_fd = -1, +#endif + +#ifdef CONFIG_VSYNC_OPENGL + .glx_context = None, + .glx_has_texture_non_power_of_two = false, + .glXGetVideoSyncSGI = NULL, + .glXWaitVideoSyncSGI = NULL, + .glXGetSyncValuesOML = NULL, + .glXWaitForMscOML = NULL, +#endif + + .xfixes_event = 0, + .xfixes_error = 0, + .damage_event = 0, + .damage_error = 0, + .render_event = 0, + .render_error = 0, + .composite_event = 0, + .composite_error = 0, + .composite_opcode = 0, + .has_name_pixmap = false, + .shape_exists = false, + .shape_event = 0, + .shape_error = 0, + .randr_exists = 0, + .randr_event = 0, + .randr_error = 0, +#ifdef CONFIG_VSYNC_OPENGL + .glx_exists = false, + .glx_event = 0, + .glx_error = 0, +#endif + .dbe_exists = false, + .xrfilter_convolution_exists = false, + + .atom_opacity = None, + .atom_frame_extents = None, + .atom_client = None, + .atom_name = None, + .atom_name_ewmh = None, + .atom_class = None, + .atom_role = None, + .atom_transient = None, + .atom_ewmh_active_win = None, + .atom_compton_shadow = None, + .atom_win_type = None, + .atom_win_type_tde_transparent_to_black = None, + .atom_win_type_tde_transparent_to_desktop = None, + .atoms_wintypes = { 0 }, + .track_atom_lst = NULL, + +#ifdef CONFIG_DBUS + .dbus_conn = NULL, + .dbus_service = NULL, +#endif + }; + + // Allocate a session and copy default values into it + session_t *ps = malloc(sizeof(session_t)); + memcpy(ps, &s_def, sizeof(session_t)); +#ifdef CONFIG_VSYNC_OPENGL_GLSL + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + glx_blur_pass_t *ppass = &ps->glx_blur_passes[i]; + ppass->unifm_factor_center = -1; + ppass->unifm_offset_x = -1; + ppass->unifm_offset_y = -1; + } +#endif + ps_g = ps; + ps->ignore_tail = &ps->ignore_head; + gettimeofday(&ps->time_start, NULL); + + wintype_arr_enable(ps->o.wintype_focus); + ps->o.wintype_focus[WINTYPE_UNKNOWN] = false; + ps->o.wintype_focus[WINTYPE_NORMAL] = false; + ps->o.wintype_focus[WINTYPE_UTILITY] = false; + + // First pass + get_cfg(ps, argc, argv, true); + + // Inherit old Display if possible, primarily for resource leak checking + if (ps_old && ps_old->dpy) + ps->dpy = ps_old->dpy; + + // Open Display + if (!ps->dpy) { + ps->dpy = XOpenDisplay(ps->o.display); + if (!ps->dpy) { + printf_errfq(1, "(): Can't open display."); + } + } + + XSetErrorHandler(xerror); + if (ps->o.synchronize) { + XSynchronize(ps->dpy, 1); + } + + ps->scr = DefaultScreen(ps->dpy); + ps->root = RootWindow(ps->dpy, ps->scr); + + ps->vis = DefaultVisual(ps->dpy, ps->scr); + ps->depth = DefaultDepth(ps->dpy, ps->scr); + + // Start listening to events on root earlier to catch all possible + // root geometry changes + XSelectInput(ps->dpy, ps->root, + SubstructureNotifyMask + | ExposureMask + | StructureNotifyMask + | PropertyChangeMask); + XFlush(ps->dpy); + + ps->root_width = DisplayWidth(ps->dpy, ps->scr); + ps->root_height = DisplayHeight(ps->dpy, ps->scr); + + if (!XRenderQueryExtension(ps->dpy, + &ps->render_event, &ps->render_error)) { + fprintf(stderr, "No render extension\n"); + exit(1); + } + + if (!XQueryExtension(ps->dpy, COMPOSITE_NAME, &ps->composite_opcode, + &ps->composite_event, &ps->composite_error)) { + fprintf(stderr, "No composite extension\n"); + exit(1); + } + + { + int composite_major = 0, composite_minor = 0; + + XCompositeQueryVersion(ps->dpy, &composite_major, &composite_minor); + + if (composite_major > 0 || composite_minor >= 2) { + ps->has_name_pixmap = true; + } + } + + if (!XDamageQueryExtension(ps->dpy, &ps->damage_event, &ps->damage_error)) { + fprintf(stderr, "No damage extension\n"); + exit(1); + } + + if (!XFixesQueryExtension(ps->dpy, &ps->xfixes_event, &ps->xfixes_error)) { + fprintf(stderr, "No XFixes extension\n"); + exit(1); + } + + // Build a safe representation of display name + { + char *display_repr = DisplayString(ps->dpy); + if (!display_repr) + display_repr = "unknown"; + display_repr = mstrcpy(display_repr); + + // Convert all special characters in display_repr name to underscore + { + char *pdisp = display_repr; + + while (*pdisp) { + if (!isalnum(*pdisp)) + *pdisp = '_'; + ++pdisp; + } + } + + ps->o.display_repr = display_repr; + } + + // Second pass + get_cfg(ps, argc, argv, false); + + // Query X Shape + if (XShapeQueryExtension(ps->dpy, &ps->shape_event, &ps->shape_error)) { + ps->shape_exists = true; + } + + if (ps->o.xrender_sync_fence) { +#ifdef CONFIG_XSYNC + // Query X Sync + if (XSyncQueryExtension(ps->dpy, &ps->xsync_event, &ps->xsync_error)) { + // TODO: Fencing may require version >= 3.0? + int major_version_return = 0, minor_version_return = 0; + if (XSyncInitialize(ps->dpy, &major_version_return, &minor_version_return)) + ps->xsync_exists = true; + } + if (!ps->xsync_exists) { + printf_errf("(): X Sync extension not found. No X Sync fence sync is " + "possible."); + exit(1); + } +#else + printf_errf("(): X Sync support not compiled in. --xrender-sync-fence " + "can't work."); + exit(1); +#endif + } + + // Query X RandR + if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) { + if (XRRQueryExtension(ps->dpy, &ps->randr_event, &ps->randr_error)) + ps->randr_exists = true; + else + printf_errf("(): No XRandR extension, automatic screen change " + "detection impossible."); + } + + // Query X DBE extension + if (ps->o.dbe) { + int dbe_ver_major = 0, dbe_ver_minor = 0; + if (XdbeQueryExtension(ps->dpy, &dbe_ver_major, &dbe_ver_minor)) + if (dbe_ver_major >= 1) + ps->dbe_exists = true; + else + fprintf(stderr, "DBE extension version too low. Double buffering " + "impossible.\n"); + else { + fprintf(stderr, "No DBE extension. Double buffering impossible.\n"); + } + if (!ps->dbe_exists) + ps->o.dbe = false; + } + + // Query X Xinerama extension + if (ps->o.xinerama_shadow_crop) { +#ifdef CONFIG_XINERAMA + int xinerama_event = 0, xinerama_error = 0; + if (XineramaQueryExtension(ps->dpy, &xinerama_event, &xinerama_error)) + ps->xinerama_exists = true; +#else + printf_errf("(): Xinerama support not compiled in."); +#endif + } + + rebuild_screen_reg(ps); + + // Overlay must be initialized before double buffer, and before creation + // of OpenGL context. + if (ps->o.paint_on_overlay) + init_overlay(ps); + + // Initialize DBE + if (ps->o.dbe && BKEND_XRENDER != ps->o.backend) { + printf_errf("(): DBE couldn't be used on GLX backend."); + ps->o.dbe = false; + } + + if (ps->o.dbe && !init_dbe(ps)) + exit(1); + + // Initialize OpenGL as early as possible + if (bkend_use_glx(ps)) { +#ifdef CONFIG_VSYNC_OPENGL + if (!glx_init(ps, true)) + exit(1); +#else + printf_errfq(1, "(): GLX backend support not compiled in."); +#endif + } + + // Initialize software optimization + if (ps->o.sw_opti) + ps->o.sw_opti = swopti_init(ps); + + // Monitor screen changes if vsync_sw is enabled and we are using + // an auto-detected refresh rate, or when Xinerama features are enabled + if (ps->randr_exists && ((ps->o.sw_opti && !ps->o.refresh_rate) + || ps->o.xinerama_shadow_crop)) + XRRSelectInput(ps->dpy, ps->root, RRScreenChangeNotifyMask); + + // Initialize VSync + if (!vsync_init(ps)) + exit(1); + + cxinerama_upd_scrs(ps); + + fprintf(stderr, "Started\n"); + + // Create registration window + if (!ps->reg_win && !register_cm(ps)) + exit(1); + + init_atoms(ps); + init_alpha_picts(ps); + + ps->gaussian_map = make_gaussian_map(ps->o.shadow_radius); + presum_gaussian(ps, ps->gaussian_map); + + { + XRenderPictureAttributes pa; + pa.subwindow_mode = IncludeInferiors; + + ps->root_picture = XRenderCreatePicture(ps->dpy, ps->root, + XRenderFindVisualFormat(ps->dpy, ps->vis), + CPSubwindowMode, &pa); + if (ps->o.paint_on_overlay) { + ps->tgt_picture = XRenderCreatePicture(ps->dpy, ps->overlay, + XRenderFindVisualFormat(ps->dpy, ps->vis), + CPSubwindowMode, &pa); + } + else { + ps->tgt_picture = ps->root_picture; + } + } + + // Initialize filters, must be preceded by OpenGL context creation + if (!init_filters(ps)) + exit(1); + + ps->black_picture = solid_picture(ps, true, 1, 0, 0, 0); + ps->white_picture = solid_picture(ps, true, 1, 1, 1, 1); + + // Generates another Picture for shadows if the color is modified by + // user + if (!ps->o.shadow_red && !ps->o.shadow_green && !ps->o.shadow_blue) { + ps->cshadow_picture = ps->black_picture; + } else { + ps->cshadow_picture = solid_picture(ps, true, 1, + ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue); + } + + fds_insert(ps, ConnectionNumber(ps->dpy), POLLIN); + ps->tmout_unredir = timeout_insert(ps, ps->o.unredir_if_possible_delay, + tmout_unredir_callback, NULL); + ps->tmout_unredir->enabled = false; + + XGrabServer(ps->dpy); + + { + Window root_return, parent_return; + Window *children; + unsigned int nchildren; + + XQueryTree(ps->dpy, ps->root, &root_return, + &parent_return, &children, &nchildren); + + for (unsigned i = 0; i < nchildren; i++) { + add_win(ps, children[i], i ? children[i-1] : None); + } + + cxfree(children); + } + + + if (ps->o.track_focus) { + recheck_focus(ps); + } + + XUngrabServer(ps->dpy); + + // Initialize DBus + if (ps->o.dbus) { +#ifdef CONFIG_DBUS + cdbus_init(ps); + if (!ps->dbus_conn) { + cdbus_destroy(ps); + ps->o.dbus = false; + } +#else + printf_errfq(1, "(): DBus support not compiled in!"); +#endif + } + + // Fork to background, if asked + if (ps->o.fork_after_register) { + if (!fork_after(ps)) { + session_destroy(ps); + return NULL; + } + } + + write_pid(ps); + + // Free the old session + if (ps_old) + free(ps_old); + + return ps; +} + +/** + * Destroy a session. + * + * Does not close the X connection or free the <code>session_t</code> + * structure, though. + * + * @param ps session to destroy + */ +static void +session_destroy(session_t *ps) { + redir_stop(ps); + + // Stop listening to events on root window + XSelectInput(ps->dpy, ps->root, 0); + +#ifdef CONFIG_DBUS + // Kill DBus connection + if (ps->o.dbus) + cdbus_destroy(ps); + + free(ps->dbus_service); +#endif + + // Free window linked list + { + win *next = NULL; + for (win *w = ps->list; w; w = next) { + // Must be put here to avoid segfault + next = w->next; + + if (IsViewable == w->a.map_state && !w->destroyed) + win_ev_stop(ps, w); + + free_win_res(ps, w); + free(w); + } + + ps->list = NULL; + } + + // Free alpha_picts + { + const int max = round(1.0 / ps->o.alpha_step) + 1; + for (int i = 0; i < max; ++i) + free_picture(ps, &ps->alpha_picts[i]); + free(ps->alpha_picts); + 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); + free_wincondlst(&ps->o.blur_background_blacklist); + free_wincondlst(&ps->o.opacity_rules); + free_wincondlst(&ps->o.paint_blacklist); + free_wincondlst(&ps->o.unredir_if_possible_blacklist); +#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 + { + ignore_t *next = NULL; + for (ignore_t *ign = ps->ignore_head; ign; ign = next) { + next = ign->next; + + free(ign); + } + + // Reset head and tail + ps->ignore_head = NULL; + ps->ignore_tail = &ps->ignore_head; + } + + // Free cshadow_picture and black_picture + if (ps->cshadow_picture == ps->black_picture) + ps->cshadow_picture = None; + else + free_picture(ps, &ps->cshadow_picture); + + free_picture(ps, &ps->black_picture); + free_picture(ps, &ps->white_picture); + + // Free tgt_{buffer,picture} and root_picture + if (ps->tgt_buffer.pict == ps->tgt_picture) + ps->tgt_buffer.pict = None; + + if (ps->tgt_picture == ps->root_picture) + ps->tgt_picture = None; + else + free_picture(ps, &ps->tgt_picture); + free_fence(ps, &ps->tgt_buffer_fence); + + free_picture(ps, &ps->root_picture); + free_paint(ps, &ps->tgt_buffer); + + // Free other X resources + free_root_tile(ps); + free_region(ps, &ps->screen_reg); + free_region(ps, &ps->all_damage); + for (int i = 0; i < CGLX_MAX_BUFFER_AGE; ++i) + free_region(ps, &ps->all_damage_last[i]); + free(ps->expose_rects); + free(ps->shadow_corner); + free(ps->shadow_top); + free(ps->gaussian_map); + + free(ps->o.config_file); + free(ps->o.write_pid_path); + free(ps->o.display); + free(ps->o.display_repr); + free(ps->o.logpath); + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + free(ps->o.blur_kerns[i]); + free(ps->blur_kerns_cache[i]); + } + free(ps->pfds_read); + free(ps->pfds_write); + free(ps->pfds_except); + free_xinerama_info(ps); + +#ifdef CONFIG_VSYNC_OPENGL + glx_destroy(ps); +#endif + + // Free double buffer + if (ps->root_dbe) { + XdbeDeallocateBackBufferName(ps->dpy, ps->root_dbe); + ps->root_dbe = None; + } + +#ifdef CONFIG_VSYNC_DRM + // Close file opened for DRM VSync + if (ps->drm_fd >= 0) { + close(ps->drm_fd); + ps->drm_fd = -1; + } +#endif + + // Release overlay window + if (ps->overlay) { + XCompositeReleaseOverlayWindow(ps->dpy, ps->overlay); + ps->overlay = None; + } + + // Free reg_win + if (ps->reg_win) { + XDestroyWindow(ps->dpy, ps->reg_win); + ps->reg_win = None; + } + + // Flush all events + XSync(ps->dpy, True); + + // Free timeouts + ps->tmout_unredir = NULL; + timeout_clear(ps); + + if (ps == ps_g) + ps_g = NULL; +} + +/** + * Do the actual work. + * + * @param ps current session + */ +static void +session_run(session_t *ps) { + win *t; + + if (ps->o.sw_opti) + ps->paint_tm_offset = get_time_timeval().tv_usec; + + ps->reg_ignore_expire = true; + + t = paint_preprocess(ps, ps->list); + + if (ps->redirected) + paint_all(ps, None, None, t); + + // Initialize idling + ps->idling = false; + + // Main loop + while (!ps->reset) { + ps->ev_received = false; + + while (mainloop(ps)) + continue; + + if (ps->o.benchmark) { + if (ps->o.benchmark_wid) { + win *w = find_win(ps, ps->o.benchmark_wid); + if (!w) { + printf_errf("(): Couldn't find specified benchmark window."); + session_destroy(ps); + exit(1); + } + add_damage_win(ps, w); + } + else { + force_repaint(ps); + } + } + + // idling will be turned off during paint_preprocess() if needed + ps->idling = true; + + t = paint_preprocess(ps, ps->list); + ps->tmout_unredir_hit = false; + + // If the screen is unredirected, free all_damage to stop painting + if (!ps->redirected || ON == ps->o.stoppaint_force) + free_region(ps, &ps->all_damage); + + XserverRegion all_damage_orig = None; + if (ps->o.resize_damage > 0) + all_damage_orig = copy_region(ps, ps->all_damage); + resize_region(ps, ps->all_damage, ps->o.resize_damage); + if (ps->all_damage && !is_region_empty(ps, ps->all_damage, NULL)) { + static int paint = 0; + paint_all(ps, ps->all_damage, all_damage_orig, t); + ps->reg_ignore_expire = false; + paint++; + if (ps->o.benchmark && paint >= ps->o.benchmark) + exit(0); + XSync(ps->dpy, False); + ps->all_damage = None; + } + free_region(ps, &all_damage_orig); + + if (ps->idling) + ps->fade_time = 0L; + } +} + +/** + * Turn on the program reset flag. + * + * This will result in compton resetting itself after next paint. + */ +static void +reset_enable(int __attribute__((unused)) signum) { + session_t * const ps = ps_g; + + ps->reset = true; +} + +/** + * The function that everybody knows. + */ +int +main(int argc, char **argv) { + // Set locale so window names with special characters are interpreted + // correctly + setlocale(LC_ALL, ""); + + // Initialize signal handlers + sigfillset(&block_mask); + usr_action.sa_handler = handle_siguser; + usr_action.sa_mask = block_mask; + usr_action.sa_flags = 0; + sigaction(SIGUSR1, &usr_action, NULL); + sigaction(SIGUSR2, &usr_action, NULL); + sigaction(SIGTERM, &usr_action, NULL); + + // Main loop + session_t *ps_old = ps_g; + while (1) { + ps_g = session_init(ps_old, argc, argv); + if (!ps_g) { + printf_errf("(): Failed to create new session."); + return 1; + } + + /* Under no circumstances should these two lines EVER be moved earlier in main() than this point */ + atexit(delete_pid_file); + write_pid_file(getpid()); + + session_run(ps_g); + ps_old = ps_g; + session_destroy(ps_g); + } + + free(ps_g); + + return 0; +} diff --git a/twin/compton-tde/compton.h b/twin/compton-tde/compton.h new file mode 100644 index 000000000..dfdbe53d3 --- /dev/null +++ b/twin/compton-tde/compton.h @@ -0,0 +1,1321 @@ +/** + * compton.h + */ + +// Throw everything in here. + + +// === Includes === + +#include "common.h" + +#include <math.h> +#include <sys/select.h> +#include <limits.h> +#include <unistd.h> +#include <getopt.h> +#include <locale.h> +#include <signal.h> + +#include <sys/types.h> +#include <pwd.h> + +#ifdef CONFIG_VSYNC_DRM +#include <fcntl.h> +// We references some definitions in drm.h, which could also be found in +// /usr/src/linux/include/drm/drm.h, but that path is probably even less +// reliable than libdrm +#include <drm.h> +#include <sys/ioctl.h> +#include <errno.h> +#endif + +// == Functions == + +// inline functions must be made static to compile correctly under clang: +// http://clang.llvm.org/compatibility.html#inline + +// Helper functions + +static void +discard_ignore(session_t *ps, unsigned long sequence); + +static void +set_ignore(session_t *ps, unsigned long sequence); + +/** + * Ignore X errors caused by next X request. + */ +static inline void +set_ignore_next(session_t *ps) { + set_ignore(ps, NextRequest(ps->dpy)); +} + +static int +should_ignore(session_t *ps, unsigned long sequence); + +/** + * Reset filter on a <code>Picture</code>. + */ +static inline void +xrfilter_reset(session_t *ps, Picture p) { + XRenderSetPictureFilter(ps->dpy, p, "Nearest", NULL, 0); +} + +/** + * Subtract two unsigned long values. + * + * Truncate to 0 if the result is negative. + */ +static inline unsigned long __attribute__((const)) +sub_unslong(unsigned long a, unsigned long b) { + return (a > b) ? a - b : 0; +} + +/** + * Set a <code>bool</code> array of all wintypes to true. + */ +static inline void +wintype_arr_enable(bool arr[]) { + wintype_t i; + + for (i = 0; i < NUM_WINTYPES; ++i) { + arr[i] = true; + } +} + +/** + * Set a <code>switch_t</code> array of all unset wintypes to true. + */ +static inline void +wintype_arr_enable_unset(switch_t arr[]) { + wintype_t i; + + for (i = 0; i < NUM_WINTYPES; ++i) + if (UNSET == arr[i]) + arr[i] = ON; +} + +/** + * Check if a window ID exists in an array of window IDs. + * + * @param arr the array of window IDs + * @param count amount of elements in the array + * @param wid window ID to search for + */ +static inline bool +array_wid_exists(const Window *arr, int count, Window wid) { + while (count--) { + if (arr[count] == wid) { + return true; + } + } + + return false; +} + +/** + * Convert a geometry_t value to XRectangle. + */ +static inline XRectangle +geom_to_rect(session_t *ps, const geometry_t *src, const XRectangle *def) { + XRectangle rect_def = { .x = 0, .y = 0, + .width = ps->root_width, .height = ps->root_height }; + if (!def) def = &rect_def; + + XRectangle rect = { .x = src->x, .y = src->y, + .width = src->wid, .height = src->hei }; + if (src->wid < 0) rect.width = def->width; + if (src->hei < 0) rect.height = def->height; + if (-1 == src->x) rect.x = def->x; + else if (src->x < 0) rect.x = ps->root_width + rect.x + 2 - rect.width; + if (-1 == src->y) rect.y = def->y; + else if (src->y < 0) rect.y = ps->root_height + rect.y + 2 - rect.height; + return rect; +} + +/** + * Convert a XRectangle to a XServerRegion. + */ +static inline XserverRegion +rect_to_reg(session_t *ps, const XRectangle *src) { + if (!src) return None; + XRectangle bound = { .x = 0, .y = 0, + .width = ps->root_width, .height = ps->root_height }; + XRectangle res = { }; + rect_crop(&res, src, &bound); + if (res.width && res.height) + return XFixesCreateRegion(ps->dpy, &res, 1); + return None; +} + +/** + * Destroy a <code>Picture</code>. + */ +inline static void +free_picture(session_t *ps, Picture *p) { + if (*p) { + XRenderFreePicture(ps->dpy, *p); + *p = None; + } +} + +/** + * Destroy a <code>Pixmap</code>. + */ +inline static void +free_pixmap(session_t *ps, Pixmap *p) { + if (*p) { + XFreePixmap(ps->dpy, *p); + *p = None; + } +} + +/** + * Destroy a <code>Damage</code>. + */ +inline static void +free_damage(session_t *ps, Damage *p) { + if (*p) { + // BadDamage will be thrown if the window is destroyed + set_ignore_next(ps); + XDamageDestroy(ps->dpy, *p); + *p = None; + } +} + +/** + * Destroy a condition list. + */ +static inline void +free_wincondlst(c2_lptr_t **pcondlst) { +#ifdef CONFIG_C2 + while ((*pcondlst = c2_free_lptr(*pcondlst))) + continue; +#endif +} + +/** + * Free Xinerama screen info. + */ +static inline void +free_xinerama_info(session_t *ps) { +#ifdef CONFIG_XINERAMA + if (ps->xinerama_scr_regs) { + for (int i = 0; i < ps->xinerama_nscrs; ++i) + free_region(ps, &ps->xinerama_scr_regs[i]); + free(ps->xinerama_scr_regs); + } + cxfree(ps->xinerama_scrs); + ps->xinerama_scrs = NULL; + ps->xinerama_nscrs = 0; +#endif +} + +/** + * Check whether a paint_t contains enough data. + */ +static inline bool +paint_isvalid(session_t *ps, const paint_t *ppaint) { + // Don't check for presence of Pixmap here, because older X Composite doesn't + // provide it + if (!ppaint) + return false; + + if (bkend_use_xrender(ps) && !ppaint->pict) + return false; + +#ifdef CONFIG_VSYNC_OPENGL + if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, None)) + return false; +#endif + + return true; +} + +/** + * Bind texture in paint_t if we are using GLX backend. + */ +static inline bool +paint_bind_tex_real(session_t *ps, paint_t *ppaint, + unsigned wid, unsigned hei, unsigned depth, bool force) { +#ifdef CONFIG_VSYNC_OPENGL + if (!ppaint->pixmap) + return false; + + if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) + return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, depth); +#endif + + return true; +} + +static inline bool +paint_bind_tex(session_t *ps, paint_t *ppaint, + unsigned wid, unsigned hei, unsigned depth, bool force) { + if (BKEND_GLX == ps->o.backend) + return paint_bind_tex_real(ps, ppaint, wid, hei, depth, force); + return true; +} + +/** + * Free data in a reg_data_t. + */ +static inline void +free_reg_data(reg_data_t *pregd) { + cxfree(pregd->rects); + pregd->rects = NULL; + pregd->nrects = 0; +} + +/** + * Free paint_t. + */ +static inline void +free_paint(session_t *ps, paint_t *ppaint) { + free_texture(ps, &ppaint->ptex); + free_picture(ps, &ppaint->pict); + free_pixmap(ps, &ppaint->pixmap); +} + +/** + * Free w->paint. + */ +static inline void +free_wpaint(session_t *ps, win *w) { + free_paint(ps, &w->paint); + free_fence(ps, &w->fence); +} + +/** + * Destroy all resources in a <code>struct _win</code>. + */ +static inline void +free_win_res(session_t *ps, win *w) { + free_region(ps, &w->extents); + free_paint(ps, &w->paint); + free_region(ps, &w->border_size); + free_paint(ps, &w->shadow_paint); + free_damage(ps, &w->damage); + free_region(ps, &w->reg_ignore); + free(w->name); + free(w->class_instance); + free(w->class_general); + free(w->role); +#ifdef CONFIG_VSYNC_OPENGL_GLSL + free_glx_bc(ps, &w->glx_blur_cache); +#endif +} + +/** + * Free root tile related things. + */ +static inline void +free_root_tile(session_t *ps) { + free_picture(ps, &ps->root_tile_paint.pict); + free_texture(ps, &ps->root_tile_paint.ptex); + if (ps->root_tile_fill) + free_pixmap(ps, &ps->root_tile_paint.pixmap); + ps->root_tile_paint.pixmap = None; + ps->root_tile_fill = false; +} + +/** + * Get current system clock in milliseconds. + */ +static inline time_ms_t +get_time_ms(void) { + struct timeval tv; + + gettimeofday(&tv, NULL); + + return tv.tv_sec % SEC_WRAP * 1000 + tv.tv_usec / 1000; +} + +/** + * Convert time from milliseconds to struct timeval. + */ +static inline struct timeval +ms_to_tv(int timeout) { + return (struct timeval) { + .tv_sec = timeout / MS_PER_SEC, + .tv_usec = timeout % MS_PER_SEC * (US_PER_SEC / MS_PER_SEC) + }; +} + +/** + * Whether an event is DamageNotify. + */ +static inline bool +isdamagenotify(session_t *ps, const XEvent *ev) { + return ps->damage_event + XDamageNotify == ev->type; +} + +/** + * Create a XTextProperty of a single string. + */ +static inline XTextProperty * +make_text_prop(session_t *ps, char *str) { + XTextProperty *pprop = cmalloc(1, XTextProperty); + + if (XmbTextListToTextProperty(ps->dpy, &str, 1, XStringStyle, pprop)) { + cxfree(pprop->value); + free(pprop); + pprop = NULL; + } + + return pprop; +} + + +/** + * Set a single-string text property on a window. + */ +static inline bool +wid_set_text_prop(session_t *ps, Window wid, Atom prop_atom, char *str) { + XTextProperty *pprop = make_text_prop(ps, str); + if (!pprop) { + printf_errf("(\"%s\"): Failed to make text property.", str); + return false; + } + + XSetTextProperty(ps->dpy, wid, pprop, prop_atom); + cxfree(pprop->value); + cxfree(pprop); + + return true; +} + +static void +run_fade(session_t *ps, win *w, unsigned steps); + +static void +set_fade_callback(session_t *ps, win *w, + void (*callback) (session_t *ps, win *w), bool exec_callback); + +/** + * Execute fade callback of a window if fading finished. + */ +static inline void +check_fade_fin(session_t *ps, win *w) { + if (w->fade_callback && w->opacity == w->opacity_tgt) { + // Must be the last line as the callback could destroy w! + set_fade_callback(ps, w, NULL, true); + } +} + +static void +set_fade_callback(session_t *ps, win *w, + void (*callback) (session_t *ps, win *w), bool exec_callback); + +static double +gaussian(double r, double x, double y); + +static conv * +make_gaussian_map(double r); + +static unsigned char +sum_gaussian(conv *map, double opacity, + int x, int y, int width, int height); + +static void +presum_gaussian(session_t *ps, conv *map); + +static XImage * +make_shadow(session_t *ps, double opacity, int width, int height); + +static bool +win_build_shadow(session_t *ps, win *w, double opacity); + +static Picture +solid_picture(session_t *ps, bool argb, double a, + double r, double g, double b); + +/** + * Stop listening for events on a particular window. + */ +static inline void +win_ev_stop(session_t *ps, win *w) { + // Will get BadWindow if the window is destroyed + set_ignore_next(ps); + XSelectInput(ps->dpy, w->id, 0); + + if (w->client_win) { + set_ignore_next(ps); + XSelectInput(ps->dpy, w->client_win, 0); + } + + if (ps->shape_exists) { + set_ignore_next(ps); + XShapeSelectInput(ps->dpy, w->id, 0); + } +} + +/** + * Get the children of a window. + * + * @param ps current session + * @param w window to check + * @param children [out] an array of child window IDs + * @param nchildren [out] number of children + * @return 1 if successful, 0 otherwise + */ +static inline bool +wid_get_children(session_t *ps, Window w, + Window **children, unsigned *nchildren) { + Window troot, tparent; + + if (!XQueryTree(ps->dpy, w, &troot, &tparent, children, nchildren)) { + *nchildren = 0; + return false; + } + + return true; +} + +/** + * Check if a window is bounding-shaped. + */ +static inline bool +wid_bounding_shaped(const session_t *ps, Window wid) { + if (ps->shape_exists) { + Bool bounding_shaped = False, clip_shaped = False; + int x_bounding, y_bounding, x_clip, y_clip; + unsigned int w_bounding, h_bounding, w_clip, h_clip; + + XShapeQueryExtents(ps->dpy, wid, &bounding_shaped, + &x_bounding, &y_bounding, &w_bounding, &h_bounding, + &clip_shaped, &x_clip, &y_clip, &w_clip, &h_clip); + return bounding_shaped; + } + + return false; +} + +/** + * Determine if a window change affects <code>reg_ignore</code> and set + * <code>reg_ignore_expire</code> accordingly. + */ +static inline void +update_reg_ignore_expire(session_t *ps, const win *w) { + if (w->to_paint && WMODE_SOLID == w->mode) + ps->reg_ignore_expire = true; +} + +/** + * Check whether a window has WM frames. + */ +static inline bool __attribute__((const)) +win_has_frame(const win *w) { + return w->a.border_width + || w->top_width || w->left_width || w->right_width || w->bottom_width; +} + +static inline void +wid_set_opacity_prop(session_t *ps, Window wid, opacity_t val) { + const unsigned long v = val; + XChangeProperty(ps->dpy, wid, ps->atom_opacity, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) &v, 1); +} + +static inline void +wid_rm_opacity_prop(session_t *ps, Window wid) { + XDeleteProperty(ps->dpy, wid, ps->atom_opacity); +} + +/** + * Dump an drawable's info. + */ +static inline void +dump_drawable(session_t *ps, Drawable drawable) { + Window rroot = None; + int x = 0, y = 0; + unsigned width = 0, height = 0, border = 0, depth = 0; + if (XGetGeometry(ps->dpy, drawable, &rroot, &x, &y, &width, &height, + &border, &depth)) { + printf_dbgf("(%#010lx): x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u\n", drawable, x, y, width, height, border, depth); + } + else { + printf_dbgf("(%#010lx): Failed\n", drawable); + } +} + +static void +win_rounded_corners(session_t *ps, win *w); + +/** + * Validate a pixmap. + * + * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there + * are better ways. + */ +static inline bool +validate_pixmap(session_t *ps, Pixmap pxmap) { + if (!pxmap) return false; + + Window rroot = None; + int rx = 0, ry = 0; + unsigned rwid = 0, rhei = 0, rborder = 0, rdepth = 0; + return XGetGeometry(ps->dpy, pxmap, &rroot, &rx, &ry, + &rwid, &rhei, &rborder, &rdepth) && rwid && rhei; +} + +/** + * Validate pixmap of a window, and destroy pixmap and picture if invalid. + */ +static inline void +win_validate_pixmap(session_t *ps, win *w) { + // Destroy pixmap and picture, if invalid + if (!validate_pixmap(ps, w->paint.pixmap)) + free_paint(ps, &w->paint); +} + +/** + * 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(session_t *ps, c2_lptr_t **pcondlst, const char *pattern); + +static long +determine_evmask(session_t *ps, Window wid, win_evmode_t mode); + +/** + * Clear leader cache of all windows. + */ +static void +clear_cache_win_leaders(session_t *ps) { + for (win *w = ps->list; w; w = w->next) + w->cache_leader = None; +} + +static win * +find_toplevel2(session_t *ps, Window wid); + +/** + * Find matched window. + */ +static inline win * +find_win_all(session_t *ps, const Window wid) { + if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) + return NULL; + + win *w = find_win(ps, wid); + if (!w) w = find_toplevel(ps, wid); + if (!w) w = find_toplevel2(ps, wid); + return w; +} + +static Window +win_get_leader_raw(session_t *ps, win *w, int recursions); + +/** + * Get the leader of a window. + * + * This function updates w->cache_leader if necessary. + */ +static inline Window +win_get_leader(session_t *ps, win *w) { + return win_get_leader_raw(ps, w, 0); +} + +/** + * Return whether a window group is really focused. + * + * @param leader leader window ID + * @return true if the window group is focused, false otherwise + */ +static inline bool +group_is_focused(session_t *ps, Window leader) { + if (!leader) + return false; + + for (win *w = ps->list; w; w = w->next) { + if (win_get_leader(ps, w) == leader && !w->destroyed + && win_is_focused_real(ps, w)) + return true; + } + + return false; +} + +static win * +recheck_focus(session_t *ps); + +static bool +get_root_tile(session_t *ps); + +static void +paint_root(session_t *ps, XserverRegion reg_paint); + +static XserverRegion +win_get_region(session_t *ps, win *w, bool use_offset); + +static XserverRegion +win_get_region_noframe(session_t *ps, win *w, bool use_offset); + +static XserverRegion +win_extents(session_t *ps, win *w); + +static XserverRegion +border_size(session_t *ps, win *w, bool use_offset); + +static Window +find_client_win(session_t *ps, Window w); + +static void +get_frame_extents(session_t *ps, win *w, Window client); + +static win * +paint_preprocess(session_t *ps, win *list); + +static void +render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, + double opacity, bool argb, bool neg, + Picture pict, glx_texture_t *ptex, + XserverRegion reg_paint, const reg_data_t *pcache_reg); + +static inline void +win_render(session_t *ps, win *w, int x, int y, int wid, int hei, double opacity, XserverRegion reg_paint, const reg_data_t *pcache_reg, Picture pict) { + const int dx = (w ? w->a.x: 0) + x; + const int dy = (w ? w->a.y: 0) + y; + const bool argb = (w && w->mode == WMODE_ARGB); + const bool neg = (w && w->invert_color); + + render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, + pict, (w ? w->paint.ptex: ps->root_tile_paint.ptex), reg_paint, pcache_reg); +} + +static inline void +set_tgt_clip(session_t *ps, XserverRegion reg, const reg_data_t *pcache_reg) { + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer.pict, 0, 0, reg); + break; +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + glx_set_clip(ps, reg, pcache_reg); + break; +#endif + } +} + +static bool +xr_blur_dst(session_t *ps, Picture tgt_buffer, + int x, int y, int wid, int hei, XFixed **blur_kerns, + XserverRegion reg_clip); + +/** + * Normalize a convolution kernel. + */ +static inline void +normalize_conv_kern(int wid, int hei, XFixed *kern) { + double sum = 0.0; + for (int i = 0; i < wid * hei; ++i) + sum += XFixedToDouble(kern[i]); + double factor = 1.0 / sum; + for (int i = 0; i < wid * hei; ++i) + kern[i] = XDoubleToFixed(XFixedToDouble(kern[i]) * factor); +} + +static void +paint_all(session_t *ps, XserverRegion region, XserverRegion region_real, win *t); + +static void +add_damage(session_t *ps, XserverRegion damage); + +static void +repair_win(session_t *ps, win *w); + +static wintype_t +wid_get_prop_wintype(session_t *ps, Window w); + +static void +map_win(session_t *ps, Window id); + +static void +finish_map_win(session_t *ps, win *w); + +static void +finish_unmap_win(session_t *ps, win *w); + +static void +unmap_callback(session_t *ps, win *w); + +static void +unmap_win(session_t *ps, win *w); + +static opacity_t +wid_get_opacity_prop(session_t *ps, Window wid, opacity_t def); + +static bool +init_filters(session_t *ps); + +/** + * Reread opacity property of a window. + */ +static inline void +win_update_opacity_prop(session_t *ps, win *w) { + w->opacity_prop = wid_get_opacity_prop(ps, w->id, OPAQUE); + if (!ps->o.detect_client_opacity || !w->client_win + || w->id == w->client_win) + w->opacity_prop_client = OPAQUE; + else + w->opacity_prop_client = wid_get_opacity_prop(ps, w->client_win, + OPAQUE); +} + +static double +get_opacity_percent(win *w); + +static void +win_determine_mode(session_t *ps, win *w); + +static void +calc_opacity(session_t *ps, win *w); + +static void +calc_dim(session_t *ps, win *w); + +static Window +wid_get_prop_window(session_t *ps, Window wid, Atom aprop); + +static void +win_update_leader(session_t *ps, win *w); + +static void +win_set_leader(session_t *ps, win *w, Window leader); + +static void +win_update_focused(session_t *ps, win *w); + +/** + * Run win_update_focused() on all windows with the same leader window. + * + * @param leader leader window ID + */ +static inline void +group_update_focused(session_t *ps, Window leader) { + if (!leader) + return; + + for (win *w = ps->list; w; w = w->next) { + if (win_get_leader(ps, w) == leader && !w->destroyed) + win_update_focused(ps, w); + } + + return; +} + +static inline void +win_set_focused(session_t *ps, win *w, bool focused); + +static void +win_on_focus_change(session_t *ps, win *w); + +static void +win_determine_fade(session_t *ps, win *w); + +static void +win_update_shape_raw(session_t *ps, win *w); + +static void +win_update_shape(session_t *ps, win *w); + +static void +win_update_prop_shadow_raw(session_t *ps, win *w); + +static void +win_update_prop_shadow(session_t *ps, win *w); + +static void +win_determine_shadow(session_t *ps, win *w); + +static void +win_determine_invert_color(session_t *ps, win *w); + +static void +win_determine_blur_background(session_t *ps, win *w); + +static void +win_on_wtype_change(session_t *ps, win *w); + +static void +win_on_factor_change(session_t *ps, win *w); + +static void +win_upd_run(session_t *ps, win *w, win_upd_t *pupd); + +static void +calc_win_size(session_t *ps, win *w); + +static void +calc_shadow_geometry(session_t *ps, win *w); + +static void +win_upd_wintype(session_t *ps, win *w); + +static void +win_mark_client(session_t *ps, win *w, Window client); + +static void +win_unmark_client(session_t *ps, win *w); + +static void +win_recheck_client(session_t *ps, win *w); + +static bool +add_win(session_t *ps, Window id, Window prev); + +static void +restack_win(session_t *ps, win *w, Window new_above); + +static void +configure_win(session_t *ps, XConfigureEvent *ce); + +static void +circulate_win(session_t *ps, XCirculateEvent *ce); + +static void +finish_destroy_win(session_t *ps, Window id); + +static void +destroy_callback(session_t *ps, win *w); + +static void +destroy_win(session_t *ps, Window id); + +static void +damage_win(session_t *ps, XDamageNotifyEvent *de); + +static int +xerror(Display *dpy, XErrorEvent *ev); + +static void +expose_root(session_t *ps, XRectangle *rects, int nrects); + +static Window +wid_get_prop_window(session_t *ps, Window wid, Atom aprop); + +static bool +wid_get_name(session_t *ps, Window w, char **name); + +static bool +wid_get_role(session_t *ps, Window w, char **role); + +static int +win_get_prop_str(session_t *ps, win *w, char **tgt, + bool (*func_wid_get_prop_str)(session_t *ps, Window wid, char **tgt)); + +static inline int +win_get_name(session_t *ps, win *w) { + int ret = win_get_prop_str(ps, w, &w->name, wid_get_name); + +#ifdef DEBUG_WINDATA + printf_dbgf("(%#010lx): client = %#010lx, name = \"%s\", " + "ret = %d\n", w->id, w->client_win, w->name, ret); +#endif + + return ret; +} + +static inline int +win_get_role(session_t *ps, win *w) { + int ret = win_get_prop_str(ps, w, &w->role, wid_get_role); + +#ifdef DEBUG_WINDATA + printf_dbgf("(%#010lx): client = %#010lx, role = \"%s\", " + "ret = %d\n", w->id, w->client_win, w->role, ret); +#endif + + return ret; +} + +static bool +win_get_class(session_t *ps, win *w); + +#ifdef DEBUG_EVENTS +static int +ev_serial(XEvent *ev); + +static const char * +ev_name(session_t *ps, XEvent *ev); + +static Window +ev_window(session_t *ps, XEvent *ev); +#endif + +static void __attribute__ ((noreturn)) +usage(int ret); + +static bool +register_cm(session_t *ps); + +inline static void +ev_focus_in(session_t *ps, XFocusChangeEvent *ev); + +inline static void +ev_focus_out(session_t *ps, XFocusChangeEvent *ev); + +inline static void +ev_create_notify(session_t *ps, XCreateWindowEvent *ev); + +inline static void +ev_configure_notify(session_t *ps, XConfigureEvent *ev); + +inline static void +ev_destroy_notify(session_t *ps, XDestroyWindowEvent *ev); + +inline static void +ev_map_notify(session_t *ps, XMapEvent *ev); + +inline static void +ev_unmap_notify(session_t *ps, XUnmapEvent *ev); + +inline static void +ev_reparent_notify(session_t *ps, XReparentEvent *ev); + +inline static void +ev_circulate_notify(session_t *ps, XCirculateEvent *ev); + +inline static void +ev_expose(session_t *ps, XExposeEvent *ev); + +static void +update_ewmh_active_win(session_t *ps); + +inline static void +ev_property_notify(session_t *ps, XPropertyEvent *ev); + +inline static void +ev_damage_notify(session_t *ps, XDamageNotifyEvent *ev); + +inline static void +ev_shape_notify(session_t *ps, XShapeEvent *ev); + +/** + * Get a region of the screen size. + */ +inline static XserverRegion +get_screen_region(session_t *ps) { + XRectangle r; + + r.x = 0; + r.y = 0; + r.width = ps->root_width; + r.height = ps->root_height; + return XFixesCreateRegion(ps->dpy, &r, 1); +} + +/** + * Resize a region. + */ +static inline void +resize_region(session_t *ps, XserverRegion region, short mod) { + if (!mod || !region) return; + + int nrects = 0, nnewrects = 0; + XRectangle *newrects = NULL; + XRectangle *rects = XFixesFetchRegion(ps->dpy, region, &nrects); + if (!rects || !nrects) + goto resize_region_end; + + // Allocate memory for new rectangle list, because I don't know if it's + // safe to write in the memory Xlib allocates + newrects = calloc(nrects, sizeof(XRectangle)); + if (!newrects) { + printf_errf("(): Failed to allocate memory."); + exit(1); + } + + // Loop through all rectangles + for (int i = 0; i < nrects; ++i) { + int x1 = max_i(rects[i].x - mod, 0); + int y1 = max_i(rects[i].y - mod, 0); + int x2 = min_i(rects[i].x + rects[i].width + mod, ps->root_width); + int y2 = min_i(rects[i].y + rects[i].height + mod, ps->root_height); + int wid = x2 - x1; + int hei = y2 - y1; + if (wid <= 0 || hei <= 0) + continue; + newrects[nnewrects].x = x1; + newrects[nnewrects].y = y1; + newrects[nnewrects].width = wid; + newrects[nnewrects].height = hei; + ++nnewrects; + } + + // Set region + XFixesSetRegion(ps->dpy, region, newrects, nnewrects); + +resize_region_end: + cxfree(rects); + free(newrects); +} + +/** + * Dump a region. + */ +static inline void +dump_region(const session_t *ps, XserverRegion region) { + int nrects = 0; + XRectangle *rects = NULL; + if (!rects && region) + rects = XFixesFetchRegion(ps->dpy, region, &nrects); + + printf_dbgf("(%#010lx): %d rects\n", region, nrects); + if (!rects) return; + for (int i = 0; i < nrects; ++i) + printf("Rect #%d: %8d, %8d, %8d, %8d\n", i, rects[i].x, rects[i].y, + rects[i].width, rects[i].height); + putchar('\n'); + fflush(stdout); + + cxfree(rects); +} + +/** + * Check if a region is empty. + * + * Keith Packard said this is slow: + * http://lists.freedesktop.org/archives/xorg/2007-November/030467.html + * + * @param ps current session + * @param region region to check for + * @param pcache_rects a place to cache the dumped rectangles + * @param ncache_nrects a place to cache the number of dumped rectangles + */ +static inline bool +is_region_empty(const session_t *ps, XserverRegion region, + reg_data_t *pcache_reg) { + int nrects = 0; + XRectangle *rects = XFixesFetchRegion(ps->dpy, region, &nrects); + + if (pcache_reg) { + pcache_reg->rects = rects; + pcache_reg->nrects = nrects; + } + else + cxfree(rects); + + return !nrects; +} + +/** + * Add a window to damaged area. + * + * @param ps current session + * @param w struct _win element representing the window + */ +static inline void +add_damage_win(session_t *ps, win *w) { + if (w->extents) { + add_damage(ps, copy_region(ps, w->extents)); + } +} + +#if defined(DEBUG_EVENTS) || defined(DEBUG_RESTACK) +static bool +ev_window_name(session_t *ps, Window wid, char **name); +#endif + +inline static void +ev_handle(session_t *ps, XEvent *ev); + +static bool +fork_after(session_t *ps); + +#ifdef CONFIG_LIBCONFIG +/** + * Wrapper of libconfig's <code>config_lookup_int</code>. + * + * To convert <code>int</code> value <code>config_lookup_bool</code> + * returns to <code>bool</code>. + */ +static inline void +lcfg_lookup_bool(const config_t *config, const char *path, + bool *value) { + int ival; + + if (config_lookup_bool(config, path, &ival)) + *value = ival; +} + +/** + * Wrapper of libconfig's <code>config_lookup_int</code>. + * + * To deal with the different value types <code>config_lookup_int</code> + * returns in libconfig-1.3 and libconfig-1.4. + */ +static inline int +lcfg_lookup_int(const config_t *config, const char *path, int *value) { +#ifndef CONFIG_LIBCONFIG_LEGACY + return config_lookup_int(config, path, value); +#else + long lval; + int ret; + + if ((ret = config_lookup_int(config, path, &lval))) + *value = lval; + + return ret; +#endif +} + +static FILE * +open_config_file(char *cpath, char **path); + +static void +parse_cfg_condlst(session_t *ps, const config_t *pcfg, c2_lptr_t **pcondlst, + const char *name); + +static void +parse_config(session_t *ps, struct options_tmp *pcfgtmp); +#endif + +static void +get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass); + +static void +init_atoms(session_t *ps); + +static void +update_refresh_rate(session_t *ps); + +static bool +swopti_init(session_t *ps); + +static void +swopti_handle_timeout(session_t *ps, struct timeval *ptv); + +#ifdef CONFIG_VSYNC_OPENGL +/** + * Ensure we have a GLX context. + */ +static inline bool +ensure_glx_context(session_t *ps) { + // Create GLX context + if (!ps->glx_context) + glx_init(ps, false); + + return ps->glx_context; +} +#endif + +static bool +vsync_drm_init(session_t *ps); + +#ifdef CONFIG_VSYNC_DRM +static int +vsync_drm_wait(session_t *ps); +#endif + +static bool +vsync_opengl_init(session_t *ps); + +static bool +vsync_opengl_oml_init(session_t *ps); + +static bool +vsync_opengl_swc_init(session_t *ps); + +static bool +vsync_opengl_mswc_init(session_t *ps); + +#ifdef CONFIG_VSYNC_OPENGL +static int +vsync_opengl_wait(session_t *ps); + +static int +vsync_opengl_oml_wait(session_t *ps); + +static void +vsync_opengl_swc_deinit(session_t *ps); + +static void +vsync_opengl_mswc_deinit(session_t *ps); +#endif + +static void +vsync_wait(session_t *ps); + +static void +init_alpha_picts(session_t *ps); + +static bool +init_dbe(session_t *ps); + +static void +init_overlay(session_t *ps); + +static void +redir_start(session_t *ps); + +static void +redir_stop(session_t *ps); + +static inline time_ms_t +timeout_get_newrun(const timeout_t *ptmout) { + return ptmout->firstrun + (max_l((ptmout->lastrun + (time_ms_t) (ptmout->interval * TIMEOUT_RUN_TOLERANCE) - ptmout->firstrun) / ptmout->interval, (ptmout->lastrun + (time_ms_t) (ptmout->interval * (1 - TIMEOUT_RUN_TOLERANCE)) - ptmout->firstrun) / ptmout->interval) + 1) * ptmout->interval; +} + +static time_ms_t +timeout_get_poll_time(session_t *ps); + +static void +timeout_clear(session_t *ps); + +static bool +tmout_unredir_callback(session_t *ps, timeout_t *tmout); + +static bool +mainloop(session_t *ps); + +#ifdef CONFIG_XINERAMA +static void +cxinerama_upd_scrs(session_t *ps); +#endif + +/** + * Get the Xinerama screen a window is on. + * + * Return an index >= 0, or -1 if not found. + */ +static inline void +cxinerama_win_upd_scr(session_t *ps, win *w) { +#ifdef CONFIG_XINERAMA + w->xinerama_scr = -1; + for (XineramaScreenInfo *s = ps->xinerama_scrs; + s < ps->xinerama_scrs + ps->xinerama_nscrs; ++s) + if (s->x_org <= w->a.x && s->y_org <= w->a.y + && s->x_org + s->width >= w->a.x + w->widthb + && s->y_org + s->height >= w->a.y + w->heightb) { + w->xinerama_scr = s - ps->xinerama_scrs; + return; + } +#endif +} + +static void +cxinerama_upd_scrs(session_t *ps); + +static session_t * +session_init(session_t *ps_old, int argc, char **argv); + +static void +session_destroy(session_t *ps); + +static void +session_run(session_t *ps); + +static void +reset_enable(int __attribute__((unused)) signum); diff --git a/twin/compton-tde/dbus.c b/twin/compton-tde/dbus.c new file mode 100644 index 000000000..8aec9ea82 --- /dev/null +++ b/twin/compton-tde/dbus.c @@ -0,0 +1,1195 @@ +/* + * 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 "dbus.h" + +/** + * Initialize D-Bus connection. + */ +bool +cdbus_init(session_t *ps) { + DBusError err = { }; + + // Initialize + dbus_error_init(&err); + + // Connect to D-Bus + // Use dbus_bus_get_private() so we can fully recycle it ourselves + ps->dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + printf_errf("(): D-Bus connection failed (%s).", err.message); + dbus_error_free(&err); + return false; + } + + if (!ps->dbus_conn) { + printf_errf("(): D-Bus connection failed for unknown reason."); + return false; + } + + // Avoid exiting on disconnect + dbus_connection_set_exit_on_disconnect(ps->dbus_conn, false); + + // Request service name + { + // Build service name + char *service = mstrjoin3(CDBUS_SERVICE_NAME, ".", ps->o.display_repr); + ps->dbus_service = service; + + // Request for the name + int ret = dbus_bus_request_name(ps->dbus_conn, service, + DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); + + if (dbus_error_is_set(&err)) { + printf_errf("(): Failed to obtain D-Bus name (%s).", err.message); + dbus_error_free(&err); + } + + if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret + && DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER != ret) { + printf_errf("(): Failed to become the primary owner of requested " + "D-Bus name (%d).", ret); + } + } + + + // Add watch handlers + if (!dbus_connection_set_watch_functions(ps->dbus_conn, + cdbus_callback_add_watch, cdbus_callback_remove_watch, + cdbus_callback_watch_toggled, ps, NULL)) { + printf_errf("(): Failed to add D-Bus watch functions."); + return false; + } + + // Add timeout handlers + if (!dbus_connection_set_timeout_functions(ps->dbus_conn, + cdbus_callback_add_timeout, cdbus_callback_remove_timeout, + cdbus_callback_timeout_toggled, ps, NULL)) { + printf_errf("(): Failed to add D-Bus timeout functions."); + return false; + } + + // Add match + dbus_bus_add_match(ps->dbus_conn, + "type='method_call',interface='" CDBUS_INTERFACE_NAME "'", &err); + if (dbus_error_is_set(&err)) { + printf_errf("(): Failed to add D-Bus match."); + dbus_error_free(&err); + return false; + } + + return true; +} + +/** + * Destroy D-Bus connection. + */ +void +cdbus_destroy(session_t *ps) { + if (ps->dbus_conn) { + // Release DBus name firstly + if (ps->dbus_service) { + DBusError err = { }; + dbus_error_init(&err); + + dbus_bus_release_name(ps->dbus_conn, ps->dbus_service, &err); + if (dbus_error_is_set(&err)) { + printf_errf("(): Failed to release DBus name (%s).", + err.message); + dbus_error_free(&err); + } + } + + // Close and unref the connection + dbus_connection_close(ps->dbus_conn); + dbus_connection_unref(ps->dbus_conn); + } +} + +/** @name DBusTimeout handling + */ +///@{ + +/** + * Callback for adding D-Bus timeout. + */ +static dbus_bool_t +cdbus_callback_add_timeout(DBusTimeout *timeout, void *data) { + session_t *ps = data; + + timeout_t *ptmout = timeout_insert(ps, dbus_timeout_get_interval(timeout), + cdbus_callback_handle_timeout, timeout); + if (ptmout) + dbus_timeout_set_data(timeout, ptmout, NULL); + + return (bool) ptmout; +} + +/** + * Callback for removing D-Bus timeout. + */ +static void +cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data) { + session_t *ps = data; + + timeout_t *ptmout = dbus_timeout_get_data(timeout); + assert(ptmout); + if (ptmout) + timeout_drop(ps, ptmout); +} + +/** + * Callback for toggling a D-Bus timeout. + */ +static void +cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data) { + timeout_t *ptmout = dbus_timeout_get_data(timeout); + + assert(ptmout); + if (ptmout) { + ptmout->enabled = dbus_timeout_get_enabled(timeout); + // Refresh interval as libdbus doc says: "Whenever a timeout is toggled, + // its interval may change." + ptmout->interval = dbus_timeout_get_interval(timeout); + } +} + +/** + * Callback for handling a D-Bus timeout. + */ +static bool +cdbus_callback_handle_timeout(session_t *ps, timeout_t *ptmout) { + assert(ptmout && ptmout->data); + if (ptmout && ptmout->data) + return dbus_timeout_handle(ptmout->data); + + return false; +} + +///@} + +/** @name DBusWatch handling + */ +///@{ + +/** + * Callback for adding D-Bus watch. + */ +static dbus_bool_t +cdbus_callback_add_watch(DBusWatch *watch, void *data) { + // Leave disabled watches alone + if (!dbus_watch_get_enabled(watch)) + return TRUE; + + session_t *ps = data; + + // Insert the file descriptor + fds_insert(ps, dbus_watch_get_unix_fd(watch), + cdbus_get_watch_cond(watch)); + + // Always return true + return TRUE; +} + +/** + * Callback for removing D-Bus watch. + */ +static void +cdbus_callback_remove_watch(DBusWatch *watch, void *data) { + session_t *ps = data; + + fds_drop(ps, dbus_watch_get_unix_fd(watch), + cdbus_get_watch_cond(watch)); +} + +/** + * Callback for toggling D-Bus watch status. + */ +static void +cdbus_callback_watch_toggled(DBusWatch *watch, void *data) { + if (dbus_watch_get_enabled(watch)) { + cdbus_callback_add_watch(watch, data); + } + else { + cdbus_callback_remove_watch(watch, data); + } +} + +///@} + +/** @name Message argument appending callbacks + */ +///@{ + +/** + * Callback to append a bool argument to a message. + */ +static bool +cdbus_apdarg_bool(session_t *ps, DBusMessage *msg, const void *data) { + assert(data); + + dbus_bool_t val = *(const bool *) data; + + if (!dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &val, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append an int32 argument to a message. + */ +static bool +cdbus_apdarg_int32(session_t *ps, DBusMessage *msg, const void *data) { + if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, data, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append an uint32 argument to a message. + */ +static bool +cdbus_apdarg_uint32(session_t *ps, DBusMessage *msg, const void *data) { + if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, data, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append a double argument to a message. + */ +static bool +cdbus_apdarg_double(session_t *ps, DBusMessage *msg, const void *data) { + if (!dbus_message_append_args(msg, DBUS_TYPE_DOUBLE, data, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append a Window argument to a message. + */ +static bool +cdbus_apdarg_wid(session_t *ps, DBusMessage *msg, const void *data) { + assert(data); + cdbus_window_t val = *(const Window *)data; + + if (!dbus_message_append_args(msg, CDBUS_TYPE_WINDOW, &val, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append an cdbus_enum_t argument to a message. + */ +static bool +cdbus_apdarg_enum(session_t *ps, DBusMessage *msg, const void *data) { + assert(data); + if (!dbus_message_append_args(msg, CDBUS_TYPE_ENUM, data, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append a string argument to a message. + */ +static bool +cdbus_apdarg_string(session_t *ps, DBusMessage *msg, const void *data) { + const char *str = data; + if (!str) + str = ""; + + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + return false; + } + + return true; +} + +/** + * Callback to append all window IDs to a message. + */ +static bool +cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data) { + // Get the number of wids we are to include + unsigned count = 0; + for (win *w = ps->list; w; w = w->next) { + if (!w->destroyed) + ++count; + } + + // Allocate memory for an array of window IDs + cdbus_window_t *arr = malloc(sizeof(cdbus_window_t) * count); + if (!arr) { + printf_errf("(): Failed to allocate memory for window ID array."); + return false; + } + + // Build the array + { + cdbus_window_t *pcur = arr; + for (win *w = ps->list; w; w = w->next) { + if (!w->destroyed) { + *pcur = w->id; + ++pcur; + assert(pcur <= arr + count); + } + } + assert(pcur == arr + count); + } + + // Append arguments + if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, + &arr, count, DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to append argument."); + free(arr); + return false; + } + + free(arr); + return true; +} +///@} + +/** + * Send a D-Bus signal. + * + * @param ps current session + * @param name signal name + * @param func a function that modifies the built message, to, for example, + * add an argument + * @param data data pointer to pass to the function + */ +static bool +cdbus_signal(session_t *ps, const char *name, + bool (*func)(session_t *ps, DBusMessage *msg, const void *data), + const void *data) { + DBusMessage* msg = NULL; + + // Create a signal + msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, + name); + if (!msg) { + printf_errf("(): Failed to create D-Bus signal."); + return false; + } + + // Append arguments onto message + if (func && !func(ps, msg, data)) { + dbus_message_unref(msg); + return false; + } + + // Send the message and flush the connection + if (!dbus_connection_send(ps->dbus_conn, msg, NULL)) { + printf_errf("(): Failed to send D-Bus signal."); + dbus_message_unref(msg); + return false; + } + dbus_connection_flush(ps->dbus_conn); + + // Free the message + dbus_message_unref(msg); + + return true; +} + +/** + * Send a D-Bus reply. + * + * @param ps current session + * @param srcmsg original message + * @param func a function that modifies the built message, to, for example, + * add an argument + * @param data data pointer to pass to the function + */ +static bool +cdbus_reply(session_t *ps, DBusMessage *srcmsg, + bool (*func)(session_t *ps, DBusMessage *msg, const void *data), + const void *data) { + DBusMessage* msg = NULL; + + // Create a reply + msg = dbus_message_new_method_return(srcmsg); + if (!msg) { + printf_errf("(): Failed to create D-Bus reply."); + return false; + } + + // Append arguments onto message + if (func && !func(ps, msg, data)) { + dbus_message_unref(msg); + return false; + } + + // Send the message and flush the connection + if (!dbus_connection_send(ps->dbus_conn, msg, NULL)) { + printf_errf("(): Failed to send D-Bus reply."); + dbus_message_unref(msg); + return false; + } + dbus_connection_flush(ps->dbus_conn); + + // Free the message + dbus_message_unref(msg); + + return true; +} + +/** + * Send a D-Bus error reply. + * + * @param ps current session + * @param msg the new error DBusMessage + */ +static bool +cdbus_reply_errm(session_t *ps, DBusMessage *msg) { + if (!msg) { + printf_errf("(): Failed to create D-Bus reply."); + return false; + } + + // Send the message and flush the connection + if (!dbus_connection_send(ps->dbus_conn, msg, NULL)) { + printf_errf("(): Failed to send D-Bus reply."); + dbus_message_unref(msg); + return false; + } + dbus_connection_flush(ps->dbus_conn); + + // Free the message + dbus_message_unref(msg); + + return true; +} + +/** + * Get n-th argument of a D-Bus message. + * + * @param count the position of the argument to get, starting from 0 + * @param type libdbus type number of the type + * @param pdest pointer to the target + * @return true if successful, false otherwise. + */ +static bool +cdbus_msg_get_arg(DBusMessage *msg, int count, const int type, void *pdest) { + assert(count >= 0); + + DBusMessageIter iter = { }; + if (!dbus_message_iter_init(msg, &iter)) { + printf_errf("(): Message has no argument."); + return false; + } + + { + const int oldcount = count; + while (count) { + if (!dbus_message_iter_next(&iter)) { + printf_errf("(): Failed to find argument %d.", oldcount); + return false; + } + --count; + } + } + + if (type != dbus_message_iter_get_arg_type(&iter)) { + printf_errf("(): Argument has incorrect type."); + return false; + } + + dbus_message_iter_get_basic(&iter, pdest); + + return true; +} + +void +cdbus_loop(session_t *ps) { + dbus_connection_read_write(ps->dbus_conn, 0); + DBusMessage *msg = NULL; + while ((msg = dbus_connection_pop_message(ps->dbus_conn))) + cdbus_process(ps, msg); +} + +/** @name Message processing + */ +///@{ + +/** + * Process a message from D-Bus. + */ +static void +cdbus_process(session_t *ps, DBusMessage *msg) { + bool success = false; + +#define cdbus_m_ismethod(method) \ + dbus_message_is_method_call(msg, CDBUS_INTERFACE_NAME, method) + + if (cdbus_m_ismethod("reset")) { + ps->reset = true; + if (!dbus_message_get_no_reply(msg)) + cdbus_reply_bool(ps, msg, true); + success = true; + } + else if (cdbus_m_ismethod("repaint")) { + force_repaint(ps); + if (!dbus_message_get_no_reply(msg)) + cdbus_reply_bool(ps, msg, true); + success = true; + } + else if (cdbus_m_ismethod("list_win")) { + success = cdbus_process_list_win(ps, msg); + } + else if (cdbus_m_ismethod("win_get")) { + success = cdbus_process_win_get(ps, msg); + } + else if (cdbus_m_ismethod("win_set")) { + success = cdbus_process_win_set(ps, msg); + } + else if (cdbus_m_ismethod("find_win")) { + success = cdbus_process_find_win(ps, msg); + } + else if (cdbus_m_ismethod("opts_get")) { + success = cdbus_process_opts_get(ps, msg); + } + else if (cdbus_m_ismethod("opts_set")) { + success = cdbus_process_opts_set(ps, msg); + } +#undef cdbus_m_ismethod + else if (dbus_message_is_method_call(msg, + "org.freedesktop.DBus.Introspectable", "Introspect")) { + success = cdbus_process_introspect(ps, msg); + } + else if (dbus_message_is_method_call(msg, + "org.freedesktop.DBus.Peer", "Ping")) { + cdbus_reply(ps, msg, NULL, NULL); + success = true; + } + else if (dbus_message_is_method_call(msg, + "org.freedesktop.DBus.Peer", "GetMachineId")) { + char *uuid = dbus_get_local_machine_id(); + if (uuid) { + cdbus_reply_string(ps, msg, uuid); + dbus_free(uuid); + success = true; + } + } + else if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") + || dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) { + success = true; + } + else { + if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) { + printf_errf("(): Error message of path \"%s\" " + "interface \"%s\", member \"%s\", error \"%s\"", + dbus_message_get_path(msg), dbus_message_get_interface(msg), + dbus_message_get_member(msg), dbus_message_get_error_name(msg)); + } + else { + printf_errf("(): Illegal message of type \"%s\", path \"%s\" " + "interface \"%s\", member \"%s\"", + cdbus_repr_msgtype(msg), dbus_message_get_path(msg), + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + } + if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) + && !dbus_message_get_no_reply(msg)) + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + success = true; + } + + // If the message could not be processed, and an reply is expected, return + // an empty reply. + if (!success && DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) + && !dbus_message_get_no_reply(msg)) + cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S); + + // Free the message + dbus_message_unref(msg); +} + +/** + * Process a list_win D-Bus request. + */ +static bool +cdbus_process_list_win(session_t *ps, DBusMessage *msg) { + cdbus_reply(ps, msg, cdbus_apdarg_wids, NULL); + + return true; +} + +/** + * Process a win_get D-Bus request. + */ +static bool +cdbus_process_win_get(session_t *ps, DBusMessage *msg) { + cdbus_window_t wid = None; + const char *target = NULL; + DBusError err = { }; + + if (!dbus_message_get_args(msg, &err, + CDBUS_TYPE_WINDOW, &wid, + DBUS_TYPE_STRING, &target, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to parse argument of \"win_get\" (%s).", + err.message); + dbus_error_free(&err); + return false; + } + + win *w = find_win(ps, wid); + + if (!w) { + printf_errf("(): Window %#010x not found.", wid); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); + return true; + } + +#define cdbus_m_win_get_do(tgt, apdarg_func) \ + if (!strcmp(MSTR(tgt), target)) { \ + apdarg_func(ps, msg, w->tgt); \ + return true; \ + } + + cdbus_m_win_get_do(id, cdbus_reply_wid); + + // next + if (!strcmp("next", target)) { + cdbus_reply_wid(ps, msg, (w->next ? w->next->id: 0)); + return true; + } + + // map_state + if (!strcmp("map_state", target)) { + cdbus_reply_bool(ps, msg, w->a.map_state); + return true; + } + + cdbus_m_win_get_do(mode, cdbus_reply_enum); + cdbus_m_win_get_do(client_win, cdbus_reply_wid); + cdbus_m_win_get_do(damaged, cdbus_reply_bool); + cdbus_m_win_get_do(destroyed, cdbus_reply_bool); + cdbus_m_win_get_do(window_type, cdbus_reply_enum); + cdbus_m_win_get_do(wmwin, cdbus_reply_bool); + cdbus_m_win_get_do(leader, cdbus_reply_wid); + // focused_real + if (!strcmp("focused_real", target)) { + cdbus_reply_bool(ps, msg, win_is_focused_real(ps, w)); + return true; + } + cdbus_m_win_get_do(fade_force, cdbus_reply_enum); + cdbus_m_win_get_do(shadow_force, cdbus_reply_enum); + cdbus_m_win_get_do(focused_force, cdbus_reply_enum); + cdbus_m_win_get_do(invert_color_force, cdbus_reply_enum); + cdbus_m_win_get_do(name, cdbus_reply_string); + cdbus_m_win_get_do(class_instance, cdbus_reply_string); + cdbus_m_win_get_do(class_general, cdbus_reply_string); + cdbus_m_win_get_do(role, cdbus_reply_string); + + cdbus_m_win_get_do(opacity, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity_tgt, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity_prop, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity_prop_client, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity_set, cdbus_reply_uint32); + + cdbus_m_win_get_do(frame_opacity, cdbus_reply_double); + cdbus_m_win_get_do(left_width, cdbus_reply_uint32); + cdbus_m_win_get_do(right_width, cdbus_reply_uint32); + cdbus_m_win_get_do(top_width, cdbus_reply_uint32); + cdbus_m_win_get_do(bottom_width, cdbus_reply_uint32); + + cdbus_m_win_get_do(shadow, cdbus_reply_bool); + cdbus_m_win_get_do(fade, cdbus_reply_bool); + cdbus_m_win_get_do(invert_color, cdbus_reply_bool); + cdbus_m_win_get_do(blur_background, cdbus_reply_bool); +#undef cdbus_m_win_get_do + + printf_errf("(): " CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; +} + +/** + * Process a win_set D-Bus request. + */ +static bool +cdbus_process_win_set(session_t *ps, DBusMessage *msg) { + cdbus_window_t wid = None; + const char *target = NULL; + DBusError err = { }; + + if (!dbus_message_get_args(msg, &err, + CDBUS_TYPE_WINDOW, &wid, + DBUS_TYPE_STRING, &target, + DBUS_TYPE_INVALID)) { + printf_errf("(): Failed to parse argument of \"win_set\" (%s).", + err.message); + dbus_error_free(&err); + return false; + } + + win *w = find_win(ps, wid); + + if (!w) { + printf_errf("(): Window %#010x not found.", wid); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); + return true; + } + +#define cdbus_m_win_set_do(tgt, type, real_type) \ + if (!strcmp(MSTR(tgt), target)) { \ + real_type val; \ + if (!cdbus_msg_get_arg(msg, 2, type, &val)) \ + return false; \ + w->tgt = val; \ + goto cdbus_process_win_set_success; \ + } + + if (!strcmp("shadow_force", target)) { + cdbus_enum_t val = UNSET; + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + return false; + win_set_shadow_force(ps, w, val); + goto cdbus_process_win_set_success; + } + + if (!strcmp("fade_force", target)) { + cdbus_enum_t val = UNSET; + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + return false; + win_set_fade_force(ps, w, val); + goto cdbus_process_win_set_success; + } + + if (!strcmp("focused_force", target)) { + cdbus_enum_t val = UNSET; + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + return false; + win_set_focused_force(ps, w, val); + goto cdbus_process_win_set_success; + } + + if (!strcmp("invert_color_force", target)) { + cdbus_enum_t val = UNSET; + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + return false; + win_set_invert_color_force(ps, w, val); + goto cdbus_process_win_set_success; + } +#undef cdbus_m_win_set_do + + printf_errf("(): " CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; + +cdbus_process_win_set_success: + if (!dbus_message_get_no_reply(msg)) + cdbus_reply_bool(ps, msg, true); + return true; +} + +/** + * Process a find_win D-Bus request. + */ +static bool +cdbus_process_find_win(session_t *ps, DBusMessage *msg) { + const char *target = NULL; + + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + return false; + + Window wid = None; + + // Find window by client window + if (!strcmp("client", target)) { + cdbus_window_t client = None; + if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) + return false; + win *w = find_toplevel(ps, client); + if (w) + wid = w->id; + } + // Find focused window + else if (!strcmp("focused", target)) { + win *w = find_focused(ps); + if (w) + wid = w->id; + } + else { + printf_errf("(): " CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; + } + + cdbus_reply_wid(ps, msg, wid); + + return true; +} + +/** + * Process a opts_get D-Bus request. + */ +static bool +cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { + const char *target = NULL; + + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + return false; + +#define cdbus_m_opts_get_do(tgt, apdarg_func) \ + if (!strcmp(MSTR(tgt), target)) { \ + apdarg_func(ps, msg, ps->o.tgt); \ + return true; \ + } + + // version + if (!strcmp("version", target)) { + cdbus_reply_string(ps, msg, COMPTON_VERSION); + return true; + } + + // pid + if (!strcmp("pid", target)) { + cdbus_reply_int32(ps, msg, getpid()); + return true; + } + + // display + if (!strcmp("display", target)) { + cdbus_reply_string(ps, msg, DisplayString(ps->dpy)); + return true; + } + + cdbus_m_opts_get_do(config_file, cdbus_reply_string); + cdbus_m_opts_get_do(display_repr, cdbus_reply_string); + cdbus_m_opts_get_do(write_pid_path, 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); + cdbus_m_opts_get_do(detect_rounded_corners, cdbus_reply_bool); + cdbus_m_opts_get_do(paint_on_overlay, cdbus_reply_bool); + cdbus_m_opts_get_do(unredir_if_possible, cdbus_reply_bool); + cdbus_m_opts_get_do(unredir_if_possible_delay, cdbus_reply_int32); + cdbus_m_opts_get_do(redirected_force, cdbus_reply_enum); + cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum); + cdbus_m_opts_get_do(logpath, cdbus_reply_string); + cdbus_m_opts_get_do(synchronize, cdbus_reply_bool); + + cdbus_m_opts_get_do(refresh_rate, cdbus_reply_int32); + cdbus_m_opts_get_do(sw_opti, cdbus_reply_bool); + if (!strcmp("vsync", target)) { + assert(ps->o.vsync < sizeof(VSYNC_STRS) / sizeof(VSYNC_STRS[0])); + cdbus_reply_string(ps, msg, VSYNC_STRS[ps->o.vsync]); + return true; + } + if (!strcmp("backend", target)) { + assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0])); + cdbus_reply_string(ps, msg, BACKEND_STRS[ps->o.backend]); + return true; + } + cdbus_m_opts_get_do(dbe, cdbus_reply_bool); + cdbus_m_opts_get_do(vsync_aggressive, cdbus_reply_bool); + + cdbus_m_opts_get_do(shadow_red, cdbus_reply_double); + cdbus_m_opts_get_do(shadow_green, cdbus_reply_double); + cdbus_m_opts_get_do(shadow_blue, cdbus_reply_double); + cdbus_m_opts_get_do(shadow_radius, cdbus_reply_int32); + cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32); + cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32); + cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double); + cdbus_m_opts_get_do(clear_shadow, cdbus_reply_bool); + cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool); + + cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32); + cdbus_m_opts_get_do(fade_in_step, cdbus_reply_int32); + cdbus_m_opts_get_do(fade_out_step, cdbus_reply_int32); + cdbus_m_opts_get_do(no_fading_openclose, cdbus_reply_bool); + + cdbus_m_opts_get_do(blur_background, cdbus_reply_bool); + cdbus_m_opts_get_do(blur_background_frame, cdbus_reply_bool); + cdbus_m_opts_get_do(blur_background_fixed, cdbus_reply_bool); + + cdbus_m_opts_get_do(inactive_dim, cdbus_reply_double); + cdbus_m_opts_get_do(inactive_dim_fixed, cdbus_reply_bool); + + cdbus_m_opts_get_do(use_ewmh_active_win, cdbus_reply_bool); + cdbus_m_opts_get_do(detect_transient, cdbus_reply_bool); + cdbus_m_opts_get_do(detect_client_leader, cdbus_reply_bool); + +#ifdef CONFIG_VSYNC_OPENGL + cdbus_m_opts_get_do(glx_no_stencil, cdbus_reply_bool); + cdbus_m_opts_get_do(glx_copy_from_front, cdbus_reply_bool); + cdbus_m_opts_get_do(glx_use_copysubbuffermesa, cdbus_reply_bool); + cdbus_m_opts_get_do(glx_no_rebind_pixmap, cdbus_reply_bool); + cdbus_m_opts_get_do(glx_swap_method, cdbus_reply_int32); +#endif + + cdbus_m_opts_get_do(track_focus, cdbus_reply_bool); + cdbus_m_opts_get_do(track_wdata, cdbus_reply_bool); + cdbus_m_opts_get_do(track_leader, cdbus_reply_bool); +#undef cdbus_m_opts_get_do + + printf_errf("(): " CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; +} + +/** + * Process a opts_set D-Bus request. + */ +static bool +cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { + const char *target = NULL; + + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + return false; + +#define cdbus_m_opts_set_do(tgt, type, real_type) \ + if (!strcmp(MSTR(tgt), target)) { \ + real_type val; \ + if (!cdbus_msg_get_arg(msg, 1, type, &val)) \ + return false; \ + ps->o.tgt = val; \ + goto cdbus_process_opts_set_success; \ + } + + // fade_delta + if (!strcmp("fade_delta", target)) { + int32_t val = 0.0; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_INT32, &val)) + return false; + ps->o.fade_delta = max_i(val, 1); + goto cdbus_process_opts_set_success; + } + + // fade_in_step + if (!strcmp("fade_in_step", target)) { + double val = 0.0; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) + return false; + ps->o.fade_in_step = normalize_d(val) * OPAQUE; + goto cdbus_process_opts_set_success; + } + + // fade_out_step + if (!strcmp("fade_out_step", target)) { + double val = 0.0; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) + return false; + ps->o.fade_out_step = normalize_d(val) * OPAQUE; + goto cdbus_process_opts_set_success; + } + + // no_fading_openclose + if (!strcmp("no_fading_openclose", target)) { + dbus_bool_t val = FALSE; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + return false; + opts_set_no_fading_openclose(ps, val); + goto cdbus_process_opts_set_success; + } + + // unredir_if_possible + if (!strcmp("unredir_if_possible", target)) { + dbus_bool_t val = FALSE; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + return false; + if (ps->o.unredir_if_possible != val) { + ps->o.unredir_if_possible = val; + ps->ev_received = true; + } + goto cdbus_process_opts_set_success; + } + + // clear_shadow + if (!strcmp("clear_shadow", target)) { + dbus_bool_t val = FALSE; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + return false; + if (ps->o.clear_shadow != val) { + ps->o.clear_shadow = val; + force_repaint(ps); + } + goto cdbus_process_opts_set_success; + } + + // track_focus + if (!strcmp("track_focus", target)) { + dbus_bool_t val = FALSE; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + return false; + // You could enable this option, but never turn if off + if (val) { + opts_init_track_focus(ps); + } + goto cdbus_process_opts_set_success; + } + + // vsync + if (!strcmp("vsync", target)) { + const char * val = NULL; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_STRING, &val)) + return false; + vsync_deinit(ps); + if (!parse_vsync(ps, val)) { + printf_errf("(): " CDBUS_ERROR_BADARG_S, 1, "Value invalid."); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADARG, CDBUS_ERROR_BADARG_S, 1, "Value invalid."); + } + else if (!vsync_init(ps)) { + printf_errf("(): " CDBUS_ERROR_CUSTOM_S, "Failed to initialize specified VSync method."); + cdbus_reply_err(ps, msg, CDBUS_ERROR_CUSTOM, CDBUS_ERROR_CUSTOM_S, "Failed to initialize specified VSync method."); + } + else + goto cdbus_process_opts_set_success; + return true; + } + + // redirected_force + if (!strcmp("redirected_force", target)) { + cdbus_enum_t val = UNSET; + if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val)) + return false; + ps->o.redirected_force = val; + force_repaint(ps); + goto cdbus_process_opts_set_success; + } + + // stoppaint_force + cdbus_m_opts_set_do(stoppaint_force, CDBUS_TYPE_ENUM, cdbus_enum_t); + +#undef cdbus_m_opts_set_do + + printf_errf("(): " CDBUS_ERROR_BADTGT_S, target); + cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); + + return true; + +cdbus_process_opts_set_success: + if (!dbus_message_get_no_reply(msg)) + cdbus_reply_bool(ps, msg, true); + return true; +} + +/** + * Process an Introspect D-Bus request. + */ +static bool +cdbus_process_introspect(session_t *ps, DBusMessage *msg) { + const static char *str_introspect = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" + " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node name='" CDBUS_OBJECT_NAME "'>\n" + " <interface name='org.freedesktop.DBus.Introspectable'>\n" + " <method name='Introspect'>\n" + " <arg name='data' direction='out' type='s' />\n" + " </method>\n" + " </interface>\n" + " <interface name='org.freedesktop.DBus.Peer'>\n" + " <method name='Ping' />\n" + " <method name='GetMachineId'>\n" + " <arg name='machine_uuid' direction='out' type='s' />\n" + " </method>\n" + " </interface>\n" + " <interface name='" CDBUS_INTERFACE_NAME "'>\n" + " <signal name='win_added'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='win_destroyed'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='win_mapped'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='win_unmapped'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='win_focusin'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <signal name='win_focusout'>\n" + " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n" + " </signal>\n" + " <method name='reset' />\n" + " <method name='repaint' />\n" + " </interface>\n" + "</node>\n"; + + cdbus_reply_string(ps, msg, str_introspect); + + return true; +} +///@} + +/** @name Core callbacks + */ +///@{ +void +cdbus_ev_win_added(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_added", w->id); +} + +void +cdbus_ev_win_destroyed(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_destroyed", w->id); +} + +void +cdbus_ev_win_mapped(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_mapped", w->id); +} + +void +cdbus_ev_win_unmapped(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_unmapped", w->id); +} + +void +cdbus_ev_win_focusout(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_focusout", w->id); +} + +void +cdbus_ev_win_focusin(session_t *ps, win *w) { + if (ps->dbus_conn) + cdbus_signal_wid(ps, "win_focusin", w->id); +} +//!@} diff --git a/twin/compton-tde/dbus.h b/twin/compton-tde/dbus.h new file mode 100644 index 000000000..a806c2de9 --- /dev/null +++ b/twin/compton-tde/dbus.h @@ -0,0 +1,252 @@ +/* + * 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 <ctype.h> +#include <sys/types.h> +#include <unistd.h> + +#define CDBUS_SERVICE_NAME "com.github.chjj.compton" +#define CDBUS_INTERFACE_NAME CDBUS_SERVICE_NAME +#define CDBUS_OBJECT_NAME "/com/github/chjj/compton" +#define CDBUS_ERROR_PREFIX CDBUS_INTERFACE_NAME ".error" +#define CDBUS_ERROR_UNKNOWN CDBUS_ERROR_PREFIX ".unknown" +#define CDBUS_ERROR_UNKNOWN_S "Well, I don't know what happened. Do you?" +#define CDBUS_ERROR_BADMSG CDBUS_ERROR_PREFIX ".bad_message" +#define CDBUS_ERROR_BADMSG_S "Unrecognized command. Beware compton " \ + "cannot make you a sandwich." +#define CDBUS_ERROR_BADARG CDBUS_ERROR_PREFIX ".bad_argument" +#define CDBUS_ERROR_BADARG_S "Failed to parse argument %d: %s" +#define CDBUS_ERROR_BADWIN CDBUS_ERROR_PREFIX ".bad_window" +#define CDBUS_ERROR_BADWIN_S "Requested window %#010lx not found." +#define CDBUS_ERROR_BADTGT CDBUS_ERROR_PREFIX ".bad_target" +#define CDBUS_ERROR_BADTGT_S "Target \"%s\" not found." +#define CDBUS_ERROR_FORBIDDEN CDBUS_ERROR_PREFIX ".forbidden" +#define CDBUS_ERROR_FORBIDDEN_S "Incorrect password, access denied." +#define CDBUS_ERROR_CUSTOM CDBUS_ERROR_PREFIX ".custom" +#define CDBUS_ERROR_CUSTOM_S "%s" + +// Window type +typedef uint32_t cdbus_window_t; +#define CDBUS_TYPE_WINDOW DBUS_TYPE_UINT32 +#define CDBUS_TYPE_WINDOW_STR DBUS_TYPE_UINT32_AS_STRING + +typedef uint16_t cdbus_enum_t; +#define CDBUS_TYPE_ENUM DBUS_TYPE_UINT16 +#define CDBUS_TYPE_ENUM_STR DBUS_TYPE_UINT16_AS_STRING + +static dbus_bool_t +cdbus_callback_add_timeout(DBusTimeout *timeout, void *data); + +static void +cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data); + +static void +cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data); + +static bool +cdbus_callback_handle_timeout(session_t *ps, timeout_t *ptmout); + +/** + * Determine the poll condition of a DBusWatch. + */ +static inline short +cdbus_get_watch_cond(DBusWatch *watch) { + const unsigned flags = dbus_watch_get_flags(watch); + short condition = POLLERR | POLLHUP; + if (flags & DBUS_WATCH_READABLE) + condition |= POLLIN; + if (flags & DBUS_WATCH_WRITABLE) + condition |= POLLOUT; + + return condition; +} + +static dbus_bool_t +cdbus_callback_add_watch(DBusWatch *watch, void *data); + +static void +cdbus_callback_remove_watch(DBusWatch *watch, void *data); + +static void +cdbus_callback_watch_toggled(DBusWatch *watch, void *data); + +static bool +cdbus_apdarg_bool(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_int32(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_uint32(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_double(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_wid(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_enum(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_string(session_t *ps, DBusMessage *msg, const void *data); + +static bool +cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data); + +/** @name DBus signal sending + */ +///@{ + +static bool +cdbus_signal(session_t *ps, const char *name, + bool (*func)(session_t *ps, DBusMessage *msg, const void *data), + const void *data); + +/** + * Send a signal with no argument. + */ +static inline bool +cdbus_signal_noarg(session_t *ps, const char *name) { + return cdbus_signal(ps, name, NULL, NULL); +} + +/** + * Send a signal with a Window ID as argument. + */ +static inline bool +cdbus_signal_wid(session_t *ps, const char *name, Window wid) { + return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid); +} + +///@} + +/** @name DBus reply sending + */ +///@{ + +static bool +cdbus_reply(session_t *ps, DBusMessage *srcmsg, + bool (*func)(session_t *ps, DBusMessage *msg, const void *data), + const void *data); + +static bool +cdbus_reply_errm(session_t *ps, DBusMessage *msg); + +#define cdbus_reply_err(ps, srcmsg, err_name, err_format, ...) \ + cdbus_reply_errm((ps), dbus_message_new_error_printf((srcmsg), (err_name), (err_format), ## __VA_ARGS__)) + +/** + * Send a reply with no argument. + */ +static inline bool +cdbus_reply_noarg(session_t *ps, DBusMessage *srcmsg) { + return cdbus_reply(ps, srcmsg, NULL, NULL); +} + +/** + * Send a reply with a bool argument. + */ +static inline bool +cdbus_reply_bool(session_t *ps, DBusMessage *srcmsg, bool bval) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_bool, &bval); +} + +/** + * Send a reply with an int32 argument. + */ +static inline bool +cdbus_reply_int32(session_t *ps, DBusMessage *srcmsg, int32_t val) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &val); +} + +/** + * Send a reply with an uint32 argument. + */ +static inline bool +cdbus_reply_uint32(session_t *ps, DBusMessage *srcmsg, uint32_t val) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_uint32, &val); +} + +/** + * Send a reply with a double argument. + */ +static inline bool +cdbus_reply_double(session_t *ps, DBusMessage *srcmsg, double val) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_double, &val); +} + +/** + * Send a reply with a wid argument. + */ +static inline bool +cdbus_reply_wid(session_t *ps, DBusMessage *srcmsg, Window wid) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_wid, &wid); +} + +/** + * Send a reply with a string argument. + */ +static inline bool +cdbus_reply_string(session_t *ps, DBusMessage *srcmsg, const char *str) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_string, str); +} + +/** + * Send a reply with a enum argument. + */ +static inline bool +cdbus_reply_enum(session_t *ps, DBusMessage *srcmsg, cdbus_enum_t eval) { + return cdbus_reply(ps, srcmsg, cdbus_apdarg_enum, &eval); +} + +///@} + +static bool +cdbus_msg_get_arg(DBusMessage *msg, int count, const int type, void *pdest); + +/** + * Return a string representation of a D-Bus message type. + */ +static inline const char * +cdbus_repr_msgtype(DBusMessage *msg) { + return dbus_message_type_to_string(dbus_message_get_type(msg)); +} + +/** @name Message processing + */ +///@{ + +static void +cdbus_process(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_list_win(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_win_get(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_win_set(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_find_win(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_opts_get(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_opts_set(session_t *ps, DBusMessage *msg); + +static bool +cdbus_process_introspect(session_t *ps, DBusMessage *msg); + +///@} diff --git a/twin/compton-tde/man/compton-tde-trans.1.html b/twin/compton-tde/man/compton-tde-trans.1.html new file mode 100644 index 000000000..a6c30fed8 --- /dev/null +++ b/twin/compton-tde/man/compton-tde-trans.1.html @@ -0,0 +1,897 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
+<meta name="generator" content="AsciiDoc 8.6.7" />
+<title>compton-trans(1)</title>
+<style type="text/css">
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+pre {
+ padding: 0;
+ margin: 0;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+tt {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+.monospaced {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
+</style>
+<script type="text/javascript">
+/*<![CDATA[*/
+var asciidoc = { // Namespace.
+
+/////////////////////////////////////////////////////////////////////
+// Table Of Contents generator
+/////////////////////////////////////////////////////////////////////
+
+/* Author: Mihai Bazon, September 2002
+ * http://students.infoiasi.ro/~mishoo
+ *
+ * Table Of Content generator
+ * Version: 0.4
+ *
+ * Feel free to use this script under the terms of the GNU General Public
+ * License, as long as you do not remove or alter this notice.
+ */
+
+ /* modified by Troy D. Hanson, September 2006. License: GPL */
+ /* modified by Stuart Rackham, 2006, 2009. License: GPL */
+
+// toclevels = 1..4.
+toc: function (toclevels) {
+
+ function getText(el) {
+ var text = "";
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
+ text += i.data;
+ else if (i.firstChild != null)
+ text += getText(i);
+ }
+ return text;
+ }
+
+ function TocEntry(el, text, toclevel) {
+ this.element = el;
+ this.text = text;
+ this.toclevel = toclevel;
+ }
+
+ function tocEntries(el, toclevels) {
+ var result = new Array;
+ var re = new RegExp('[hH]([1-'+(toclevels+1)+'])');
+ // Function that scans the DOM tree for header elements (the DOM2
+ // nodeIterator API would be a better technique but not supported by all
+ // browsers).
+ var iterate = function (el) {
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
+ var mo = re.exec(i.tagName);
+ if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
+ result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
+ }
+ iterate(i);
+ }
+ }
+ }
+ iterate(el);
+ return result;
+ }
+
+ var toc = document.getElementById("toc");
+ if (!toc) {
+ return;
+ }
+
+ // Delete existing TOC entries in case we're reloading the TOC.
+ var tocEntriesToRemove = [];
+ var i;
+ for (i = 0; i < toc.childNodes.length; i++) {
+ var entry = toc.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div'
+ && entry.getAttribute("class")
+ && entry.getAttribute("class").match(/^toclevel/))
+ tocEntriesToRemove.push(entry);
+ }
+ for (i = 0; i < tocEntriesToRemove.length; i++) {
+ toc.removeChild(tocEntriesToRemove[i]);
+ }
+
+ // Rebuild TOC entries.
+ var entries = tocEntries(document.getElementById("content"), toclevels);
+ for (var i = 0; i < entries.length; ++i) {
+ var entry = entries[i];
+ if (entry.element.id == "")
+ entry.element.id = "_toc_" + i;
+ var a = document.createElement("a");
+ a.href = "#" + entry.element.id;
+ a.appendChild(document.createTextNode(entry.text));
+ var div = document.createElement("div");
+ div.appendChild(a);
+ div.className = "toclevel" + entry.toclevel;
+ toc.appendChild(div);
+ }
+ if (entries.length == 0)
+ toc.parentNode.removeChild(toc);
+},
+
+
+/////////////////////////////////////////////////////////////////////
+// Footnotes generator
+/////////////////////////////////////////////////////////////////////
+
+/* Based on footnote generation code from:
+ * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
+ */
+
+footnotes: function () {
+ // Delete existing footnote entries in case we're reloading the footnodes.
+ var i;
+ var noteholder = document.getElementById("footnotes");
+ if (!noteholder) {
+ return;
+ }
+ var entriesToRemove = [];
+ for (i = 0; i < noteholder.childNodes.length; i++) {
+ var entry = noteholder.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
+ entriesToRemove.push(entry);
+ }
+ for (i = 0; i < entriesToRemove.length; i++) {
+ noteholder.removeChild(entriesToRemove[i]);
+ }
+
+ // Rebuild footnote entries.
+ var cont = document.getElementById("content");
+ var spans = cont.getElementsByTagName("span");
+ var refs = {};
+ var n = 0;
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnote") {
+ n++;
+ var note = spans[i].getAttribute("data-note");
+ if (!note) {
+ // Use [\s\S] in place of . so multi-line matches work.
+ // Because JavaScript has no s (dotall) regex flag.
+ note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
+ spans[i].innerHTML =
+ "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ spans[i].setAttribute("data-note", note);
+ }
+ noteholder.innerHTML +=
+ "<div class='footnote' id='_footnote_" + n + "'>" +
+ "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
+ n + "</a>. " + note + "</div>";
+ var id =spans[i].getAttribute("id");
+ if (id != null) refs["#"+id] = n;
+ }
+ }
+ if (n == 0)
+ noteholder.parentNode.removeChild(noteholder);
+ else {
+ // Process footnoterefs.
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnoteref") {
+ var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
+ href = href.match(/#.*/)[0]; // Because IE return full URL.
+ n = refs[href];
+ spans[i].innerHTML =
+ "[<a href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ }
+ }
+ }
+},
+
+install: function(toclevels) {
+ var timerId;
+
+ function reinstall() {
+ asciidoc.footnotes();
+ if (toclevels) {
+ asciidoc.toc(toclevels);
+ }
+ }
+
+ function reinstallAndRemoveTimer() {
+ clearInterval(timerId);
+ reinstall();
+ }
+
+ timerId = setInterval(reinstall, 500);
+ if (document.addEventListener)
+ document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
+ else
+ window.onload = reinstallAndRemoveTimer;
+}
+
+}
+asciidoc.install();
+/*]]>*/
+</script>
+</head>
+<body class="manpage">
+<div id="header">
+<h1>
+compton-trans(1) Manual Page
+</h1>
+<h2>NAME</h2>
+<div class="sectionbody">
+<p>compton-trans -
+ an opacity setter tool
+</p>
+</div>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_synopsis">SYNOPSIS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>compton-trans</strong> [-w <em>WINDOW_ID</em>] [-n <em>WINDOW_NAME</em>] [-c] [-s] <em>OPACITY</em></p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_description">DESCRIPTION</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>compton-trans</strong> is a bash script that sets <em>_NET_WM_WINDOW_OPACITY</em> attribute of a window using standard X11 command-line utilities, including <strong>xprop</strong>(1) and <strong>xwininfo</strong>(1). It is similar to <strong>transset</strong>(1) or <strong>transset-df</strong>(1).</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_options">OPTIONS</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>-w</strong> <em>WINDOW_ID</em>
+</dt>
+<dd>
+<p>
+Specify the window id of the target window.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-n</strong> <em>WINDOW_NAME</em>
+</dt>
+<dd>
+<p>
+Specify and try to match a window name.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-c</strong>
+</dt>
+<dd>
+<p>
+Specify the currently active window as target. Only works if EWMH <em>_NET_ACTIVE_WINDOW</em> property exists on root window.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-s</strong>
+</dt>
+<dd>
+<p>
+Select target window with mouse cursor. This is the default if no window has been specified.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-o</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+Specify the new opacity value for the window. This value can be anywhere from 1-100. If it is prefixed with a plus or minus (+/-), this will increment or decrement from the target window’s current opacity instead.
+</p>
+</dd>
+</dl></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_examples">EXAMPLES</h2>
+<div class="sectionbody">
+<div class="ulist"><ul>
+<li>
+<p>
+Set the opacity of the window with specific window ID to 75%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -w "$WINDOWID" 75</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Set the opacity of the window with the name "urxvt" to 75%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -n "urxvt" 75</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Set current window to opacity of 75%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -c 75</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Select target window and set opacity to 75%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -s 75</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Increment opacity of current active window by 5%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -c +5</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Decrement opacity of current active window by 5%:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>compton-trans -c -- -5</tt></pre>
+</div></div>
+</li>
+</ul></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_bugs">BUGS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Please report any bugs you find to <a href="https://github.com/chjj/compton">https://github.com/chjj/compton</a> .</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_authors">AUTHORS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Christopher Jeffrey (<a href="https://github.com/chjj">https://github.com/chjj</a>).</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_see_also">SEE ALSO</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><a href="compton.1.html"><strong>compton</strong>(1)</a>, <strong>xprop</strong>(1), <strong>xwininfo</strong>(1)</p></div>
+</div>
+</div>
+</div>
+<div id="footnotes"><hr /></div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2014-03-30 20:46:04 CDT
+</div>
+</div>
+</body>
+</html>
diff --git a/twin/compton-tde/man/compton-tde.1.html b/twin/compton-tde/man/compton-tde.1.html new file mode 100644 index 000000000..26d2a3b21 --- /dev/null +++ b/twin/compton-tde/man/compton-tde.1.html @@ -0,0 +1,1603 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
+<meta name="generator" content="AsciiDoc 8.6.7" />
+<title>compton(1)</title>
+<style type="text/css">
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+pre {
+ padding: 0;
+ margin: 0;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+tt {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+.monospaced {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
+</style>
+<script type="text/javascript">
+/*<![CDATA[*/
+var asciidoc = { // Namespace.
+
+/////////////////////////////////////////////////////////////////////
+// Table Of Contents generator
+/////////////////////////////////////////////////////////////////////
+
+/* Author: Mihai Bazon, September 2002
+ * http://students.infoiasi.ro/~mishoo
+ *
+ * Table Of Content generator
+ * Version: 0.4
+ *
+ * Feel free to use this script under the terms of the GNU General Public
+ * License, as long as you do not remove or alter this notice.
+ */
+
+ /* modified by Troy D. Hanson, September 2006. License: GPL */
+ /* modified by Stuart Rackham, 2006, 2009. License: GPL */
+
+// toclevels = 1..4.
+toc: function (toclevels) {
+
+ function getText(el) {
+ var text = "";
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
+ text += i.data;
+ else if (i.firstChild != null)
+ text += getText(i);
+ }
+ return text;
+ }
+
+ function TocEntry(el, text, toclevel) {
+ this.element = el;
+ this.text = text;
+ this.toclevel = toclevel;
+ }
+
+ function tocEntries(el, toclevels) {
+ var result = new Array;
+ var re = new RegExp('[hH]([1-'+(toclevels+1)+'])');
+ // Function that scans the DOM tree for header elements (the DOM2
+ // nodeIterator API would be a better technique but not supported by all
+ // browsers).
+ var iterate = function (el) {
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
+ var mo = re.exec(i.tagName);
+ if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
+ result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
+ }
+ iterate(i);
+ }
+ }
+ }
+ iterate(el);
+ return result;
+ }
+
+ var toc = document.getElementById("toc");
+ if (!toc) {
+ return;
+ }
+
+ // Delete existing TOC entries in case we're reloading the TOC.
+ var tocEntriesToRemove = [];
+ var i;
+ for (i = 0; i < toc.childNodes.length; i++) {
+ var entry = toc.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div'
+ && entry.getAttribute("class")
+ && entry.getAttribute("class").match(/^toclevel/))
+ tocEntriesToRemove.push(entry);
+ }
+ for (i = 0; i < tocEntriesToRemove.length; i++) {
+ toc.removeChild(tocEntriesToRemove[i]);
+ }
+
+ // Rebuild TOC entries.
+ var entries = tocEntries(document.getElementById("content"), toclevels);
+ for (var i = 0; i < entries.length; ++i) {
+ var entry = entries[i];
+ if (entry.element.id == "")
+ entry.element.id = "_toc_" + i;
+ var a = document.createElement("a");
+ a.href = "#" + entry.element.id;
+ a.appendChild(document.createTextNode(entry.text));
+ var div = document.createElement("div");
+ div.appendChild(a);
+ div.className = "toclevel" + entry.toclevel;
+ toc.appendChild(div);
+ }
+ if (entries.length == 0)
+ toc.parentNode.removeChild(toc);
+},
+
+
+/////////////////////////////////////////////////////////////////////
+// Footnotes generator
+/////////////////////////////////////////////////////////////////////
+
+/* Based on footnote generation code from:
+ * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
+ */
+
+footnotes: function () {
+ // Delete existing footnote entries in case we're reloading the footnodes.
+ var i;
+ var noteholder = document.getElementById("footnotes");
+ if (!noteholder) {
+ return;
+ }
+ var entriesToRemove = [];
+ for (i = 0; i < noteholder.childNodes.length; i++) {
+ var entry = noteholder.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
+ entriesToRemove.push(entry);
+ }
+ for (i = 0; i < entriesToRemove.length; i++) {
+ noteholder.removeChild(entriesToRemove[i]);
+ }
+
+ // Rebuild footnote entries.
+ var cont = document.getElementById("content");
+ var spans = cont.getElementsByTagName("span");
+ var refs = {};
+ var n = 0;
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnote") {
+ n++;
+ var note = spans[i].getAttribute("data-note");
+ if (!note) {
+ // Use [\s\S] in place of . so multi-line matches work.
+ // Because JavaScript has no s (dotall) regex flag.
+ note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
+ spans[i].innerHTML =
+ "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ spans[i].setAttribute("data-note", note);
+ }
+ noteholder.innerHTML +=
+ "<div class='footnote' id='_footnote_" + n + "'>" +
+ "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
+ n + "</a>. " + note + "</div>";
+ var id =spans[i].getAttribute("id");
+ if (id != null) refs["#"+id] = n;
+ }
+ }
+ if (n == 0)
+ noteholder.parentNode.removeChild(noteholder);
+ else {
+ // Process footnoterefs.
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnoteref") {
+ var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
+ href = href.match(/#.*/)[0]; // Because IE return full URL.
+ n = refs[href];
+ spans[i].innerHTML =
+ "[<a href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ }
+ }
+ }
+},
+
+install: function(toclevels) {
+ var timerId;
+
+ function reinstall() {
+ asciidoc.footnotes();
+ if (toclevels) {
+ asciidoc.toc(toclevels);
+ }
+ }
+
+ function reinstallAndRemoveTimer() {
+ clearInterval(timerId);
+ reinstall();
+ }
+
+ timerId = setInterval(reinstall, 500);
+ if (document.addEventListener)
+ document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
+ else
+ window.onload = reinstallAndRemoveTimer;
+}
+
+}
+asciidoc.install();
+/*]]>*/
+</script>
+</head>
+<body class="manpage">
+<div id="header">
+<h1>
+compton(1) Manual Page
+</h1>
+<h2>NAME</h2>
+<div class="sectionbody">
+<p>compton -
+ a compositor for X11
+</p>
+</div>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_synopsis">SYNOPSIS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>compton</strong> [<em>OPTIONS</em>]</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_warning">WARNING</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>This man page may be less up-to-date than the usage text in compton (<tt>compton -h</tt>).</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_description">DESCRIPTION</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>compton is a compositor based on Dana Jansens' version of xcompmgr (which itself was written by Keith Packard). It includes some improvements over the original xcompmgr, like window frame opacity and inactive window transparency.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_options">OPTIONS</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>-h</strong>, <strong>--help</strong>
+</dt>
+<dd>
+<p>
+ Get the usage text embedded in program code, which may be more up-to-date than this man page.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-d</strong> <em>DISPLAY</em>
+</dt>
+<dd>
+<p>
+ Display to be managed.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-r</strong> <em>RADIUS</em>
+</dt>
+<dd>
+<p>
+ The blur radius for shadows, in pixels. (defaults to 12)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-o</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+ The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-l</strong> <em>OFFSET</em>
+</dt>
+<dd>
+<p>
+ The left offset for shadows, in pixels. (defaults to -15)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-t</strong> <em>OFFSET</em>
+</dt>
+<dd>
+<p>
+ The top offset for shadows, in pixels. (defaults to -15)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-I</strong> <em>OPACITY_STEP</em>
+</dt>
+<dd>
+<p>
+ Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-O</strong> <em>OPACITY_STEP</em>
+</dt>
+<dd>
+<p>
+ Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-D</strong> <em>MILLISECONDS</em>
+</dt>
+<dd>
+<p>
+ The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-m</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+ Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-c</strong>
+</dt>
+<dd>
+<p>
+ Enabled client-side shadows on windows. Note desktop windows (windows with <em>_NET_WM_WINDOW_TYPE_DESKTOP</em>) never get shadow.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-C</strong>
+</dt>
+<dd>
+<p>
+ Avoid drawing shadows on dock/panel windows.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-z</strong>
+</dt>
+<dd>
+<p>
+ Zero the part of the shadow’s mask behind the window. Note this may not work properly on ARGB windows with fully transparent areas.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-f</strong>
+</dt>
+<dd>
+<p>
+ Fade windows in/out when opening/closing and when opacity changes, unless <strong>--no-fading-openclose</strong> is used.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-F</strong>
+</dt>
+<dd>
+<p>
+ Equals <strong>-f</strong>. Deprecated.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-i</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+ Opacity of inactive windows. (0.1 - 1.0, disabled by default)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-e</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+ Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-G</strong>
+</dt>
+<dd>
+<p>
+ Don’t draw shadows on drag-and-drop windows.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-b</strong>
+</dt>
+<dd>
+<p>
+ Daemonize process. Fork to background after initialization.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-S</strong>
+</dt>
+<dd>
+<p>
+ Enable synchronous X operation (for debugging).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--config</strong> <em>PATH</em>
+</dt>
+<dd>
+<p>
+ Look for configuration file at the path. See <strong>CONFIGURATION FILES</strong> section below for where compton looks for a configuration file by default.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--write-pid-path</strong> <em>PATH</em>
+</dt>
+<dd>
+<p>
+ Write process ID to a file.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-red</strong> <em>VALUE</em>
+</dt>
+<dd>
+<p>
+ Red color value of shadow (0.0 - 1.0, defaults to 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-green</strong> <em>VALUE</em>
+</dt>
+<dd>
+<p>
+ Green color value of shadow (0.0 - 1.0, defaults to 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-blue</strong> <em>VALUE</em>
+</dt>
+<dd>
+<p>
+ Blue color value of shadow (0.0 - 1.0, defaults to 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--inactive-opacity-override</strong>
+</dt>
+<dd>
+<p>
+ Let inactive opacity set by <strong>-i</strong> overrides the windows' <em>_NET_WM_OPACITY</em> values.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--active-opacity</strong> <em>OPACITY</em>
+</dt>
+<dd>
+<p>
+ Default opacity for active windows. (0.0 - 1.0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--inactive-dim</strong> <em>VALUE</em>
+</dt>
+<dd>
+<p>
+ Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--mark-wmwin-focused</strong>
+</dt>
+<dd>
+<p>
+ Try to detect WM windows (a non-override-redirect window with no child that has <tt>WM_STATE</tt>) and mark them as active.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--mark-ovredir-focused</strong>
+</dt>
+<dd>
+<p>
+ Mark override-redirect windows that doesn’t have a child window with <tt>WM_STATE</tt> focused.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--no-fading-openclose</strong>
+</dt>
+<dd>
+<p>
+ Do not fade on window open/close.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-ignore-shaped</strong>
+</dt>
+<dd>
+<p>
+ Do not paint shadows on shaped windows. Note shaped windows here means windows setting its shape through X Shape extension. Those using ARGB background is beyond our control.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--detect-rounded-corners</strong>
+</dt>
+<dd>
+<p>
+ Try to detect windows with rounded corners and don’t consider them shaped windows. The accuracy is not very high, unfortunately.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--detect-client-opacity</strong>
+</dt>
+<dd>
+<p>
+ Detect <em>_NET_WM_OPACITY</em> on client windows, useful for window managers not passing <em>_NET_WM_OPACITY</em> of client windows to frame windows.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--refresh-rate</strong> <em>REFRESH_RATE</em>
+</dt>
+<dd>
+<p>
+ Specify refresh rate of the screen. If not specified or 0, compton will try detecting this with X RandR extension.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--vsync</strong> <em>VSYNC_METHOD</em>
+</dt>
+<dd>
+<p>
+ Set VSync method. VSync methods currently available:
+</p>
+<div class="openblock">
+<div class="content">
+<div class="ulist"><ul>
+<li>
+<p>
+<em>none</em>: No VSync
+</p>
+</li>
+<li>
+<p>
+<em>drm</em>: VSync with <em>DRM_IOCTL_WAIT_VBLANK</em>. May only work on some drivers.
+</p>
+</li>
+<li>
+<p>
+<em>opengl</em>: Try to VSync with <em>SGI_video_sync</em> OpenGL extension. Only work on some drivers.
+</p>
+</li>
+<li>
+<p>
+<em>opengl-oml</em>: Try to VSync with <em>OML_sync_control</em> OpenGL extension. Only work on some drivers.
+</p>
+</li>
+<li>
+<p>
+<em>opengl-swc</em>: Try to VSync with <em>SGI_swap_control</em> OpenGL extension. Only work on some drivers. Works only with GLX backend. Known to be most effective on many drivers. Does not actually control paint timing, only buffer swap is affected, so it doesn’t have the effect of <strong>--sw-opti</strong> unlike other methods. Experimental.
+</p>
+</li>
+<li>
+<p>
+<em>opengl-mswc</em>: Try to VSync with <em>MESA_swap_control</em> OpenGL extension. Basically the same as <em>opengl-swc</em> above, except the extension we use.
+</p>
+</li>
+</ul></div>
+<div class="paragraph"><p>(Note some VSync methods may not be enabled at compile time.)</p></div>
+</div></div>
+</dd>
+<dt class="hdlist1">
+<strong>--vsync-aggressive</strong>
+</dt>
+<dd>
+<p>
+ Attempt to send painting request before VBlank and do XFlush() during VBlank. Reported to work pretty terribly. This switch may be lifted out at any moment.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--alpha-step</strong> <em>VALUE</em>
+</dt>
+<dd>
+<p>
+ X Render backend: Step for pregenerating alpha pictures. (0.01 - 1.0, defaults to 0.03)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--dbe</strong>
+</dt>
+<dd>
+<p>
+ Enable DBE painting mode, intended to use with VSync to (hopefully) eliminate tearing. Reported to have no effect, though.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--paint-on-overlay</strong>
+</dt>
+<dd>
+<p>
+ Painting on X Composite overlay window instead of on root window.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--sw-opti</strong>
+</dt>
+<dd>
+<p>
+ Limit compton to repaint at most once every 1 / <em>refresh_rate</em> second to boost performance. This should not be used with <strong>--vsync</strong> drm/opengl/opengl-oml as they essentially does <strong>--sw-opti</strong>'s job already, unless you wish to specify a lower refresh rate than the actual value.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--use-ewmh-active-win</strong>
+</dt>
+<dd>
+<p>
+ Use EWMH <em>_NET_ACTIVE_WINDOW</em> to determine currently focused window, rather than listening to <em>FocusIn</em>/<em>FocusOut</em> event. Might have more accuracy, provided that the WM supports it.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--respect-prop-shadow</strong>
+</dt>
+<dd>
+<p>
+ Respect <em>_COMPTON_SHADOW</em>. This a prototype-level feature, which you must not rely on.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--unredir-if-possible</strong>
+</dt>
+<dd>
+<p>
+ Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows. Known to cause flickering when redirecting/unredirecting windows. <strong>--paint-on-overlay</strong> may make the flickering less obvious.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--unredir-if-possible-delay</strong> <em>MILLISECONDS</em>
+</dt>
+<dd>
+<p>
+ Delay before unredirecting the window, in milliseconds. Defaults to 0.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--unredir-if-possible-exclude</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Conditions of windows that shouldn’t be considered full-screen for unredirecting screen.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-exclude</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Specify a list of conditions of windows that should have no shadow.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--fade-exclude</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Specify a list of conditions of windows that should not be faded.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--focus-exclude</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Specify a list of conditions of windows that should always be considered focused.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--inactive-dim-fixed</strong>
+</dt>
+<dd>
+<p>
+ Use fixed inactive dim value, instead of adjusting according to window opacity.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--detect-transient</strong>
+</dt>
+<dd>
+<p>
+ Use <em>WM_TRANSIENT_FOR</em> to group windows, and consider windows in the same group focused at the same time.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--detect-client-leader</strong>
+</dt>
+<dd>
+<p>
+ Use <em>WM_CLIENT_LEADER</em> to group windows, and consider windows in the same group focused at the same time. <em>WM_TRANSIENT_FOR</em> has higher priority if <strong>--detect-transient</strong> is enabled, too.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--blur-background</strong>
+</dt>
+<dd>
+<p>
+ Blur background of semi-transparent / ARGB windows. Bad in performance, with driver-dependent behavior. The name of the switch may change without prior notifications.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--blur-background-frame</strong>
+</dt>
+<dd>
+<p>
+ Blur background of windows when the window frame is not opaque. Implies <strong>--blur-background</strong>. Bad in performance, with driver-dependent behavior. The name may change.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--blur-background-fixed</strong>
+</dt>
+<dd>
+<p>
+ Use fixed blur strength rather than adjusting according to window opacity.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--blur-kern</strong> <em>MATRIX</em>
+</dt>
+<dd>
+<p>
+ Specify the blur convolution kernel, with the following format:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...</tt></pre>
+</div></div>
+<div class="paragraph"><p>The element in the center must not be included, it will be forever 1.0 or changing based on opacity, depending on whether you have <tt>--blur-background-fixed</tt>. Yet the automatic adjustment of blur factor may not work well with a custom blur kernel.</p></div>
+<div class="paragraph"><p>A 7x7 Guassian blur kernel (sigma = 0.84089642) looks like:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt>--blur-kern '7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003'</tt></pre>
+</div></div>
+<div class="paragraph"><p>May also be one of the predefined kernels: <tt>3x3box</tt> (default), <tt>5x5box</tt>, <tt>7x7box</tt>, <tt>3x3gaussian</tt>, <tt>5x5gaussian</tt>, <tt>7x7gaussian</tt>, <tt>9x9gaussian</tt>, <tt>11x11gaussian</tt>. All Guassian kernels are generated with sigma = 0.84089642 . You may use the accompanied <tt>compton-convgen.py</tt> to generate blur kernels.</p></div>
+</dd>
+<dt class="hdlist1">
+<strong>--blur-background-exclude</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Exclude conditions for background blur.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--resize-damage</strong> <em>INTEGER</em>
+</dt>
+<dd>
+<p>
+ Resize damaged region by a specific number of pixels. A positive value enlarges it while a negative one shrinks it. If the value is positive, those additional pixels will not be actually painted to screen, only used in blur calculation, and such. (Due to technical limitations, with <strong>--dbe</strong> or <strong>--glx-swap-method</strong>, those pixels will still be incorrectly painted to screen.) Primarily used to fix the line corruption issues of blur, in which case you should use the blur radius value here (e.g. with a 3x3 kernel, you should use <strong>--resize-damage</strong> 1, with a 5x5 one you use <strong>--resize-damage</strong> 2, and so on). May or may not work with <tt>--glx-no-stencil</tt>. Shrinking doesn’t function correctly.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--invert-color-include</strong> <em>CONDITION</em>
+</dt>
+<dd>
+<p>
+ Specify a list of conditions of windows that should be painted with inverted color. Resource-hogging, and is not well tested.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--opacity-rule</strong> <em>OPACITY</em>:'CONDITION'
+</dt>
+<dd>
+<p>
+ Specify a list of opacity rules, in the format <tt>PERCENT:PATTERN</tt>, like <tt>50:name *= "Firefox"</tt>. compton-trans is recommended over this. Note we do not distinguish 100% and unset, and we don’t make any guarantee about possible conflicts with other programs that set <em>_NET_WM_WINDOW_OPACITY</em> on frame or client windows.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--shadow-exclude-reg</strong> <em>GEOMETRY</em>
+</dt>
+<dd>
+<p>
+ Specify a X geometry that describes the region in which shadow should not be painted in, such as a dock window region. Use <tt>--shadow-exclude-reg x10+0-0</tt>, for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--xinerama-shadow-crop</strong>
+</dt>
+<dd>
+<p>
+ Crop shadow of a window fully on a particular Xinerama screen to the screen.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--backend</strong> <em>BACKEND</em>
+</dt>
+<dd>
+<p>
+ Specify the backend to use: <tt>xrender</tt> or <tt>glx</tt>. GLX (OpenGL) backend generally has much superior performance as far as you have a graphic card/chip and driver.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-no-stencil</strong>
+</dt>
+<dd>
+<p>
+ GLX backend: Avoid using stencil buffer, useful if you don’t have a stencil buffer. Might cause incorrect opacity when rendering transparent content (but never practically happened) and may not work with <strong>--blur-background</strong>. My tests show a 15% performance boost. Recommended.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-copy-from-front</strong>
+</dt>
+<dd>
+<p>
+ GLX backend: Copy unmodified regions from front buffer instead of redrawing them all. My tests with nvidia-drivers show a 10% decrease in performance when the whole screen is modified, but a 20% increase when only 1/4 is. My tests on nouveau show terrible slowdown. Useful with <tt>--glx-swap-method</tt>, as well.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-use-copysubbuffermesa</strong>
+</dt>
+<dd>
+<p>
+ GLX backend: Use <em>MESA_copy_sub_buffer</em> to do partial screen update. My tests on nouveau shows a 200% performance boost when only 1/4 of the screen is updated. May break VSync and is not available on some drivers. Overrides <strong>--glx-copy-from-front</strong>.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-no-rebind-pixmap</strong>
+</dt>
+<dd>
+<p>
+ GLX backend: Avoid rebinding pixmap on window damage. Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe). Recommended if it works.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-swap-method</strong> undefined/exchange/copy/3/4/5/6/buffer-age
+</dt>
+<dd>
+<p>
+ GLX backend: GLX buffer swap method we assume. Could be <tt>undefined</tt> (0), <tt>copy</tt> (1), <tt>exchange</tt> (2), 3-6, or <tt>buffer-age</tt> (-1). <tt>undefined</tt> is the slowest and the safest, and the default value. <tt>copy</tt> is fastest, but may fail on some drivers, 2-6 are gradually slower but safer (6 is still faster than 0). Usually, double buffer means 2, triple buffer means 3. <tt>buffer-age</tt> means auto-detect using <em>GLX_EXT_buffer_age</em>, supported by some drivers. Useless with <strong>--glx-use-copysubbuffermesa</strong>. Partially breaks <tt>--resize-damage</tt>. Defaults to <tt>undefined</tt>.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--glx-use-gpushader4</strong>
+</dt>
+<dd>
+<p>
+ GLX backend: Use <em>GL_EXT_gpu_shader4</em> for some optimization on blur GLSL code. My tests on GTX 670 show no noticeable effect.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--dbus</strong>
+</dt>
+<dd>
+<p>
+ Enable remote control via D-Bus. See the <strong>D-BUS API</strong> section below for more details.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--benchmark</strong> <em>CYCLES</em>
+</dt>
+<dd>
+<p>
+ Benchmark mode. Repeatedly paint until reaching the specified cycles.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--benchmark-wid</strong> <em>WINDOW_ID</em>
+</dt>
+<dd>
+<p>
+ Specify window ID to repaint in benchmark mode. If omitted or is 0, the whole screen is repainted.
+</p>
+</dd>
+</dl></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_format_of_conditions">FORMAT OF CONDITIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators.</p></div>
+<div class="paragraph"><p>A condition with "exists" operator looks like this:</p></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt><NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE></tt></pre>
+</div></div>
+<div class="paragraph"><p>With equals operator it looks like:</p></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt><NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OP QUALIFIER> <MATCH TYPE> = <PATTERN></tt></pre>
+</div></div>
+<div class="paragraph"><p>With greater-than/less-than operators it looks like:</p></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt><NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OPERATOR> <PATTERN></tt></pre>
+</div></div>
+<div class="paragraph"><p><em>NEGATION</em> (optional) is one or more exclamation marks;</p></div>
+<div class="paragraph"><p><em>TARGET</em> is either a predefined target name, or the name of a window property to match. Supported predefined targets are <tt>id</tt>, <tt>x</tt>, <tt>y</tt>, <tt>x2</tt> (x + widthb), <tt>y2</tt>, <tt>width</tt>, <tt>height</tt>, <tt>widthb</tt> (width + 2 * border), <tt>heightb</tt>, <tt>override_redirect</tt>, <tt>argb</tt> (whether the window has an ARGB visual), <tt>focused</tt>, <tt>wmwin</tt> (whether the window looks like a WM window, i.e. has no child window with <tt>WM_STATE</tt> and is not override-redirected), <tt>client</tt> (ID of client window), <tt>window_type</tt> (window type in string), <tt>leader</tt> (ID of window leader), <tt>name</tt>, <tt>class_g</tt> (= <tt>WM_CLASS[1]</tt>), <tt>class_i</tt> (= <tt>WM_CLASS[0]</tt>), and <tt>role</tt>.</p></div>
+<div class="paragraph"><p><em>CLIENT/FRAME</em> is a single <tt>@</tt> if the window attribute should be be looked up on client window, nothing if on frame window;</p></div>
+<div class="paragraph"><p><em>INDEX</em> (optional) is the index number of the property to look up. For example, <tt>[2]</tt> means look at the third value in the property. Do not specify it for predefined targets.</p></div>
+<div class="paragraph"><p><em>FORMAT</em> (optional) specifies the format of the property, 8, 16, or 32. On absence we use format X reports. Do not specify it for predefined or string targets.</p></div>
+<div class="paragraph"><p><em>TYPE</em> is a single character representing the type of the property to match for: <tt>c</tt> for <em>CARDINAL</em>, <tt>a</tt> for <em>ATOM</em>, <tt>w</tt> for <em>WINDOW</em>, <tt>d</tt> for <em>DRAWABLE</em>, <tt>s</tt> for <em>STRING</em> (and any other string types, such as <em>UTF8_STRING</em>). Do not specify it for predefined targets.</p></div>
+<div class="paragraph"><p><em>OP QUALIFIER</em> (optional), applicable only for equals operator, could be <tt>?</tt> (ignore-case).</p></div>
+<div class="paragraph"><p><em>MATCH TYPE</em> (optional), applicable only for equals operator, could be nothing (exact match), <tt>*</tt> (match anywhere), <tt>^</tt> (match from start), <tt>%</tt> (wildcard), or <tt>~</tt> (PCRE regular expression).</p></div>
+<div class="paragraph"><p><em>OPERATOR</em> is one of <tt>=</tt> (equals), <tt><</tt>, <tt>></tt>, <tt><=</tt>, <tt>=></tt>, or nothing (exists). Exists operator checks whether a property exists on a window (but for predefined targets, exists means != 0 then).</p></div>
+<div class="paragraph"><p><em>PATTERN</em> is either an integer or a string enclosed by single or double quotes. Python-3-style escape sequences and raw string are supported in the string format.</p></div>
+<div class="paragraph"><p>Supported logical operators are <tt>&&</tt> (and) and <tt>||</tt> (or). <tt>&&</tt> has higher precedence than <tt>||</tt>, left-to-right associativity. Use parentheses to change precedence.</p></div>
+<div class="paragraph"><p>Examples:</p></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt># If the window is focused
+focused
+focused = 1
+# If the window is not override-redirected
+!override_redirect
+override_redirect = false
+override_redirect != true
+override_redirect != 1
+# If the window is a menu
+window_type *= "menu"
+_NET_WM_WINDOW_TYPE@:a *= "MENU"
+# If the window name contains "Firefox", ignore case
+name *?= "Firefox"
+_NET_WM_NAME@:s *?= "Firefox"
+# If the window name ends with "Firefox"
+name %= "*Firefox"
+name ~= "Firefox$"
+# If the window has a property _COMPTON_SHADOW with value 0, type CARDINAL,
+# format 32, value 0, on its frame window
+_COMPTON_SHADOW:32c = 0
+# If the third value of _NET_FRAME_EXTENTS is less than 20, or there's no
+# _NET_FRAME_EXTENTS property on client window
+_NET_FRAME_EXTENTS@[2]:32c < 20 || !_NET_FRAME_EXTENTS@:32c
+# The pattern here will be parsed as "dd4"
+name = "\x64\x64\o64"
+# The pattern here will be parsed as "\x64\x64\x64"
+name = r"\x64\x64\o64"</tt></pre>
+</div></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_legacy_format_of_conditions">LEGACY FORMAT OF CONDITIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>This is the old condition format we once used. Support of this format might be removed in the future.</p></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>condition = TARGET:TYPE[FLAGS]:PATTERN</tt></pre>
+</div></div>
+<div class="paragraph"><p><em>TARGET</em> is one of "n" (window name), "i" (window class instance), "g" (window general class), and "r" (window role).</p></div>
+<div class="paragraph"><p><em>TYPE</em> is one of "e" (exact match), "a" (match anywhere), "s" (match from start), "w" (wildcard), and "p" (PCRE regular expressions, if compiled with the support).</p></div>
+<div class="paragraph"><p><em>FLAGS</em> could be a series of flags. Currently the only defined flag is "i" (ignore case).</p></div>
+<div class="paragraph"><p><em>PATTERN</em> is the actual pattern string.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_configuration_files">CONFIGURATION FILES</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>compton could read from a configuration file if libconfig support is compiled in. If <strong>--config</strong> is not used, compton will seek for a configuration file in <tt>$XDG_CONFIG_HOME/compton.conf</tt> (<tt>~/.config/compton.conf</tt>, usually), then <tt>~/.compton.conf</tt>, then <tt>compton.conf</tt> under <tt>$XDG_DATA_DIRS</tt> (often <tt>/etc/xdg/compton.conf</tt>).</p></div>
+<div class="paragraph"><p>compton uses general libconfig configuration file format. A sample configuration file is available as <tt>compton.sample.conf</tt> in the source tree. Most commandline switches each could be replaced with an option in configuration file, thus documented above. Window-type-specific settings are exposed only in configuration file and has the following format:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt>wintypes:
+{
+ WINDOW_TYPE = { fade = BOOL; shadow = BOOL; opacity = FLOAT; focus = BOOL; };
+};</tt></pre>
+</div></div>
+<div class="paragraph"><p><em>WINDOW_TYPE</em> is one of the 15 window types defined in EWMH standard: "unknown", "desktop", "dock", "toolbar", "menu", "utility", "splash", "dialog", "normal", "dropdown_menu", "popup_menu", "tooltip", "notify", "combo", and "dnd". "fade" and "shadow" controls window-type-specific shadow and fade settings. "opacity" controls default opacity of the window type. "focus" controls whether the window of this type is to be always considered focused. (By default, all window types except "normal" and "dialog" has this on.)</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_signals">SIGNALS</h2>
+<div class="sectionbody">
+<div class="ulist"><ul>
+<li>
+<p>
+compton reinitializes itself upon receiving <tt>SIGUSR1</tt>.
+</p>
+</li>
+</ul></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_d_bus_api">D-BUS API</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>It’s possible to control compton via D-Bus messages, by running compton with <strong>--dbus</strong> and send messages to <tt>com.github.chjj.compton.<DISPLAY></tt>. <tt><DISPLAY></tt> is the display used by compton, with all non-alphanumeric characters transformed to underscores. For <tt>DISPLAY=:0.0</tt> you should use <tt>com.github.chjj.compton._0_0</tt>, for example.</p></div>
+<div class="paragraph"><p>The D-Bus methods and signals are not yet stable, thus undocumented right now.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_examples">EXAMPLES</h2>
+<div class="sectionbody">
+<div class="ulist"><ul>
+<li>
+<p>
+Disable configuration file parsing:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton --config /dev/null</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Run compton with client-side shadow and fading, disable shadow on dock windows and drag-and-drop windows:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton -cCGf</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Same thing as above, plus making inactive windows 80% transparent, making frame 80% transparent, don’t fade on window open/close, enable software optimization, and fork to background:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton -bcCGf -i 0.8 -e 0.8 --no-fading-openclose --sw-opti</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Draw white shadows:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton -c --shadow-red 1 --shadow-green 1 --shadow-blue 1</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Avoid drawing shadows on wbar window:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton -c --shadow-exclude 'class_g = "wbar"'</tt></pre>
+</div></div>
+</li>
+<li>
+<p>
+Enable OpenGL SGI_swap_control VSync with GLX backend:
+</p>
+<div class="listingblock">
+<div class="content">
+<pre><tt>$ compton --backend glx --vsync opengl-swc</tt></pre>
+</div></div>
+</li>
+</ul></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_bugs">BUGS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Please report any you find to <a href="https://github.com/chjj/compton">https://github.com/chjj/compton</a> .</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_authors">AUTHORS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>xcompmgr, originally written by Keith Packard, with contributions from Matthew Allum, Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn, Ely Levy, Phil Blundell, and Carl Worth. Compton by Christopher Jeffrey, based on Dana Jansens' original work, with contributions from Richard Grenville.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_resources">RESOURCES</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Homepage: <a href="https://github.com/chjj/compton">https://github.com/chjj/compton</a></p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_see_also">SEE ALSO</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>xcompmgr</strong>(1), <a href="compton-trans.html"><strong>compton-trans</strong>(1)</a></p></div>
+</div>
+</div>
+</div>
+<div id="footnotes"><hr /></div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2014-03-30 20:46:04 CDT
+</div>
+</div>
+</body>
+</html>
diff --git a/twin/compton-tde/man/compton-trans.1 b/twin/compton-tde/man/compton-trans.1 new file mode 100644 index 000000000..8f4351ac3 --- /dev/null +++ b/twin/compton-tde/man/compton-trans.1 @@ -0,0 +1,201 @@ +'\" t +.\" Title: compton-trans +.\" Author: [see the "AUTHORS" section] +.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/> +.\" Date: 03/31/2014 +.\" Manual: LOCAL USER COMMANDS +.\" Source: compton nightly-20121114 +.\" Language: English +.\" +.TH "COMPTON\-TRANS" "1" "03/31/2014" "compton nightly\-20121114" "LOCAL USER COMMANDS" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +compton-trans \- an opacity setter tool +.SH "SYNOPSIS" +.sp +\fBcompton\-trans\fR [\-w \fIWINDOW_ID\fR] [\-n \fIWINDOW_NAME\fR] [\-c] [\-s] \fIOPACITY\fR +.SH "DESCRIPTION" +.sp +\fBcompton\-trans\fR is a bash script that sets \fI_NET_WM_WINDOW_OPACITY\fR attribute of a window using standard X11 command\-line utilities, including \fBxprop\fR(1) and \fBxwininfo\fR(1)\&. It is similar to \fBtransset\fR(1) or \fBtransset\-df\fR(1)\&. +.SH "OPTIONS" +.PP +\fB\-w\fR \fIWINDOW_ID\fR +.RS 4 +Specify the window id of the target window\&. +.RE +.PP +\fB\-n\fR \fIWINDOW_NAME\fR +.RS 4 +Specify and try to match a window name\&. +.RE +.PP +\fB\-c\fR +.RS 4 +Specify the currently active window as target\&. Only works if EWMH +\fI_NET_ACTIVE_WINDOW\fR +property exists on root window\&. +.RE +.PP +\fB\-s\fR +.RS 4 +Select target window with mouse cursor\&. This is the default if no window has been specified\&. +.RE +.PP +\fB\-o\fR \fIOPACITY\fR +.RS 4 +Specify the new opacity value for the window\&. This value can be anywhere from 1\-100\&. If it is prefixed with a plus or minus (+/\-), this will increment or decrement from the target window\(cqs current opacity instead\&. +.RE +.SH "EXAMPLES" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Set the opacity of the window with specific window ID to 75%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-w "$WINDOWID" 75 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Set the opacity of the window with the name "urxvt" to 75%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-n "urxvt" 75 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Set current window to opacity of 75%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-c 75 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Select target window and set opacity to 75%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-s 75 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Increment opacity of current active window by 5%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-c +5 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Decrement opacity of current active window by 5%: +.sp +.if n \{\ +.RS 4 +.\} +.nf +compton\-trans \-c \-\- \-5 +.fi +.if n \{\ +.RE +.\} +.RE +.SH "BUGS" +.sp +Please report any bugs you find to https://github\&.com/chjj/compton \&. +.SH "AUTHORS" +.sp +Christopher Jeffrey (https://github\&.com/chjj)\&. +.SH "SEE ALSO" +.sp +\fBcompton\fR(1), \fBxprop\fR(1), \fBxwininfo\fR(1) diff --git a/twin/compton-tde/man/compton.1 b/twin/compton-tde/man/compton.1 new file mode 100644 index 000000000..964c4158d --- /dev/null +++ b/twin/compton-tde/man/compton.1 @@ -0,0 +1,904 @@ +'\" t +.\" Title: compton +.\" Author: [see the "AUTHORS" section] +.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/> +.\" Date: 03/31/2014 +.\" Manual: LOCAL USER COMMANDS +.\" Source: compton nightly-20130421 +.\" Language: English +.\" +.TH "COMPTON" "1" "03/31/2014" "compton nightly\-20130421" "LOCAL USER COMMANDS" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +compton \- a compositor for X11 +.SH "SYNOPSIS" +.sp +\fBcompton\fR [\fIOPTIONS\fR] +.SH "WARNING" +.sp +This man page may be less up\-to\-date than the usage text in compton (compton \-h)\&. +.SH "DESCRIPTION" +.sp +compton is a compositor based on Dana Jansens\*(Aq version of xcompmgr (which itself was written by Keith Packard)\&. It includes some improvements over the original xcompmgr, like window frame opacity and inactive window transparency\&. +.SH "OPTIONS" +.PP +\fB\-h\fR, \fB\-\-help\fR +.RS 4 +Get the usage text embedded in program code, which may be more up\-to\-date than this man page\&. +.RE +.PP +\fB\-d\fR \fIDISPLAY\fR +.RS 4 +Display to be managed\&. +.RE +.PP +\fB\-r\fR \fIRADIUS\fR +.RS 4 +The blur radius for shadows, in pixels\&. (defaults to 12) +.RE +.PP +\fB\-o\fR \fIOPACITY\fR +.RS 4 +The opacity of shadows\&. (0\&.0 \- 1\&.0, defaults to 0\&.75) +.RE +.PP +\fB\-l\fR \fIOFFSET\fR +.RS 4 +The left offset for shadows, in pixels\&. (defaults to \-15) +.RE +.PP +\fB\-t\fR \fIOFFSET\fR +.RS 4 +The top offset for shadows, in pixels\&. (defaults to \-15) +.RE +.PP +\fB\-I\fR \fIOPACITY_STEP\fR +.RS 4 +Opacity change between steps while fading in\&. (0\&.01 \- 1\&.0, defaults to 0\&.028) +.RE +.PP +\fB\-O\fR \fIOPACITY_STEP\fR +.RS 4 +Opacity change between steps while fading out\&. (0\&.01 \- 1\&.0, defaults to 0\&.03) +.RE +.PP +\fB\-D\fR \fIMILLISECONDS\fR +.RS 4 +The time between steps in fade step, in milliseconds\&. (> 0, defaults to 10) +.RE +.PP +\fB\-m\fR \fIOPACITY\fR +.RS 4 +Default opacity for dropdown menus and popup menus\&. (0\&.0 \- 1\&.0, defaults to 1\&.0) +.RE +.PP +\fB\-c\fR +.RS 4 +Enabled client\-side shadows on windows\&. Note desktop windows (windows with +\fI_NET_WM_WINDOW_TYPE_DESKTOP\fR) never get shadow\&. +.RE +.PP +\fB\-C\fR +.RS 4 +Avoid drawing shadows on dock/panel windows\&. +.RE +.PP +\fB\-z\fR +.RS 4 +Zero the part of the shadow\(cqs mask behind the window\&. Note this may not work properly on ARGB windows with fully transparent areas\&. +.RE +.PP +\fB\-f\fR +.RS 4 +Fade windows in/out when opening/closing and when opacity changes, unless +\fB\-\-no\-fading\-openclose\fR +is used\&. +.RE +.PP +\fB\-F\fR +.RS 4 +Equals +\fB\-f\fR\&. Deprecated\&. +.RE +.PP +\fB\-i\fR \fIOPACITY\fR +.RS 4 +Opacity of inactive windows\&. (0\&.1 \- 1\&.0, disabled by default) +.RE +.PP +\fB\-e\fR \fIOPACITY\fR +.RS 4 +Opacity of window titlebars and borders\&. (0\&.1 \- 1\&.0, disabled by default) +.RE +.PP +\fB\-G\fR +.RS 4 +Don\(cqt draw shadows on drag\-and\-drop windows\&. +.RE +.PP +\fB\-b\fR +.RS 4 +Daemonize process\&. Fork to background after initialization\&. +.RE +.PP +\fB\-S\fR +.RS 4 +Enable synchronous X operation (for debugging)\&. +.RE +.PP +\fB\-\-config\fR \fIPATH\fR +.RS 4 +Look for configuration file at the path\&. See +\fBCONFIGURATION FILES\fR +section below for where compton looks for a configuration file by default\&. +.RE +.PP +\fB\-\-write\-pid\-path\fR \fIPATH\fR +.RS 4 +Write process ID to a file\&. +.RE +.PP +\fB\-\-shadow\-red\fR \fIVALUE\fR +.RS 4 +Red color value of shadow (0\&.0 \- 1\&.0, defaults to 0)\&. +.RE +.PP +\fB\-\-shadow\-green\fR \fIVALUE\fR +.RS 4 +Green color value of shadow (0\&.0 \- 1\&.0, defaults to 0)\&. +.RE +.PP +\fB\-\-shadow\-blue\fR \fIVALUE\fR +.RS 4 +Blue color value of shadow (0\&.0 \- 1\&.0, defaults to 0)\&. +.RE +.PP +\fB\-\-inactive\-opacity\-override\fR +.RS 4 +Let inactive opacity set by +\fB\-i\fR +overrides the windows\*(Aq +\fI_NET_WM_OPACITY\fR +values\&. +.RE +.PP +\fB\-\-active\-opacity\fR \fIOPACITY\fR +.RS 4 +Default opacity for active windows\&. (0\&.0 \- 1\&.0) +.RE +.PP +\fB\-\-inactive\-dim\fR \fIVALUE\fR +.RS 4 +Dim inactive windows\&. (0\&.0 \- 1\&.0, defaults to 0\&.0) +.RE +.PP +\fB\-\-mark\-wmwin\-focused\fR +.RS 4 +Try to detect WM windows (a non\-override\-redirect window with no child that has +WM_STATE) and mark them as active\&. +.RE +.PP +\fB\-\-mark\-ovredir\-focused\fR +.RS 4 +Mark override\-redirect windows that doesn\(cqt have a child window with +WM_STATE +focused\&. +.RE +.PP +\fB\-\-no\-fading\-openclose\fR +.RS 4 +Do not fade on window open/close\&. +.RE +.PP +\fB\-\-shadow\-ignore\-shaped\fR +.RS 4 +Do not paint shadows on shaped windows\&. Note shaped windows here means windows setting its shape through X Shape extension\&. Those using ARGB background is beyond our control\&. +.RE +.PP +\fB\-\-detect\-rounded\-corners\fR +.RS 4 +Try to detect windows with rounded corners and don\(cqt consider them shaped windows\&. The accuracy is not very high, unfortunately\&. +.RE +.PP +\fB\-\-detect\-client\-opacity\fR +.RS 4 +Detect +\fI_NET_WM_OPACITY\fR +on client windows, useful for window managers not passing +\fI_NET_WM_OPACITY\fR +of client windows to frame windows\&. +.RE +.PP +\fB\-\-refresh\-rate\fR \fIREFRESH_RATE\fR +.RS 4 +Specify refresh rate of the screen\&. If not specified or 0, compton will try detecting this with X RandR extension\&. +.RE +.PP +\fB\-\-vsync\fR \fIVSYNC_METHOD\fR +.RS 4 +Set VSync method\&. VSync methods currently available: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fInone\fR: No VSync +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fIdrm\fR: VSync with +\fIDRM_IOCTL_WAIT_VBLANK\fR\&. May only work on some drivers\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fIopengl\fR: Try to VSync with +\fISGI_video_sync\fR +OpenGL extension\&. Only work on some drivers\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fIopengl\-oml\fR: Try to VSync with +\fIOML_sync_control\fR +OpenGL extension\&. Only work on some drivers\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fIopengl\-swc\fR: Try to VSync with +\fISGI_swap_control\fR +OpenGL extension\&. Only work on some drivers\&. Works only with GLX backend\&. Known to be most effective on many drivers\&. Does not actually control paint timing, only buffer swap is affected, so it doesn\(cqt have the effect of +\fB\-\-sw\-opti\fR +unlike other methods\&. Experimental\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} + +\fIopengl\-mswc\fR: Try to VSync with +\fIMESA_swap_control\fR +OpenGL extension\&. Basically the same as +\fIopengl\-swc\fR +above, except the extension we use\&. +.RE +.sp +(Note some VSync methods may not be enabled at compile time\&.) +.RE +.PP +\fB\-\-vsync\-aggressive\fR +.RS 4 +Attempt to send painting request before VBlank and do XFlush() during VBlank\&. Reported to work pretty terribly\&. This switch may be lifted out at any moment\&. +.RE +.PP +\fB\-\-alpha\-step\fR \fIVALUE\fR +.RS 4 +X Render backend: Step for pregenerating alpha pictures\&. (0\&.01 \- 1\&.0, defaults to 0\&.03) +.RE +.PP +\fB\-\-dbe\fR +.RS 4 +Enable DBE painting mode, intended to use with VSync to (hopefully) eliminate tearing\&. Reported to have no effect, though\&. +.RE +.PP +\fB\-\-paint\-on\-overlay\fR +.RS 4 +Painting on X Composite overlay window instead of on root window\&. +.RE +.PP +\fB\-\-sw\-opti\fR +.RS 4 +Limit compton to repaint at most once every 1 / +\fIrefresh_rate\fR +second to boost performance\&. This should not be used with +\fB\-\-vsync\fR +drm/opengl/opengl\-oml as they essentially does +\fB\-\-sw\-opti\fR\*(Aqs job already, unless you wish to specify a lower refresh rate than the actual value\&. +.RE +.PP +\fB\-\-use\-ewmh\-active\-win\fR +.RS 4 +Use EWMH +\fI_NET_ACTIVE_WINDOW\fR +to determine currently focused window, rather than listening to +\fIFocusIn\fR/\fIFocusOut\fR +event\&. Might have more accuracy, provided that the WM supports it\&. +.RE +.PP +\fB\-\-respect\-prop\-shadow\fR +.RS 4 +Respect +\fI_COMPTON_SHADOW\fR\&. This a prototype\-level feature, which you must not rely on\&. +.RE +.PP +\fB\-\-unredir\-if\-possible\fR +.RS 4 +Unredirect all windows if a full\-screen opaque window is detected, to maximize performance for full\-screen windows\&. Known to cause flickering when redirecting/unredirecting windows\&. +\fB\-\-paint\-on\-overlay\fR +may make the flickering less obvious\&. +.RE +.PP +\fB\-\-unredir\-if\-possible\-delay\fR \fIMILLISECONDS\fR +.RS 4 +Delay before unredirecting the window, in milliseconds\&. Defaults to 0\&. +.RE +.PP +\fB\-\-unredir\-if\-possible\-exclude\fR \fICONDITION\fR +.RS 4 +Conditions of windows that shouldn\(cqt be considered full\-screen for unredirecting screen\&. +.RE +.PP +\fB\-\-shadow\-exclude\fR \fICONDITION\fR +.RS 4 +Specify a list of conditions of windows that should have no shadow\&. +.RE +.PP +\fB\-\-fade\-exclude\fR \fICONDITION\fR +.RS 4 +Specify a list of conditions of windows that should not be faded\&. +.RE +.PP +\fB\-\-focus\-exclude\fR \fICONDITION\fR +.RS 4 +Specify a list of conditions of windows that should always be considered focused\&. +.RE +.PP +\fB\-\-inactive\-dim\-fixed\fR +.RS 4 +Use fixed inactive dim value, instead of adjusting according to window opacity\&. +.RE +.PP +\fB\-\-detect\-transient\fR +.RS 4 +Use +\fIWM_TRANSIENT_FOR\fR +to group windows, and consider windows in the same group focused at the same time\&. +.RE +.PP +\fB\-\-detect\-client\-leader\fR +.RS 4 +Use +\fIWM_CLIENT_LEADER\fR +to group windows, and consider windows in the same group focused at the same time\&. +\fIWM_TRANSIENT_FOR\fR +has higher priority if +\fB\-\-detect\-transient\fR +is enabled, too\&. +.RE +.PP +\fB\-\-blur\-background\fR +.RS 4 +Blur background of semi\-transparent / ARGB windows\&. Bad in performance, with driver\-dependent behavior\&. The name of the switch may change without prior notifications\&. +.RE +.PP +\fB\-\-blur\-background\-frame\fR +.RS 4 +Blur background of windows when the window frame is not opaque\&. Implies +\fB\-\-blur\-background\fR\&. Bad in performance, with driver\-dependent behavior\&. The name may change\&. +.RE +.PP +\fB\-\-blur\-background\-fixed\fR +.RS 4 +Use fixed blur strength rather than adjusting according to window opacity\&. +.RE +.PP +\fB\-\-blur\-kern\fR \fIMATRIX\fR +.RS 4 +Specify the blur convolution kernel, with the following format: +.sp +.if n \{\ +.RS 4 +.\} +.nf +WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5\&.\&.\&. +.fi +.if n \{\ +.RE +.\} +.sp +The element in the center must not be included, it will be forever 1\&.0 or changing based on opacity, depending on whether you have +\-\-blur\-background\-fixed\&. Yet the automatic adjustment of blur factor may not work well with a custom blur kernel\&. +.sp +A 7x7 Guassian blur kernel (sigma = 0\&.84089642) looks like: +.sp +.if n \{\ +.RS 4 +.\} +.nf +\-\-blur\-kern \*(Aq7,7,0\&.000003,0\&.000102,0\&.000849,0\&.001723,0\&.000849,0\&.000102,0\&.000003,0\&.000102,0\&.003494,0\&.029143,0\&.059106,0\&.029143,0\&.003494,0\&.000102,0\&.000849,0\&.029143,0\&.243117,0\&.493069,0\&.243117,0\&.029143,0\&.000849,0\&.001723,0\&.059106,0\&.493069,0\&.493069,0\&.059106,0\&.001723,0\&.000849,0\&.029143,0\&.243117,0\&.493069,0\&.243117,0\&.029143,0\&.000849,0\&.000102,0\&.003494,0\&.029143,0\&.059106,0\&.029143,0\&.003494,0\&.000102,0\&.000003,0\&.000102,0\&.000849,0\&.001723,0\&.000849,0\&.000102,0\&.000003\*(Aq +.fi +.if n \{\ +.RE +.\} +.sp +May also be one of the predefined kernels: +3x3box +(default), +5x5box, +7x7box, +3x3gaussian, +5x5gaussian, +7x7gaussian, +9x9gaussian, +11x11gaussian\&. All Guassian kernels are generated with sigma = 0\&.84089642 \&. You may use the accompanied +compton\-convgen\&.py +to generate blur kernels\&. +.RE +.PP +\fB\-\-blur\-background\-exclude\fR \fICONDITION\fR +.RS 4 +Exclude conditions for background blur\&. +.RE +.PP +\fB\-\-resize\-damage\fR \fIINTEGER\fR +.RS 4 +Resize damaged region by a specific number of pixels\&. A positive value enlarges it while a negative one shrinks it\&. If the value is positive, those additional pixels will not be actually painted to screen, only used in blur calculation, and such\&. (Due to technical limitations, with +\fB\-\-dbe\fR +or +\fB\-\-glx\-swap\-method\fR, those pixels will still be incorrectly painted to screen\&.) Primarily used to fix the line corruption issues of blur, in which case you should use the blur radius value here (e\&.g\&. with a 3x3 kernel, you should use +\fB\-\-resize\-damage\fR +1, with a 5x5 one you use +\fB\-\-resize\-damage\fR +2, and so on)\&. May or may not work with +\-\-glx\-no\-stencil\&. Shrinking doesn\(cqt function correctly\&. +.RE +.PP +\fB\-\-invert\-color\-include\fR \fICONDITION\fR +.RS 4 +Specify a list of conditions of windows that should be painted with inverted color\&. Resource\-hogging, and is not well tested\&. +.RE +.PP +\fB\-\-opacity\-rule\fR \fIOPACITY\fR:\*(AqCONDITION\*(Aq +.RS 4 +Specify a list of opacity rules, in the format +PERCENT:PATTERN, like +50:name *= "Firefox"\&. compton\-trans is recommended over this\&. Note we do not distinguish 100% and unset, and we don\(cqt make any guarantee about possible conflicts with other programs that set +\fI_NET_WM_WINDOW_OPACITY\fR +on frame or client windows\&. +.RE +.PP +\fB\-\-shadow\-exclude\-reg\fR \fIGEOMETRY\fR +.RS 4 +Specify a X geometry that describes the region in which shadow should not be painted in, such as a dock window region\&. Use +\-\-shadow\-exclude\-reg x10+0\-0, for example, if the 10 pixels on the bottom of the screen should not have shadows painted on\&. +.RE +.PP +\fB\-\-xinerama\-shadow\-crop\fR +.RS 4 +Crop shadow of a window fully on a particular Xinerama screen to the screen\&. +.RE +.PP +\fB\-\-backend\fR \fIBACKEND\fR +.RS 4 +Specify the backend to use: +xrender +or +glx\&. GLX (OpenGL) backend generally has much superior performance as far as you have a graphic card/chip and driver\&. +.RE +.PP +\fB\-\-glx\-no\-stencil\fR +.RS 4 +GLX backend: Avoid using stencil buffer, useful if you don\(cqt have a stencil buffer\&. Might cause incorrect opacity when rendering transparent content (but never practically happened) and may not work with +\fB\-\-blur\-background\fR\&. My tests show a 15% performance boost\&. Recommended\&. +.RE +.PP +\fB\-\-glx\-copy\-from\-front\fR +.RS 4 +GLX backend: Copy unmodified regions from front buffer instead of redrawing them all\&. My tests with nvidia\-drivers show a 10% decrease in performance when the whole screen is modified, but a 20% increase when only 1/4 is\&. My tests on nouveau show terrible slowdown\&. Useful with +\-\-glx\-swap\-method, as well\&. +.RE +.PP +\fB\-\-glx\-use\-copysubbuffermesa\fR +.RS 4 +GLX backend: Use +\fIMESA_copy_sub_buffer\fR +to do partial screen update\&. My tests on nouveau shows a 200% performance boost when only 1/4 of the screen is updated\&. May break VSync and is not available on some drivers\&. Overrides +\fB\-\-glx\-copy\-from\-front\fR\&. +.RE +.PP +\fB\-\-glx\-no\-rebind\-pixmap\fR +.RS 4 +GLX backend: Avoid rebinding pixmap on window damage\&. Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe)\&. Recommended if it works\&. +.RE +.PP +\fB\-\-glx\-swap\-method\fR undefined/exchange/copy/3/4/5/6/buffer\-age +.RS 4 +GLX backend: GLX buffer swap method we assume\&. Could be +undefined +(0), +copy +(1), +exchange +(2), 3\-6, or +buffer\-age +(\-1)\&. +undefined +is the slowest and the safest, and the default value\&. +copy +is fastest, but may fail on some drivers, 2\-6 are gradually slower but safer (6 is still faster than 0)\&. Usually, double buffer means 2, triple buffer means 3\&. +buffer\-age +means auto\-detect using +\fIGLX_EXT_buffer_age\fR, supported by some drivers\&. Useless with +\fB\-\-glx\-use\-copysubbuffermesa\fR\&. Partially breaks +\-\-resize\-damage\&. Defaults to +undefined\&. +.RE +.PP +\fB\-\-glx\-use\-gpushader4\fR +.RS 4 +GLX backend: Use +\fIGL_EXT_gpu_shader4\fR +for some optimization on blur GLSL code\&. My tests on GTX 670 show no noticeable effect\&. +.RE +.PP +\fB\-\-dbus\fR +.RS 4 +Enable remote control via D\-Bus\&. See the +\fBD\-BUS API\fR +section below for more details\&. +.RE +.PP +\fB\-\-benchmark\fR \fICYCLES\fR +.RS 4 +Benchmark mode\&. Repeatedly paint until reaching the specified cycles\&. +.RE +.PP +\fB\-\-benchmark\-wid\fR \fIWINDOW_ID\fR +.RS 4 +Specify window ID to repaint in benchmark mode\&. If omitted or is 0, the whole screen is repainted\&. +.RE +.SH "FORMAT OF CONDITIONS" +.sp +Some options accept a condition string to match certain windows\&. A condition string is formed by one or more conditions, joined by logical operators\&. +.sp +A condition with "exists" operator looks like this: +.sp +.if n \{\ +.RS 4 +.\} +.nf +<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> +.fi +.if n \{\ +.RE +.\} +.sp +With equals operator it looks like: +.sp +.if n \{\ +.RS 4 +.\} +.nf +<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OP QUALIFIER> <MATCH TYPE> = <PATTERN> +.fi +.if n \{\ +.RE +.\} +.sp +With greater\-than/less\-than operators it looks like: +.sp +.if n \{\ +.RS 4 +.\} +.nf +<NEGATION> <TARGET> <CLIENT/FRAME> [<INDEX>] : <FORMAT> <TYPE> <NEGATION> <OPERATOR> <PATTERN> +.fi +.if n \{\ +.RE +.\} +.sp +\fINEGATION\fR (optional) is one or more exclamation marks; +.sp +\fITARGET\fR is either a predefined target name, or the name of a window property to match\&. Supported predefined targets are id, x, y, x2 (x + widthb), y2, width, height, widthb (width + 2 * border), heightb, override_redirect, argb (whether the window has an ARGB visual), focused, wmwin (whether the window looks like a WM window, i\&.e\&. has no child window with WM_STATE and is not override\-redirected), client (ID of client window), window_type (window type in string), leader (ID of window leader), name, class_g (= WM_CLASS[1]), class_i (= WM_CLASS[0]), and role\&. +.sp +\fICLIENT/FRAME\fR is a single @ if the window attribute should be be looked up on client window, nothing if on frame window; +.sp +\fIINDEX\fR (optional) is the index number of the property to look up\&. For example, [2] means look at the third value in the property\&. Do not specify it for predefined targets\&. +.sp +\fIFORMAT\fR (optional) specifies the format of the property, 8, 16, or 32\&. On absence we use format X reports\&. Do not specify it for predefined or string targets\&. +.sp +\fITYPE\fR is a single character representing the type of the property to match for: c for \fICARDINAL\fR, a for \fIATOM\fR, w for \fIWINDOW\fR, d for \fIDRAWABLE\fR, s for \fISTRING\fR (and any other string types, such as \fIUTF8_STRING\fR)\&. Do not specify it for predefined targets\&. +.sp +\fIOP QUALIFIER\fR (optional), applicable only for equals operator, could be ? (ignore\-case)\&. +.sp +\fIMATCH TYPE\fR (optional), applicable only for equals operator, could be nothing (exact match), * (match anywhere), ^ (match from start), % (wildcard), or ~ (PCRE regular expression)\&. +.sp +\fIOPERATOR\fR is one of = (equals), <, >, <=, =>, or nothing (exists)\&. Exists operator checks whether a property exists on a window (but for predefined targets, exists means != 0 then)\&. +.sp +\fIPATTERN\fR is either an integer or a string enclosed by single or double quotes\&. Python\-3\-style escape sequences and raw string are supported in the string format\&. +.sp +Supported logical operators are && (and) and || (or)\&. && has higher precedence than ||, left\-to\-right associativity\&. Use parentheses to change precedence\&. +.sp +Examples: +.sp +.if n \{\ +.RS 4 +.\} +.nf +# If the window is focused +focused +focused = 1 +# If the window is not override\-redirected +!override_redirect +override_redirect = false +override_redirect != true +override_redirect != 1 +# If the window is a menu +window_type *= "menu" +_NET_WM_WINDOW_TYPE@:a *= "MENU" +# If the window name contains "Firefox", ignore case +name *?= "Firefox" +_NET_WM_NAME@:s *?= "Firefox" +# If the window name ends with "Firefox" +name %= "*Firefox" +name ~= "Firefox$" +# If the window has a property _COMPTON_SHADOW with value 0, type CARDINAL, +# format 32, value 0, on its frame window +_COMPTON_SHADOW:32c = 0 +# If the third value of _NET_FRAME_EXTENTS is less than 20, or there\*(Aqs no +# _NET_FRAME_EXTENTS property on client window +_NET_FRAME_EXTENTS@[2]:32c < 20 || !_NET_FRAME_EXTENTS@:32c +# The pattern here will be parsed as "dd4" +name = "\ex64\ex64\eo64" +# The pattern here will be parsed as "\ex64\ex64\ex64" +name = r"\ex64\ex64\eo64" +.fi +.if n \{\ +.RE +.\} +.SH "LEGACY FORMAT OF CONDITIONS" +.sp +This is the old condition format we once used\&. Support of this format might be removed in the future\&. +.sp +.if n \{\ +.RS 4 +.\} +.nf +condition = TARGET:TYPE[FLAGS]:PATTERN +.fi +.if n \{\ +.RE +.\} +.sp +\fITARGET\fR is one of "n" (window name), "i" (window class instance), "g" (window general class), and "r" (window role)\&. +.sp +\fITYPE\fR is one of "e" (exact match), "a" (match anywhere), "s" (match from start), "w" (wildcard), and "p" (PCRE regular expressions, if compiled with the support)\&. +.sp +\fIFLAGS\fR could be a series of flags\&. Currently the only defined flag is "i" (ignore case)\&. +.sp +\fIPATTERN\fR is the actual pattern string\&. +.SH "CONFIGURATION FILES" +.sp +compton could read from a configuration file if libconfig support is compiled in\&. If \fB\-\-config\fR is not used, compton will seek for a configuration file in $XDG_CONFIG_HOME/compton\&.conf (~/\&.config/compton\&.conf, usually), then ~/\&.compton\&.conf, then compton\&.conf under $XDG_DATA_DIRS (often /etc/xdg/compton\&.conf)\&. +.sp +compton uses general libconfig configuration file format\&. A sample configuration file is available as compton\&.sample\&.conf in the source tree\&. Most commandline switches each could be replaced with an option in configuration file, thus documented above\&. Window\-type\-specific settings are exposed only in configuration file and has the following format: +.sp +.if n \{\ +.RS 4 +.\} +.nf +wintypes: +{ + WINDOW_TYPE = { fade = BOOL; shadow = BOOL; opacity = FLOAT; focus = BOOL; }; +}; +.fi +.if n \{\ +.RE +.\} +.sp +\fIWINDOW_TYPE\fR is one of the 15 window types defined in EWMH standard: "unknown", "desktop", "dock", "toolbar", "menu", "utility", "splash", "dialog", "normal", "dropdown_menu", "popup_menu", "tooltip", "notify", "combo", and "dnd"\&. "fade" and "shadow" controls window\-type\-specific shadow and fade settings\&. "opacity" controls default opacity of the window type\&. "focus" controls whether the window of this type is to be always considered focused\&. (By default, all window types except "normal" and "dialog" has this on\&.) +.SH "SIGNALS" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +compton reinitializes itself upon receiving +SIGUSR1\&. +.RE +.SH "D-BUS API" +.sp +It\(cqs possible to control compton via D\-Bus messages, by running compton with \fB\-\-dbus\fR and send messages to com\&.github\&.chjj\&.compton\&.<DISPLAY>\&. <DISPLAY> is the display used by compton, with all non\-alphanumeric characters transformed to underscores\&. For DISPLAY=:0\&.0 you should use com\&.github\&.chjj\&.compton\&._0_0, for example\&. +.sp +The D\-Bus methods and signals are not yet stable, thus undocumented right now\&. +.SH "EXAMPLES" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Disable configuration file parsing: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-\-config /dev/null +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Run compton with client\-side shadow and fading, disable shadow on dock windows and drag\-and\-drop windows: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-cCGf +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Same thing as above, plus making inactive windows 80% transparent, making frame 80% transparent, don\(cqt fade on window open/close, enable software optimization, and fork to background: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-bcCGf \-i 0\&.8 \-e 0\&.8 \-\-no\-fading\-openclose \-\-sw\-opti +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Draw white shadows: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-c \-\-shadow\-red 1 \-\-shadow\-green 1 \-\-shadow\-blue 1 +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Avoid drawing shadows on wbar window: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-c \-\-shadow\-exclude \*(Aqclass_g = "wbar"\*(Aq +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Enable OpenGL SGI_swap_control VSync with GLX backend: +.sp +.if n \{\ +.RS 4 +.\} +.nf +$ compton \-\-backend glx \-\-vsync opengl\-swc +.fi +.if n \{\ +.RE +.\} +.RE +.SH "BUGS" +.sp +Please report any you find to https://github\&.com/chjj/compton \&. +.SH "AUTHORS" +.sp +xcompmgr, originally written by Keith Packard, with contributions from Matthew Allum, Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn, Ely Levy, Phil Blundell, and Carl Worth\&. Compton by Christopher Jeffrey, based on Dana Jansens\*(Aq original work, with contributions from Richard Grenville\&. +.SH "RESOURCES" +.sp +Homepage: https://github\&.com/chjj/compton +.SH "SEE ALSO" +.sp +\fBxcompmgr\fR(1), \fBcompton\-trans\fR(1) diff --git a/twin/compton-tde/opengl.c b/twin/compton-tde/opengl.c new file mode 100644 index 000000000..bbef9bf16 --- /dev/null +++ b/twin/compton-tde/opengl.c @@ -0,0 +1,1741 @@ +/* + * Compton - a compositor for X11 + * + * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard + * + * Copyright (c) 2011-2013, Christopher Jeffrey + * Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net> + * See LICENSE for more information. + * + */ + +#include "opengl.h" + +#ifdef CONFIG_GLX_SYNC +void +xr_glx_sync(session_t *ps, Drawable d, XSyncFence *pfence) { + if (*pfence) { + // GLsync sync = ps->glFenceSyncProc(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + GLsync sync = ps->glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, *pfence, 0); + /* GLenum ret = ps->glClientWaitSyncProc(sync, GL_SYNC_FLUSH_COMMANDS_BIT, + 1000); + assert(GL_CONDITION_SATISFIED == ret); */ + XSyncTriggerFence(ps->dpy, *pfence); + XFlush(ps->dpy); + ps->glWaitSyncProc(sync, 0, GL_TIMEOUT_IGNORED); + // ps->glDeleteSyncProc(sync); + // XSyncResetFence(ps->dpy, *pfence); + } + glx_check_err(ps); +} +#endif + +static inline GLXFBConfig +get_fbconfig_from_visualinfo(session_t *ps, const XVisualInfo *visualinfo) { + int nelements = 0; + GLXFBConfig *fbconfigs = glXGetFBConfigs(ps->dpy, visualinfo->screen, + &nelements); + for (int i = 0; i < nelements; ++i) { + int visual_id = 0; + if (Success == glXGetFBConfigAttrib(ps->dpy, fbconfigs[i], GLX_VISUAL_ID, &visual_id) + && visual_id == visualinfo->visualid) + return fbconfigs[i]; + } + + return NULL; +} + +#ifdef DEBUG_GLX_DEBUG_CONTEXT +static void +glx_debug_msg_callback(GLenum source, GLenum type, + GLuint id, GLenum severity, GLsizei length, const GLchar *message, + GLvoid *userParam) { + printf_dbgf("(): source 0x%04X, type 0x%04X, id %u, severity 0x%0X, \"%s\"\n", + source, type, id, severity, message); +} +#endif + +/** + * Initialize OpenGL. + */ +bool +glx_init(session_t *ps, bool need_render) { + bool success = false; + XVisualInfo *pvis = NULL; + + // Check for GLX extension + if (!ps->glx_exists) { + if (glXQueryExtension(ps->dpy, &ps->glx_event, &ps->glx_error)) + ps->glx_exists = true; + else { + printf_errf("(): No GLX extension."); + goto glx_init_end; + } + } + + // Get XVisualInfo + pvis = get_visualinfo_from_visual(ps, ps->vis); + if (!pvis) { + printf_errf("(): Failed to acquire XVisualInfo for current visual."); + goto glx_init_end; + } + + // Ensure the visual is double-buffered + if (need_render) { + int value = 0; + if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) { + printf_errf("(): Root visual is not a GL visual."); + goto glx_init_end; + } + + if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) + || !value) { + printf_errf("(): Root visual is not a double buffered GL visual."); + goto glx_init_end; + } + } + + // Ensure GLX_EXT_texture_from_pixmap exists + if (need_render && !glx_hasglxext(ps, "GLX_EXT_texture_from_pixmap")) + goto glx_init_end; + + if (!ps->glx_context) { + // Get GLX context +#ifndef DEBUG_GLX_DEBUG_CONTEXT + ps->glx_context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE); +#else + { + GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis); + if (!fbconfig) { + printf_errf("(): Failed to get GLXFBConfig for root visual %#lx.", + pvis->visualid); + goto glx_init_end; + } + + f_glXCreateContextAttribsARB p_glXCreateContextAttribsARB = + (f_glXCreateContextAttribsARB) + glXGetProcAddress((const GLubyte *) "glXCreateContextAttribsARB"); + if (!p_glXCreateContextAttribsARB) { + printf_errf("(): Failed to get glXCreateContextAttribsARB()."); + goto glx_init_end; + } + + static const int attrib_list[] = { + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, + None + }; + ps->glx_context = p_glXCreateContextAttribsARB(ps->dpy, fbconfig, NULL, + GL_TRUE, attrib_list); + } +#endif + + if (!ps->glx_context) { + printf_errf("(): Failed to get GLX context."); + goto glx_init_end; + } + + // Attach GLX context + if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), ps->glx_context)) { + printf_errf("(): Failed to attach GLX context."); + goto glx_init_end; + } + +#ifdef DEBUG_GLX_DEBUG_CONTEXT + { + f_DebugMessageCallback p_DebugMessageCallback = + (f_DebugMessageCallback) + glXGetProcAddress((const GLubyte *) "glDebugMessageCallback"); + if (!p_DebugMessageCallback) { + printf_errf("(): Failed to get glDebugMessageCallback(0."); + goto glx_init_end; + } + p_DebugMessageCallback(glx_debug_msg_callback, ps); + } +#endif + + } + + // Ensure we have a stencil buffer. X Fixes does not guarantee rectangles + // in regions don't overlap, so we must use stencil buffer to make sure + // we don't paint a region for more than one time, I think? + if (need_render && !ps->o.glx_no_stencil) { + GLint val = 0; + glGetIntegerv(GL_STENCIL_BITS, &val); + if (!val) { + printf_errf("(): Target window doesn't have stencil buffer."); + goto glx_init_end; + } + } + + // Check GL_ARB_texture_non_power_of_two, requires a GLX context and + // must precede FBConfig fetching + if (need_render) + ps->glx_has_texture_non_power_of_two = glx_hasglext(ps, + "GL_ARB_texture_non_power_of_two"); + + // Acquire function addresses + if (need_render) { +#ifdef DEBUG_GLX_MARK + ps->glStringMarkerGREMEDY = (f_StringMarkerGREMEDY) + glXGetProcAddress((const GLubyte *) "glStringMarkerGREMEDY"); + ps->glFrameTerminatorGREMEDY = (f_FrameTerminatorGREMEDY) + glXGetProcAddress((const GLubyte *) "glFrameTerminatorGREMEDY"); +#endif + + ps->glXBindTexImageProc = (f_BindTexImageEXT) + glXGetProcAddress((const GLubyte *) "glXBindTexImageEXT"); + ps->glXReleaseTexImageProc = (f_ReleaseTexImageEXT) + glXGetProcAddress((const GLubyte *) "glXReleaseTexImageEXT"); + if (!ps->glXBindTexImageProc || !ps->glXReleaseTexImageProc) { + printf_errf("(): Failed to acquire glXBindTexImageEXT() / glXReleaseTexImageEXT()."); + goto glx_init_end; + } + + if (ps->o.glx_use_copysubbuffermesa) { + ps->glXCopySubBufferProc = (f_CopySubBuffer) + glXGetProcAddress((const GLubyte *) "glXCopySubBufferMESA"); + if (!ps->glXCopySubBufferProc) { + printf_errf("(): Failed to acquire glXCopySubBufferMESA()."); + goto glx_init_end; + } + } + +#ifdef CONFIG_GLX_SYNC + ps->glFenceSyncProc = (f_FenceSync) + glXGetProcAddress((const GLubyte *) "glFenceSync"); + ps->glIsSyncProc = (f_IsSync) + glXGetProcAddress((const GLubyte *) "glIsSync"); + ps->glDeleteSyncProc = (f_DeleteSync) + glXGetProcAddress((const GLubyte *) "glDeleteSync"); + ps->glClientWaitSyncProc = (f_ClientWaitSync) + glXGetProcAddress((const GLubyte *) "glClientWaitSync"); + ps->glWaitSyncProc = (f_WaitSync) + glXGetProcAddress((const GLubyte *) "glWaitSync"); + ps->glImportSyncEXT = (f_ImportSyncEXT) + glXGetProcAddress((const GLubyte *) "glImportSyncEXT"); + if (!ps->glFenceSyncProc || !ps->glIsSyncProc || !ps->glDeleteSyncProc + || !ps->glClientWaitSyncProc || !ps->glWaitSyncProc + || !ps->glImportSyncEXT) { + printf_errf("(): Failed to acquire GLX sync functions."); + goto glx_init_end; + } +#endif + } + + // Acquire FBConfigs + if (need_render && !glx_update_fbconfig(ps)) + goto glx_init_end; + + // Render preparations + if (need_render) { + glx_on_root_change(ps); + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_BLEND); + + if (!ps->o.glx_no_stencil) { + // Initialize stencil buffer + glClear(GL_STENCIL_BUFFER_BIT); + glDisable(GL_STENCIL_TEST); + glStencilMask(0x1); + glStencilFunc(GL_EQUAL, 0x1, 0x1); + } + + // Clear screen + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // glXSwapBuffers(ps->dpy, get_tgt_window(ps)); + } + + success = true; + +glx_init_end: + cxfree(pvis); + + if (!success) + glx_destroy(ps); + + return success; +} + +/** + * Destroy GLX related resources. + */ +void +glx_destroy(session_t *ps) { +#ifdef CONFIG_VSYNC_OPENGL_GLSL + // Free GLSL shaders/programs + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + glx_blur_pass_t *ppass = &ps->glx_blur_passes[i]; + if (ppass->frag_shader) + glDeleteShader(ppass->frag_shader); + if (ppass->prog) + glDeleteProgram(ppass->prog); + } +#endif + + // Free FBConfigs + for (int i = 0; i <= OPENGL_MAX_DEPTH; ++i) { + free(ps->glx_fbconfigs[i]); + ps->glx_fbconfigs[i] = NULL; + } + + // Destroy GLX context + if (ps->glx_context) { + glXDestroyContext(ps->dpy, ps->glx_context); + ps->glx_context = NULL; + } +} + +/** + * Callback to run on root window size change. + */ +void +glx_on_root_change(session_t *ps) { + glViewport(0, 0, ps->root_width, ps->root_height); + + // Initialize matrix, copied from dcompmgr + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, ps->root_width, 0, ps->root_height, -1000.0, 1000.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +/** + * Initialize GLX blur filter. + */ +bool +glx_init_blur(session_t *ps) { + assert(ps->o.blur_kerns[0]); + + // Allocate PBO if more than one blur kernel is present + if (ps->o.blur_kerns[1]) { +#ifdef CONFIG_VSYNC_OPENGL_FBO + // Try to generate a framebuffer + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + if (!fbo) { + printf_errf("(): Failed to generate Framebuffer. Cannot do " + "multi-pass blur with GLX backend."); + return false; + } + glDeleteFramebuffers(1, &fbo); +#else + printf_errf("(): FBO support not compiled in. Cannot do multi-pass blur " + "with GLX backend."); + return false; +#endif + } + +#ifdef CONFIG_VSYNC_OPENGL_GLSL + { + char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + static const char *FRAG_SHADER_BLUR_PREFIX = + "#version 110\n" + "%s" + "uniform float offset_x;\n" + "uniform float offset_y;\n" + "uniform float factor_center;\n" + "uniform %s tex_scr;\n" + "\n" + "void main() {\n" + " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);\n"; + static const char *FRAG_SHADER_BLUR_ADD = + " sum += float(%.7g) * %s(tex_scr, vec2(gl_TexCoord[0].x + offset_x * float(%d), gl_TexCoord[0].y + offset_y * float(%d)));\n"; + static const char *FRAG_SHADER_BLUR_ADD_GPUSHADER4 = + " sum += float(%.7g) * %sOffset(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y), ivec2(%d, %d));\n"; + static const char *FRAG_SHADER_BLUR_SUFFIX = + " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * factor_center;\n" + " gl_FragColor = sum / (factor_center + float(%.7g));\n" + "}\n"; + + const bool use_texture_rect = !ps->glx_has_texture_non_power_of_two; + const char *sampler_type = (use_texture_rect ? + "sampler2DRect": "sampler2D"); + const char *texture_func = (use_texture_rect ? + "texture2DRect": "texture2D"); + const char *shader_add = FRAG_SHADER_BLUR_ADD; + char *extension = mstrcpy(""); + if (use_texture_rect) + mstrextend(&extension, "#extension GL_ARB_texture_rectangle : require\n"); + if (ps->o.glx_use_gpushader4) { + mstrextend(&extension, "#extension GL_EXT_gpu_shader4 : require\n"); + shader_add = FRAG_SHADER_BLUR_ADD_GPUSHADER4; + } + + for (int i = 0; i < MAX_BLUR_PASS && ps->o.blur_kerns[i]; ++i) { + XFixed *kern = ps->o.blur_kerns[i]; + if (!kern) + break; + + glx_blur_pass_t *ppass = &ps->glx_blur_passes[i]; + + // Build shader + { + int wid = XFixedToDouble(kern[0]), hei = XFixedToDouble(kern[1]); + int nele = wid * hei - 1; + int len = strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) + strlen(extension) + (strlen(shader_add) + strlen(texture_func) + 42) * nele + strlen(FRAG_SHADER_BLUR_SUFFIX) + strlen(texture_func) + 12 + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + printf_errf("(): Failed to allocate %d bytes for shader string.", len); + return false; + } + { + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + double sum = 0.0; + for (int j = 0; j < hei; ++j) { + for (int k = 0; k < wid; ++k) { + if (hei / 2 == j && wid / 2 == k) + continue; + double val = XFixedToDouble(kern[2 + j * wid + k]); + if (0.0 == val) + continue; + sum += val; + sprintf(pc, shader_add, val, texture_func, k - wid / 2, j - hei / 2); + pc += strlen(pc); + assert(strlen(shader_str) < len); + } + } + + sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum); + assert(strlen(shader_str) < len); + } + ppass->frag_shader = glx_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + } + + if (!ppass->frag_shader) { + printf_errf("(): Failed to create fragment shader %d.", i); + return false; + } + + // Build program + ppass->prog = glx_create_program(&ppass->frag_shader, 1); + if (!ppass->prog) { + printf_errf("(): Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) { \ + ppass->target = glGetUniformLocation(ppass->prog, name); \ + if (ppass->target < 0) { \ + printf_errf("(): Failed to get location of %d-th uniform '" name "'. Might be troublesome.", i); \ + } \ + } + + P_GET_UNIFM_LOC("factor_center", unifm_factor_center); + if (!ps->o.glx_use_gpushader4) { + P_GET_UNIFM_LOC("offset_x", unifm_offset_x); + P_GET_UNIFM_LOC("offset_y", unifm_offset_y); + } +#undef P_GET_UNIFM_LOC + } + free(extension); + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + } + + + glx_check_err(ps); + + return true; +#else + printf_errf("(): GLSL support not compiled in. Cannot do blur with GLX backend."); + return false; +#endif +} + +/** + * @brief Update the FBConfig of given depth. + */ +static inline void +glx_update_fbconfig_bydepth(session_t *ps, int depth, glx_fbconfig_t *pfbcfg) { + // Make sure the depth is sane + if (depth < 0 || depth > OPENGL_MAX_DEPTH) + return; + + // Compare new FBConfig with current one + if (glx_cmp_fbconfig(ps, ps->glx_fbconfigs[depth], pfbcfg) < 0) { +#ifdef DEBUG_GLX + printf_dbgf("(%d): %#x overrides %#x, target %#x.\n", depth, (unsigned) pfbcfg->cfg, (ps->glx_fbconfigs[depth] ? (unsigned) ps->glx_fbconfigs[depth]->cfg: 0), pfbcfg->texture_tgts); +#endif + if (!ps->glx_fbconfigs[depth]) { + ps->glx_fbconfigs[depth] = malloc(sizeof(glx_fbconfig_t)); + allocchk(ps->glx_fbconfigs[depth]); + } + (*ps->glx_fbconfigs[depth]) = *pfbcfg; + } +} + +/** + * Get GLX FBConfigs for all depths. + */ +static bool +glx_update_fbconfig(session_t *ps) { + // Acquire all FBConfigs and loop through them + int nele = 0; + GLXFBConfig* pfbcfgs = glXGetFBConfigs(ps->dpy, ps->scr, &nele); + + for (GLXFBConfig *pcur = pfbcfgs; pcur < pfbcfgs + nele; pcur++) { + glx_fbconfig_t fbinfo = { + .cfg = *pcur, + .texture_fmt = 0, + .texture_tgts = 0, + .y_inverted = false, + }; + int id = (int) (pcur - pfbcfgs); + int depth = 0, depth_alpha = 0, val = 0; + + // Skip over multi-sampled visuals + // http://people.freedesktop.org/~glisse/0001-glx-do-not-use-multisample-visual-config-for-front-o.patch +#ifdef GLX_SAMPLES + if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_SAMPLES, &val) + && val > 1) + continue; +#endif + + if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BUFFER_SIZE, &depth) + || Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_ALPHA_SIZE, &depth_alpha)) { + printf_errf("(): Failed to retrieve buffer size and alpha size of FBConfig %d.", id); + continue; + } + if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &fbinfo.texture_tgts)) { + printf_errf("(): Failed to retrieve BIND_TO_TEXTURE_TARGETS_EXT of FBConfig %d.", id); + continue; + } + + int visualdepth = 0; + { + XVisualInfo *pvi = glXGetVisualFromFBConfig(ps->dpy, *pcur); + if (!pvi) { + // On nvidia-drivers-325.08 this happens slightly too often... + // printf_errf("(): Failed to retrieve X Visual of FBConfig %d.", id); + continue; + } + visualdepth = pvi->depth; + cxfree(pvi); + } + + bool rgb = false; + bool rgba = false; + + if (depth >= 32 && depth_alpha && Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_RGBA_EXT, &val) && val) + rgba = true; + + if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_RGB_EXT, &val) && val) + rgb = true; + + if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_Y_INVERTED_EXT, &val)) + fbinfo.y_inverted = val; + + { + int tgtdpt = depth - depth_alpha; + if (tgtdpt == visualdepth && tgtdpt < 32 && rgb) { + fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGB_EXT; + glx_update_fbconfig_bydepth(ps, tgtdpt, &fbinfo); + } + } + + if (depth == visualdepth && rgba) { + fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGBA_EXT; + glx_update_fbconfig_bydepth(ps, depth, &fbinfo); + } + } + + cxfree(pfbcfgs); + + // Sanity checks + if (!ps->glx_fbconfigs[ps->depth]) { + printf_errf("(): No FBConfig found for default depth %d.", ps->depth); + return false; + } + + if (!ps->glx_fbconfigs[32]) { + printf_errf("(): No FBConfig found for depth 32. Expect crazy things."); + } + +#ifdef DEBUG_GLX + printf_dbgf("(): %d-bit: %#3x, 32-bit: %#3x\n", + ps->depth, (int) ps->glx_fbconfigs[ps->depth]->cfg, + (int) ps->glx_fbconfigs[32]->cfg); +#endif + + return true; +} + +static inline int +glx_cmp_fbconfig_cmpattr(session_t *ps, + const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b, + int attr) { + int attr_a = 0, attr_b = 0; + + // TODO: Error checking + glXGetFBConfigAttrib(ps->dpy, pfbc_a->cfg, attr, &attr_a); + glXGetFBConfigAttrib(ps->dpy, pfbc_b->cfg, attr, &attr_b); + + return attr_a - attr_b; +} + +/** + * Compare two GLX FBConfig's to find the preferred one. + */ +static int +glx_cmp_fbconfig(session_t *ps, + const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b) { + int result = 0; + + if (!pfbc_a) + return -1; + if (!pfbc_b) + return 1; + +#define P_CMPATTR_LT(attr) { if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) return -result; } +#define P_CMPATTR_GT(attr) { if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) return result; } + + P_CMPATTR_LT(GLX_BIND_TO_TEXTURE_RGBA_EXT); + P_CMPATTR_LT(GLX_DOUBLEBUFFER); + P_CMPATTR_LT(GLX_STENCIL_SIZE); + P_CMPATTR_LT(GLX_DEPTH_SIZE); + P_CMPATTR_GT(GLX_BIND_TO_MIPMAP_TEXTURE_EXT); + + return 0; +} + +/** + * Bind a X pixmap to an OpenGL texture. + */ +bool +glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, Pixmap pixmap, + unsigned width, unsigned height, unsigned depth) { + if (!pixmap) { + printf_errf("(%#010lx): Binding to an empty pixmap. This can't work.", + pixmap); + return false; + } + + glx_texture_t *ptex = *pptex; + bool need_release = true; + + // Allocate structure + if (!ptex) { + static const glx_texture_t GLX_TEX_DEF = { + .texture = 0, + .glpixmap = 0, + .pixmap = 0, + .target = 0, + .width = 0, + .height = 0, + .depth = 0, + .y_inverted = false, + }; + + ptex = malloc(sizeof(glx_texture_t)); + allocchk(ptex); + memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); + *pptex = ptex; + } + + // Release pixmap if parameters are inconsistent + if (ptex->texture && ptex->pixmap != pixmap) { + glx_release_pixmap(ps, ptex); + } + + // Create GLX pixmap + if (!ptex->glpixmap) { + need_release = false; + + // Retrieve pixmap parameters, if they aren't provided + if (!(width && height && depth)) { + Window rroot = None; + int rx = 0, ry = 0; + unsigned rbdwid = 0; + if (!XGetGeometry(ps->dpy, pixmap, &rroot, &rx, &ry, + &width, &height, &rbdwid, &depth)) { + printf_errf("(%#010lx): Failed to query Pixmap info.", pixmap); + return false; + } + if (depth > OPENGL_MAX_DEPTH) { + printf_errf("(%d): Requested depth higher than %d.", depth, + OPENGL_MAX_DEPTH); + return false; + } + } + + const glx_fbconfig_t *pcfg = ps->glx_fbconfigs[depth]; + if (!pcfg) { + printf_errf("(%d): Couldn't find FBConfig with requested depth.", depth); + return false; + } + + // Determine texture target, copied from compiz + // The assumption we made here is the target never changes based on any + // pixmap-specific parameters, and this may change in the future + GLenum tex_tgt = 0; + if (GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts + && ps->glx_has_texture_non_power_of_two) + tex_tgt = GLX_TEXTURE_2D_EXT; + else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & pcfg->texture_tgts) + tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; + else if (!(GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts)) + tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; + else + tex_tgt = GLX_TEXTURE_2D_EXT; + +#ifdef DEBUG_GLX + printf_dbgf("(): depth %d, tgt %#x, rgba %d\n", depth, tex_tgt, + (GLX_TEXTURE_FORMAT_RGBA_EXT == pcfg->texture_fmt)); +#endif + + GLint attrs[] = { + GLX_TEXTURE_FORMAT_EXT, + pcfg->texture_fmt, + GLX_TEXTURE_TARGET_EXT, + tex_tgt, + 0, + }; + + ptex->glpixmap = glXCreatePixmap(ps->dpy, pcfg->cfg, pixmap, attrs); + ptex->pixmap = pixmap; + ptex->target = (GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D: + GL_TEXTURE_RECTANGLE); + ptex->width = width; + ptex->height = height; + ptex->depth = depth; + ptex->y_inverted = pcfg->y_inverted; + } + if (!ptex->glpixmap) { + printf_errf("(): Failed to allocate GLX pixmap."); + return false; + } + + glEnable(ptex->target); + + // Create texture + if (!ptex->texture) { + need_release = false; + + GLuint texture = 0; + glGenTextures(1, &texture); + glBindTexture(ptex->target, texture); + + glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(ptex->target, 0); + + ptex->texture = texture; + } + if (!ptex->texture) { + printf_errf("(): Failed to allocate texture."); + return false; + } + + glBindTexture(ptex->target, ptex->texture); + + // The specification requires rebinding whenever the content changes... + // We can't follow this, too slow. + if (need_release) + ps->glXReleaseTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); + + ps->glXBindTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); + + // Cleanup + glBindTexture(ptex->target, 0); + glDisable(ptex->target); + + glx_check_err(ps); + + return true; +} + +/** + * @brief Release binding of a texture. + */ +void +glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { + // Release binding + if (ptex->glpixmap && ptex->texture) { + glBindTexture(ptex->target, ptex->texture); + ps->glXReleaseTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); + glBindTexture(ptex->target, 0); + } + + // Free GLX Pixmap + if (ptex->glpixmap) { + glXDestroyPixmap(ps->dpy, ptex->glpixmap); + ptex->glpixmap = 0; + } + + glx_check_err(ps); +} + +/** + * Preprocess function before start painting. + */ +void +glx_paint_pre(session_t *ps, XserverRegion *preg) { + ps->glx_z = 0.0; + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Get buffer age + bool trace_damage = (ps->o.glx_swap_method < 0 || ps->o.glx_swap_method > 1); + + // Trace raw damage regions + XserverRegion newdamage = None; + if (trace_damage && *preg) + newdamage = copy_region(ps, *preg); + + // OpenGL doesn't support partial repaint without GLX_MESA_copy_sub_buffer, + // we could redraw the whole screen or copy unmodified pixels from + // front buffer with --glx-copy-from-front. + if (ps->o.glx_use_copysubbuffermesa || !*preg) { + } + else { + int buffer_age = ps->o.glx_swap_method; + + // Getting buffer age + { + // Query GLX_EXT_buffer_age for buffer age + if (SWAPM_BUFFER_AGE == buffer_age) { + unsigned val = 0; + glXQueryDrawable(ps->dpy, get_tgt_window(ps), + GLX_BACK_BUFFER_AGE_EXT, &val); + buffer_age = val; + } + + // Buffer age too high + if (buffer_age > CGLX_MAX_BUFFER_AGE + 1) + buffer_age = 0; + + // Make sure buffer age >= 0 + buffer_age = max_i(buffer_age, 0); + + // Check if we have we have empty regions + if (buffer_age > 1) { + for (int i = 0; i < buffer_age - 1; ++i) + if (!ps->all_damage_last[i]) { buffer_age = 0; break; } + } + } + + // Do nothing for buffer_age 1 (copy) + if (1 != buffer_age) { + // Copy pixels + if (ps->o.glx_copy_from_front) { + // Determine copy area + XserverRegion reg_copy = XFixesCreateRegion(ps->dpy, NULL, 0); + if (!buffer_age) { + XFixesSubtractRegion(ps->dpy, reg_copy, ps->screen_reg, *preg); + } + else { + for (int i = 0; i < buffer_age - 1; ++i) + XFixesUnionRegion(ps->dpy, reg_copy, reg_copy, + ps->all_damage_last[i]); + XFixesSubtractRegion(ps->dpy, reg_copy, reg_copy, *preg); + } + + // Actually copy pixels + { + GLfloat raster_pos[4]; + GLfloat curx = 0.0f, cury = 0.0f; + glGetFloatv(GL_CURRENT_RASTER_POSITION, raster_pos); + glReadBuffer(GL_FRONT); + glRasterPos2f(0.0, 0.0); + { + int nrects = 0; + XRectangle *rects = XFixesFetchRegion(ps->dpy, reg_copy, &nrects); + for (int i = 0; i < nrects; ++i) { + const int x = rects[i].x; + const int y = ps->root_height - rects[i].y - rects[i].height; + // Kwin patch says glRasterPos2f() causes artifacts on bottom + // screen edge with some drivers + glBitmap(0, 0, 0, 0, x - curx, y - cury, NULL); + curx = x; + cury = y; + glCopyPixels(x, y, rects[i].width, rects[i].height, GL_COLOR); + } + cxfree(rects); + } + glReadBuffer(GL_BACK); + glRasterPos4fv(raster_pos); + } + + free_region(ps, ®_copy); + } + + // Determine paint area + if (ps->o.glx_copy_from_front) { } + else if (buffer_age) { + for (int i = 0; i < buffer_age - 1; ++i) + XFixesUnionRegion(ps->dpy, *preg, *preg, ps->all_damage_last[i]); + } + else { + free_region(ps, preg); + } + } + } + + if (trace_damage) { + free_region(ps, &ps->all_damage_last[CGLX_MAX_BUFFER_AGE - 1]); + memmove(ps->all_damage_last + 1, ps->all_damage_last, + (CGLX_MAX_BUFFER_AGE - 1) * sizeof(XserverRegion)); + ps->all_damage_last[0] = newdamage; + } + + glx_set_clip(ps, *preg, NULL); + +#ifdef DEBUG_GLX_PAINTREG + glx_render_color(ps, 0, 0, ps->root_width, ps->root_height, 0, *preg, NULL); +#endif + + glx_check_err(ps); +} + +/** + * Set clipping region on the target window. + */ +void +glx_set_clip(session_t *ps, XserverRegion reg, const reg_data_t *pcache_reg) { + // Quit if we aren't using stencils + if (ps->o.glx_no_stencil) + return; + + static XRectangle rect_blank = { .x = 0, .y = 0, .width = 0, .height = 0 }; + + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + + if (!reg) + return; + + int nrects = 0; + XRectangle *rects_free = NULL; + const XRectangle *rects = NULL; + if (pcache_reg) { + rects = pcache_reg->rects; + nrects = pcache_reg->nrects; + } + if (!rects) { + nrects = 0; + rects = rects_free = XFixesFetchRegion(ps->dpy, reg, &nrects); + } + // Use one empty rectangle if the region is empty + if (!nrects) { + cxfree(rects_free); + rects_free = NULL; + nrects = 1; + rects = &rect_blank; + } + + assert(nrects); + if (1 == nrects) { + glEnable(GL_SCISSOR_TEST); + glScissor(rects[0].x, ps->root_height - rects[0].y - rects[0].height, + rects[0].width, rects[0].height); + } + else { + glEnable(GL_STENCIL_TEST); + glClear(GL_STENCIL_BUFFER_BIT); + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDepthMask(GL_FALSE); + glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); + + glBegin(GL_QUADS); + + for (int i = 0; i < nrects; ++i) { + GLint rx = rects[i].x; + GLint ry = ps->root_height - rects[i].y; + GLint rxe = rx + rects[i].width; + GLint rye = ry - rects[i].height; + GLint z = 0; + +#ifdef DEBUG_GLX + printf_dbgf("(): Rect %d: %d, %d, %d, %d\n", i, rx, ry, rxe, rye); +#endif + + glVertex3i(rx, ry, z); + glVertex3i(rxe, ry, z); + glVertex3i(rxe, rye, z); + glVertex3i(rx, rye, z); + } + + glEnd(); + + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + // glDepthMask(GL_TRUE); + } + + cxfree(rects_free); + + glx_check_err(ps); +} + +#define P_PAINTREG_START() \ + XserverRegion reg_new = None; \ + XRectangle rec_all = { .x = dx, .y = dy, .width = width, .height = height }; \ + XRectangle *rects = &rec_all; \ + int nrects = 1; \ + \ + if (ps->o.glx_no_stencil && reg_tgt) { \ + if (pcache_reg) { \ + rects = pcache_reg->rects; \ + nrects = pcache_reg->nrects; \ + } \ + else { \ + reg_new = XFixesCreateRegion(ps->dpy, &rec_all, 1); \ + XFixesIntersectRegion(ps->dpy, reg_new, reg_new, reg_tgt); \ + \ + nrects = 0; \ + rects = XFixesFetchRegion(ps->dpy, reg_new, &nrects); \ + } \ + } \ + glBegin(GL_QUADS); \ + \ + for (int ri = 0; ri < nrects; ++ri) { \ + XRectangle crect; \ + rect_crop(&crect, &rects[ri], &rec_all); \ + \ + if (!crect.width || !crect.height) \ + continue; \ + +#define P_PAINTREG_END() \ + } \ + glEnd(); \ + \ + if (rects && rects != &rec_all && !(pcache_reg && pcache_reg->rects == rects)) \ + cxfree(rects); \ + free_region(ps, ®_new); \ + +static inline GLuint +glx_gen_texture(session_t *ps, GLenum tex_tgt, int width, int height) { + GLuint tex = 0; + glGenTextures(1, &tex); + if (!tex) return 0; + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex); + glTexParameteri(tex_tgt, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(tex_tgt, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(tex_tgt, 0, GL_RGB, width, height, 0, GL_RGB, + GL_UNSIGNED_BYTE, NULL); + glBindTexture(tex_tgt, 0); + + return tex; +} + +static inline void +glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int basex, int basey, + int dx, int dy, int width, int height) { + if (width > 0 && height > 0) + glCopyTexSubImage2D(tex_tgt, 0, dx - basex, dy - basey, + dx, ps->root_height - dy - height, width, height); +} + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +/** + * Blur contents in a particular region. + */ +bool +glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, + GLfloat factor_center, + XserverRegion reg_tgt, const reg_data_t *pcache_reg, + glx_blur_cache_t *pbc) { + assert(ps->glx_blur_passes[0].prog); + const bool more_passes = ps->glx_blur_passes[1].prog; + const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); + const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); + bool ret = false; + + // Calculate copy region size + glx_blur_cache_t ibc = { .width = 0, .height = 0 }; + if (!pbc) + pbc = &ibc; + + int mdx = dx, mdy = dy, mwidth = width, mheight = height; +#ifdef DEBUG_GLX + printf_dbgf("(): %d, %d, %d, %d\n", mdx, mdy, mwidth, mheight); +#endif + + /* + if (ps->o.resize_damage > 0) { + int inc_x = 0, inc_y = 0; + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + XFixed *kern = ps->o.blur_kerns[i]; + if (!kern) break; + inc_x += XFixedToDouble(kern[0]) / 2; + inc_y += XFixedToDouble(kern[1]) / 2; + } + inc_x = min_i(ps->o.resize_damage, inc_x); + inc_y = min_i(ps->o.resize_damage, inc_y); + + mdx = max_i(dx - inc_x, 0); + mdy = max_i(dy - inc_y, 0); + int mdx2 = min_i(dx + width + inc_x, ps->root_width), + mdy2 = min_i(dy + height + inc_y, ps->root_height); + mwidth = mdx2 - mdx; + mheight = mdy2 - mdy; + } + */ + + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (ps->glx_has_texture_non_power_of_two) + tex_tgt = GL_TEXTURE_2D; + + // Free textures if size inconsistency discovered + if (mwidth != pbc->width || mheight != pbc->height) + free_glx_bc_resize(ps, pbc); + + // Generate FBO and textures if needed + if (!pbc->textures[0]) + pbc->textures[0] = glx_gen_texture(ps, tex_tgt, mwidth, mheight); + GLuint tex_scr = pbc->textures[0]; + if (more_passes && !pbc->textures[1]) + pbc->textures[1] = glx_gen_texture(ps, tex_tgt, mwidth, mheight); + pbc->width = mwidth; + pbc->height = mheight; + GLuint tex_scr2 = pbc->textures[1]; +#ifdef CONFIG_VSYNC_OPENGL_FBO + if (more_passes && !pbc->fbo) + glGenFramebuffers(1, &pbc->fbo); + const GLuint fbo = pbc->fbo; +#endif + + if (!tex_scr || (more_passes && !tex_scr2)) { + printf_errf("(): Failed to allocate texture."); + goto glx_blur_dst_end; + } +#ifdef CONFIG_VSYNC_OPENGL_FBO + if (more_passes && !fbo) { + printf_errf("(): Failed to allocate framebuffer."); + goto glx_blur_dst_end; + } +#endif + + // Read destination pixels into a texture + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex_scr); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); + /* + if (tex_scr2) { + glBindTexture(tex_tgt, tex_scr2); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, dx - mdx); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy + height, + mwidth, mdy + mheight - dy - height); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy, dx - mdx, height); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, dx + width, dy, + mdx + mwidth - dx - width, height); + } */ + + // Texture scaling factor + GLfloat texfac_x = 1.0f, texfac_y = 1.0f; + if (GL_TEXTURE_2D == tex_tgt) { + texfac_x /= mwidth; + texfac_y /= mheight; + } + + // Paint it back + if (more_passes) { + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + } + + bool last_pass = false; + for (int i = 0; !last_pass; ++i) { + last_pass = !ps->glx_blur_passes[i + 1].prog; + assert(i < MAX_BLUR_PASS - 1); + const glx_blur_pass_t *ppass = &ps->glx_blur_passes[i]; + assert(ppass->prog); + + assert(tex_scr); + glBindTexture(tex_tgt, tex_scr); + +#ifdef CONFIG_VSYNC_OPENGL_FBO + if (!last_pass) { + static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 }; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex_scr2, 0); + glDrawBuffers(1, DRAWBUFS); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) + != GL_FRAMEBUFFER_COMPLETE) { + printf_errf("(): Framebuffer attachment failed."); + goto glx_blur_dst_end; + } + } + else { + static const GLenum DRAWBUFS[2] = { GL_BACK }; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDrawBuffers(1, DRAWBUFS); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + } +#endif + + // Color negation for testing... + // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + // glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); + // glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(ppass->prog); + if (ppass->unifm_offset_x >= 0) + glUniform1f(ppass->unifm_offset_x, texfac_x); + if (ppass->unifm_offset_y >= 0) + glUniform1f(ppass->unifm_offset_y, texfac_y); + if (ppass->unifm_factor_center >= 0) + glUniform1f(ppass->unifm_factor_center, factor_center); + + { + P_PAINTREG_START(); + { + const GLfloat rx = (crect.x - mdx) * texfac_x; + const GLfloat ry = (mheight - (crect.y - mdy)) * texfac_y; + const GLfloat rxe = rx + crect.width * texfac_x; + const GLfloat rye = ry - crect.height * texfac_y; + GLfloat rdx = crect.x - mdx; + GLfloat rdy = mheight - crect.y + mdy; + GLfloat rdxe = rdx + crect.width; + GLfloat rdye = rdy - crect.height; + + if (last_pass) { + rdx = crect.x; + rdy = ps->root_height - crect.y; + rdxe = rdx + crect.width; + rdye = rdy - crect.height; + } + +#ifdef DEBUG_GLX + printf_dbgf("(): %f, %f, %f, %f -> %f, %f, %f, %f\n", rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); +#endif + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + glUseProgram(0); + + // Swap tex_scr and tex_scr2 + { + GLuint tmp = tex_scr2; + tex_scr2 = tex_scr; + tex_scr = tmp; + } + } + + ret = true; + +glx_blur_dst_end: +#ifdef CONFIG_VSYNC_OPENGL_FBO + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif + glBindTexture(tex_tgt, 0); + glDisable(tex_tgt); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + if (&ibc == pbc) { + free_glx_bc(ps, pbc); + } + + glx_check_err(ps); + + return ret; +} +#endif + +bool +glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, + GLfloat factor, XserverRegion reg_tgt, const reg_data_t *pcache_reg) { + // It's possible to dim in glx_render(), but it would be over-complicated + // considering all those mess in color negation and modulation + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.0f, 0.0f, 0.0f, factor); + + { + P_PAINTREG_START(); + { + GLint rdx = crect.x; + GLint rdy = ps->root_height - crect.y; + GLint rdxe = rdx + crect.width; + GLint rdye = rdy - crect.height; + + glVertex3i(rdx, rdy, z); + glVertex3i(rdxe, rdy, z); + glVertex3i(rdxe, rdye, z); + glVertex3i(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + glEnd(); + + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + glDisable(GL_BLEND); + + glx_check_err(ps); + + return true; +} + +/** + * @brief Render a region with texture data. + */ +bool +glx_render(session_t *ps, const glx_texture_t *ptex, + int x, int y, int dx, int dy, int width, int height, int z, + double opacity, bool neg, + XserverRegion reg_tgt, const reg_data_t *pcache_reg) { + if (!ptex || !ptex->texture) { + printf_errf("(): Missing texture."); + return false; + } + +#ifdef DEBUG_GLX_PAINTREG + glx_render_dots(ps, dx, dy, width, height, z, reg_tgt, pcache_reg); + return true; +#endif + + const bool argb = (GLX_TEXTURE_FORMAT_RGBA_EXT == + ps->glx_fbconfigs[ptex->depth]->texture_fmt); + bool dual_texture = false; + + // It's required by legacy versions of OpenGL to enable texture target + // before specifying environment. Thanks to madsy for telling me. + glEnable(ptex->target); + + // Enable blending if needed + if (opacity < 1.0 || argb) { + + glEnable(GL_BLEND); + + // Needed for handling opacity of ARGB texture + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // This is all weird, but X Render is using premultiplied ARGB format, and + // we need to use those things to correct it. Thanks to derhass for help. + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(opacity, opacity, opacity, opacity); + } + + // Color negation + if (neg) { + // Simple color negation + if (!glIsEnabled(GL_BLEND)) { + glEnable(GL_COLOR_LOGIC_OP); + glLogicOp(GL_COPY_INVERTED); + } + // ARGB texture color negation + else if (argb) { + dual_texture = true; + + // Use two texture stages because the calculation is too complicated, + // thanks to madsy for providing code + // Texture stage 0 + glActiveTexture(GL_TEXTURE0); + + // Negation for premultiplied color: color = A - C + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + + // Pass texture alpha through + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); + + // Texture stage 1 + glActiveTexture(GL_TEXTURE1); + glEnable(ptex->target); + glBindTexture(ptex->target, ptex->texture); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + // Modulation with constant factor + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA); + + // Modulation with constant factor + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); + + glActiveTexture(GL_TEXTURE0); + } + // RGB blend color negation + else { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + // Modulation with constant factor + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + + // Modulation with constant factor + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); + } + } + +#ifdef DEBUG_GLX + printf_dbgf("(): Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", x, y, width, height, dx, dy, ptex->width, ptex->height, z); +#endif + + // Bind texture + glBindTexture(ptex->target, ptex->texture); + if (dual_texture) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(ptex->target, ptex->texture); + glActiveTexture(GL_TEXTURE0); + } + + // Painting + { + P_PAINTREG_START(); + { + GLfloat rx = (double) (crect.x - dx + x); + GLfloat ry = (double) (crect.y - dy + y); + GLfloat rxe = rx + (double) crect.width; + GLfloat rye = ry + (double) crect.height; + // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] [0-1] + // Thanks to amonakov for pointing out! + if (GL_TEXTURE_2D == ptex->target) { + rx = rx / ptex->width; + ry = ry / ptex->height; + rxe = rxe / ptex->width; + rye = rye / ptex->height; + } + GLint rdx = crect.x; + GLint rdy = ps->root_height - crect.y; + GLint rdxe = rdx + crect.width; + GLint rdye = rdy - crect.height; + + // Invert Y if needed, this may not work as expected, though. I don't + // have such a FBConfig to test with. + if (!ptex->y_inverted) { + ry = 1.0 - ry; + rye = 1.0 - rye; + } + +#ifdef DEBUG_GLX + printf_dbgf("(): Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d\n", ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); +#endif + +#define P_TEXCOORD(cx, cy) { \ + if (dual_texture) { \ + glMultiTexCoord2f(GL_TEXTURE0, cx, cy); \ + glMultiTexCoord2f(GL_TEXTURE1, cx, cy); \ + } \ + else glTexCoord2f(cx, cy); \ +} + P_TEXCOORD(rx, ry); + glVertex3i(rdx, rdy, z); + + P_TEXCOORD(rxe, ry); + glVertex3i(rdxe, rdy, z); + + P_TEXCOORD(rxe, rye); + glVertex3i(rdxe, rdye, z); + + P_TEXCOORD(rx, rye); + glVertex3i(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + // Cleanup + glBindTexture(ptex->target, 0); + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_BLEND); + glDisable(GL_COLOR_LOGIC_OP); + glDisable(ptex->target); + + if (dual_texture) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(ptex->target, 0); + glDisable(ptex->target); + glActiveTexture(GL_TEXTURE0); + } + + glx_check_err(ps); + + return true; +} + +/** + * @brief Render a region with a specified color. + */ +bool +glx_render_specified_color(session_t *ps, int color, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg) { + + glColor4f(color, + color, + color, + 1.0f + ); + + { + P_PAINTREG_START(); + { + GLint rdx = crect.x; + GLint rdy = ps->root_height - crect.y; + GLint rdxe = rdx + crect.width; + GLint rdye = rdy - crect.height; + + glVertex3i(rdx, rdy, z); + glVertex3i(rdxe, rdy, z); + glVertex3i(rdxe, rdye, z); + glVertex3i(rdx, rdye, z); + } + P_PAINTREG_END(); + } + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + + glx_check_err(ps); + + return true; +} + +/** + * Render a region with color. + */ +static void +glx_render_color(session_t *ps, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg) { + static int color = 0; + + color = color % (3 * 3 * 3 - 1) + 1; + glColor4f(1.0 / 3.0 * (color / (3 * 3)), + 1.0 / 3.0 * (color % (3 * 3) / 3), + 1.0 / 3.0 * (color % 3), + 1.0f + ); + z -= 0.2; + + { + P_PAINTREG_START(); + { + GLint rdx = crect.x; + GLint rdy = ps->root_height - crect.y; + GLint rdxe = rdx + crect.width; + GLint rdye = rdy - crect.height; + + glVertex3i(rdx, rdy, z); + glVertex3i(rdxe, rdy, z); + glVertex3i(rdxe, rdye, z); + glVertex3i(rdx, rdye, z); + } + P_PAINTREG_END(); + } + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + + glx_check_err(ps); +} + +/** + * Render a region with dots. + */ +static void +glx_render_dots(session_t *ps, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg) { + glColor4f(0.0f, 0.0f, 0.0f, 1.0f); + z -= 0.1; + + { + P_PAINTREG_START(); + { + static const GLint BLK_WID = 5, BLK_HEI = 5; + + glEnd(); + glPointSize(1.0); + glBegin(GL_POINTS); + + GLint rdx = crect.x; + GLint rdy = ps->root_height - crect.y; + GLint rdxe = rdx + crect.width; + GLint rdye = rdy - crect.height; + rdx = (rdx) / BLK_WID * BLK_WID; + rdy = (rdy) / BLK_HEI * BLK_HEI; + rdxe = (rdxe) / BLK_WID * BLK_WID; + rdye = (rdye) / BLK_HEI * BLK_HEI; + + for (GLint cdx = rdx; cdx < rdxe; cdx += BLK_WID) + for (GLint cdy = rdy; cdy > rdye; cdy -= BLK_HEI) + glVertex3i(cdx + BLK_WID / 2, cdy - BLK_HEI / 2, z); + } + P_PAINTREG_END(); + } + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + + glx_check_err(ps); +} + +/** + * Swap buffer with glXCopySubBufferMESA(). + */ +void +glx_swap_copysubbuffermesa(session_t *ps, XserverRegion reg) { + int nrects = 0; + XRectangle *rects = XFixesFetchRegion(ps->dpy, reg, &nrects); + + if (1 == nrects && rect_is_fullscreen(ps, rects[0].x, rects[0].y, + rects[0].width, rects[0].height)) { + glXSwapBuffers(ps->dpy, get_tgt_window(ps)); + } + else { + glx_set_clip(ps, None, NULL); + for (int i = 0; i < nrects; ++i) { + const int x = rects[i].x; + const int y = ps->root_height - rects[i].y - rects[i].height; + const int wid = rects[i].width; + const int hei = rects[i].height; + +#ifdef DEBUG_GLX + printf_dbgf("(): %d, %d, %d, %d\n", x, y, wid, hei); +#endif + ps->glXCopySubBufferProc(ps->dpy, get_tgt_window(ps), x, y, wid, hei); + } + } + + glx_check_err(ps); + + cxfree(rects); +} + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +GLuint +glx_create_shader(GLenum shader_type, const char *shader_str) { +#ifdef DEBUG_GLX_GLSL + printf("glx_create_shader(): ===\n%s\n===\n", shader_str); + fflush(stdout); +#endif + + bool success = false; + GLuint shader = glCreateShader(shader_type); + if (!shader) { + printf_errf("(): Failed to create shader with type %d.", shader_type); + goto glx_create_shader_end; + } + glShaderSource(shader, 1, &shader_str, NULL); + glCompileShader(shader); + + // Get shader status + { + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetShaderInfoLog(shader, log_len, NULL, log); + printf_errf("(): Failed to compile shader with type %d: %s", + shader_type, log); + } + goto glx_create_shader_end; + } + } + + success = true; + +glx_create_shader_end: + if (shader && !success) { + glDeleteShader(shader); + shader = 0; + } + + return shader; +} + +GLuint +glx_create_program(const GLuint * const shaders, int nshaders) { + bool success = false; + GLuint program = glCreateProgram(); + if (!program) { + printf_errf("(): Failed to create program."); + goto glx_create_program_end; + } + + for (int i = 0; i < nshaders; ++i) + glAttachShader(program, shaders[i]); + glLinkProgram(program); + + // Get program status + { + GLint status = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetProgramInfoLog(program, log_len, NULL, log); + printf_errf("(): Failed to link program: %s", log); + } + goto glx_create_program_end; + } + } + success = true; + +glx_create_program_end: + if (program) { + for (int i = 0; i < nshaders; ++i) + glDetachShader(program, shaders[i]); + } + if (program && !success) { + glDeleteProgram(program); + program = 0; + } + + return program; +} +#endif + diff --git a/twin/compton-tde/opengl.h b/twin/compton-tde/opengl.h new file mode 100644 index 000000000..8628e36d3 --- /dev/null +++ b/twin/compton-tde/opengl.h @@ -0,0 +1,145 @@ +/* + * 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 <ctype.h> +#include <locale.h> + +#ifdef DEBUG_GLX_ERR + +/** + * Get a textual representation of an OpenGL error. + */ +static inline const char * +glx_dump_err_str(GLenum err) { + switch (err) { + CASESTRRET(GL_NO_ERROR); + CASESTRRET(GL_INVALID_ENUM); + CASESTRRET(GL_INVALID_VALUE); + CASESTRRET(GL_INVALID_OPERATION); + CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION); + CASESTRRET(GL_OUT_OF_MEMORY); + CASESTRRET(GL_STACK_UNDERFLOW); + CASESTRRET(GL_STACK_OVERFLOW); + } + + return NULL; +} + +/** + * Check for GLX error. + * + * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ + */ +static inline void +glx_check_err_(session_t *ps, const char *func, int line) { + if (!ps->glx_context) return; + + GLenum err = GL_NO_ERROR; + + while (GL_NO_ERROR != (err = glGetError())) { + print_timestamp(ps); + printf("%s():%d: GLX error ", func, line); + const char *errtext = glx_dump_err_str(err); + if (errtext) { + printf_dbg("%s\n", errtext); + } + else { + printf_dbg("%d\n", err); + } + } +} + +#define glx_check_err(ps) glx_check_err_(ps, __func__, __LINE__) +#else +#define glx_check_err(ps) ((void) 0) +#endif + +/** + * Check if a word is in string. + */ +static inline bool +wd_is_in_str(const char *haystick, const char *needle) { + if (!haystick) + return false; + + assert(*needle); + + const char *pos = haystick - 1; + while ((pos = strstr(pos + 1, needle))) { + // Continue if it isn't a word boundary + if (((pos - haystick) && !isspace(*(pos - 1))) + || (strlen(pos) > strlen(needle) && !isspace(pos[strlen(needle)]))) + continue; + return true; + } + + return false; +} + +/** + * Check if a GLX extension exists. + */ +static inline bool +glx_hasglxext(session_t *ps, const char *ext) { + const char *glx_exts = glXQueryExtensionsString(ps->dpy, ps->scr); + if (!glx_exts) { + printf_errf("(): Failed get GLX extension list."); + return false; + } + + bool found = wd_is_in_str(glx_exts, ext); + if (!found) + printf_errf("(): Missing GLX extension %s.", ext); + + return found; +} + +/** + * Check if a GLX extension exists. + */ +static inline bool +glx_hasglext(session_t *ps, const char *ext) { + const char *gl_exts = (const char *) glGetString(GL_EXTENSIONS); + if (!gl_exts) { + printf_errf("(): Failed get GL extension list."); + return false; + } + + bool found = wd_is_in_str(gl_exts, ext); + if (!found) + printf_errf("(): Missing GL extension %s.", ext); + + return found; +} + +static inline XVisualInfo * +get_visualinfo_from_visual(session_t *ps, Visual *visual) { + XVisualInfo vreq = { .visualid = XVisualIDFromVisual(visual) }; + int nitems = 0; + + return XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems); +} + +static bool +glx_update_fbconfig(session_t *ps); + +static int +glx_cmp_fbconfig(session_t *ps, + const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b); + +static void +glx_render_color(session_t *ps, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg); + +static void +glx_render_dots(session_t *ps, int dx, int dy, int width, int height, int z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg); |