c2: match using stored window properties

And removing the session_t parameter from c2_match.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-02-16 15:19:36 +00:00
parent 3246cd1e31
commit 008720fbc8
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
3 changed files with 165 additions and 207 deletions

339
src/c2.c
View File

@ -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;
}

View File

@ -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);

View File

@ -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);