mirror of
https://github.com/yshui/picom.git
synced 2024-11-11 13:51:02 -05:00
c2: store tracked property values per-window
Store properties used in c2 conditions in the window state, so we don't need to re-query them every time we evaluate the rules. First step to remove the dependency on X connection from c2_match. Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
5159f4da37
commit
f2626cc930
5 changed files with 284 additions and 0 deletions
261
src/c2.c
261
src/c2.c
|
@ -13,6 +13,8 @@
|
|||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <fnmatch.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <uthash.h>
|
||||
|
@ -67,11 +69,43 @@ static_assert(sizeof(struct c2_tracked_property_key) == 8, "Padding bytes in "
|
|||
struct c2_tracked_property {
|
||||
UT_hash_handle hh;
|
||||
struct c2_tracked_property_key key;
|
||||
unsigned int id;
|
||||
/// Highest indices of this property that
|
||||
/// are tracked. -1 mean all indices are tracked.
|
||||
int max_indices;
|
||||
};
|
||||
|
||||
struct c2_state {
|
||||
struct c2_tracked_property *tracked_properties;
|
||||
struct atom *atoms;
|
||||
xcb_get_property_cookie_t *cookies;
|
||||
uint32_t *propert_lengths;
|
||||
};
|
||||
|
||||
// TODO(yshui) this has some overlap with winprop_t, consider merging them.
|
||||
struct c2_property_value {
|
||||
union {
|
||||
struct {
|
||||
char *string;
|
||||
};
|
||||
struct {
|
||||
int64_t numbers[4];
|
||||
};
|
||||
struct {
|
||||
int64_t *array;
|
||||
unsigned int capacity;
|
||||
};
|
||||
};
|
||||
/// Number of items if the property is a number type,
|
||||
/// or number of bytes in the string if the property is a string type.
|
||||
uint32_t length;
|
||||
enum {
|
||||
C2_PROPERTY_TYPE_STRING,
|
||||
C2_PROPERTY_TYPE_NUMBER,
|
||||
C2_PROPERTY_TYPE_ATOM,
|
||||
} type;
|
||||
bool valid;
|
||||
bool needs_update;
|
||||
};
|
||||
|
||||
/// Initializer for c2_ptr_t.
|
||||
|
@ -1121,8 +1155,14 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t
|
|||
if (property == NULL) {
|
||||
property = cmalloc(struct c2_tracked_property);
|
||||
property->key = key;
|
||||
property->id = HASH_COUNT(state->tracked_properties);
|
||||
HASH_ADD_KEYPTR(hh, state->tracked_properties, &property->key,
|
||||
sizeof(property->key), property);
|
||||
property->max_indices = pleaf->index;
|
||||
} else if (pleaf->index == -1) {
|
||||
property->max_indices = -1;
|
||||
} else if (property->max_indices >= 0 && pleaf->index > property->max_indices) {
|
||||
property->max_indices = pleaf->index;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1815,9 +1855,230 @@ void c2_state_free(struct c2_state *state) {
|
|||
HASH_DEL(state->tracked_properties, property);
|
||||
free(property);
|
||||
}
|
||||
free(state->propert_lengths);
|
||||
free(state->cookies);
|
||||
free(state);
|
||||
}
|
||||
|
||||
void c2_window_state_init(const struct c2_state *state, struct c2_window_state *window_state) {
|
||||
auto property_count = HASH_COUNT(state->tracked_properties);
|
||||
window_state->values = ccalloc(property_count, struct c2_property_value);
|
||||
for (size_t i = 0; i < property_count; i++) {
|
||||
window_state->values[i].needs_update = true;
|
||||
window_state->values[i].valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void c2_window_state_destroy(const struct c2_state *state,
|
||||
struct c2_window_state *window_state) {
|
||||
size_t property_count = HASH_COUNT(state->tracked_properties);
|
||||
for (size_t i = 0; i < property_count; i++) {
|
||||
auto values = &window_state->values[i];
|
||||
if (values->type == C2_PROPERTY_TYPE_STRING) {
|
||||
free(window_state->values[i].string);
|
||||
} else if (values->length > ARR_SIZE(values->numbers)) {
|
||||
free(window_state->values[i].array);
|
||||
}
|
||||
}
|
||||
free(window_state->values);
|
||||
}
|
||||
|
||||
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) {
|
||||
struct c2_tracked_property *p;
|
||||
struct c2_tracked_property_key key = {
|
||||
.property = property,
|
||||
.is_on_frame = is_on_frame,
|
||||
};
|
||||
HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p);
|
||||
if (p) {
|
||||
window_state->values[p->id].needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
c2_window_state_update_one_from_reply(struct c2_state *state,
|
||||
struct c2_property_value *value, xcb_atom_t property,
|
||||
xcb_get_property_reply_t *reply, xcb_connection_t *c) {
|
||||
auto len = to_u32_checked(xcb_get_property_value_length(reply));
|
||||
void *data = xcb_get_property_value(reply);
|
||||
bool property_is_string = x_is_type_string(state->atoms, reply->type);
|
||||
value->needs_update = false;
|
||||
value->valid = false;
|
||||
if (reply->type == XCB_ATOM_NONE) {
|
||||
// Property doesn't exist on this window
|
||||
log_verbose("Property %s doesn't exist on this window",
|
||||
get_atom_name_cached(state->atoms, property));
|
||||
return;
|
||||
}
|
||||
if ((property_is_string && reply->format != 8) ||
|
||||
(reply->format != 8 && reply->format != 16 && reply->format != 32)) {
|
||||
log_error("Invalid property type and format combination: property %s, "
|
||||
"type: %s, format: %d.",
|
||||
get_atom_name_cached(state->atoms, property),
|
||||
get_atom_name_cached(state->atoms, reply->type), reply->format);
|
||||
return;
|
||||
}
|
||||
|
||||
log_verbose("Updating property %s, length = %u, format = %d",
|
||||
get_atom_name_cached(state->atoms, property), len, reply->format);
|
||||
value->valid = true;
|
||||
if (len == 0) {
|
||||
value->length = 0;
|
||||
return;
|
||||
}
|
||||
if (property_is_string) {
|
||||
bool nul_terminated = ((char *)data)[len - 1] == '\0';
|
||||
value->length = len;
|
||||
value->type = C2_PROPERTY_TYPE_STRING;
|
||||
if (!nul_terminated) {
|
||||
value->length += 1;
|
||||
}
|
||||
value->string = crealloc(value->string, value->length);
|
||||
memcpy(value->string, data, len);
|
||||
if (!nul_terminated) {
|
||||
value->string[len] = '\0';
|
||||
}
|
||||
} else {
|
||||
size_t step = reply->format / 8;
|
||||
bool is_signed = reply->type == XCB_ATOM_INTEGER;
|
||||
value->length = len * 8 / reply->format;
|
||||
if (reply->type == XCB_ATOM_ATOM) {
|
||||
value->type = C2_PROPERTY_TYPE_ATOM;
|
||||
} else {
|
||||
value->type = C2_PROPERTY_TYPE_NUMBER;
|
||||
}
|
||||
|
||||
int64_t *storage = value->numbers;
|
||||
if (value->length > ARR_SIZE(value->numbers)) {
|
||||
if (value->capacity < value->length) {
|
||||
value->array = crealloc(value->array, value->length);
|
||||
value->capacity = value->length;
|
||||
}
|
||||
storage = value->array;
|
||||
}
|
||||
for (uint32_t i = 0; i < value->length; i++) {
|
||||
auto item = (char *)data + i * step;
|
||||
if (is_signed) {
|
||||
switch (reply->format) {
|
||||
case 8: storage[i] = *(int8_t *)item; break;
|
||||
case 16: storage[i] = *(int16_t *)item; break;
|
||||
case 32: storage[i] = *(int32_t *)item; break;
|
||||
default: unreachable();
|
||||
}
|
||||
} else {
|
||||
switch (reply->format) {
|
||||
case 8: storage[i] = *(uint8_t *)item; break;
|
||||
case 16: storage[i] = *(uint16_t *)item; break;
|
||||
case 32: storage[i] = *(uint32_t *)item; break;
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
log_verbose("Property %s[%d] = %" PRId64,
|
||||
get_atom_name_cached(state->atoms, property), i,
|
||||
storage[i]);
|
||||
if (reply->type == XCB_ATOM_ATOM) {
|
||||
// Prefetch the atom name so it will be available
|
||||
// during `c2_match`. We don't need the return value here.
|
||||
get_atom_name(state->atoms, (xcb_atom_t)storage[i], c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void c2_window_state_update_from_replies(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 refetch) {
|
||||
HASH_ITER2(state->tracked_properties, p) {
|
||||
if (!window_state->values[p->id].needs_update) {
|
||||
continue;
|
||||
}
|
||||
xcb_window_t window = p->key.is_on_frame ? frame_win : client_win;
|
||||
xcb_get_property_reply_t *reply =
|
||||
xcb_get_property_reply(c, state->cookies[p->id], NULL);
|
||||
if (!reply) {
|
||||
log_warn("Failed to get property %d for window %#010x, some "
|
||||
"window rules might not work.",
|
||||
p->id, window);
|
||||
window_state->values[p->id].valid = false;
|
||||
window_state->values[p->id].needs_update = false;
|
||||
continue;
|
||||
}
|
||||
bool property_is_string = x_is_type_string(state->atoms, reply->type);
|
||||
if (reply->bytes_after > 0 && (property_is_string || p->max_indices < 0)) {
|
||||
if (!refetch) {
|
||||
log_warn("Did property %d for window %#010x change while "
|
||||
"we were fetching it? some window rules might "
|
||||
"not work.",
|
||||
p->id, window);
|
||||
window_state->values[p->id].valid = false;
|
||||
window_state->values[p->id].needs_update = false;
|
||||
} else {
|
||||
state->propert_lengths[p->id] += reply->bytes_after;
|
||||
state->cookies[p->id] = xcb_get_property(
|
||||
c, 0, window, p->key.property, XCB_GET_PROPERTY_TYPE_ANY,
|
||||
0, (state->propert_lengths[p->id] + 3) / 4);
|
||||
}
|
||||
} else {
|
||||
c2_window_state_update_one_from_reply(
|
||||
state, &window_state->values[p->id], p->key.property, reply, c);
|
||||
}
|
||||
free(reply);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
size_t property_count = HASH_COUNT(state->tracked_properties);
|
||||
if (!state->cookies) {
|
||||
state->cookies = ccalloc(property_count, xcb_get_property_cookie_t);
|
||||
}
|
||||
if (!state->propert_lengths) {
|
||||
state->propert_lengths = ccalloc(property_count, uint32_t);
|
||||
}
|
||||
memset(state->cookies, 0, property_count * sizeof(xcb_get_property_cookie_t));
|
||||
|
||||
log_verbose("Updating c2 window state for window %#010x (frame %#010x)",
|
||||
client_win, frame_win);
|
||||
|
||||
// Because we don't know the length of all properties (i.e. if they are string
|
||||
// properties, or for properties matched with `[*]`). We do this in 3 steps:
|
||||
// 1. Send requests to all properties we need. Use `max_indices` to determine
|
||||
// the length, or use 0 if it's unknown.
|
||||
// 2. From the replies to (1), for properties we know the length of, we update
|
||||
// the values. For those we don't, use the length information from the
|
||||
// replies to send a new request with the correct length.
|
||||
// 3. Update the rest of the properties.
|
||||
|
||||
// Step 1
|
||||
HASH_ITER2(state->tracked_properties, p) {
|
||||
if (!window_state->values[p->id].needs_update) {
|
||||
continue;
|
||||
}
|
||||
uint32_t length = 0;
|
||||
if (p->max_indices >= 0) {
|
||||
// length is in 4 bytes units
|
||||
length = (uint32_t)p->max_indices + 1;
|
||||
}
|
||||
|
||||
xcb_window_t window = p->key.is_on_frame ? frame_win : client_win;
|
||||
// xcb_get_property long_length is in units of 4-byte,
|
||||
// so use `ceil(length / 4)`. same below.
|
||||
state->cookies[p->id] = xcb_get_property(
|
||||
c, 0, window, p->key.property, XCB_GET_PROPERTY_TYPE_ANY, 0, length);
|
||||
state->propert_lengths[p->id] = length * 4;
|
||||
}
|
||||
|
||||
// Step 2
|
||||
c2_window_state_update_from_replies(state, window_state, c, client_win, frame_win, true);
|
||||
// Step 3
|
||||
c2_window_state_update_from_replies(state, window_state, c, client_win, frame_win,
|
||||
false);
|
||||
}
|
||||
|
||||
bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property) {
|
||||
struct c2_tracked_property *p;
|
||||
struct c2_tracked_property_key key = {
|
||||
|
|
14
src/c2.h
14
src/c2.h
|
@ -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,6 +40,14 @@ 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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1220,6 +1220,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);
|
||||
|
@ -1472,6 +1473,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 +1731,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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue