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
1a5c80e353
commit
3246cd1e31
5 changed files with 263 additions and 0 deletions
241
src/c2.c
241
src/c2.c
|
@ -13,6 +13,7 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <fnmatch.h>
|
#include <fnmatch.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <uthash.h>
|
#include <uthash.h>
|
||||||
|
@ -67,12 +68,39 @@ static_assert(sizeof(struct c2_tracked_property_key) == 8, "Padding bytes in "
|
||||||
struct c2_tracked_property {
|
struct c2_tracked_property {
|
||||||
UT_hash_handle hh;
|
UT_hash_handle hh;
|
||||||
struct c2_tracked_property_key key;
|
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;
|
||||||
bool is_string;
|
bool is_string;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct c2_state {
|
struct c2_state {
|
||||||
struct c2_tracked_property *tracked_properties;
|
struct c2_tracked_property *tracked_properties;
|
||||||
struct atom *atoms;
|
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;
|
||||||
|
bool valid;
|
||||||
|
bool needs_update;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initializer for c2_ptr_t.
|
/// Initializer for c2_ptr_t.
|
||||||
|
@ -1164,8 +1192,10 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t
|
||||||
if (property == NULL) {
|
if (property == NULL) {
|
||||||
property = cmalloc(struct c2_tracked_property);
|
property = cmalloc(struct c2_tracked_property);
|
||||||
property->key = key;
|
property->key = key;
|
||||||
|
property->id = HASH_COUNT(state->tracked_properties);
|
||||||
HASH_ADD_KEYPTR(hh, state->tracked_properties, &property->key,
|
HASH_ADD_KEYPTR(hh, state->tracked_properties, &property->key,
|
||||||
sizeof(property->key), property);
|
sizeof(property->key), property);
|
||||||
|
property->max_indices = pleaf->index;
|
||||||
property->is_string = pleaf->type == C2_L_TSTRING;
|
property->is_string = pleaf->type == C2_L_TSTRING;
|
||||||
} else {
|
} else {
|
||||||
if (property->is_string != (pleaf->type == C2_L_TSTRING)) {
|
if (property->is_string != (pleaf->type == C2_L_TSTRING)) {
|
||||||
|
@ -1183,6 +1213,9 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t
|
||||||
pleaf->op = C2_L_OFALSE;
|
pleaf->op = C2_L_OFALSE;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (property->max_indices >= 0 && pleaf->index > property->max_indices) {
|
||||||
|
property->max_indices = pleaf->index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1896,9 +1929,217 @@ void c2_state_free(struct c2_state *state) {
|
||||||
HASH_DEL(state->tracked_properties, property);
|
HASH_DEL(state->tracked_properties, property);
|
||||||
free(property);
|
free(property);
|
||||||
}
|
}
|
||||||
|
free(state->propert_lengths);
|
||||||
|
free(state->cookies);
|
||||||
free(state);
|
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++) {
|
||||||
|
free(window_state->values[i].string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bool is_string,
|
||||||
|
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 = reply->type == XCB_ATOM_STRING ||
|
||||||
|
reply->type == state->atoms->aUTF8_STRING ||
|
||||||
|
reply->type == state->atoms->aC_STRING;
|
||||||
|
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 != is_string) {
|
||||||
|
log_warn("Property %s %s string, but it's %s used as a string "
|
||||||
|
"in rules. The rules will not work.",
|
||||||
|
get_atom_name_cached(state->atoms, property),
|
||||||
|
property_is_string ? "is" : "isn't", is_string ? "" : "not");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((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 = %d, format = %d",
|
||||||
|
get_atom_name_cached(state->atoms, property), len, reply->format);
|
||||||
|
value->valid = true;
|
||||||
|
if (len == 0) {
|
||||||
|
value->length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string) {
|
||||||
|
bool nul_terminated = ((char *)data)[len - 1] == '\0';
|
||||||
|
value->length = len;
|
||||||
|
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;
|
||||||
|
value->length = len * 8 / reply->format;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (reply->bytes_after > 0 && p->is_string) {
|
||||||
|
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,
|
||||||
|
p->is_string, 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->is_string && p->max_indices >= 0) {
|
||||||
|
length = (uint32_t)(p->max_indices + 1) * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + 3) / 4);
|
||||||
|
state->propert_lengths[p->id] = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property) {
|
||||||
struct c2_tracked_property *p;
|
struct c2_tracked_property *p;
|
||||||
struct c2_tracked_property_key key = {
|
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 _c2_lptr c2_lptr_t;
|
||||||
typedef struct session session_t;
|
typedef struct session session_t;
|
||||||
struct c2_state;
|
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 atom;
|
||||||
struct managed_win;
|
struct managed_win;
|
||||||
|
|
||||||
|
@ -34,6 +40,14 @@ struct c2_state *c2_state_new(struct atom *atoms);
|
||||||
void c2_state_free(struct c2_state *state);
|
void c2_state_free(struct c2_state *state);
|
||||||
/// Returns true if value of the property is used in any condition.
|
/// 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);
|
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,
|
bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
|
||||||
void **pdata);
|
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
|
// Check for other atoms we are tracking
|
||||||
if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) {
|
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);
|
auto w = find_managed_win(ps, ev->window);
|
||||||
if (!w) {
|
if (!w) {
|
||||||
w = find_toplevel(ps, ev->window);
|
w = find_toplevel(ps, ev->window);
|
||||||
|
change_is_on_frame = false;
|
||||||
}
|
}
|
||||||
if (w) {
|
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
|
// Set FACTOR_CHANGED so rules based on properties will be
|
||||||
// re-evaluated.
|
// re-evaluated.
|
||||||
// Don't need to set property stale here, since that only
|
// Don't need to set property stale here, since that only
|
||||||
|
|
|
@ -1260,6 +1260,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) {
|
void win_on_factor_change(session_t *ps, struct managed_win *w) {
|
||||||
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
|
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
|
// Focus and is_fullscreen needs to be updated first, as other rules might depend
|
||||||
// on the focused state of the window
|
// on the focused state of the window
|
||||||
win_update_focused(ps, w);
|
win_update_focused(ps, w);
|
||||||
|
@ -1768,6 +1769,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
||||||
ps->atoms->a_NET_WM_STATE,
|
ps->atoms->a_NET_WM_STATE,
|
||||||
};
|
};
|
||||||
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
|
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
|
#ifdef CONFIG_DBUS
|
||||||
// Send D-Bus signal
|
// Send D-Bus signal
|
||||||
|
|
|
@ -284,6 +284,8 @@ struct managed_win {
|
||||||
/// The custom window shader to use when rendering.
|
/// The custom window shader to use when rendering.
|
||||||
struct shader_info *fg_shader;
|
struct shader_info *fg_shader;
|
||||||
|
|
||||||
|
struct c2_window_state c2_state;
|
||||||
|
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
/// Textures and FBO background blur use.
|
/// Textures and FBO background blur use.
|
||||||
glx_blur_cache_t glx_blur_cache;
|
glx_blur_cache_t glx_blur_cache;
|
||||||
|
|
Loading…
Reference in a new issue