diff --git a/CHANGELOG.md b/CHANGELOG.md index 15fad6c7e2..f1bd759415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Universal window rules (#1284). One option to rule them all! Added new configuration option `rules` to replace all existing rule options, and to provide more flexibility on top of that. See [picom(1)](https://picom.app/#_window_rules) for more details. * `@include` directives in config file now also search in `$XDG_CONFIG_HOME/picom/include` and `$XDG_CONFIG_DIRS/picom/include`, in addition to relative to the config file's parent directory. * Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170) -* New `picom-inspect` tool, which lets you test out your picom rules. Sample output: +* New `picom-inspect` tool, which lets you test out your picom rules. `man picom-inspect(1)` for more details. Sample output: ``` ... diff --git a/man/picom-inspect.1.adoc b/man/picom-inspect.1.adoc index f27fa8408b..7b33a8893a 100644 --- a/man/picom-inspect.1.adoc +++ b/man/picom-inspect.1.adoc @@ -19,12 +19,16 @@ DESCRIPTION OPTIONS ------- -*picom-inspect* accept the exact same set of options as *picom*. Naturally, most of those options will not be relevant. +*picom-inspect* accepts all options that *picom* does. Naturally, most of those options will not be relevant. These are some of the options you might find useful (See *picom*(1) for descriptions of what they do): *--config*, *--log-level*, *--log-file*, all the options related to rules. +*picom-inspect* also accepts some extra options: ::: + + *--monitor*:: Keep *picom-inspect* running in a loop, and dump information every time something changed about a window. + NOTES ----- *picom-inspect* is prototype right now. If you find any bug, for example, if rules are matched differently compared to *picom*, please submit bug reports to: diff --git a/src/c2.c b/src/c2.c index 0654e07cee..5f80f3e3fb 100644 --- a/src/c2.c +++ b/src/c2.c @@ -38,18 +38,24 @@ #define C2_MAX_LEVELS 10 -typedef struct _c2_b c2_b_t; -typedef struct _c2_l c2_l_t; +typedef struct c2_condition_node_branch c2_condition_node_branch; +typedef struct c2_condition_node_leaf c2_condition_node_leaf; -/// Pointer to a condition tree. +enum c2_condition_node_type { + C2_NODE_TYPE_BRANCH, + C2_NODE_TYPE_LEAF, + C2_NODE_TYPE_TRUE, +}; + +/// Fat, typed pointer to a condition tree node. typedef struct { - bool isbranch : 1; - bool istrue : 1; + enum c2_condition_node_type type; union { - c2_b_t *b; - c2_l_t *l; + struct c2_condition_node_branch *b; + struct c2_condition_node_leaf *l; }; -} c2_ptr_t; + bool neg; +} c2_condition_node_ptr; struct c2_tracked_property_key { xcb_atom_t property; @@ -102,9 +108,8 @@ struct c2_property_value { }; /// Initializer for c2_ptr_t. -static const c2_ptr_t C2_PTR_INIT = { - .isbranch = false, - .istrue = false, +static const c2_condition_node_ptr C2_NODE_PTR_INIT = { + .type = C2_NODE_TYPE_LEAF, .l = NULL, }; @@ -117,11 +122,10 @@ typedef enum { } c2_b_op_t; /// Structure for branch element in a window condition -struct _c2_b { - bool neg : 1; +struct c2_condition_node_branch { c2_b_op_t op; - c2_ptr_t opr1; - c2_ptr_t opr2; + c2_condition_node_ptr opr1; + c2_condition_node_ptr opr2; }; /// Initializer for c2_b_t. @@ -129,8 +133,7 @@ struct _c2_b { {.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; +struct c2_condition_node_leaf { enum { C2_L_OEXISTS = 0, C2_L_OEQ, @@ -199,8 +202,7 @@ struct _c2_l { static const unsigned int C2_L_INVALID_TARGET_ID = UINT_MAX; /// Initializer for c2_l_t. -static const c2_l_t C2_L_INIT = { - .neg = false, +static const c2_condition_node_leaf C2_LEAF_NODE_INIT = { .op = C2_L_OEXISTS, .match = C2_L_MEXACT, .match_ignorecase = false, @@ -216,20 +218,15 @@ static const c2_l_t C2_L_INIT = { }; /// Linked list type of conditions. -struct _c2_lptr { - c2_ptr_t ptr; +struct c2_condition { + c2_condition_node_ptr root; void *data; - struct _c2_lptr *next; + struct list_node siblings; }; -/// Structure representing a predefined target. -typedef struct { - const char *name; -} c2_predef_t; - // clang-format off // Predefined targets. -static struct { +static const struct { const char *name; bool is_string; bool deprecated; @@ -285,30 +282,14 @@ static inline int strcmp_wd(const char *needle, const char *src) { return 0; } -/** - * Return whether a c2_ptr_t is empty. - */ -static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) { - return !(p.isbranch ? (bool)p.b : (bool)p.l); -} - -/** - * Reset a c2_ptr_t. - */ -static inline void c2_ptr_reset(c2_ptr_t *pp) { - if (pp) { - *pp = C2_PTR_INIT; - } -} - /** * Combine two condition trees. */ -static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) { - c2_ptr_t p = {.isbranch = true, .b = NULL}; - p.b = cmalloc(c2_b_t); +static inline c2_condition_node_ptr +c2h_comb_tree(c2_b_op_t op, c2_condition_node_ptr p1, c2_condition_node_ptr p2) { + c2_condition_node_ptr p = {.type = C2_NODE_TYPE_BRANCH, .b = NULL}; + p.b = cmalloc(struct c2_condition_node_branch); - p.b->neg = false; p.b->op = op; p.b->opr1 = p1; p.b->opr2 = p2; @@ -343,49 +324,44 @@ static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) { return c2h_b_opp(op1) - c2h_b_opp(op2); } -static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level); - -static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult); - -static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult); - -static void c2_free(c2_ptr_t p); - -static size_t c2_condition_to_str(c2_ptr_t p, char *output, size_t len); -static const char *c2_condition_to_str2(c2_ptr_t ptr); +static int +c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, int level); +static int c2_parse_target(const char *pattern, int offset, c2_condition_node_ptr *presult); +static int c2_parse_op(const char *pattern, int offset, c2_condition_node_ptr *presult); +static int c2_parse_pattern(const char *pattern, int offset, c2_condition_node_ptr *presult); +static int c2_parse_legacy(const char *pattern, int offset, c2_condition_node_ptr *presult); +static void c2_free(c2_condition_node_ptr p); +static size_t c2_condition_node_to_str(c2_condition_node_ptr p, char *output, size_t len); +static const char *c2_condition_node_to_str2(c2_condition_node_ptr ptr); +static bool +c2_tree_postprocess(struct c2_state *state, xcb_connection_t *c, c2_condition_node_ptr node); /** * Wrapper of c2_free(). */ -static inline void c2_freep(c2_ptr_t *pp) { +static inline void c2_freep(c2_condition_node_ptr *pp) { if (pp) { c2_free(*pp); - c2_ptr_reset(pp); + *pp = C2_NODE_PTR_INIT; } } -static const char *c2h_dump_str_tgt(const c2_l_t *pleaf); - /** * Parse a condition string. */ -c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { +struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void *data) { if (!pattern) { return NULL; } // Parse the pattern - c2_ptr_t result = C2_PTR_INIT; + auto result = C2_NODE_PTR_INIT; int offset = -1; if (strlen(pattern) >= 2 && ':' == pattern[1]) { offset = c2_parse_legacy(pattern, 0, &result); } else { - offset = c2_parse_grp(pattern, 0, &result, 0); + offset = c2_parse_group(pattern, 0, &result, 0); } if (offset < 0) { @@ -395,15 +371,14 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { // Insert to pcondlst { - auto plptr = cmalloc(c2_lptr_t); - *plptr = (c2_lptr_t){ - .ptr = result, + auto plptr = cmalloc(struct c2_condition); + *plptr = (struct c2_condition){ + .root = result, .data = data, - .next = NULL, }; - if (pcondlst) { - plptr->next = *pcondlst; - *pcondlst = plptr; + list_init_head(&plptr->siblings); + if (list) { + list_insert_after(list, &plptr->siblings); } #ifdef DEBUG_C2 @@ -419,8 +394,8 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { /** * Parse a condition string with a prefix. */ -c2_lptr_t * -c2_parse_with_prefix(c2_lptr_t **pcondlst, const char *pattern, +c2_condition * +c2_parse_with_prefix(struct list_node *list, const char *pattern, void *(*parse_prefix)(const char *input, const char **end, void *), void (*free_value)(void *), void *user_data) { char *pattern_start = NULL; @@ -428,7 +403,7 @@ c2_parse_with_prefix(c2_lptr_t **pcondlst, const char *pattern, if (pattern_start == NULL) { return NULL; } - auto ret = c2_parse(pcondlst, pattern_start, val); + auto ret = c2_parse(list, pattern_start, val); if (!ret && free_value) { free_value(val); } @@ -437,17 +412,17 @@ c2_parse_with_prefix(c2_lptr_t **pcondlst, const char *pattern, TEST_CASE(c2_parse) { char str[1024]; - c2_lptr_t *cond = c2_parse(NULL, "name = \"xterm\"", NULL); + struct c2_condition *cond = c2_parse(NULL, "name = \"xterm\"", NULL); struct atom *atoms = init_mock_atoms(); struct c2_state *state = c2_state_new(atoms); TEST_NOTEQUAL(cond, NULL); - TEST_TRUE(!cond->ptr.isbranch); - TEST_NOTEQUAL(cond->ptr.l, NULL); - TEST_EQUAL(cond->ptr.l->op, C2_L_OEQ); - TEST_EQUAL(cond->ptr.l->ptntype, C2_L_PTSTRING); - TEST_STREQUAL(cond->ptr.l->ptnstr, "xterm"); + TEST_EQUAL(!cond->root.type, C2_NODE_TYPE_BRANCH); + TEST_NOTEQUAL(cond->root.l, NULL); + TEST_EQUAL(cond->root.l->op, C2_L_OEQ); + TEST_EQUAL(cond->root.l->ptntype, C2_L_PTSTRING); + TEST_STREQUAL(cond->root.l->ptnstr, "xterm"); - size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + size_t len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "name = \"xterm\"", len); struct wm *wm = wm_new(); @@ -457,36 +432,36 @@ TEST_CASE(c2_parse) { .name = "xterm", .tree_ref = node, }; - TEST_TRUE(c2_match(state, &test_win, cond, NULL)); - c2_list_postprocess(state, NULL, cond); + TEST_TRUE(c2_match_one(state, &test_win, cond, NULL)); + c2_tree_postprocess(state, NULL, cond->root); TEST_EQUAL(HASH_COUNT(state->tracked_properties), 0); c2_state_free(state); destroy_atoms(atoms); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "argb", NULL); TEST_NOTEQUAL(cond, NULL); - TEST_TRUE(!cond->ptr.isbranch); - TEST_EQUAL(cond->ptr.l->ptntype, C2_L_PTINT); - c2_list_free(&cond, NULL); + TEST_NOTEQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); + TEST_EQUAL(cond->root.l->ptntype, C2_L_PTINT); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "argb = 'b'", NULL); TEST_EQUAL(cond, NULL); cond = c2_parse(NULL, "_GTK_FRAME_EXTENTS@:c", NULL); TEST_NOTEQUAL(cond, NULL); - TEST_TRUE(!cond->ptr.isbranch); - TEST_NOTEQUAL(cond->ptr.l, NULL); - TEST_EQUAL(cond->ptr.l->op, C2_L_OEXISTS); - TEST_EQUAL(cond->ptr.l->match, C2_L_MEXACT); - TEST_EQUAL(cond->ptr.l->predef, C2_L_PUNDEFINED); - TEST_TRUE(cond->ptr.l->target_on_client); - TEST_NOTEQUAL(cond->ptr.l->tgt, NULL); - TEST_STREQUAL(cond->ptr.l->tgt, "_GTK_FRAME_EXTENTS"); + TEST_NOTEQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); + TEST_NOTEQUAL(cond->root.l, NULL); + TEST_EQUAL(cond->root.l->op, C2_L_OEXISTS); + TEST_EQUAL(cond->root.l->match, C2_L_MEXACT); + TEST_EQUAL(cond->root.l->predef, C2_L_PUNDEFINED); + TEST_TRUE(cond->root.l->target_on_client); + TEST_NOTEQUAL(cond->root.l->tgt, NULL); + TEST_STREQUAL(cond->root.l->tgt, "_GTK_FRAME_EXTENTS"); atoms = init_mock_atoms(); state = c2_state_new(atoms); - c2_list_postprocess(state, NULL, cond); + c2_tree_postprocess(state, NULL, cond->root); TEST_EQUAL(HASH_COUNT(state->tracked_properties), 1); HASH_ITER2(state->tracked_properties, prop) { TEST_EQUAL(prop->key.property, @@ -496,65 +471,72 @@ TEST_CASE(c2_parse) { c2_state_free(state); destroy_atoms(atoms); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "_GTK_FRAME_EXTENTS@[0]", len); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); + + cond = c2_parse( + NULL, "!(name != \"xterm\" && class_g *= \"XTerm\") || !name != \"yterm\"", NULL); + TEST_NOTEQUAL(cond, NULL); + TEST_STREQUAL(c2_condition_to_str(cond), "(!(name != \"xterm\" && class_g *= " + "\"XTerm\") || name = \"yterm\")"); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "name = \"xterm\" && class_g *= \"XTerm\"", NULL); TEST_NOTEQUAL(cond, NULL); - TEST_TRUE(cond->ptr.isbranch); - TEST_NOTEQUAL(cond->ptr.b, NULL); - TEST_EQUAL(cond->ptr.b->op, C2_B_OAND); - TEST_NOTEQUAL(cond->ptr.b->opr1.l, NULL); - TEST_NOTEQUAL(cond->ptr.b->opr2.l, NULL); - TEST_EQUAL(cond->ptr.b->opr1.l->op, C2_L_OEQ); - TEST_EQUAL(cond->ptr.b->opr1.l->match, C2_L_MEXACT); - TEST_EQUAL(cond->ptr.b->opr1.l->ptntype, C2_L_PTSTRING); - TEST_EQUAL(cond->ptr.b->opr2.l->op, C2_L_OEQ); - TEST_EQUAL(cond->ptr.b->opr2.l->match, C2_L_MCONTAINS); - TEST_EQUAL(cond->ptr.b->opr2.l->ptntype, C2_L_PTSTRING); - TEST_STREQUAL(cond->ptr.b->opr1.l->tgt, "name"); - TEST_EQUAL(cond->ptr.b->opr1.l->predef, C2_L_PNAME); - TEST_STREQUAL(cond->ptr.b->opr2.l->tgt, "class_g"); - TEST_EQUAL(cond->ptr.b->opr2.l->predef, C2_L_PCLASSG); + TEST_EQUAL(cond->root.type, C2_NODE_TYPE_BRANCH); + TEST_NOTEQUAL(cond->root.b, NULL); + TEST_EQUAL(cond->root.b->op, C2_B_OAND); + TEST_NOTEQUAL(cond->root.b->opr1.l, NULL); + TEST_NOTEQUAL(cond->root.b->opr2.l, NULL); + TEST_EQUAL(cond->root.b->opr1.l->op, C2_L_OEQ); + TEST_EQUAL(cond->root.b->opr1.l->match, C2_L_MEXACT); + TEST_EQUAL(cond->root.b->opr1.l->ptntype, C2_L_PTSTRING); + TEST_EQUAL(cond->root.b->opr2.l->op, C2_L_OEQ); + TEST_EQUAL(cond->root.b->opr2.l->match, C2_L_MCONTAINS); + TEST_EQUAL(cond->root.b->opr2.l->ptntype, C2_L_PTSTRING); + TEST_STREQUAL(cond->root.b->opr1.l->tgt, "name"); + TEST_EQUAL(cond->root.b->opr1.l->predef, C2_L_PNAME); + TEST_STREQUAL(cond->root.b->opr2.l->tgt, "class_g"); + TEST_EQUAL(cond->root.b->opr2.l->predef, C2_L_PCLASSG); atoms = init_mock_atoms(); state = c2_state_new(atoms); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "(name = \"xterm\" && class_g *= \"XTerm\")", len); test_win.class_general = "XTerm"; - TEST_TRUE(c2_match(state, &test_win, cond, NULL)); + TEST_TRUE(c2_match_one(state, &test_win, cond, NULL)); test_win.class_general = "asdf"; - TEST_TRUE(!c2_match(state, &test_win, cond, NULL)); - c2_list_free(&cond, NULL); + TEST_TRUE(!c2_match_one(state, &test_win, cond, NULL)); + c2_free_condition(cond, NULL); c2_state_free(state); destroy_atoms(atoms); cond = c2_parse(NULL, "_NET_WM_STATE[1]:32a *='_NET_WM_STATE_HIDDEN'", NULL); - TEST_EQUAL(cond->ptr.l->index, 1); - TEST_STREQUAL(cond->ptr.l->tgt, "_NET_WM_STATE"); - TEST_STREQUAL(cond->ptr.l->ptnstr, "_NET_WM_STATE_HIDDEN"); + TEST_EQUAL(cond->root.l->index, 1); + TEST_STREQUAL(cond->root.l->tgt, "_NET_WM_STATE"); + TEST_STREQUAL(cond->root.l->ptnstr, "_NET_WM_STATE_HIDDEN"); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "_NET_WM_STATE[1] *= \"_NET_WM_STATE_HIDDEN\"", len); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "_NET_WM_STATE[*]:32a*='_NET_WM_STATE_HIDDEN'", NULL); - TEST_EQUAL(cond->ptr.l->index, -1); + TEST_EQUAL(cond->root.l->index, -1); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "_NET_WM_STATE[*] *= \"_NET_WM_STATE_HIDDEN\"", len); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "!class_i:0s", NULL); TEST_NOTEQUAL(cond, NULL); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, "!class_i", len); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "_NET_WM_STATE = '_NET_WM_STATE_HIDDEN'", NULL); TEST_NOTEQUAL(cond, NULL); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); cond = c2_parse(NULL, "1A:\n1111111111111ar1", NULL); TEST_EQUAL(cond, NULL); @@ -573,9 +555,9 @@ TEST_CASE(c2_parse) { "\"\\n\\n\\x8a\\b\\n^\\n*0\\n[\\n[\\n\\n\\b\\n\")"; cond = c2_parse(NULL, rule, NULL); TEST_NOTEQUAL(cond, NULL); - len = c2_condition_to_str(cond->ptr, str, sizeof(str)); + len = c2_condition_node_to_str(cond->root, str, sizeof(str)); TEST_STREQUAL3(str, rule, len); - c2_list_free(&cond, NULL); + c2_free_condition(cond, NULL); wm_free_mock_window(wm, test_win.tree_ref); wm_free(wm); @@ -599,7 +581,8 @@ TEST_CASE(c2_parse) { * * @return offset of next character in string */ -static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) { +static int +c2_parse_group(const char *pattern, int offset, c2_condition_node_ptr *presult, int level) { if (!pattern) { return -1; } @@ -615,11 +598,11 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // 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}; + c2_condition_node_ptr eles[2] = {C2_NODE_PTR_INIT, C2_NODE_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; + c2_condition_node_ptr *pele = eles; // Negation flag of next operator bool neg = false; // Whether we are expecting an element immediately, is true at first, or @@ -683,20 +666,20 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int assert(!elei || ops[elei]); // If we are out of space - if (2 == elei) { + if (elei == 2) { --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]); + eles[1] = C2_NODE_PTR_INIT; 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_INIT); - assert(eles[1].isbranch); + eles[1] = c2h_comb_tree(ops[2], eles[1], C2_NODE_PTR_INIT); + assert(eles[1].type == C2_NODE_TYPE_BRANCH); pele = &eles[1].b->opr2; } // The last operator always needs to be reset @@ -705,17 +688,16 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // It's a subgroup if it starts with '(' if ('(' == pattern[offset]) { - if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) { + if ((offset = c2_parse_group(pattern, offset + 1, pele, level + 1)) < 0) { goto fail; } - } - // Otherwise it's a leaf - else { + } else { + // Otherwise it's a leaf if ((offset = c2_parse_target(pattern, offset, pele)) < 0) { goto fail; } - assert(!pele->isbranch && !c2_ptr_isempty(*pele)); + assert(pele->type != C2_NODE_TYPE_BRANCH && pele->l != NULL); if ((offset = c2_parse_op(pattern, offset, pele)) < 0) { goto fail; @@ -753,11 +735,7 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // Apply negation if (neg) { neg = false; - if (pele->isbranch) { - pele->b->neg = !pele->b->neg; - } else { - pele->l->neg = !pele->l->neg; - } + pele->neg = !pele->neg; } next_expected = false; @@ -782,7 +760,7 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int assert(2 == elei); assert(ops[1]); eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]); - c2_ptr_reset(&eles[1]); + eles[1] = C2_NODE_PTR_INIT; } *presult = eles[0]; @@ -803,17 +781,18 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int /** * Parse the target part of a rule. */ -static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { +static int c2_parse_target(const char *pattern, int offset, c2_condition_node_ptr *presult) { // Initialize leaf - presult->isbranch = false; - presult->l = cmalloc(c2_l_t); + presult->type = C2_NODE_TYPE_LEAF; + presult->neg = false; + presult->l = cmalloc(c2_condition_node_leaf); - c2_l_t *const pleaf = presult->l; - *pleaf = C2_L_INIT; + auto const pleaf = presult->l; + *pleaf = C2_LEAF_NODE_INIT; // Parse negation marks while ('!' == pattern[offset]) { - pleaf->neg = !pleaf->neg; + presult->neg = !presult->neg; ++offset; C2H_SKIP_SPACES(); } @@ -946,13 +925,13 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { /** * 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; +static int c2_parse_op(const char *pattern, int offset, c2_condition_node_ptr *presult) { + auto const pleaf = presult->l; // Parse negation marks C2H_SKIP_SPACES(); while ('!' == pattern[offset]) { - pleaf->neg = !pleaf->neg; + presult->neg = !presult->neg; ++offset; C2H_SKIP_SPACES(); } @@ -1013,8 +992,8 @@ static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) { /** * Parse the pattern part of a leaf. */ -static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) { - c2_l_t *const pleaf = presult->l; +static int c2_parse_pattern(const char *pattern, int offset, c2_condition_node_ptr *presult) { + auto const pleaf = presult->l; // Exists operator cannot have pattern if (!pleaf->op) { @@ -1156,17 +1135,18 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) /** * Parse a condition with legacy syntax. */ -static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { +static int c2_parse_legacy(const char *pattern, int offset, c2_condition_node_ptr *presult) { if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' || !strchr(pattern + offset + 2, ':')) { c2_error("Legacy parser: Invalid format."); } // Allocate memory for new leaf - auto pleaf = cmalloc(c2_l_t); - presult->isbranch = false; + auto pleaf = cmalloc(c2_condition_node_leaf); + presult->type = C2_NODE_TYPE_LEAF; presult->l = pleaf; - *pleaf = C2_L_INIT; + presult->neg = false; + *pleaf = C2_LEAF_NODE_INIT; pleaf->op = C2_L_OEQ; pleaf->ptntype = C2_L_PTSTRING; @@ -1216,7 +1196,8 @@ static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { /** * Do postprocessing on a condition leaf. */ -static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t *pleaf) { +static bool +c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_condition_node_leaf *pleaf) { // Get target atom if it's not a predefined one if (pleaf->predef == C2_L_PUNDEFINED) { pleaf->tgtatom = get_atom_with_nul(state->atoms, pleaf->tgt, c); @@ -1295,85 +1276,78 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t return true; } -static bool c2_tree_postprocess(struct c2_state *state, xcb_connection_t *c, c2_ptr_t node) { - if (node.istrue) { - return true; - } - if (!node.isbranch) { - return c2_l_postprocess(state, c, node.l); +static bool +c2_tree_postprocess(struct c2_state *state, xcb_connection_t *c, c2_condition_node_ptr node) { + switch (node.type) { + case C2_NODE_TYPE_TRUE: return true; + case C2_NODE_TYPE_LEAF: return c2_l_postprocess(state, c, node.l); + case C2_NODE_TYPE_BRANCH: + return c2_tree_postprocess(state, c, node.b->opr1) && + c2_tree_postprocess(state, c, node.b->opr2); + default: unreachable(); } - - return c2_tree_postprocess(state, c, node.b->opr1) && - c2_tree_postprocess(state, c, node.b->opr2); } -bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, c2_lptr_t *list) { - c2_lptr_t *head = list; - while (head) { - if (!c2_tree_postprocess(state, c, head->ptr)) { +bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, struct list_node *list) { + list_foreach(c2_condition, i, list, siblings) { + if (!c2_tree_postprocess(state, c, i->root)) { return false; } - head = head->next; } return true; } /** * Free a condition tree. */ -static void c2_free(c2_ptr_t p) { +static void c2_free(c2_condition_node_ptr p) { // For a branch element - if (p.isbranch) { - c2_b_t *const pbranch = p.b; - - if (!pbranch) { + switch (p.type) { + case C2_NODE_TYPE_BRANCH: + if (p.b == NULL) { 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) { + c2_free(p.b->opr1); + c2_free(p.b->opr2); + free(p.b); + return; + case C2_NODE_TYPE_LEAF: + // For a leaf element + if (!p.l) { return; } - free(pleaf->tgt); - free(pleaf->ptnstr); + free(p.l->tgt); + free(p.l->ptnstr); #ifdef CONFIG_REGEX_PCRE - pcre2_code_free(pleaf->regex_pcre); - pcre2_match_data_free(pleaf->regex_pcre_match); + pcre2_code_free(p.l->regex_pcre); + pcre2_match_data_free(p.l->regex_pcre_match); #endif - free(pleaf); + free(p.l); + default: return; } } /** * Free a condition tree in c2_lptr_t. */ -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) { +void c2_free_condition(c2_condition *lp, c2_userdata_free f) { if (!lp) { - return NULL; + return; } - c2_lptr_t *pnext = lp->next; if (f) { f(lp->data); } lp->data = NULL; - c2_free(lp->ptr); + c2_free(lp->root); free(lp); - - return pnext; } /** * Get a string representation of a rule target. */ -static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { +static const char *c2h_dump_str_tgt(const c2_condition_node_leaf *pleaf) { if (pleaf->predef != C2_L_PUNDEFINED) { return C2_PREDEFS[pleaf->predef].name; } @@ -1386,7 +1360,8 @@ static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { * the null terminator. * null terminator will not be written to the output. */ -static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { +static size_t +c2_condition_node_to_str(const c2_condition_node_ptr p, char *output, size_t len) { #define push_char(c) \ if (offset < len) \ output[offset] = (c); \ @@ -1402,27 +1377,27 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { offset += strlen(str); \ } while (false) size_t offset = 0; - if (p.isbranch) { + char number[128]; + switch (p.type) { + case C2_NODE_TYPE_BRANCH: // Branch, i.e. logical operators &&, ||, XOR - const c2_b_t *const pbranch = p.b; - - if (!pbranch) { + if (p.b == NULL) { return 0; } - if (pbranch->neg) { + if (p.neg) { push_char('!'); } push_char('('); if (len > offset) { - offset += c2_condition_to_str(pbranch->opr1, output + offset, - len - offset); + offset += c2_condition_node_to_str(p.b->opr1, output + offset, + len - offset); } else { - offset += c2_condition_to_str(pbranch->opr1, NULL, 0); + offset += c2_condition_node_to_str(p.b->opr1, NULL, 0); } - switch (pbranch->op) { + switch (p.b->op) { case C2_B_OAND: push_str(" && "); break; case C2_B_OOR: push_str(" || "); break; case C2_B_OXOR: push_str(" XOR "); break; @@ -1430,52 +1405,50 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { } if (len > offset) { - offset += c2_condition_to_str(pbranch->opr2, output + offset, - len - offset); + offset += c2_condition_node_to_str(p.b->opr2, output + offset, + len - offset); } else { - offset += c2_condition_to_str(pbranch->opr2, NULL, 0); + offset += c2_condition_node_to_str(p.b->opr2, NULL, 0); } push_str(")"); - } else { + break; + case C2_NODE_TYPE_LEAF: // Leaf node - const c2_l_t *const pleaf = p.l; - char number[128]; - - if (!pleaf) { + if (!p.l) { return 0; } - if (C2_L_OEXISTS == pleaf->op && pleaf->neg) { + if (C2_L_OEXISTS == p.l->op && p.neg) { push_char('!'); } // Print target name, type, and format - const char *target_str = c2h_dump_str_tgt(pleaf); + const char *target_str = c2h_dump_str_tgt(p.l); push_str(target_str); - if (pleaf->target_on_client) { + if (p.l->target_on_client) { push_char('@'); } - if (pleaf->predef == C2_L_PUNDEFINED) { - if (pleaf->index < 0) { + if (p.l->predef == C2_L_PUNDEFINED) { + if (p.l->index < 0) { push_str("[*]"); } else { - sprintf(number, "[%d]", pleaf->index); + sprintf(number, "[%d]", p.l->index); push_str(number); } } - if (C2_L_OEXISTS == pleaf->op) { + if (C2_L_OEXISTS == p.l->op) { return offset; } // Print operator push_char(' '); - if (C2_L_OEXISTS != pleaf->op && pleaf->neg) { + if (C2_L_OEXISTS != p.l->op && p.neg) { push_char('!'); } - switch (pleaf->match) { + switch (p.l->match) { case C2_L_MEXACT: break; case C2_L_MCONTAINS: push_char('*'); break; case C2_L_MSTART: push_char('^'); break; @@ -1483,11 +1456,11 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { case C2_L_MWILDCARD: push_char('%'); break; } - if (pleaf->match_ignorecase) { + if (p.l->match_ignorecase) { push_char('?'); } - switch (pleaf->op) { + switch (p.l->op) { case C2_L_OEXISTS: break; case C2_L_OEQ: push_str("="); break; case C2_L_OGT: push_str(">"); break; @@ -1498,16 +1471,16 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { // Print pattern push_char(' '); - switch (pleaf->ptntype) { + switch (p.l->ptntype) { case C2_L_PTINT: - sprintf(number, "%ld", pleaf->ptnint); + sprintf(number, "%ld", p.l->ptnint); push_str(number); break; case C2_L_PTSTRING: // TODO(yshui) Escape string before printing out? push_char('"'); - for (int i = 0; pleaf->ptnstr[i]; i++) { - switch (pleaf->ptnstr[i]) { + for (int i = 0; p.l->ptnstr[i]; i++) { + switch (p.l->ptnstr[i]) { case '\\': push_str("\\\\"); break; case '"': push_str("\\\""); break; case '\a': push_str("\\a"); break; @@ -1518,11 +1491,11 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { case '\t': push_str("\\t"); break; case '\n': push_str("\\n"); break; default: - if (isprint(pleaf->ptnstr[i])) { - push_char(pleaf->ptnstr[i]); + if (isprint(p.l->ptnstr[i])) { + push_char(p.l->ptnstr[i]); } else { sprintf(number, "\\x%02x", - (unsigned char)pleaf->ptnstr[i]); + (unsigned char)p.l->ptnstr[i]); push_str(number); } break; @@ -1532,6 +1505,9 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { break; default: assert(0); break; } + break; + case C2_NODE_TYPE_TRUE: push_str("(default)"); break; + default: unreachable(); } #undef push_char #undef push_str @@ -1541,9 +1517,9 @@ static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { /// Wrapper of c2_condition_to_str which uses an internal static buffer, and /// returns a nul terminated string. The returned string is only valid until the /// next call to this function, and should not be freed. -static const char *c2_condition_to_str2(c2_ptr_t ptr) { +static const char *c2_condition_node_to_str2(c2_condition_node_ptr ptr) { static thread_local char buf[4096]; - auto len = c2_condition_to_str(ptr, buf, sizeof(buf)); + auto len = c2_condition_node_to_str(ptr, buf, sizeof(buf)); if (len >= sizeof(buf)) { // Resulting string is too long, clobber the last character with a nul. buf[sizeof(buf) - 1] = '\0'; @@ -1553,8 +1529,8 @@ static const char *c2_condition_to_str2(c2_ptr_t ptr) { return buf; } -const char *c2_lptr_to_str(const c2_lptr_t *ptr) { - return c2_condition_to_str2(ptr->ptr); +const char *c2_condition_to_str(const c2_condition *ptr) { + return c2_condition_node_to_str2(ptr->root); } /// Get the list of target number values from a struct c2_property_value @@ -1576,7 +1552,7 @@ c2_values_get_number_targets(const struct c2_property_value *values, int index, return NULL; } -static inline bool c2_int_op(const c2_l_t *leaf, int64_t target) { +static inline bool c2_int_op(const c2_condition_node_leaf *leaf, int64_t target) { switch (leaf->op) { case C2_L_OEXISTS: return leaf->predef != C2_L_PUNDEFINED ? target : true; case C2_L_OEQ: return target == leaf->ptnint; @@ -1588,7 +1564,7 @@ static inline bool c2_int_op(const c2_l_t *leaf, int64_t target) { unreachable(); } -static bool c2_match_once_leaf_int(const struct win *w, const c2_l_t *leaf) { +static bool c2_match_once_leaf_int(const struct win *w, const c2_condition_node_leaf *leaf) { // Get the value if (leaf->predef != C2_L_PUNDEFINED) { // A predefined target @@ -1663,7 +1639,7 @@ static bool c2_match_once_leaf_int(const struct win *w, const c2_l_t *leaf) { return false; } -static bool c2_string_op(const c2_l_t *leaf, const char *target) { +static bool c2_string_op(const c2_condition_node_leaf *leaf, const char *target) { if (leaf->op == C2_L_OEXISTS) { return true; } @@ -1706,9 +1682,8 @@ static bool c2_string_op(const c2_l_t *leaf, const char *target) { unreachable(); } -static bool -c2_match_once_leaf_string(struct atom *atoms, const struct win *w, const c2_l_t *leaf) { - +static bool c2_match_once_leaf_string(struct atom *atoms, const struct win *w, + const c2_condition_node_leaf *leaf) { // A predefined target const char *predef_target = NULL; if (leaf->predef != C2_L_PUNDEFINED) { @@ -1800,30 +1775,32 @@ c2_match_once_leaf_string(struct atom *atoms, const struct win *w, const c2_l_t * * For internal use. */ -static inline bool -c2_match_once_leaf(struct c2_state *state, const struct win *w, const c2_l_t *leaf) { - assert(leaf); +static inline bool c2_match_once_leaf(const struct c2_state *state, const struct win *w, + const c2_condition_node_ptr leaf) { + assert(leaf.type == C2_NODE_TYPE_LEAF); + assert(leaf.l); const xcb_window_t wid = - (leaf->target_on_client ? win_client_id(w, /*fallback_to_self=*/true) : win_id(w)); + (leaf.l->target_on_client ? win_client_id(w, /*fallback_to_self=*/true) + : win_id(w)); // Return if wid is missing - if (leaf->predef == C2_L_PUNDEFINED && !wid) { + if (leaf.l->predef == C2_L_PUNDEFINED && !wid) { log_debug("Window ID missing."); return false; } log_verbose("Matching window %#010x (%s) against condition %s", wid, w->name, - c2_condition_to_str2((c2_ptr_t){.l = (c2_l_t *)leaf, .isbranch = false})); + c2_condition_node_to_str2(leaf)); - unsigned int pattern_type = leaf->ptntype; + unsigned int pattern_type = leaf.l->ptntype; if (pattern_type == C2_L_PTUNDEFINED) { - if (leaf->target_id == C2_L_INVALID_TARGET_ID) { + if (leaf.l->target_id == C2_L_INVALID_TARGET_ID) { log_debug("Leaf target ID is invalid, skipping. Most likely a " "list postprocessing failure."); return false; } - auto values = &w->c2_state.values[leaf->target_id]; + auto values = &w->c2_state.values[leaf.l->target_id]; if (values->type == C2_PROPERTY_TYPE_STRING) { pattern_type = C2_L_PTSTRING; } else { @@ -1833,9 +1810,9 @@ c2_match_once_leaf(struct c2_state *state, const struct win *w, const c2_l_t *le switch (pattern_type) { // Deal with integer patterns - case C2_L_PTINT: return c2_match_once_leaf_int(w, leaf); + case C2_L_PTINT: return c2_match_once_leaf_int(w, leaf.l); // String patterns - case C2_L_PTSTRING: return c2_match_once_leaf_string(state->atoms, w, leaf); + case C2_L_PTSTRING: return c2_match_once_leaf_string(state->atoms, w, leaf.l); default: unreachable(); } } @@ -1845,66 +1822,69 @@ c2_match_once_leaf(struct c2_state *state, const struct win *w, const c2_l_t *le * * @return true if matched, false otherwise. */ -static bool c2_match_once(struct c2_state *state, const struct win *w, const c2_ptr_t cond) { +static bool c2_match_once(const struct c2_state *state, const struct win *w, + const c2_condition_node_ptr node) { bool result = false; - if (cond.isbranch) { + switch (node.type) { + case C2_NODE_TYPE_BRANCH: // Handle a branch (and/or/xor operation) - const c2_b_t *pb = cond.b; - - if (!pb) { + if (!node.b) { return false; } log_verbose("Matching window %#010x (%s) against condition %s", win_id(w), - w->name, c2_condition_to_str2(cond)); + w->name, c2_condition_node_to_str2(node)); - switch (pb->op) { + switch (node.b->op) { case C2_B_OAND: - result = (c2_match_once(state, w, pb->opr1) && - c2_match_once(state, w, pb->opr2)); + result = (c2_match_once(state, w, node.b->opr1) && + c2_match_once(state, w, node.b->opr2)); break; case C2_B_OOR: - result = (c2_match_once(state, w, pb->opr1) || - c2_match_once(state, w, pb->opr2)); + result = (c2_match_once(state, w, node.b->opr1) || + c2_match_once(state, w, node.b->opr2)); break; case C2_B_OXOR: - result = (c2_match_once(state, w, pb->opr1) != - c2_match_once(state, w, pb->opr2)); + result = (c2_match_once(state, w, node.b->opr1) != + c2_match_once(state, w, node.b->opr2)); break; default: unreachable(); } log_debug("(%#010x): branch: result = %d, pattern = %s", win_id(w), - result, c2_condition_to_str2(cond)); - } else if (cond.istrue) { - return true; - } else { + result, c2_condition_node_to_str2(node)); + break; + case C2_NODE_TYPE_TRUE: return true; + case C2_NODE_TYPE_LEAF: // A leaf - const c2_l_t *pleaf = cond.l; - - if (!pleaf) { + if (node.l == NULL) { return false; } - result = c2_match_once_leaf(state, w, pleaf); + result = c2_match_once_leaf(state, w, node); log_debug("(%#010x): leaf: result = %d, client = %#010x, pattern = %s", win_id(w), result, win_client_id(w, false), - c2_condition_to_str2(cond)); + c2_condition_node_to_str2(node)); + break; + default: unreachable(); } // Postprocess the result - if (cond.isbranch ? cond.b->neg : cond.l->neg) { + if (node.neg) { result = !result; } return result; } -c2_lptr_t *c2_new_true(void) { - auto ret = ccalloc(1, c2_lptr_t); - ret->ptr = (c2_ptr_t){.istrue = true}; +c2_condition *c2_new_true(struct list_node *list) { + auto ret = ccalloc(1, c2_condition); + ret->root = (c2_condition_node_ptr){.type = C2_NODE_TYPE_TRUE}; + if (list) { + list_insert_after(list, &ret->siblings); + } return ret; } @@ -1915,13 +1895,13 @@ c2_lptr_t *c2_new_true(void) { * @param pdata a place to return the data * @return true if matched, false otherwise. */ -bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst, - void **pdata) { +bool c2_match(struct c2_state *state, const struct win *w, + const struct list_node *conditions, void **pdata) { // Then go through the whole linked list - for (; condlst; condlst = condlst->next) { - if (c2_match_once(state, w, condlst->ptr)) { + list_foreach(c2_condition, i, conditions, siblings) { + if (c2_match_once(state, w, i->root)) { if (pdata) { - *pdata = condlst->data; + *pdata = i->data; } return true; } @@ -1931,45 +1911,55 @@ bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *cond } /// Match a window against the first condition in a condition linked list. -bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst, - void **pdata) { - if (!condlst) { +bool c2_match_one(const struct c2_state *state, const struct win *w, + const c2_condition *condition, void **pdata) { + if (!condition) { return false; } - if (c2_match_once(state, w, condlst->ptr)) { + if (c2_match_once(state, w, condition->root)) { if (pdata) { - *pdata = condlst->data; + *pdata = condition->data; } return true; } return false; } -/// Iterate over all conditions in a condition linked list. Call the callback for -/// each of the conditions. If the callback returns true, the iteration stops -/// early. -/// -/// Returns whether the iteration was stopped early. -bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) { - for (auto i = condlist; i; i = i->next) { - if (cb(i, data)) { - return true; - } - } - return false; -} - /// Return user data stored in a condition. -void *c2_list_get_data(const c2_lptr_t *condlist) { - return condlist->data; +void *c2_condition_get_data(const c2_condition *condition) { + return condition->data; } -void *c2_list_set_data(c2_lptr_t *condlist, void *data) { - void *old = condlist->data; - condlist->data = data; +void *c2_condition_set_data(c2_condition *condition, void *data) { + void *old = condition->data; + condition->data = data; return old; } +c2_condition *c2_condition_list_entry(struct list_node *list) { + return list == NULL ? NULL : list_entry(list, c2_condition, siblings); +} + +c2_condition *c2_condition_list_next(struct list_node *list, c2_condition *condition) { + if (condition == NULL) { + return NULL; + } + if (condition->siblings.next == list) { + return NULL; + } + return c2_condition_list_entry(condition->siblings.next); +} + +c2_condition *c2_condition_list_prev(struct list_node *list, c2_condition *condition) { + if (condition == NULL) { + return NULL; + } + if (condition->siblings.prev == list) { + return NULL; + } + return c2_condition_list_entry(condition->siblings.prev); +} + struct c2_state *c2_state_new(struct atom *atoms) { auto ret = ccalloc(1, struct c2_state); ret->atoms = atoms; @@ -2220,9 +2210,3 @@ bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property) { HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p); return p != NULL; } - -void c2_condlist_insert(c2_lptr_t **pcondlst, c2_lptr_t *pnew) { - c2_lptr_t *pcur = *pcondlst; - pnew->next = pcur; - *pcondlst = pnew; -} diff --git a/src/c2.h b/src/c2.h index 5cdd33e3be..fb6a86eb2f 100644 --- a/src/c2.h +++ b/src/c2.h @@ -8,7 +8,9 @@ #include #include -typedef struct _c2_lptr c2_lptr_t; +#include "utils/list.h" + +typedef struct c2_condition c2_condition; typedef struct session session_t; struct c2_state; /// Per-window state used for c2 condition matching. @@ -19,19 +21,20 @@ struct c2_window_state { }; struct atom; struct win; +struct list_node; typedef void (*c2_userdata_free)(void *); -c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); +struct c2_condition *c2_parse(struct list_node *list, const char *pattern, void *data); /// Parse a condition that has a prefix. The prefix is parsed by `parse_prefix`. If /// `free_value` is not NULL, it will be called to free the value returned by /// `parse_prefix` when error occurs. -c2_lptr_t * -c2_parse_with_prefix(c2_lptr_t **pcondlst, const char *pattern, +c2_condition * +c2_parse_with_prefix(struct list_node *list, const char *pattern, void *(*parse_prefix)(const char *input, const char **end, void *), void (*free_value)(void *), void *user_data); -c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f); +void c2_free_condition(c2_condition *lp, c2_userdata_free f); /// Create a new c2_state object. This is used for maintaining the internal state /// used for c2 condition matching. This state object holds a reference to the @@ -50,30 +53,46 @@ void c2_window_state_update(struct c2_state *state, struct c2_window_state *wind xcb_connection_t *c, xcb_window_t client_win, xcb_window_t frame_win); -bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst, - void **pdata); -bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst, - void **pdata); +bool c2_match(struct c2_state *state, const struct win *w, + const struct list_node *conditions, void **pdata); +bool c2_match_one(const struct c2_state *state, const struct win *w, + const c2_condition *condlst, void **pdata); -bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, c2_lptr_t *list); -typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data); -bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data); +bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, struct list_node *list); /// Return user data stored in a condition. -void *c2_list_get_data(const c2_lptr_t *condlist); +void *c2_condition_get_data(const c2_condition *condition); /// Set user data stored in a condition. Return the old user data. -void *c2_list_set_data(c2_lptr_t *condlist, void *data); -/// Convert a c2_lptr_t to string. The returned string is only valid until the +void *c2_condition_set_data(c2_condition *condlist, void *data); +/// Convert a c2_condition to string. The returned string is only valid until the /// next call to this function, and should not be freed. -const char *c2_lptr_to_str(const c2_lptr_t *); -void c2_condlist_insert(c2_lptr_t **pcondlst, c2_lptr_t *pnew); +const char *c2_condition_to_str(const c2_condition *); +c2_condition *c2_condition_list_next(struct list_node *list, c2_condition *condition); +c2_condition *c2_condition_list_prev(struct list_node *list, c2_condition *condition); +c2_condition *c2_condition_list_entry(struct list_node *list); /// Create a new condition list with a single condition that is always true. -c2_lptr_t *c2_new_true(void); +c2_condition *c2_new_true(struct list_node *list); + +#define c2_condition_list_foreach(list, i) \ + for (c2_condition *i = \ + list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->next); \ + i; i = c2_condition_list_next(list, i)) +#define c2_condition_list_foreach_rev(list, i) \ + for (c2_condition *i = \ + list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->prev); \ + i; i = c2_condition_list_prev(list, i)) + +#define c2_condition_list_foreach_safe(list, i, n) \ + for (c2_condition *i = \ + list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->next), \ + *n = c2_condition_list_next(list, i); \ + i; i = n, n = c2_condition_list_next(list, i)) /** * Destroy a condition list. */ -static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) { - while ((*pcondlst = c2_free_lptr(*pcondlst, f))) { +static inline void c2_list_free(struct list_node *list, c2_userdata_free f) { + c2_condition_list_foreach_safe(list, i, ni) { + c2_free_condition(i, f); } - *pcondlst = NULL; + list_init_head(list); } diff --git a/src/config.c b/src/config.c index 9141223d00..29bb17c0a1 100644 --- a/src/config.c +++ b/src/config.c @@ -724,6 +724,21 @@ bool parse_config(options_t *opt, const char *config_file) { // clang-format on list_init_head(&opt->included_config_files); + list_init_head(&opt->unredir_if_possible_blacklist); + list_init_head(&opt->paint_blacklist); + list_init_head(&opt->shadow_blacklist); + list_init_head(&opt->shadow_clip_list); + list_init_head(&opt->fade_blacklist); + list_init_head(&opt->blur_background_blacklist); + list_init_head(&opt->invert_color_list); + list_init_head(&opt->window_shader_fg_rules); + list_init_head(&opt->opacity_rules); + list_init_head(&opt->rounded_corners_blacklist); + list_init_head(&opt->corner_radius_rules); + list_init_head(&opt->focus_blacklist); + list_init_head(&opt->transparent_clipping_blacklist); + list_init_head(&opt->rules); + opt->all_scripts = dynarr_new(struct script *, 4); return parse_config_libconfig(opt, config_file); } diff --git a/src/config.h b/src/config.h index a7361b0173..786c9fed57 100644 --- a/src/config.h +++ b/src/config.h @@ -59,8 +59,6 @@ typedef struct win_option { bool clip_shadow_above; } win_option_t; -typedef struct _c2_lptr c2_lptr_t; - enum vblank_scheduler_type { /// X Present extension based vblank events VBLANK_SCHEDULER_PRESENT, @@ -241,6 +239,9 @@ typedef struct options { bool print_diagnostics; /// Render to a separate window instead of taking over the screen bool debug_mode; + /// For picom-inspect only, dump windows in a loop + bool inspect_monitor; + xcb_window_t inspect_win; // === General === /// Use the legacy backends? bool use_legacy_backends; @@ -273,7 +274,7 @@ typedef struct options { 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; + struct list_node unredir_if_possible_blacklist; /// Delay before unredirecting screen, in milliseconds. int unredir_if_possible_delay; /// Forced redirection setting through D-Bus. @@ -289,7 +290,7 @@ typedef struct options { /// Window to constantly repaint in benchmark mode. 0 for full-screen. xcb_window_t benchmark_wid; /// A list of conditions of windows not to paint. - c2_lptr_t *paint_blacklist; + struct list_node paint_blacklist; /// Whether to show all X errors. bool show_all_xerrors; /// Whether to avoid acquiring X Selection. @@ -318,13 +319,13 @@ typedef struct options { int shadow_offset_x, shadow_offset_y; double shadow_opacity; /// Shadow blacklist. A linked list of conditions. - c2_lptr_t *shadow_blacklist; + struct list_node shadow_blacklist; /// Whether bounding-shaped window should be ignored. bool shadow_ignore_shaped; /// Whether to crop shadow to the very X RandR monitor. bool crop_shadow_to_monitor; /// Don't draw shadow over these windows. A linked list of conditions. - c2_lptr_t *shadow_clip_list; + struct list_node shadow_clip_list; bool shadow_enable; // === Fading === @@ -339,7 +340,7 @@ typedef struct options { /// Whether to disable fading on ARGB managed destroyed windows. bool no_fading_destroyed_argb; /// Fading blacklist. A linked list of conditions. - c2_lptr_t *fade_blacklist; + struct list_node fade_blacklist; bool fading_enable; // === Opacity === @@ -374,7 +375,7 @@ typedef struct options { /// to window opacity. bool blur_background_fixed; /// Background blur blacklist. A linked list of conditions. - c2_lptr_t *blur_background_blacklist; + struct list_node blur_background_blacklist; /// Blur convolution kernel. struct conv **blur_kerns; /// Number of convolution kernels @@ -382,24 +383,24 @@ typedef struct options { /// Custom fragment shader for painting windows char *window_shader_fg; /// Rules to change custom fragment shader for painting windows. - c2_lptr_t *window_shader_fg_rules; + struct list_node window_shader_fg_rules; /// 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; + struct list_node invert_color_list; /// Rules to change window opacity. - c2_lptr_t *opacity_rules; + struct list_node opacity_rules; /// Limit window brightness double max_brightness; // Radius of rounded window corners int corner_radius; /// Rounded corners blacklist. A linked list of conditions. - c2_lptr_t *rounded_corners_blacklist; + struct list_node rounded_corners_blacklist; /// Rounded corner rules. A linked list of conditions. - c2_lptr_t *corner_radius_rules; + struct list_node corner_radius_rules; // === Focus related === /// Whether to try to detect WM windows and mark them as focused. @@ -409,7 +410,7 @@ typedef struct options { /// 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; + struct list_node focus_blacklist; /// Whether to do window grouping with WM_TRANSIENT_FOR. bool detect_transient; /// Whether to do window grouping with WM_CLIENT_LEADER. @@ -427,7 +428,7 @@ typedef struct options { bool transparent_clipping; /// A list of conditions of windows to which transparent clipping /// should not apply - c2_lptr_t *transparent_clipping_blacklist; + struct list_node transparent_clipping_blacklist; bool dithered_present; // === Animation === @@ -435,7 +436,7 @@ typedef struct options { /// Array of all the scripts used in `animations`. This is a dynarr. struct script **all_scripts; - c2_lptr_t *rules; + struct list_node rules; bool has_both_style_of_rules; } options_t; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 1378ea6fd2..aff1b38b5b 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -124,7 +124,7 @@ FILE *open_config_file(const char *cpath, char **ppath) { /** * Parse a condition list in configuration file. */ -bool must_use parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) { +bool must_use parse_cfg_condlst(struct list_node *list, const config_t *pcfg, const char *name) { config_setting_t *setting = config_lookup(pcfg, name); if (setting == NULL) { return true; @@ -133,15 +133,14 @@ bool must_use parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, cons if (config_setting_is_array(setting)) { int i = config_setting_length(setting); while (i--) { - if (!c2_parse(pcondlst, - config_setting_get_string_elem(setting, i), NULL)) { + if (!c2_parse(list, config_setting_get_string_elem(setting, i), NULL)) { return false; } } } // Treat it as a single pattern if it's a string else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { - if (!c2_parse(pcondlst, config_setting_get_string(setting), NULL)) { + if (!c2_parse(list, config_setting_get_string(setting), NULL)) { return false; } } @@ -152,7 +151,7 @@ bool must_use parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, cons * Parse a window corner radius rule list in configuration file. */ static inline bool -parse_cfg_condlst_with_prefix(c2_lptr_t **condlst, const config_t *pcfg, const char *name, +parse_cfg_condlst_with_prefix(struct list_node *list, const config_t *pcfg, const char *name, void *(*parse_prefix)(const char *, const char **, void *), void (*free_value)(void *), void *user_data) { config_setting_t *setting = config_lookup(pcfg, name); @@ -164,7 +163,7 @@ parse_cfg_condlst_with_prefix(c2_lptr_t **condlst, const config_t *pcfg, const c int i = config_setting_length(setting); while (i--) { if (!c2_parse_with_prefix( - condlst, config_setting_get_string_elem(setting, i), + list, config_setting_get_string_elem(setting, i), parse_prefix, free_value, user_data)) { return false; } @@ -172,7 +171,7 @@ parse_cfg_condlst_with_prefix(c2_lptr_t **condlst, const config_t *pcfg, const c } // Treat it as a single pattern if it's a string else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { - if (!c2_parse_with_prefix(condlst, config_setting_get_string(setting), + if (!c2_parse_with_prefix(list, config_setting_get_string(setting), parse_prefix, free_value, user_data)) { return false; } @@ -576,7 +575,8 @@ static const struct { {"transparent-clipping", offsetof(struct window_maybe_options, transparent_clipping)}, }; -static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scripts) { +static c2_condition * +parse_rule(struct list_node *rules, config_setting_t *setting, struct script ***out_scripts) { if (!config_setting_is_group(setting)) { log_error("Invalid rule at line %d. It must be a group.", config_setting_source_line(setting)); @@ -585,9 +585,9 @@ static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scr int ival; double fval; const char *sval; - c2_lptr_t *rule = NULL; + c2_condition *rule = NULL; if (config_setting_lookup_string(setting, "match", &sval)) { - rule = c2_parse(NULL, sval, NULL); + rule = c2_parse(rules, sval, NULL); if (!rule) { log_error("Failed to parse rule at line %d.", config_setting_source_line(setting)); @@ -595,12 +595,12 @@ static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scr } } else { // If no match condition is specified, it matches all windows - rule = c2_new_true(); + rule = c2_new_true(rules); } auto wopts = cmalloc(struct window_maybe_options); *wopts = WIN_MAYBE_OPTIONS_DEFAULT; - c2_list_set_data(rule, wopts); + c2_condition_set_data(rule, wopts); for (size_t i = 0; i < ARR_SIZE(all_window_options); i++) { if (config_setting_lookup_bool(setting, all_window_options[i].name, &ival)) { @@ -630,8 +630,8 @@ static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scr return rule; } -static void -parse_rules(config_setting_t *setting, struct script ***out_scripts, c2_lptr_t **rules) { +static void parse_rules(struct list_node *rules, config_setting_t *setting, + struct script ***out_scripts) { if (!config_setting_is_list(setting)) { log_error("Invalid value for \"rules\" at line %d. It must be a list.", config_setting_source_line(setting)); @@ -640,10 +640,7 @@ parse_rules(config_setting_t *setting, struct script ***out_scripts, c2_lptr_t * const auto length = (unsigned int)config_setting_length(setting); for (unsigned int i = 0; i < length; i++) { auto sub = config_setting_get_elem(setting, i); - auto rule = parse_rule(sub, out_scripts); - if (rule != NULL) { - c2_condlist_insert(rules, rule); - } + parse_rule(rules, sub, out_scripts); } } @@ -766,7 +763,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { config_setting_t *rules = config_lookup(&cfg, "rules"); if (rules) { - parse_rules(rules, &opt->all_scripts, &opt->rules); + parse_rules(&opt->rules, rules, &opt->all_scripts); } // --dbus @@ -795,7 +792,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { // -i (inactive_opacity) if (config_lookup_float(&cfg, "inactive-opacity", &dval)) { opt->inactive_opacity = normalize_d(dval); - if (opt->rules) { + if (!list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("inactive-opacity"); opt->has_both_style_of_rules = true; } @@ -803,7 +800,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { // --active_opacity if (config_lookup_float(&cfg, "active-opacity", &dval)) { opt->active_opacity = normalize_d(dval); - if (opt->rules) { + if (!list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("active-opacity"); opt->has_both_style_of_rules = true; } @@ -859,30 +856,31 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { } // --inactive-opacity-override if (lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override) && - opt->rules != NULL) { + !list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("inactive-opacity-override"); opt->has_both_style_of_rules = true; } // --inactive-dim - if (config_lookup_float(&cfg, "inactive-dim", &opt->inactive_dim) && opt->rules != NULL) { + if (config_lookup_float(&cfg, "inactive-dim", &opt->inactive_dim) && + !list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("inactive-dim"); opt->has_both_style_of_rules = true; } // --mark-wmwin-focused if (lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &opt->mark_wmwin_focused) && - opt->rules != NULL) { + !list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("mark-wmwin-focused"); opt->has_both_style_of_rules = true; } // --mark-ovredir-focused if (lcfg_lookup_bool(&cfg, "mark-ovredir-focused", &opt->mark_ovredir_focused) && - opt->rules != NULL) { + !list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("mark-ovredir-focused"); opt->has_both_style_of_rules = true; } // --shadow-ignore-shaped if (lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped) && - opt->rules != NULL) { + !list_is_empty(&opt->rules)) { log_warn_both_style_of_rules("shadow-ignore-shaped"); opt->has_both_style_of_rules = true; } @@ -965,7 +963,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { // --dithered_present lcfg_lookup_bool(&cfg, "dithered-present", &opt->dithered_present); - if (opt->rules != NULL) { + if (!list_is_empty(&opt->rules)) { static const char *rule_list[] = { "transparent-clipping-exclude", "shadow-exclude", @@ -987,18 +985,18 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { opt->has_both_style_of_rules = true; } } - } else if (!parse_cfg_condlst(&cfg, &opt->transparent_clipping_blacklist, + } else if (!parse_cfg_condlst(&opt->transparent_clipping_blacklist, &cfg, "transparent-clipping-exclude") || - !parse_cfg_condlst(&cfg, &opt->shadow_blacklist, "shadow-exclude") || - !parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above") || - !parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude") || - !parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude") || - !parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include") || - !parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, + !parse_cfg_condlst(&opt->shadow_blacklist, &cfg, "shadow-exclude") || + !parse_cfg_condlst(&opt->shadow_clip_list, &cfg, "clip-shadow-above") || + !parse_cfg_condlst(&opt->fade_blacklist, &cfg, "fade-exclude") || + !parse_cfg_condlst(&opt->focus_blacklist, &cfg, "focus-exclude") || + !parse_cfg_condlst(&opt->invert_color_list, &cfg, "invert-color-include") || + !parse_cfg_condlst(&opt->blur_background_blacklist, &cfg, "blur-background-exclude") || - !parse_cfg_condlst(&cfg, &opt->unredir_if_possible_blacklist, + !parse_cfg_condlst(&opt->unredir_if_possible_blacklist, &cfg, "unredir-if-possible-exclude") || - !parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, + !parse_cfg_condlst(&opt->rounded_corners_blacklist, &cfg, "rounded-corners-exclude") || !parse_cfg_condlst_with_prefix( &opt->corner_radius_rules, &cfg, "corner-radius-rules", @@ -1139,7 +1137,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { // Wintype settings // XXX ! Refactor all the wintype_* arrays into a struct - if (opt->rules == NULL) { + if (list_is_empty(&opt->rules)) { for (wintype_t i = 0; i < NUM_WINTYPES; ++i) { parse_wintype_config(&cfg, WINTYPES[i].name, &opt->wintype_option[i], &opt->wintype_option_mask[i]); diff --git a/src/inspect.c b/src/inspect.c index d0df492e2a..7da0f8f6b6 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -11,92 +11,18 @@ #include "inspect.h" -#include "atom.h" -#include "backend/backend.h" #include "c2.h" #include "common.h" #include "config.h" #include "log.h" -#include "options.h" -#include "utils/misc.h" +#include "utils/console.h" +#include "utils/dynarr.h" +#include "utils/str.h" #include "wm/defs.h" #include "wm/win.h" #include "x.h" -static struct win * -setup_window(struct x_connection *c, struct atom *atoms, struct options *options, - struct wm *wm, struct c2_state *state, xcb_window_t target) { - // Pretend we are the compositor, and build up the window state - auto cursor = wm_find(wm, target); - if (cursor == NULL) { - log_fatal("Could not find window %#010x", target); - wm_free(wm); - return NULL; - } - - auto toplevel = wm_ref_toplevel_of(wm, cursor); - BUG_ON_NULL(toplevel); - struct win *w = ccalloc(1, struct win); - w->state = WSTATE_MAPPED; - w->tree_ref = toplevel; - log_debug("Toplevel is %#010x", wm_ref_win_id(toplevel)); - log_debug("Client is %#010x", win_client_id(w, true)); - win_update_wintype(c, atoms, w); - win_update_frame_extents(c, atoms, w, win_client_id(w, /*fallback_to_self=*/true), - options->frame_opacity); - // TODO(yshui) get leader - win_update_name(c, atoms, w); - win_update_class(c, atoms, w); - win_update_role(c, atoms, w); - - auto geometry_reply = XCB_AWAIT(xcb_get_geometry, c->c, win_id(w)); - w->g = (struct win_geometry){ - .x = geometry_reply->x, - .y = geometry_reply->y, - .width = geometry_reply->width, - .height = geometry_reply->height, - }; - free(geometry_reply); - - auto shape_info = xcb_get_extension_data(c->c, &xcb_shape_id); - win_on_win_size_change(w, options->shadow_offset_x, options->shadow_offset_y, - options->shadow_radius); - win_update_bounding_shape(c, w, shape_info->present, options->detect_rounded_corners); - win_update_prop_fullscreen(c, atoms, w); - - // Determine if the window is focused - xcb_window_t wid = XCB_NONE; - bool exists; - if (options->use_ewmh_active_win) { - wid_get_prop_window(c, c->screen_info->root, atoms->a_NET_ACTIVE_WINDOW, - &exists); - } else { - // Determine the currently focused window so we can apply appropriate - // opacity on it - xcb_get_input_focus_reply_t *reply = - xcb_get_input_focus_reply(c->c, xcb_get_input_focus(c->c), NULL); - - if (reply) { - wid = reply->focus; - free(reply); - } - } - if (wid == win_id(w) || wid == win_client_id(w, /*fallback_to_self=*/false)) { - w->is_focused = true; - } - - auto attributes_reply = XCB_AWAIT(xcb_get_window_attributes, c->c, win_id(w)); - w->a = *attributes_reply; - w->pictfmt = x_get_pictform_for_visual(c, w->a.visual); - free(attributes_reply); - - c2_window_state_init(state, &w->c2_state); - c2_window_state_update(state, &w->c2_state, c->c, - win_client_id(w, /*fallback_to_self=*/true), win_id(w)); - return w; -} - -xcb_window_t select_window(struct x_connection *c) { +xcb_window_t inspect_select_window(struct x_connection *c) { xcb_font_t font = x_new_id(c); xcb_cursor_t cursor = x_new_id(c); const char font_name[] = "cursor"; @@ -153,120 +79,55 @@ xcb_window_t select_window(struct x_connection *c) { } struct c2_match_state { - struct c2_state *state; - struct win *w; + const struct c2_state *state; + const struct win *w; bool print_value; }; -bool c2_match_once_and_log(const c2_lptr_t *cond, void *data) { - struct c2_match_state *state = data; +static bool c2_match_and_log(const struct list_node *list, const struct c2_state *state, + const struct win *w, bool print_value) { void *rule_data = NULL; - printf(" %s ... ", c2_lptr_to_str(cond)); - bool matched = c2_match_one(state->state, state->w, cond, rule_data); - printf("%s", matched ? "\033[1;32mmatched\033[0m" : "not matched"); - if (state->print_value && matched) { - printf("/%lu", (unsigned long)(intptr_t)rule_data); - state->print_value = false; + c2_condition_list_foreach((struct list_node *)list, i) { + printf(" %s ... ", c2_condition_to_str(i)); + bool matched = c2_match_one(state, w, i, rule_data); + printf("%s", matched ? "\033[1;32mmatched\033[0m" : "not matched"); + if (print_value && matched) { + printf("/%lu", (unsigned long)(intptr_t)rule_data); + print_value = false; + } + printf("\n"); } - printf("\n"); return false; } -#define BOLD(str) "\033[1m" str "\033[0m" - -int inspect_main(int argc, char **argv, const char *config_file) { - Display *dpy = XOpenDisplay(NULL); - if (!dpy) { - log_fatal("Can't open display"); - return 1; - } - struct x_connection c; - x_connection_init(&c, dpy); - - xcb_prefetch_extension_data(c.c, &xcb_shape_id); - - struct options options; - if (!parse_config(&options, config_file)) { - return 1; - } - - // Parse all of the rest command line options - options.backend = backend_find("dummy"); - if (!get_cfg(&options, argc, argv)) { - log_fatal("Failed to get configuration, usually mean you have specified " - "invalid options."); - return 1; - } - - auto atoms = init_atoms(c.c); - auto state = c2_state_new(atoms); - options_postprocess_c2_lists(state, &c, &options); - - struct wm *wm = wm_new(); - - wm_import_start(wm, &c, atoms, c.screen_info->root, NULL); - // Process events until the window tree is consistent - while (x_has_pending_requests(&c)) { - auto ev = x_poll_for_event(&c); - if (ev == NULL) { - continue; - } - switch (ev->response_type) { - case XCB_CREATE_NOTIFY:; - auto create = (xcb_create_notify_event_t *)ev; - auto parent = wm_find(wm, create->parent); - wm_import_start(wm, &c, atoms, - ((xcb_create_notify_event_t *)ev)->window, parent); - break; - case XCB_DESTROY_NOTIFY: - wm_destroy(wm, ((xcb_destroy_notify_event_t *)ev)->window); - break; - case XCB_REPARENT_NOTIFY:; - auto reparent = (xcb_reparent_notify_event_t *)ev; - wm_reparent(wm, reparent->window, reparent->parent); - break; - default: - // Ignore ConfigureNotify and CirculateNotify, because we don't - // use stacking order for window rules. - break; - } - free(ev); +void inspect_dump_window(const struct c2_state *state, const struct options *opts, + const struct win *w) { + if (list_is_empty(&opts->rules)) { + printf("Checking " BOLD("transparent-clipping-exclude") ":\n"); + c2_match_and_log(&opts->transparent_clipping_blacklist, state, w, false); + printf("Checking " BOLD("shadow-exclude") ":\n"); + c2_match_and_log(&opts->shadow_blacklist, state, w, false); + printf("Checking " BOLD("fade-exclude") ":\n"); + c2_match_and_log(&opts->fade_blacklist, state, w, false); + printf("Checking " BOLD("clip-shadow-above") ":\n"); + c2_match_and_log(&opts->shadow_clip_list, state, w, true); + printf("Checking " BOLD("focus-exclude") ":\n"); + c2_match_and_log(&opts->focus_blacklist, state, w, false); + printf("Checking " BOLD("invert-color-include") ":\n"); + c2_match_and_log(&opts->invert_color_list, state, w, false); + printf("Checking " BOLD("blur-background-exclude") ":\n"); + c2_match_and_log(&opts->blur_background_blacklist, state, w, false); + printf("Checking " BOLD("unredir-if-possible-exclude") ":\n"); + c2_match_and_log(&opts->unredir_if_possible_blacklist, state, w, false); + printf("Checking " BOLD("rounded-corners-exclude") ":\n"); + c2_match_and_log(&opts->rounded_corners_blacklist, state, w, false); + + printf("Checking " BOLD("opacity-rule") ":\n"); + c2_match_and_log(&opts->opacity_rules, state, w, true); + printf("Checking " BOLD("corner-radius-rule") ":\n"); + c2_match_and_log(&opts->corner_radius_rules, state, w, true); } - auto target = select_window(&c); - log_info("Target window: %#x", target); - auto w = setup_window(&c, atoms, &options, wm, state, target); - struct c2_match_state match_state = { - .state = state, - .w = w, - }; - printf("Checking " BOLD("transparent-clipping-exclude") ":\n"); - c2_list_foreach(options.transparent_clipping_blacklist, c2_match_once_and_log, - &match_state); - printf("Checking " BOLD("shadow-exclude") ":\n"); - c2_list_foreach(options.shadow_blacklist, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("fade-exclude") ":\n"); - c2_list_foreach(options.fade_blacklist, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("clip-shadow-above") ":\n"); - c2_list_foreach(options.shadow_clip_list, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("focus-exclude") ":\n"); - c2_list_foreach(options.focus_blacklist, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("invert-color-include") ":\n"); - c2_list_foreach(options.invert_color_list, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("blur-background-exclude") ":\n"); - c2_list_foreach(options.blur_background_blacklist, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("unredir-if-possible-exclude") ":\n"); - c2_list_foreach(options.unredir_if_possible_blacklist, c2_match_once_and_log, - &match_state); - printf("Checking " BOLD("rounded-corners-exclude") ":\n"); - c2_list_foreach(options.rounded_corners_blacklist, c2_match_once_and_log, &match_state); - - match_state.print_value = true; - printf("Checking " BOLD("opacity-rule") ":\n"); - c2_list_foreach(options.opacity_rules, c2_match_once_and_log, &match_state); - printf("Checking " BOLD("corner-radius-rule") ":\n"); - c2_list_foreach(options.corner_radius_rules, c2_match_once_and_log, &match_state); - printf("\nHere are some rule(s) that match this window:\n"); if (w->name != NULL) { printf(" name = '%s'\n", w->name); @@ -292,21 +153,88 @@ int inspect_main(int argc, char **argv, const char *config_file) { printf(" bounding_shaped\n"); } printf(" border_width = %d\n", w->g.border_width); +} - pixman_region32_fini(&w->bounding_shape); - free(w->name); - free(w->class_instance); - free(w->class_general); - free(w->role); - c2_window_state_destroy(state, &w->c2_state); - free(w); +void inspect_dump_window_maybe_options(struct window_maybe_options wopts) { + bool nothing = true; + printf(" Applying:\n"); + if (wopts.shadow != TRI_UNKNOWN) { + printf(" shadow = %s\n", wopts.shadow == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.fade != TRI_UNKNOWN) { + printf(" fade = %s\n", wopts.fade == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.blur_background != TRI_UNKNOWN) { + printf(" blur_background = %s\n", + wopts.blur_background == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.invert_color != TRI_UNKNOWN) { + printf(" invert_color = %s\n", + wopts.invert_color == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.clip_shadow_above != TRI_UNKNOWN) { + printf(" clip_shadow_above = %s\n", + wopts.clip_shadow_above == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.transparent_clipping != TRI_UNKNOWN) { + printf(" transparent_clipping = %s\n", + wopts.transparent_clipping == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.full_shadow != TRI_UNKNOWN) { + printf(" full_shadow = %s\n", + wopts.full_shadow == TRI_TRUE ? "true" : "false"); + nothing = false; + } + if (wopts.unredir != WINDOW_UNREDIR_INVALID) { + const char *str = NULL; + switch (wopts.unredir) { + case WINDOW_UNREDIR_WHEN_POSSIBLE_ELSE_TERMINATE: str = "true"; break; + case WINDOW_UNREDIR_TERMINATE: str = "false"; break; + case WINDOW_UNREDIR_FORCED: str = "\"forced\""; break; + case WINDOW_UNREDIR_PASSIVE: str = "\"passive\""; break; + case WINDOW_UNREDIR_WHEN_POSSIBLE: str = "\"preferred\""; break; + default: unreachable(); + } + printf(" unredir = %s\n", str); + nothing = false; + } + if (!safe_isnan(wopts.opacity)) { + printf(" opacity = %f\n", wopts.opacity); + nothing = false; + } + if (!safe_isnan(wopts.dim)) { + printf(" dim = %f\n", wopts.dim); + nothing = false; + } + if (wopts.corner_radius >= 0) { + printf(" corner_radius = %d\n", wopts.corner_radius); + nothing = false; + } - wm_free(wm); + char **animation_triggers = dynarr_new(char *, 0); + for (int i = 0; i <= ANIMATION_TRIGGER_LAST; i++) { + if (wopts.animations[i].script != NULL) { + char *name = NULL; + casprintf(&name, "\"%s\"", animation_trigger_names[i]); + dynarr_push(animation_triggers, name); + } + } + if (dynarr_len(animation_triggers) > 0) { + char *animation_triggers_str = dynarr_join(animation_triggers, ", "); + printf(" animations = { triggers = [%s]; }\n", animation_triggers_str); + free(animation_triggers_str); + nothing = false; + } else { + dynarr_free_pod(animation_triggers); + } - log_deinit_tls(); - c2_state_free(state); - destroy_atoms(atoms); - options_destroy(&options); - XCloseDisplay(c.dpy); - return 0; + if (nothing) { + printf(" (nothing)\n"); + } } diff --git a/src/inspect.h b/src/inspect.h index eb700737f9..891ddc0630 100644 --- a/src/inspect.h +++ b/src/inspect.h @@ -3,5 +3,14 @@ #pragma once #include +#include "wm/win.h" + +struct x_connection; +struct c2_state; +struct options; int inspect_main(int argc, char **argv, const char *config_file); +xcb_window_t inspect_select_window(struct x_connection *c); +void inspect_dump_window(const struct c2_state *state, const struct options *opts, + const struct win *w); +void inspect_dump_window_maybe_options(struct window_maybe_options wopts); diff --git a/src/log.c b/src/log.c index 0ac4b4f2c1..6fa7de5833 100644 --- a/src/log.c +++ b/src/log.c @@ -17,6 +17,7 @@ #include "compiler.h" #include "log.h" +#include "utils/console.h" #include "utils/misc.h" thread_local struct log *tls_logger; @@ -274,7 +275,6 @@ static void file_logger_destroy(struct log_target *tgt) { free(tgt); } -#define ANSI(x) "\033[" x "m" static const char *terminal_colorize_begin(enum log_level level) { switch (level) { case LOG_LEVEL_TRACE: return ANSI("30;2"); diff --git a/src/options.c b/src/options.c index b03e4e8ef9..2d8383aa53 100644 --- a/src/options.c +++ b/src/options.c @@ -59,6 +59,7 @@ struct picom_option { int has_arg; struct picom_arg arg; const char *help; + const char *argv0; }; static bool set_flag(const struct picom_option * /*opt*/, const struct picom_arg *arg, @@ -70,7 +71,7 @@ static bool set_flag(const struct picom_option * /*opt*/, const struct picom_arg static bool set_rule_flag(const struct picom_option *arg_opt, const struct picom_arg *arg, const char * /*arg_str*/, void *output) { auto opt = (struct options *)output; - if (opt->rules != NULL) { + if (!list_is_empty(&opt->rules)) { log_warn_both_style_of_rules(arg_opt->long_name); opt->has_both_style_of_rules = true; return true; @@ -114,7 +115,7 @@ static bool store_float(const struct picom_option *opt, const struct picom_arg * static bool store_rule_float(const struct picom_option *arg_opt, const struct picom_arg *arg, const char *arg_str, void *output) { auto opt = (struct options *)output; - if (opt->rules != NULL) { + if (!list_is_empty(&opt->rules)) { log_warn_both_style_of_rules(arg_opt->long_name); opt->has_both_style_of_rules = true; return true; @@ -147,12 +148,12 @@ static bool store_rules(const struct picom_option *arg_opt, const struct picom_a const char *arg_str, void *output) { const struct picom_rules_parser *parser = arg->user_data; struct options *opt = (struct options *)output; - if (opt->rules != NULL) { + if (!list_is_empty(&opt->rules)) { log_warn_both_style_of_rules(arg_opt->long_name); opt->has_both_style_of_rules = true; return true; } - auto rules = (c2_lptr_t **)(output + arg->offset); + auto rules = (struct list_node *)(output + arg->offset); if (!parser->parse_prefix) { return c2_parse(rules, arg_str, NULL) != NULL; } @@ -452,6 +453,8 @@ static const struct picom_option picom_options[] = { "window is fullscreen based only on its size and coordinates."}, [804] = {"realtime" , ENABLE(use_realtime_scheduling) , "Enable realtime scheduling. This might reduce latency, but might also cause " "other issues. Disable this if you see the compositor being killed."}, + [805] = {"monitor" , ENABLE(inspect_monitor) , "For picom-inspect, run in a loop and dump information every time something " + "changed about a window.", "picom-inspect"}, // Flags that takes an argument ['r'] = {"shadow-radius" , INTEGER(shadow_radius, 0, INT_MAX) , "The blur radius for shadows. (default 12)"}, @@ -634,12 +637,19 @@ static void usage(const char *argv0, int ret) { line_wrap = window_size.ws_col; } + const char *basename = strrchr(argv0, '/') ? strrchr(argv0, '/') + 1 : argv0; + size_t help_indent = 0; for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { if (picom_options[i].help == NULL) { // Hide options with no help message. continue; } + if (picom_options[i].argv0 != NULL && + strcmp(picom_options[i].argv0, basename) != 0) { + // Hide options that are not for this program. + continue; + } auto option_len = strlen(picom_options[i].long_name) + 2 + 4; if (picom_options[i].arg.name) { option_len += strlen(picom_options[i].arg.name) + 1; @@ -654,6 +664,11 @@ static void usage(const char *argv0, int ret) { if (picom_options[i].help == NULL) { continue; } + if (picom_options[i].argv0 != NULL && + strcmp(picom_options[i].argv0, basename) != 0) { + // Hide options that are not for this program. + continue; + } size_t option_len = 8; fprintf(f, " "); if ((i > 'a' && i < 'z') || (i > 'A' && i < 'Z')) { @@ -841,7 +856,7 @@ static bool sanitize_options(struct options *opt) { dynarr_clear(opt->all_scripts, script_ptr_deinit); } - if (opt->window_shader_fg || opt->window_shader_fg_rules) { + if (opt->window_shader_fg || !list_is_empty(&opt->window_shader_fg_rules)) { log_warn("The new shader interface is not supported by the " "legacy glx backend. You may want to use " "--glx-fshader-win instead."); @@ -943,10 +958,15 @@ bool get_cfg(options_t *opt, int argc, char *const *argv) { int o = 0, longopt_idx = -1; bool failed = false; optind = 1; + const char *basename = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { if (o == '?' || o == ':' || picom_options[o].arg.handler == NULL) { usage(argv[0], 1); failed = true; + } else if (picom_options[o].argv0 != NULL && + strcmp(picom_options[o].argv0, basename) != 0) { + log_error("Invalid option %s", argv[optind - 1]); + failed = true; } else if (!picom_options[o].arg.handler( &picom_options[o], &picom_options[o].arg, optarg, opt)) { failed = true; @@ -1008,27 +1028,27 @@ bool get_cfg(options_t *opt, int argc, char *const *argv) { void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c, struct options *option) { - if (option->rules) { - if (!c2_list_postprocess(state, c->c, option->rules)) { + if (!list_is_empty(&option->rules)) { + if (!c2_list_postprocess(state, c->c, &option->rules)) { log_error("Post-processing of rules failed, some of your rules " "might not work"); } return; } - if (!(c2_list_postprocess(state, c->c, option->unredir_if_possible_blacklist) && - c2_list_postprocess(state, c->c, option->paint_blacklist) && - c2_list_postprocess(state, c->c, option->shadow_blacklist) && - c2_list_postprocess(state, c->c, option->shadow_clip_list) && - c2_list_postprocess(state, c->c, option->fade_blacklist) && - c2_list_postprocess(state, c->c, option->blur_background_blacklist) && - c2_list_postprocess(state, c->c, option->invert_color_list) && - c2_list_postprocess(state, c->c, option->window_shader_fg_rules) && - c2_list_postprocess(state, c->c, option->opacity_rules) && - c2_list_postprocess(state, c->c, option->rounded_corners_blacklist) && - c2_list_postprocess(state, c->c, option->corner_radius_rules) && - c2_list_postprocess(state, c->c, option->focus_blacklist) && - c2_list_postprocess(state, c->c, option->transparent_clipping_blacklist))) { + if (!(c2_list_postprocess(state, c->c, &option->unredir_if_possible_blacklist) && + c2_list_postprocess(state, c->c, &option->paint_blacklist) && + c2_list_postprocess(state, c->c, &option->shadow_blacklist) && + c2_list_postprocess(state, c->c, &option->shadow_clip_list) && + c2_list_postprocess(state, c->c, &option->fade_blacklist) && + c2_list_postprocess(state, c->c, &option->blur_background_blacklist) && + c2_list_postprocess(state, c->c, &option->invert_color_list) && + c2_list_postprocess(state, c->c, &option->window_shader_fg_rules) && + c2_list_postprocess(state, c->c, &option->opacity_rules) && + c2_list_postprocess(state, c->c, &option->rounded_corners_blacklist) && + c2_list_postprocess(state, c->c, &option->corner_radius_rules) && + c2_list_postprocess(state, c->c, &option->focus_blacklist) && + c2_list_postprocess(state, c->c, &option->transparent_clipping_blacklist))) { log_error("Post-processing of conditionals failed, some of your " "rules might not work"); } diff --git a/src/picom.c b/src/picom.c index 2fb0e0594f..ca10d8c8b5 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1986,10 +1986,6 @@ static bool load_shader_source(session_t *ps, const char *path) { return true; } -static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) { - return load_shader_source(data, c2_list_get_data(cond)); -} - static struct window_options win_options_from_config(const struct options *opts) { struct window_options ret = { .blur_background = opts->blur_method != BLUR_METHOD_NONE, @@ -2193,6 +2189,17 @@ static session_t *session_init(int argc, char **argv, Display *dpy, return NULL; } + const char *basename = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; + + if (strcmp(basename, "picom-inspect") == 0) { + ps->o.backend = backend_find("dummy"); + ps->o.print_diagnostics = false; + ps->o.dbus = false; + if (!ps->o.inspect_monitor) { + ps->o.inspect_win = inspect_select_window(&ps->c); + } + } + ps->window_options_default = win_options_from_config(&ps->o); if (ps->o.window_shader_fg) { @@ -2227,9 +2234,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, options_postprocess_c2_lists(ps->c2_state, &ps->c, &ps->o); // Load shader source file specified in the shader rules - if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) { - log_error("Failed to load shader source file for some of the window " - "shader rules"); + c2_condition_list_foreach(&ps->o.window_shader_fg_rules, i) { + if (!load_shader_source(ps, c2_condition_get_data(i))) { + log_error("Failed to load shader source file for some of the " + "window shader rules"); + } } if (load_shader_source(ps, ps->o.window_shader_fg)) { log_error("Failed to load window shader source file"); @@ -2734,11 +2743,6 @@ int PICOM_MAIN(int argc, char **argv) { return exit_code; } - char *exe_name = basename(argv[0]); - if (strcmp(exe_name, "picom-inspect") == 0) { - return inspect_main(argc, argv, config_file); - } - int pfds[2]; if (need_fork) { if (pipe2(pfds, O_CLOEXEC)) { diff --git a/src/utils/console.h b/src/utils/console.h new file mode 100644 index 0000000000..df6242bfe9 --- /dev/null +++ b/src/utils/console.h @@ -0,0 +1,6 @@ +#pragma once + +/// Generate ANSI escape code +#define ANSI(x) "\033[" x "m" +/// Create a string that will print `str` in bold when output to terminal +#define BOLD(str) "\033[1m" str "\033[0m" diff --git a/src/wm/win.c b/src/wm/win.c index ff03ac73f6..e7599073e7 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -24,10 +24,12 @@ #include "compiler.h" #include "config.h" #include "dbus.h" +#include "inspect.h" #include "log.h" #include "picom.h" #include "region.h" #include "render.h" +#include "utils/console.h" #include "utils/misc.h" #include "x.h" @@ -109,7 +111,7 @@ static bool win_is_focused(session_t *ps, struct win *w) { (ps->o.mark_wmwin_focused && is_wmwin) || (ps->o.mark_ovredir_focused && wm_ref_client_of(w->tree_ref) == NULL && !is_wmwin) || (w->a.map_state == XCB_MAP_STATE_VIEWABLE && - c2_match(ps->c2_state, w, ps->o.focus_blacklist, NULL))) { + c2_match(ps->c2_state, w, &ps->o.focus_blacklist, NULL))) { return true; } return false; @@ -846,7 +848,7 @@ static void win_determine_shadow(session_t *ps, struct win *w) { if (!ps->o.wintype_option[index_of_lowest_one(w->window_types)].shadow) { log_debug("Shadow disabled by wintypes"); w->options.shadow = TRI_FALSE; - } else if (c2_match(ps->c2_state, w, ps->o.shadow_blacklist, NULL)) { + } else if (c2_match(ps->c2_state, w, &ps->o.shadow_blacklist, NULL)) { log_debug("Shadow disabled by shadow-exclude"); w->options.shadow = TRI_FALSE; } else if (ps->o.shadow_ignore_shaped && w->bounding_shaped && !w->rounded_corners) { @@ -892,7 +894,7 @@ bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms static void win_determine_clip_shadow_above(session_t *ps, struct win *w) { bool should_crop = (ps->o.wintype_option[index_of_lowest_one(w->window_types)].clip_shadow_above || - c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL)); + c2_match(ps->c2_state, w, &ps->o.shadow_clip_list, NULL)); w->options.clip_shadow_above = should_crop ? TRI_TRUE : TRI_UNKNOWN; } @@ -905,7 +907,7 @@ static void win_determine_invert_color(session_t *ps, struct win *w) { return; } - if (c2_match(ps->c2_state, w, ps->o.invert_color_list, NULL)) { + if (c2_match(ps->c2_state, w, &ps->o.invert_color_list, NULL)) { w->options.invert_color = TRI_TRUE; } } @@ -925,7 +927,7 @@ static void win_determine_blur_background(session_t *ps, struct win *w) { if (!ps->o.wintype_option[index_of_lowest_one(w->window_types)].blur_background) { log_debug("Blur background disabled by wintypes"); w->options.blur_background = TRI_FALSE; - } else if (c2_match(ps->c2_state, w, ps->o.blur_background_blacklist, NULL)) { + } else if (c2_match(ps->c2_state, w, &ps->o.blur_background_blacklist, NULL)) { log_debug("Blur background disabled by blur-background-exclude"); w->options.blur_background = TRI_FALSE; } @@ -937,13 +939,14 @@ static void win_determine_blur_background(session_t *ps, struct win *w) { */ static void win_determine_rounded_corners(session_t *ps, struct win *w) { void *radius_override = NULL; - bool blacklisted = c2_match(ps->c2_state, w, ps->o.rounded_corners_blacklist, NULL); + bool blacklisted = c2_match(ps->c2_state, w, &ps->o.rounded_corners_blacklist, NULL); if (blacklisted) { w->options.corner_radius = 0; return; } - bool matched = c2_match(ps->c2_state, w, ps->o.corner_radius_rules, &radius_override); + bool matched = + c2_match(ps->c2_state, w, &ps->o.corner_radius_rules, &radius_override); if (matched) { log_debug("Window %#010x (%s) matched corner rule! %d", win_id(w), w->name, (int)(long)radius_override); @@ -978,7 +981,7 @@ static void win_determine_fg_shader(session_t *ps, struct win *w) { void *val = NULL; w->options.shader = NULL; - if (c2_match(ps->c2_state, w, ps->o.window_shader_fg_rules, &val)) { + if (c2_match(ps->c2_state, w, &ps->o.window_shader_fg_rules, &val)) { struct shader_info *shader = NULL; HASH_FIND_STR(ps->shaders, val, shader); w->options.shader = shader; @@ -995,28 +998,32 @@ void win_update_opacity_rule(session_t *ps, struct win *w) { double opacity = NAN; void *val = NULL; - if (c2_match(ps->c2_state, w, ps->o.opacity_rules, &val)) { + if (c2_match(ps->c2_state, w, &ps->o.opacity_rules, &val)) { opacity = ((double)(long)val) / 100.0; } w->options.opacity = opacity; } -struct win_update_rule_params { - struct win *w; - struct session *ps; - struct window_maybe_options options; -}; - -static bool win_update_rule(const c2_lptr_t *rule, void *args) { - auto params = (struct win_update_rule_params *)args; +static bool +win_update_rule(struct session *ps, struct win *w, const c2_condition *rule, bool inspect) { void *pdata = NULL; - if (!c2_match_one(params->ps->c2_state, params->w, rule, &pdata)) { + if (inspect) { + printf(" %s ... ", c2_condition_to_str(rule)); + } + bool matched = c2_match_one(ps->c2_state, w, rule, &pdata); + if (inspect) { + printf("%s\n", matched ? ANSI("1;32") "matched\033[0m" : "not matched"); + } + if (!matched) { return false; } auto wopts_next = (struct window_maybe_options *)pdata; - params->options = win_maybe_options_fold(params->options, *wopts_next); + if (inspect) { + inspect_dump_window_maybe_options(*wopts_next); + } + w->options = win_maybe_options_fold(*wopts_next, w->options); return false; } @@ -1027,14 +1034,21 @@ static bool win_update_rule(const c2_lptr_t *rule, void *args) { */ void win_on_factor_change(session_t *ps, struct win *w) { auto wid = win_client_id(w, /*fallback_to_self=*/true); + bool inspect = (ps->o.inspect_win != XCB_NONE && win_id(w) == ps->o.inspect_win) || + ps->o.inspect_monitor; log_debug("Window %#010x, client %#010x (%s) factor change", win_id(w), wid, w->name); c2_window_state_update(ps->c2_state, &w->c2_state, ps->c.c, wid, win_id(w)); // Focus and is_fullscreen needs to be updated first, as other rules might depend // on the focused state of the window win_update_is_fullscreen(ps, w); + if (ps->o.inspect_monitor) { + printf("Window %#010x (Client %#010x):\n======\n\n", win_id(w), + win_client_id(w, /*fallback_to_self=*/true)); + } + assert(w->window_types != 0); - if (ps->o.rules == NULL) { + if (list_is_empty(&ps->o.rules)) { bool focused = win_is_focused(ps, w); auto window_type = index_of_lowest_one(w->window_types); // Universal rules take precedence over wintype_option and @@ -1057,11 +1071,11 @@ void win_on_factor_change(session_t *ps, struct win *w) { w->options.fade = TRI_UNKNOWN; w->options.transparent_clipping = TRI_UNKNOWN; if (w->a.map_state == XCB_MAP_STATE_VIEWABLE && - c2_match(ps->c2_state, w, ps->o.paint_blacklist, NULL)) { + c2_match(ps->c2_state, w, &ps->o.paint_blacklist, NULL)) { w->options.paint = TRI_FALSE; } if (w->a.map_state == XCB_MAP_STATE_VIEWABLE && - c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL)) { + c2_match(ps->c2_state, w, &ps->o.unredir_if_possible_blacklist, NULL)) { if (ps->o.wintype_option[window_type].redir_ignore) { w->options.unredir = WINDOW_UNREDIR_PASSIVE; } else { @@ -1077,23 +1091,23 @@ void win_on_factor_change(session_t *ps, struct win *w) { w->options.unredir = WINDOW_UNREDIR_WHEN_POSSIBLE; } - if (c2_match(ps->c2_state, w, ps->o.fade_blacklist, NULL)) { + if (c2_match(ps->c2_state, w, &ps->o.fade_blacklist, NULL)) { w->options.fade = TRI_FALSE; } - if (c2_match(ps->c2_state, w, ps->o.transparent_clipping_blacklist, NULL)) { + if (c2_match(ps->c2_state, w, &ps->o.transparent_clipping_blacklist, NULL)) { w->options.transparent_clipping = TRI_FALSE; } w->options.full_shadow = tri_from_bool(ps->o.wintype_option[window_type].full_shadow); } else { - struct win_update_rule_params params = { - .w = w, - .ps = ps, - .options = WIN_MAYBE_OPTIONS_DEFAULT, - }; + w->options = WIN_MAYBE_OPTIONS_DEFAULT; assert(w->state == WSTATE_MAPPED); - c2_list_foreach(ps->o.rules, win_update_rule, ¶ms); - w->options = params.options; + if (inspect) { + printf("Checking " BOLD("window rules") ":\n"); + } + c2_condition_list_foreach_rev(&ps->o.rules, i) { + win_update_rule(ps, w, i, inspect); + } if (safe_isnan(w->options.opacity) && w->has_opacity_prop) { w->options.opacity = ((double)w->opacity_prop) / OPAQUE; } @@ -1115,6 +1129,14 @@ void win_on_factor_change(session_t *ps, struct win *w) { (win_client_id(w, /*fallback_to_self=*/false) == ps->debug_window))) { w->options.paint = TRI_FALSE; } + + if (inspect) { + inspect_dump_window(ps->c2_state, &ps->o, w); + printf("\n"); + if (!ps->o.inspect_monitor) { + quit(ps); + } + } } /**