diff --git a/src/c2.c b/src/c2.c index 331c51e3..7ebf1e0e 100644 --- a/src/c2.c +++ b/src/c2.c @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -212,7 +213,7 @@ static const c2_predef_t C2_PREDEFS[] = { }; /** - * Get the numeric property value from a win_prop_t. + * Get the numeric property value from a winprop_t. */ static inline long winprop_get_int(winprop_t prop, size_t index) { long tgt = 0; @@ -231,6 +232,68 @@ static inline long winprop_get_int(winprop_t prop, size_t index) { return tgt; } +/** + * Get the string representation of all atom values of a winprop_t. + * + * TODO(tryone144): Is this better off in x.c? + */ +static inline bool +winprop_get_atoms(session_t *ps, const winprop_t prop, char ***pstrlst, size_t *pnstr) { + if (prop.nitems == 0) { + return false; + } + + // Query atom names from the X server + auto name_cookies = ccalloc(prop.nitems, xcb_get_atom_name_cookie_t); + auto name_replies = ccalloc(prop.nitems, xcb_get_atom_name_reply_t *); + size_t cookie_count = 0; + size_t reply_count = 0; + size_t length = 0; + + for (size_t i = 0; i < prop.nitems; ++i) { + auto atom = (xcb_atom_t)winprop_get_int(prop, i); + if (atom) { + name_cookies[cookie_count] = xcb_get_atom_name(ps->c, atom); + ++cookie_count; + } + } + for (size_t i = 0; i < cookie_count; ++i) { + auto reply = xcb_get_atom_name_reply(ps->c, name_cookies[i], NULL); + if (reply) { + name_replies[reply_count] = reply; + ++reply_count; + length += (size_t)xcb_get_atom_name_name_length(reply) + 1; + } + } + + // Allocate the pointers and the strings together + size_t nstr = reply_count; + void *buf = NULL; + if (posix_memalign(&buf, alignof(char *), length + sizeof(char *) * nstr + 1) != 0) { + abort(); + } + + char **ret = buf; + char *strlst = buf + sizeof(char *) * nstr; + for (size_t i = 0; i < nstr; ++i) { + auto reply = name_replies[i]; + auto len = (size_t)xcb_get_atom_name_name_length(reply); + memcpy(strlst, xcb_get_atom_name_name(reply), len); + strlst[len] = '\0'; // atom name reply is not null terminated + free(reply); + + ret[i] = strlst; + strlst += len + 1; + } + + free(name_cookies); + free(name_replies); + + *pnstr = nstr; + *pstrlst = ret; + return true; +} + /** * Compare next word in a string with another string. */ @@ -333,7 +396,7 @@ static void attr_unused c2_dump(c2_ptr_t p); 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); +static bool c2_match_once(session_t *ps, struct managed_win *w, const c2_ptr_t cond); /** * Parse a condition string. @@ -1308,7 +1371,7 @@ static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { * * For internal use. */ -static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w, +static inline void c2_match_once_leaf(session_t *ps, struct managed_win *w, const c2_l_t *pleaf, bool *pres, bool *perr) { assert(pleaf); @@ -1319,13 +1382,12 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w return; } - const int idx = (pleaf->index < 0 ? 0 : pleaf->index); + const unsigned int idx = (unsigned int)(pleaf->index < 0 ? 0 : pleaf->index); switch (pleaf->ptntype) { // Deal with integer patterns case C2_L_PTINT: { long *targets = NULL; - long *targets_free = NULL; size_t ntargets = 0; // Get the value @@ -1367,30 +1429,52 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w } // A raw window property else { - int word_count = 1; - if (pleaf->index < 0) { + struct property_cache *cached_prop; + HASH_FIND_INT(w->cached_props, &pleaf->tgtatom, cached_prop); + + if (!cached_prop) { + cached_prop = ccalloc(1, struct property_cache); + cached_prop->atom = pleaf->tgtatom; + cached_prop->type = PROP_CACHE_TINT; + HASH_ADD_INT(w->cached_props, atom, cached_prop); + log_trace("Cache property %d for window %#010x", + cached_prop->atom, wid); + // Get length of property in 32-bit multiples auto prop_info = x_get_prop_info(ps, wid, pleaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - } - winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, word_count, - c2_get_atom_type(pleaf), pleaf->format); + int word_count = + to_int_checked((prop_info.length + 4 - 1) / 4); + winprop_t prop = + x_get_prop(ps, wid, pleaf->tgtatom, word_count, + c2_get_atom_type(pleaf), pleaf->format); - ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - if (ntargets > 0) { - targets = targets_free = ccalloc(ntargets, long); - *perr = false; - for (size_t i = 0; i < ntargets; ++i) { - targets[i] = winprop_get_int(prop, i); + cached_prop->nitems = prop.nitems; + cached_prop->items.integer = + ccalloc(cached_prop->nitems, long); + for (size_t i = 0; i < cached_prop->nitems; ++i) { + cached_prop->items.integer[i] = + winprop_get_int(prop, i); + } + free_winprop(&prop); + } else { + log_trace("Use cached property %d for window %#010x", + cached_prop->atom, wid); + } + + assert(cached_prop->type == PROP_CACHE_TINT); + if (idx < cached_prop->nitems) { + ntargets = (pleaf->index < 0 ? cached_prop->nitems + : min2(cached_prop->nitems, 1)); + if (ntargets > 0) { + targets = cached_prop->items.integer + idx; } } - free_winprop(&prop); } - if (*perr) { - goto fail_int; + if (ntargets == 0) { + return; } + *perr = false; // Do comparison bool res = false; @@ -1412,18 +1496,10 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w } } *pres = res; - - fail_int: - // Free property values after usage, if necessary - if (targets_free) { - free(targets_free); - } } break; // String patterns case C2_L_PTSTRING: { - const char **targets = NULL; - const char **targets_free = NULL; - const char **targets_free_inner = NULL; + char **targets = NULL; size_t ntargets = 0; // A predefined target @@ -1440,63 +1516,77 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w default: assert(0); break; } ntargets = 1; - targets = &predef_target; - } - // An atom type property, convert it to string - else if (pleaf->type == C2_L_TATOM) { - int word_count = 1; - if (pleaf->index < 0) { - // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(ps, wid, pleaf->tgtatom); - word_count = to_int_checked((prop_info.length + 4 - 1) / 4); - } - winprop_t prop = - x_get_prop_with_offset(ps, wid, pleaf->tgtatom, idx, word_count, - c2_get_atom_type(pleaf), pleaf->format); + targets = (char **)&predef_target; + } else { + struct property_cache *cached_prop; + HASH_FIND_INT(w->cached_props, &pleaf->tgtatom, cached_prop); - ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); - targets = targets_free = (const char **)ccalloc(2 * ntargets, char *); - targets_free_inner = targets + ntargets; + if (!cached_prop) { + cached_prop = ccalloc(1, struct property_cache); + cached_prop->atom = pleaf->tgtatom; + cached_prop->type = PROP_CACHE_TSTRING; + HASH_ADD_INT(w->cached_props, atom, cached_prop); + log_trace("Cache property %d for window %#010x", + cached_prop->atom, wid); - 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, xcb_get_atom_name(ps->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); + // An atom type property, convert it to string + if (pleaf->type == C2_L_TATOM) { + // Get length of property in 32-bit multiples + auto prop_info = + x_get_prop_info(ps, wid, pleaf->tgtatom); + int word_count = + to_int_checked((prop_info.length + 4 - 1) / 4); + winprop_t prop = x_get_prop( + ps, wid, pleaf->tgtatom, word_count, + c2_get_atom_type(pleaf), pleaf->format); + + char **strlst = NULL; + size_t nstr = 0; + if (winprop_get_atoms(ps, prop, &strlst, &nstr)) { + if (nstr > 0 && strlen(strlst[0]) > 0) { + cached_prop->nitems = nstr; + cached_prop->items.string = strlst; + } else { + free(strlst); + } + } + free_winprop(&prop); + } + // Not an atom type, just fetch the string list + else { + char **strlst = NULL; + size_t nstr = 0; + if (wid_get_text_prop(ps, wid, pleaf->tgtatom, + &strlst, &nstr)) { + if (nstr > 0 && strlen(strlst[0]) > 0) { + cached_prop->nitems = nstr; + cached_prop->items.string = strlst; + } else { + free(strlst); + } } } + } else { + log_trace("Use cached property %d for window %#010x", + cached_prop->atom, wid); } - free_winprop(&prop); - } - // Not an atom type, just fetch the string list - else { - char **strlst = NULL; - int nstr = 0; - if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) { - if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) { - ntargets = to_u32_checked(nstr); - targets = (const char **)strlst; - } else if (nstr > idx) { - ntargets = 1; - targets = (const char **)strlst + idx; + + assert(cached_prop->type == PROP_CACHE_TSTRING); + if (idx < cached_prop->nitems) { + ntargets = (pleaf->index < 0 ? cached_prop->nitems + : min2(cached_prop->nitems, 1)); + if (ntargets > 0) { + targets = cached_prop->items.string + idx; } } - if (strlst) { - targets_free = (const char **)strlst; - } } - if (ntargets == 0) { - goto fail_str; + if (ntargets == 0 || !targets) { + return; } for (size_t i = 0; i < ntargets; ++i) { if (!targets[i]) { - goto fail_str; + return; } } *perr = false; @@ -1558,20 +1648,6 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w } } *pres = res; - - fail_str: - // 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); - } } break; default: assert(0); break; } @@ -1582,7 +1658,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w * * @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(session_t *ps, struct managed_win *w, const c2_ptr_t cond) { bool result = false; bool error = true; @@ -1658,8 +1734,7 @@ 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) { +bool c2_match(session_t *ps, struct managed_win *w, const c2_lptr_t *condlst, void **pdata) { assert(ps->server_grabbed); // Then go through the whole linked list for (; condlst; condlst = condlst->next) { diff --git a/src/c2.h b/src/c2.h index d6b1d372..a1be1ca8 100644 --- a/src/c2.h +++ b/src/c2.h @@ -21,6 +21,6 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); c2_lptr_t *c2_free_lptr(c2_lptr_t *lp); -bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata); +bool c2_match(session_t *ps, struct managed_win *w, const c2_lptr_t *condlst, void **pdata); bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); diff --git a/src/event.c b/src/event.c index 9296e536..dca93c85 100644 --- a/src/event.c +++ b/src/event.c @@ -574,11 +574,9 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t w = find_toplevel(ps, ev->window); } if (w) { - // Set FACTOR_CHANGED so rules based on properties will be - // re-evaluated. - // Don't need to set property stale here, since that only - // concerns properties we explicitly check. - win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); + // Explicitly mark property as stale so we don't have to + // evict the whole cache + win_set_property_stale(w, ev->atom); } break; } diff --git a/src/win.c b/src/win.c index dff7eac6..d9babf81 100644 --- a/src/win.c +++ b/src/win.c @@ -376,6 +376,8 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) { } } +/// Returns true if the `prop` property is stale +static bool win_check_property_stale(struct managed_win *w, xcb_atom_t prop); /// Returns true if the `prop` property is stale, as well as clears the stale flag. static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop); /// Returns true if any of the properties are stale, as well as clear all the stale flags. @@ -384,6 +386,18 @@ static void win_clear_all_properties_stale(struct managed_win *w); /// Fetch new window properties from the X server, and run appropriate updates. Might set /// WIN_FLAGS_FACTOR_CHANGED static void win_update_properties(session_t *ps, struct managed_win *w) { + // Evict previously cached property values + struct property_cache *prop, *tmp_prop; + HASH_ITER(hh, w->cached_props, prop, tmp_prop) { + if (win_check_property_stale(w, prop->atom)) { + log_trace("Evict cached property %d for window %#010x", + prop->atom, w->base.id); + HASH_DEL(w->cached_props, prop); + free(prop->items.data); + free(prop); + } + } + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) { win_update_wintype(ps, w); } @@ -609,7 +623,7 @@ static bool attr_pure win_has_rounded_corners(const struct managed_win *w) { int win_update_name(session_t *ps, struct managed_win *w) { char **strlst = NULL; - int nstr = 0; + size_t nstr = 0; if (!w->client_win) { return 0; @@ -644,7 +658,7 @@ int win_update_name(session_t *ps, struct managed_win *w) { static int win_update_role(session_t *ps, struct managed_win *w) { char **strlst = NULL; - int nstr = 0; + size_t nstr = 0; if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) { return -1; @@ -1406,6 +1420,13 @@ void free_win_res(session_t *ps, struct managed_win *w) { free(w->stale_props); w->stale_props = NULL; w->stale_props_capacity = 0; + + struct property_cache *prop, *tmp_prop; + HASH_ITER(hh, w->cached_props, prop, tmp_prop) { + HASH_DEL(w->cached_props, prop); + free(prop->items.data); + free(prop); + } } /// Insert a new window after list_node `prev` @@ -1478,6 +1499,7 @@ struct win *fill_win(session_t *ps, struct win *w) { // change .stale_props = NULL, .stale_props_capacity = 0, + .cached_props = NULL, // Runtime variables, updated by dbus .fade_force = UNSET, @@ -1755,7 +1777,7 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int */ bool win_update_class(session_t *ps, struct managed_win *w) { char **strlst = NULL; - int nstr = 0; + size_t nstr = 0; // Can't do anything if there's no client window if (!w->client_win) @@ -2704,6 +2726,16 @@ static void win_clear_all_properties_stale(struct managed_win *w) { win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); } +static bool win_check_property_stale(struct managed_win *w, xcb_atom_t prop) { + const auto bits_per_element = sizeof(*w->stale_props) * 8; + if (prop >= w->stale_props_capacity * bits_per_element) { + return false; + } + + const auto mask = 1UL << (prop % bits_per_element); + return w->stale_props[prop / bits_per_element] & mask; +} + static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) { const auto bits_per_element = sizeof(*w->stale_props) * 8; if (prop >= w->stale_props_capacity * bits_per_element) { diff --git a/src/win.h b/src/win.h index 279d8da0..e6906b85 100644 --- a/src/win.h +++ b/src/win.h @@ -50,6 +50,26 @@ typedef struct { } glx_blur_cache_t; #endif +struct property_cache { + UT_hash_handle hh; + xcb_atom_t atom; + size_t nitems; + // XXX We can specify different types for the same atom given we have two rules. + // Not sure if this is desired behaviour, but it breaks the cache. + enum { PROP_CACHE_TUNDEFINED, + PROP_CACHE_TINT, + PROP_CACHE_TSTRING, + } type; + // TODO(tryone144): We should be fine with a simple (void *) to items and casting + // it to the proper type while rule-matching as the data-type should be fixed for + // any given atom. + union { + void *data; + long *integer; + char **string; + } items; +}; + /// An entry in the window stack. May or may not correspond to a window we know about. struct window_stack_entry { struct list_node stack_neighbour; @@ -140,6 +160,8 @@ struct managed_win { uint64_t *stale_props; /// number of uint64_ts that has been allocated for stale_props uint64_t stale_props_capacity; + /// Cached property-values for rule matching. + struct property_cache *cached_props; /// Whether this window is marked fullscreen in _NET_WM_STATE. DO NOT USE /// DIRECTLY! Query fullscreen state with `win_is_fullscreen`. bool fullscreen; diff --git a/src/x.c b/src/x.c index 3ee11ac4..efbc10ac 100644 --- a/src/x.c +++ b/src/x.c @@ -112,7 +112,7 @@ xcb_window_t wid_get_prop_window(session_t *ps, xcb_window_t wid, xcb_atom_t apr * Get the value of a text property of a window. */ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, - int *pnstr) { + size_t *pnstr) { assert(ps->server_grabbed); auto prop_info = x_get_prop_info(ps, wid, prop); auto type = prop_info.type; @@ -160,7 +160,7 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** if (nstr == 0) { // The property is set to an empty string, in that case, we return one // string - char **strlst = malloc(sizeof(char *)); + char **strlst = cmalloc(char *); strlst[0] = ""; *pnstr = 1; *pstrlst = strlst; @@ -187,7 +187,7 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** nstr += 1; } - *pnstr = to_int_checked(nstr); + *pnstr = nstr; *pstrlst = ret; free(r); return true; diff --git a/src/x.h b/src/x.h index daa31199..7e9a36c5 100644 --- a/src/x.h +++ b/src/x.h @@ -162,7 +162,7 @@ xcb_window_t wid_get_prop_window(session_t *ps, xcb_window_t wid, xcb_atom_t apr * @param[out] pnstr Number of strings in the array */ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, - int *pnstr); + size_t *pnstr); const xcb_render_pictforminfo_t * x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t);