Merge pull request #1201 from yshui/cached-c2

This commit is contained in:
Yuxuan Shui 2024-02-19 00:04:57 +01:00 committed by GitHub
commit bef295f4c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 704 additions and 348 deletions

View File

@ -1,4 +1,5 @@
#include <string.h>
#include <uthash.h>
#include <xcb/xcb.h>
#include "atom.h"
@ -10,12 +11,21 @@
struct atom_entry {
struct cache_handle entry;
UT_hash_handle hh;
xcb_atom_t atom;
};
static inline int atom_getter(struct cache *cache attr_unused, const char *atom_name,
size_t keylen, struct cache_handle **value, void *user_data) {
struct atom_impl {
struct atom base;
struct cache c;
struct atom_entry *atom_to_name;
cache_getter_t getter;
};
static inline int atom_getter(struct cache *cache, const char *atom_name, size_t keylen,
struct cache_handle **value, void *user_data) {
xcb_connection_t *c = user_data;
auto atoms = container_of(cache, struct atom_impl, c);
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
c, xcb_intern_atom(c, 0, to_u16_checked(keylen), atom_name), NULL);
@ -31,19 +41,32 @@ static inline int atom_getter(struct cache *cache attr_unused, const char *atom_
struct atom_entry *entry = ccalloc(1, struct atom_entry);
entry->atom = atom;
HASH_ADD_INT(atoms->atom_to_name, atom, entry);
*value = &entry->entry;
return 0;
}
static inline void
atom_entry_free(struct cache *cache attr_unused, struct cache_handle *handle) {
struct atom_entry *entry = cache_entry(handle, struct atom_entry, entry);
static inline int
known_atom_getter(struct cache *cache attr_unused, const char *atom_name attr_unused,
size_t keylen attr_unused, struct cache_handle **value, void *user_data) {
auto atom = *(xcb_atom_t *)user_data;
struct atom_entry *entry = ccalloc(1, struct atom_entry);
entry->atom = atom;
*value = &entry->entry;
return 0;
}
static inline void atom_entry_free(struct cache *cache, struct cache_handle *handle) {
auto entry = cache_entry(handle, struct atom_entry, entry);
auto atoms = container_of(cache, struct atom_impl, c);
HASH_DEL(atoms->atom_to_name, entry);
free(entry);
}
xcb_atom_t get_atom(struct atom *a, const char *key, size_t keylen, xcb_connection_t *c) {
struct cache_handle *entry = NULL;
if (cache_get_or_fetch(&a->c, key, keylen, &entry, c, atom_getter) < 0) {
auto atoms = container_of(a, struct atom_impl, base);
if (cache_get_or_fetch(&atoms->c, key, keylen, &entry, c, atoms->getter) < 0) {
log_error("Failed to get atom %s", key);
return XCB_NONE;
}
@ -51,23 +74,89 @@ xcb_atom_t get_atom(struct atom *a, const char *key, size_t keylen, xcb_connecti
}
xcb_atom_t get_atom_cached(struct atom *a, const char *key, size_t keylen) {
return cache_entry(cache_get(&a->c, key, keylen), struct atom_entry, entry)->atom;
auto atoms = container_of(a, struct atom_impl, base);
return cache_entry(cache_get(&atoms->c, key, keylen), struct atom_entry, entry)->atom;
}
const char *get_atom_name(struct atom *a, xcb_atom_t atom, xcb_connection_t *c) {
struct atom_entry *entry = NULL;
auto atoms = container_of(a, struct atom_impl, base);
HASH_FIND(hh, atoms->atom_to_name, &atom, sizeof(xcb_atom_t), entry);
if (!entry) {
BUG_ON(c == NULL);
auto r = xcb_get_atom_name_reply(c, xcb_get_atom_name(c, atom), NULL);
if (!r) {
log_error("Failed to get atom name");
return NULL;
}
char *atom_name = xcb_get_atom_name_name(r);
auto len = (size_t)xcb_get_atom_name_name_length(r);
struct cache_handle *handle = NULL;
cache_get_or_fetch(&atoms->c, atom_name, len, &handle, &atom, known_atom_getter);
entry = cache_entry(handle, struct atom_entry, entry);
HASH_ADD_INT(atoms->atom_to_name, atom, entry);
free(r);
}
return entry->entry.key;
}
const char *get_atom_name_cached(struct atom *a, xcb_atom_t atom) {
struct atom_entry *entry = NULL;
auto atoms = container_of(a, struct atom_impl, base);
HASH_FIND(hh, atoms->atom_to_name, &atom, sizeof(xcb_atom_t), entry);
if (!entry) {
return NULL;
}
return entry->entry.key;
}
static inline struct atom *init_atoms_impl(xcb_connection_t *c, cache_getter_t getter) {
auto atoms = ccalloc(1, struct atom_impl);
atoms->c = CACHE_INIT;
atoms->getter = getter;
#define ATOM_GET(x) atoms->base.a##x = get_atom(&atoms->base, #x, sizeof(#x) - 1, c)
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
#undef ATOM_GET
return &atoms->base;
}
/**
* Create a new atom structure and fetch all predefined atoms
*/
struct atom *init_atoms(xcb_connection_t *c) {
auto atoms = ccalloc(1, struct atom);
atoms->c = CACHE_INIT;
#define ATOM_GET(x) atoms->a##x = get_atom(atoms, #x, sizeof(#x) - 1, c)
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
#undef ATOM_GET
return atoms;
return init_atoms_impl(c, atom_getter);
}
void destroy_atoms(struct atom *a) {
cache_invalidate_all(&a->c, atom_entry_free);
auto atoms = container_of(a, struct atom_impl, base);
cache_invalidate_all(&atoms->c, atom_entry_free);
assert(atoms->atom_to_name == NULL);
free(a);
}
#if defined(UNIT_TEST) || defined(CONFIG_FUZZER)
static inline int mock_atom_getter(struct cache *cache, const char *atom_name attr_unused,
size_t atom_len attr_unused, struct cache_handle **value,
void *user_data attr_unused) {
auto atoms = container_of(cache, struct atom_impl, c);
xcb_atom_t atom = (xcb_atom_t)HASH_COUNT(atoms->atom_to_name) + 1;
struct atom_entry *entry = ccalloc(1, struct atom_entry);
entry->atom = atom;
HASH_ADD_INT(atoms->atom_to_name, atom, entry);
*value = &entry->entry;
return 0;
}
struct atom *init_mock_atoms(void) {
return init_atoms_impl(NULL, mock_atom_getter);
}
#else
struct atom *init_mock_atoms(void) {
abort();
}
#endif

View File

@ -53,7 +53,6 @@
struct atom_entry;
struct atom {
struct cache c;
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
};
@ -71,5 +70,13 @@ xcb_atom_t get_atom_cached(struct atom *a, const char *key, size_t keylen);
static inline xcb_atom_t get_atom_cached_with_nul(struct atom *a, const char *key) {
return get_atom_cached(a, key, strlen(key));
}
const char *get_atom_name(struct atom *a, xcb_atom_t, xcb_connection_t *c);
const char *get_atom_name_cached(struct atom *a, xcb_atom_t atom);
void destroy_atoms(struct atom *a);
/// A mock atom object for unit testing. Successive calls to get_atom will return
/// secutive integers as atoms, starting from 1. Calling get_atom_name with atoms
/// previously seen will result in the string that was used to create the atom; if
/// the atom was never returned by get_atom, it will abort.
struct atom *init_mock_atoms(void);

858
src/c2.c

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,12 @@
typedef struct _c2_lptr c2_lptr_t;
typedef struct session session_t;
struct c2_state;
/// Per-window state used for c2 condition matching.
struct c2_window_state {
/// An array of window properties. Exact how many
/// properties there are is stored inside `struct c2_state`.
struct c2_property_value *values;
};
struct atom;
struct managed_win;
@ -34,9 +40,17 @@ struct c2_state *c2_state_new(struct atom *atoms);
void c2_state_free(struct c2_state *state);
/// Returns true if value of the property is used in any condition.
bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property);
void c2_window_state_init(const struct c2_state *state, struct c2_window_state *window_state);
void c2_window_state_destroy(const struct c2_state *state, struct c2_window_state *window_state);
void c2_window_state_mark_dirty(const struct c2_state *state,
struct c2_window_state *window_state, xcb_atom_t property,
bool is_on_frame);
void c2_window_state_update(struct c2_state *state, struct c2_window_state *window_state,
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

@ -575,11 +575,15 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
// Check for other atoms we are tracking
if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) {
bool change_is_on_frame = true;
auto w = find_managed_win(ps, ev->window);
if (!w) {
w = find_toplevel(ps, ev->window);
change_is_on_frame = false;
}
if (w) {
c2_window_state_mark_dirty(ps->c2_state, &w->c2_state, ev->atom,
change_is_on_frame);
// Set FACTOR_CHANGED so rules based on properties will be
// re-evaluated.
// Don't need to set property stale here, since that only

View File

@ -98,7 +98,7 @@ endif
if cc.has_argument('-fsanitize=fuzzer')
c2_fuzz = executable('c2_fuzz', srcs + ['fuzzer/c2.c'],
c_args: cflags + ['-fsanitize=fuzzer', '-Dmain=__main__'],
c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'],
link_args: ['-fsanitize=fuzzer'],
dependencies: [ base_deps, deps, test_h_dep ],
build_by_default: false,

View File

@ -2791,10 +2791,16 @@ static void session_run(session_t *ps) {
ev_run(ps->loop, 0);
}
#ifdef CONFIG_FUZZER
#define PICOM_MAIN(...) no_main(__VA_ARGS__)
#else
#define PICOM_MAIN(...) main(__VA_ARGS__)
#endif
/**
* The function that everybody knows.
*/
int main(int argc, char **argv) {
int PICOM_MAIN(int argc, char **argv) {
// Set locale so window names with special characters are interpreted
// correctly
setlocale(LC_ALL, "");

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;
}
@ -959,7 +959,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 &&
@ -1015,7 +1015,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;
}
@ -1038,7 +1038,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);
@ -1127,7 +1127,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;
@ -1142,7 +1142,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);
}
@ -1153,8 +1153,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 {
@ -1181,7 +1182,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;
}
@ -1204,7 +1205,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;
}
@ -1220,6 +1221,7 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
*/
void win_on_factor_change(session_t *ps, struct managed_win *w) {
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
c2_window_state_update(ps->c2_state, &w->c2_state, ps->c.c, w->client_win, w->base.id);
// Focus and is_fullscreen needs to be updated first, as other rules might depend
// on the focused state of the window
win_update_focused(ps, w);
@ -1235,17 +1237,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);
@ -1472,6 +1474,7 @@ void free_win_res(session_t *ps, struct managed_win *w) {
free(w->stale_props);
w->stale_props = NULL;
w->stale_props_capacity = 0;
c2_window_state_destroy(ps->c2_state, &w->c2_state);
}
/// Insert a new window after list_node `prev`
@ -1729,6 +1732,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
ps->atoms->a_NET_WM_STATE,
};
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
c2_window_state_init(ps->c2_state, &new->c2_state);
#ifdef CONFIG_DBUS
// Send D-Bus signal

View File

@ -284,6 +284,8 @@ struct managed_win {
/// The custom window shader to use when rendering.
struct shader_info *fg_shader;
struct c2_window_state c2_state;
#ifdef CONFIG_OPENGL
/// Textures and FBO background blur use.
glx_blur_cache_t glx_blur_cache;