From 008720fbc825ec5ef7cfd03b4236c2970dc59474 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 16 Feb 2024 15:19:36 +0000 Subject: [PATCH] c2: match using stored window properties And removing the session_t parameter from c2_match. Signed-off-by: Yuxuan Shui --- src/c2.c | 339 ++++++++++++++++++++++++------------------------------ src/c2.h | 4 +- src/win.c | 29 ++--- 3 files changed, 165 insertions(+), 207 deletions(-) diff --git a/src/c2.c b/src/c2.c index 6d402487..1ec55850 100644 --- a/src/c2.c +++ b/src/c2.c @@ -150,6 +150,7 @@ struct _c2_l { } match : 3; bool match_ignorecase : 1; char *tgt; + unsigned int target_id; xcb_atom_t tgtatom; bool tgt_onframe; int index; @@ -259,26 +260,6 @@ static const c2_predef_t C2_PREDEFS[] = { [C2_L_PROLE] = {"role", C2_L_TSTRING, 0}, }; -/** - * Get the numeric property value from a win_prop_t. - */ -static inline long winprop_get_int(winprop_t prop, size_t index) { - long tgt = 0; - - if (!prop.nitems || index >= prop.nitems) { - return 0; - } - - switch (prop.format) { - case 8: tgt = *(prop.p8 + index); break; - case 16: tgt = *(prop.p16 + index); break; - case 32: tgt = *(prop.p32 + index); break; - default: assert(0); break; - } - - return tgt; -} - /** * Compare next word in a string with another string. */ @@ -382,10 +363,6 @@ static const char *c2h_dump_str_tgt(const c2_l_t *pleaf); static const char *c2h_dump_str_type(const c2_l_t *pleaf); -static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf); - -static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond); - /** * Parse a condition string. */ @@ -1217,6 +1194,7 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t property->max_indices = pleaf->index; } } + pleaf->target_id = property->id; } // Warn about lower case characters in target name @@ -1371,7 +1349,7 @@ static const char *c2h_dump_str_type(const c2_l_t *pleaf) { * the null terminator. * null terminator will not be written to the output. */ -static size_t c2_condition_to_str(c2_ptr_t p, char *output, size_t len) { +static size_t c2_condition_to_str(const c2_ptr_t p, char *output, size_t len) { #define push_char(c) \ if (offset < len) \ output[offset] = (c); \ @@ -1514,32 +1492,49 @@ static size_t c2_condition_to_str(c2_ptr_t p, char *output, size_t len) { return offset; } -/** - * Get the type atom of a condition. - */ -static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { - switch (pleaf->type) { - case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL; - case C2_L_TWINDOW: return XCB_ATOM_WINDOW; - case C2_L_TSTRING: return XCB_ATOM_STRING; - case C2_L_TATOM: return XCB_ATOM_ATOM; - case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE; - default: assert(0); break; +/// Like `c2_condition_to_str`, but uses a internal static buffer, and adds a +/// nul byte to the end of the string. The returned string is only valid until +/// the next call to this function, and shouldn't be freed. +static const char *c2_condition_to_str2(const c2_ptr_t ptr) { + static thread_local char buf[4096]; + size_t len = c2_condition_to_str(ptr, buf, sizeof(buf)); + if (len >= sizeof(buf)) { + buf[sizeof(buf) - 1] = '\0'; + } else { + buf[len] = '\0'; } - unreachable(); + return buf; } -static bool -c2_match_once_leaf_int(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) { - long long *targets = NULL; - long long *targets_free = NULL; - size_t ntargets = 0; +static inline bool c2_int_op(const c2_l_t *leaf, size_t ntargets, const int64_t *targets) { + for (size_t i = 0; i < ntargets; ++i) { + long long tgt = targets[i]; + bool matches; + switch (leaf->op) { + case C2_L_OEXISTS: + matches = (leaf->predef != C2_L_PUNDEFINED ? tgt : true); + break; + case C2_L_OEQ: matches = (tgt == leaf->ptnint); break; + case C2_L_OGT: matches = (tgt > leaf->ptnint); break; + case C2_L_OGTEQ: matches = (tgt >= leaf->ptnint); break; + case C2_L_OLT: matches = (tgt < leaf->ptnint); break; + case C2_L_OLTEQ: matches = (tgt <= leaf->ptnint); break; + case C2_L_OFALSE: unreachable(); + } + if (matches) { + return true; + } + } + return false; +} + +static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *leaf) { const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id); // Get the value - // A predefined target - long long predef_target = 0; if (leaf->predef != C2_L_PUNDEFINED) { + // A predefined target + int64_t predef_target = 0; switch (leaf->predef) { case C2_L_PID: predef_target = wid; break; case C2_L_PX: predef_target = w->g.x; break; @@ -1565,58 +1560,41 @@ c2_match_once_leaf_int(session_t *ps, const struct managed_win *w, const c2_l_t assert(false); return false; } - ntargets = 1; - targets = &predef_target; + return c2_int_op(leaf, 1, &predef_target); + } + + // A raw window property + auto values = &w->c2_state.values[leaf->target_id]; + assert(!values->needs_update); + if (!values->valid) { + log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt, + w->client_win, w->name); + return false; + } + + int64_t *targets = NULL; + size_t ntargets = 0; + if (leaf->index < 0) { + if (values->length > ARR_SIZE(values->numbers)) { + targets = values->array; + } else { + targets = values->numbers; + } + ntargets = values->length; } else { - // A raw window property - int word_count = 1; - int offset = leaf->index; - if (leaf->index < 0) { - // index < 0 means match any index - // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(&ps->c, wid, leaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - offset = 0; + if ((size_t)leaf->index >= values->length) { + // index is out of bounds + return false; } - winprop_t prop = - x_get_prop_with_offset(&ps->c, wid, leaf->tgtatom, offset, word_count, - c2_get_atom_type(leaf), leaf->format); - - ntargets = (leaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - if (ntargets > 0) { - targets = targets_free = ccalloc(ntargets, long long); - for (size_t i = 0; i < ntargets; ++i) { - targets[i] = winprop_get_int(prop, i); - } + if (values->length > ARR_SIZE(values->numbers)) { + targets = &values->array[leaf->index]; + } else { + targets = &values->numbers[leaf->index]; } - free_winprop(&prop); + ntargets = 1; } - // Do comparison - bool matches = false; - for (size_t i = 0; i < ntargets; ++i) { - long long tgt = targets[i]; - switch (leaf->op) { - case C2_L_OEXISTS: - matches = (leaf->predef != C2_L_PUNDEFINED ? tgt : true); - break; - case C2_L_OEQ: matches = (tgt == leaf->ptnint); break; - case C2_L_OGT: matches = (tgt > leaf->ptnint); break; - case C2_L_OGTEQ: matches = (tgt >= leaf->ptnint); break; - case C2_L_OLT: matches = (tgt < leaf->ptnint); break; - case C2_L_OLTEQ: matches = (tgt <= leaf->ptnint); break; - default: log_error("Unknown operator %d.", leaf->op); assert(false); - } - if (matches) { - break; - } - } - - // Free property values after usage, if necessary - if (targets_free) { - free(targets_free); - } - return matches; + return c2_int_op(leaf, ntargets, targets); } static bool c2_string_op(const c2_l_t *leaf, const char *target) { @@ -1662,14 +1640,8 @@ static bool c2_string_op(const c2_l_t *leaf, const char *target) { unreachable(); } -static bool -c2_match_once_leaf_string(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) { - - const char **targets = NULL; - const char **targets_free = NULL; - const char **targets_free_inner = NULL; - size_t ntargets = 0; - const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id); +static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_win *w, + const c2_l_t *leaf) { // A predefined target const char *predef_target = NULL; @@ -1680,94 +1652,72 @@ c2_match_once_leaf_string(session_t *ps, const struct managed_win *w, const c2_l case C2_L_PCLASSG: predef_target = w->class_general; break; case C2_L_PCLASSI: predef_target = w->class_instance; break; case C2_L_PROLE: predef_target = w->role; break; - default: assert(0); break; + default: unreachable(); } - ntargets = 1; - targets = &predef_target; - } else if (leaf->type == C2_L_TATOM) { - // An atom type property, convert it to string - int word_count = 1; - int offset = leaf->index; + if (!predef_target) { + return false; + } + log_verbose("Matching against predefined target %s", predef_target); + return c2_string_op(leaf, predef_target); + } + + auto values = &w->c2_state.values[leaf->target_id]; + assert(!values->needs_update); + if (!values->valid) { + log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt, + w->client_win, w->name); + return false; + } + + if (leaf->type == C2_L_TATOM) { + size_t ntargets = 0; + int64_t *targets; if (leaf->index < 0) { - // index < 0 means match any index - // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(&ps->c, wid, leaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - offset = 0; + ntargets = values->length; + if (ntargets > ARR_SIZE(w->c2_state.values->numbers)) { + targets = values->array; + } else { + targets = values->numbers; + } + } else { + ntargets = 1; + targets = &values->numbers[leaf->index]; } - winprop_t prop = - x_get_prop_with_offset(&ps->c, wid, leaf->tgtatom, offset, word_count, - c2_get_atom_type(leaf), leaf->format); - - ntargets = (leaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - targets = targets_free = (const char **)ccalloc(2 * ntargets, char *); - targets_free_inner = targets + ntargets; for (size_t i = 0; i < ntargets; ++i) { - xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i); - if (atom) { - xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( - ps->c.c, xcb_get_atom_name(ps->c.c, atom), NULL); - if (reply) { - targets[i] = targets_free_inner[i] = strndup( - xcb_get_atom_name_name(reply), - (size_t)xcb_get_atom_name_name_length(reply)); - free(reply); - } + auto atom = (xcb_atom_t)targets[i]; + const char *atom_name = get_atom_name_cached(atoms, atom); + log_verbose("(%zu/%zu) Atom %u is %s", i, ntargets, atom, atom_name); + assert(atom_name != NULL); + if (atom_name && c2_string_op(leaf, atom_name)) { + return true; } } - free_winprop(&prop); - } else { - // Not an atom type, just fetch the string list - char **strlst = NULL; - int nstr = 0; - int index = max2(0, leaf->index); - if (wid_get_text_prop(&ps->c, ps->atoms, wid, leaf->tgtatom, &strlst, &nstr)) { - if (leaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) { - ntargets = to_u32_checked(nstr); - targets = (const char **)strlst; - } else if (nstr > index) { - ntargets = 1; - targets = (const char **)strlst + index; + return false; + } + + // Not an atom type, value is a list of nul separated strings + if (leaf->index < 0) { + size_t offset = 0; + while (offset < values->length) { + if (c2_string_op(leaf, values->string + offset)) { + return true; } + offset += strlen(values->string + offset) + 1; } - if (strlst) { - targets_free = (const char **)strlst; - } + return false; } - - bool matches = false; - if (ntargets == 0) { - goto fail; + size_t offset = 0; + int index = leaf->index; + while (offset < values->length && index != 0) { + offset += strlen(values->string + offset) + 1; + index -= 1; } - for (size_t i = 0; i < ntargets; ++i) { - if (!targets[i]) { - goto fail; - } + if (index != 0 || values->length == 0) { + // index is out of bounds + return false; } - - // Actual matching - for (size_t i = 0; i < ntargets; ++i) { - if (c2_string_op(leaf, targets[i])) { - matches = true; - break; - } - } - -fail: - // Free the string after usage, if necessary - if (targets_free_inner) { - for (size_t i = 0; i < ntargets; ++i) { - if (targets_free_inner[i]) { - free((void *)targets_free_inner[i]); - } - } - } - // Free property values after usage, if necessary - if (targets_free) { - free(targets_free); - } - return matches; + return c2_string_op(leaf, values->string + offset); } /** @@ -1776,7 +1726,7 @@ fail: * For internal use. */ static inline bool -c2_match_once_leaf(session_t *ps, const struct managed_win *w, const c2_l_t *leaf) { +c2_match_once_leaf(struct c2_state *state, const struct managed_win *w, const c2_l_t *leaf) { assert(leaf); const xcb_window_t wid = (leaf->tgt_onframe ? w->client_win : w->base.id); @@ -1789,14 +1739,18 @@ c2_match_once_leaf(session_t *ps, const struct managed_win *w, const c2_l_t *lea } if (leaf->op == C2_L_OFALSE) { - return leaf->neg; + log_debug("Condition is annulled."); + 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})); + switch (leaf->ptntype) { // Deal with integer patterns - case C2_L_PTINT: return c2_match_once_leaf_int(ps, w, leaf); + case C2_L_PTINT: return c2_match_once_leaf_int(w, leaf); // String patterns - case C2_L_PTSTRING: return c2_match_once_leaf_string(ps, w, leaf); + case C2_L_PTSTRING: return c2_match_once_leaf_string(state->atoms, w, leaf); default: log_error("Unknown pattern type %d.", leaf->ptntype); assert(false); @@ -1809,7 +1763,8 @@ c2_match_once_leaf(session_t *ps, const struct managed_win *w, const c2_l_t *lea * * @return true if matched, false otherwise. */ -static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) { +static bool +c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_t cond) { bool result = false; char condition_str[1024]; @@ -1821,18 +1776,21 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p return false; } + log_verbose("Matching window %#010x (%s) against condition %s", + w->base.id, w->name, c2_condition_to_str2(cond)); + switch (pb->op) { case C2_B_OAND: - result = (c2_match_once(ps, w, pb->opr1) && - c2_match_once(ps, w, pb->opr2)); + result = (c2_match_once(state, w, pb->opr1) && + c2_match_once(state, w, pb->opr2)); break; case C2_B_OOR: - result = (c2_match_once(ps, w, pb->opr1) || - c2_match_once(ps, w, pb->opr2)); + result = (c2_match_once(state, w, pb->opr1) || + c2_match_once(state, w, pb->opr2)); break; case C2_B_OXOR: - result = (c2_match_once(ps, w, pb->opr1) != - c2_match_once(ps, w, pb->opr2)); + result = (c2_match_once(state, w, pb->opr1) != + c2_match_once(state, w, pb->opr2)); break; default: log_error("Unknown boolean operator %d.", pb->op); @@ -1855,7 +1813,7 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p return false; } - result = c2_match_once_leaf(ps, w, pleaf); + result = c2_match_once_leaf(state, w, pleaf); if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) { size_t len = @@ -1882,12 +1840,11 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p * @param pdata a place to return the data * @return true if matched, false otherwise. */ -bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, - void **pdata) { - assert(ps->server_grabbed); +bool c2_match(struct c2_state *state, const struct managed_win *w, + const c2_lptr_t *condlst, void **pdata) { // Then go through the whole linked list for (; condlst; condlst = condlst->next) { - if (c2_match_once(ps, w, condlst->ptr)) { + if (c2_match_once(state, w, condlst->ptr)) { if (pdata) { *pdata = condlst->data; } diff --git a/src/c2.h b/src/c2.h index f73848dd..90a3dca1 100644 --- a/src/c2.h +++ b/src/c2.h @@ -49,8 +49,8 @@ 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(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, - void **pdata); +bool c2_match(struct c2_state *state, const struct managed_win *w, + const c2_lptr_t *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); diff --git a/src/win.c b/src/win.c index ce30b53c..a8978ee9 100644 --- a/src/win.c +++ b/src/win.c @@ -150,7 +150,7 @@ static void win_update_focused(session_t *ps, struct managed_win *w) { (ps->o.mark_wmwin_focused && w->wmwin) || (ps->o.mark_ovredir_focused && w->base.id == w->client_win && !w->wmwin) || (w->a.map_state == XCB_MAP_STATE_VIEWABLE && - c2_match(ps, w, ps->o.focus_blacklist, NULL))) { + c2_match(ps->c2_state, w, ps->o.focus_blacklist, NULL))) { w->focused = true; } @@ -999,7 +999,7 @@ static void win_determine_shadow(session_t *ps, struct managed_win *w) { if (!ps->o.wintype_option[w->window_type].shadow) { log_debug("Shadow disabled by wintypes"); shadow_new = false; - } else if (c2_match(ps, 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"); shadow_new = false; } else if (ps->o.shadow_ignore_shaped && w->bounding_shaped && @@ -1055,7 +1055,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 managed_win *w) { bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above || - c2_match(ps, w, ps->o.shadow_clip_list, NULL)); + c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL)); w->clip_shadow_above = should_crop; } @@ -1078,7 +1078,7 @@ static void win_determine_invert_color(session_t *ps, struct managed_win *w) { if (UNSET != w->invert_color_force) { invert_color_new = w->invert_color_force; } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - invert_color_new = c2_match(ps, w, ps->o.invert_color_list, NULL); + invert_color_new = c2_match(ps->c2_state, w, ps->o.invert_color_list, NULL); } win_set_invert_color(ps, w, invert_color_new); @@ -1167,7 +1167,7 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) if (!ps->o.wintype_option[w->window_type].blur_background) { log_debug("Blur background disabled by wintypes"); blur_background_new = false; - } else if (c2_match(ps, 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"); blur_background_new = false; @@ -1182,7 +1182,7 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) */ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { void *radius_override = NULL; - if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) { + if (c2_match(ps->c2_state, w, ps->o.corner_radius_rules, &radius_override)) { log_debug("Matched corner rule! %d", w->corner_radius); } @@ -1193,8 +1193,9 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) // Don't round full screen windows & excluded windows, // unless we find a corner override in corner_radius_rules - if (!radius_override && ((w && w->is_fullscreen) || - c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL))) { + if (!radius_override && + ((w && w->is_fullscreen) || + c2_match(ps->c2_state, w, ps->o.rounded_corners_blacklist, NULL))) { w->corner_radius = 0; log_debug("Not rounding corners for window %#010x", w->base.id); } else { @@ -1221,7 +1222,7 @@ static void win_determine_fg_shader(session_t *ps, struct managed_win *w) { auto shader_new = ps->o.window_shader_fg; void *val = NULL; - if (c2_match(ps, w, ps->o.window_shader_fg_rules, &val)) { + if (c2_match(ps->c2_state, w, ps->o.window_shader_fg_rules, &val)) { shader_new = val; } @@ -1244,7 +1245,7 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) { double opacity = 1.0; bool is_set = false; void *val = NULL; - if (c2_match(ps, w, ps->o.opacity_rules, &val)) { + if (c2_match(ps->c2_state, w, ps->o.opacity_rules, &val)) { opacity = ((double)(long)val) / 100.0; is_set = true; } @@ -1276,17 +1277,17 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { log_debug("Window mode changed to %d", w->mode); win_update_opacity_rule(ps, w); if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - w->paint_excluded = c2_match(ps, w, ps->o.paint_blacklist, NULL); + w->paint_excluded = c2_match(ps->c2_state, w, ps->o.paint_blacklist, NULL); } if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { w->unredir_if_possible_excluded = - c2_match(ps, w, ps->o.unredir_if_possible_blacklist, NULL); + c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL); } - w->fade_excluded = c2_match(ps, w, ps->o.fade_blacklist, NULL); + w->fade_excluded = c2_match(ps->c2_state, w, ps->o.fade_blacklist, NULL); w->transparent_clipping_excluded = - c2_match(ps, w, ps->o.transparent_clipping_blacklist, NULL); + c2_match(ps->c2_state, w, ps->o.transparent_clipping_blacklist, NULL); win_update_opacity_target(ps, w);