mirror of https://github.com/yshui/picom.git
Merge pull request #1201 from yshui/cached-c2
This commit is contained in:
commit
bef295f4c4
119
src/atom.c
119
src/atom.c
|
@ -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
|
|
@ -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);
|
780
src/c2.c
780
src/c2.c
|
@ -10,8 +10,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <fnmatch.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <uthash.h>
|
||||
|
@ -66,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.
|
||||
|
@ -119,9 +154,14 @@ struct _c2_l {
|
|||
} match : 3;
|
||||
bool match_ignorecase : 1;
|
||||
char *tgt;
|
||||
unsigned int target_id;
|
||||
xcb_atom_t tgtatom;
|
||||
bool tgt_onframe;
|
||||
int index;
|
||||
// TODO(yshui) translate some of the pre-defined targets to
|
||||
// generic window properties. e.g. `name = "xterm"`
|
||||
// should be translated to:
|
||||
// "WM_NAME = 'xterm' || _NET_WM_NAME = 'xterm'"
|
||||
enum {
|
||||
C2_L_PUNDEFINED = -1,
|
||||
C2_L_PID = 0,
|
||||
|
@ -217,26 +257,6 @@ static const char *C2_PREDEFS[] = {
|
|||
[C2_L_PROLE] = "role",
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -339,8 +359,6 @@ static inline void c2_freep(c2_ptr_t *pp) {
|
|||
|
||||
static const char *c2h_dump_str_tgt(const c2_l_t *pleaf);
|
||||
|
||||
static bool c2_match_once(session_t *ps, const struct managed_win *w, c2_ptr_t cond);
|
||||
|
||||
/**
|
||||
* Parse a condition string.
|
||||
*/
|
||||
|
@ -388,9 +406,12 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
|
|||
|
||||
TEST_CASE(c2_parse) {
|
||||
log_init_tls();
|
||||
// log_add_target_tls(stderr_logger_new());
|
||||
|
||||
char str[1024];
|
||||
c2_lptr_t *cond = c2_parse(NULL, "name = \"xterm\"", NULL);
|
||||
struct atom *atoms = init_mock_atoms();
|
||||
struct c2_state *state = c2_state_new(atoms);
|
||||
TEST_NOTEQUAL(cond, NULL);
|
||||
TEST_TRUE(!cond->ptr.isbranch);
|
||||
TEST_NOTEQUAL(cond->ptr.l, NULL);
|
||||
|
@ -400,6 +421,15 @@ TEST_CASE(c2_parse) {
|
|||
|
||||
size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str));
|
||||
TEST_STREQUAL3(str, "name = \"xterm\"", len);
|
||||
|
||||
struct managed_win test_win = {
|
||||
.name = "xterm",
|
||||
};
|
||||
TEST_TRUE(c2_match(state, &test_win, cond, NULL));
|
||||
c2_list_postprocess(state, NULL, cond);
|
||||
TEST_EQUAL(HASH_COUNT(state->tracked_properties), 0);
|
||||
c2_state_free(state);
|
||||
destroy_atoms(atoms);
|
||||
c2_list_free(&cond, NULL);
|
||||
|
||||
cond = c2_parse(NULL, "_GTK_FRAME_EXTENTS@:c", NULL);
|
||||
|
@ -413,6 +443,18 @@ TEST_CASE(c2_parse) {
|
|||
TEST_NOTEQUAL(cond->ptr.l->tgt, NULL);
|
||||
TEST_STREQUAL(cond->ptr.l->tgt, "_GTK_FRAME_EXTENTS");
|
||||
|
||||
atoms = init_mock_atoms();
|
||||
state = c2_state_new(atoms);
|
||||
c2_list_postprocess(state, NULL, cond);
|
||||
TEST_EQUAL(HASH_COUNT(state->tracked_properties), 1);
|
||||
HASH_ITER2(state->tracked_properties, prop) {
|
||||
TEST_EQUAL(prop->key.property,
|
||||
get_atom_with_nul(state->atoms, "_GTK_FRAME_EXTENTS", NULL));
|
||||
TEST_EQUAL(prop->max_indices, 0);
|
||||
}
|
||||
c2_state_free(state);
|
||||
destroy_atoms(atoms);
|
||||
|
||||
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
|
||||
TEST_STREQUAL3(str, "_GTK_FRAME_EXTENTS@[0]", len);
|
||||
c2_list_free(&cond, NULL);
|
||||
|
@ -435,9 +477,17 @@ TEST_CASE(c2_parse) {
|
|||
TEST_STREQUAL(cond->ptr.b->opr2.l->tgt, "class_g");
|
||||
TEST_EQUAL(cond->ptr.b->opr2.l->predef, C2_L_PCLASSG);
|
||||
|
||||
atoms = init_mock_atoms();
|
||||
state = c2_state_new(atoms);
|
||||
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
|
||||
TEST_STREQUAL3(str, "(name = \"xterm\" && class_g *= \"XTerm\")", len);
|
||||
test_win.class_general = "XTerm";
|
||||
TEST_TRUE(c2_match(state, &test_win, cond, NULL));
|
||||
test_win.class_general = "asdf";
|
||||
TEST_TRUE(!c2_match(state, &test_win, cond, NULL));
|
||||
c2_list_free(&cond, NULL);
|
||||
c2_state_free(state);
|
||||
destroy_atoms(atoms);
|
||||
|
||||
cond = c2_parse(NULL, "_NET_WM_STATE[1]:32a *='_NET_WM_STATE_HIDDEN'", NULL);
|
||||
TEST_EQUAL(cond->ptr.l->index, 1);
|
||||
|
@ -1120,9 +1170,16 @@ 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;
|
||||
}
|
||||
pleaf->target_id = property->id;
|
||||
}
|
||||
|
||||
// Warn about lower case characters in target name
|
||||
|
@ -1156,9 +1213,7 @@ static bool c2_l_postprocess(struct c2_state *state, xcb_connection_t *c, c2_l_t
|
|||
PCRE2_UCHAR buffer[256];
|
||||
pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
|
||||
log_error("Pattern \"%s\": PCRE regular expression "
|
||||
"parsing "
|
||||
"failed on "
|
||||
"offset %zu: %s",
|
||||
"parsing failed on offset %zu: %s",
|
||||
pleaf->ptnstr, erroffset, buffer);
|
||||
return false;
|
||||
}
|
||||
|
@ -1261,7 +1316,7 @@ static const char *c2h_dump_str_tgt(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); \
|
||||
|
@ -1428,46 +1483,45 @@ static const char *c2_condition_to_str2(c2_ptr_t ptr) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a window against a single leaf window condition.
|
||||
*
|
||||
* For internal use.
|
||||
*/
|
||||
static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w,
|
||||
const c2_l_t *pleaf, bool *pres, bool *perr) {
|
||||
assert(pleaf);
|
||||
|
||||
const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id);
|
||||
|
||||
// Return if wid is missing
|
||||
if (pleaf->predef == C2_L_PUNDEFINED && !wid) {
|
||||
return;
|
||||
/// Get the list of target number values from a struct c2_property_value
|
||||
static inline const int64_t *
|
||||
c2_values_get_number_targets(const struct c2_property_value *values, int index, size_t *n) {
|
||||
auto storage = values->numbers;
|
||||
if (values->length > ARR_SIZE(values->numbers)) {
|
||||
storage = values->array;
|
||||
}
|
||||
if (index < 0) {
|
||||
*n = values->length;
|
||||
return storage;
|
||||
}
|
||||
if ((size_t)index < values->length) {
|
||||
*n = 1;
|
||||
return &storage[index];
|
||||
}
|
||||
*n = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const int idx = (pleaf->index < 0 ? 0 : pleaf->index);
|
||||
auto type = pleaf->ptntype;
|
||||
if (type == C2_L_PTUNDEFINED) {
|
||||
auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
|
||||
if (x_is_type_string(ps->atoms, prop_info.type)) {
|
||||
type = C2_L_PTSTRING;
|
||||
} else {
|
||||
type = C2_L_PTINT;
|
||||
static inline bool c2_int_op(const c2_l_t *leaf, int64_t target) {
|
||||
switch (leaf->op) {
|
||||
case C2_L_OEXISTS: return leaf->predef != C2_L_PUNDEFINED ? target : true;
|
||||
case C2_L_OEQ: return target == leaf->ptnint;
|
||||
case C2_L_OGT: return target > leaf->ptnint;
|
||||
case C2_L_OGTEQ: return target >= leaf->ptnint;
|
||||
case C2_L_OLT: return target < leaf->ptnint;
|
||||
case C2_L_OLTEQ: return target <= leaf->ptnint;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
// Deal with integer patterns
|
||||
case C2_L_PTINT: {
|
||||
long long *targets = NULL;
|
||||
long long *targets_free = NULL;
|
||||
size_t ntargets = 0;
|
||||
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
|
||||
if (leaf->predef != C2_L_PUNDEFINED) {
|
||||
// A predefined target
|
||||
long long predef_target = 0;
|
||||
if (pleaf->predef != C2_L_PUNDEFINED) {
|
||||
*perr = false;
|
||||
switch (pleaf->predef) {
|
||||
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;
|
||||
case C2_L_PY: predef_target = w->g.y; break;
|
||||
|
@ -1487,223 +1541,187 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
|
||||
case C2_L_PCLIENT: predef_target = w->client_win; break;
|
||||
case C2_L_PLEADER: predef_target = w->leader; break;
|
||||
default:
|
||||
*perr = true;
|
||||
assert(0);
|
||||
break;
|
||||
default: unreachable();
|
||||
}
|
||||
ntargets = 1;
|
||||
targets = &predef_target;
|
||||
return c2_int_op(leaf, predef_target);
|
||||
}
|
||||
|
||||
// A raw window property
|
||||
else {
|
||||
int word_count = 1;
|
||||
if (pleaf->index < 0) {
|
||||
// Get length of property in 32-bit multiples
|
||||
auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
|
||||
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
||||
}
|
||||
winprop_t prop = x_get_prop_with_offset(
|
||||
&ps->c, wid, pleaf->tgtatom, idx, word_count, 0, 0);
|
||||
|
||||
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
||||
if (ntargets > 0) {
|
||||
targets = targets_free = ccalloc(ntargets, long long);
|
||||
*perr = false;
|
||||
for (size_t i = 0; i < ntargets; ++i) {
|
||||
targets[i] = winprop_get_int(prop, i);
|
||||
}
|
||||
}
|
||||
free_winprop(&prop);
|
||||
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 (*perr) {
|
||||
goto fail_int;
|
||||
if (values->type == C2_PROPERTY_TYPE_STRING) {
|
||||
log_error("Property %s is not an integer", leaf->tgt);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do comparison
|
||||
bool res = false;
|
||||
for (size_t i = 0; i < ntargets; ++i) {
|
||||
long long tgt = targets[i];
|
||||
switch (pleaf->op) {
|
||||
case C2_L_OEXISTS:
|
||||
res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true);
|
||||
break;
|
||||
case C2_L_OEQ: res = (tgt == pleaf->ptnint); break;
|
||||
case C2_L_OGT: res = (tgt > pleaf->ptnint); break;
|
||||
case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break;
|
||||
case C2_L_OLT: res = (tgt < pleaf->ptnint); break;
|
||||
case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break;
|
||||
default: *perr = true; assert(0);
|
||||
}
|
||||
if (res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*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;
|
||||
size_t ntargets = 0;
|
||||
winprop_info_t prop_info = {0};
|
||||
if (pleaf->predef == C2_L_PUNDEFINED) {
|
||||
prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
|
||||
auto targets = c2_values_get_number_targets(values, leaf->index, &ntargets);
|
||||
for (size_t i = 0; i < ntargets; i++) {
|
||||
if (c2_int_op(leaf, targets[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool c2_string_op(const c2_l_t *leaf, const char *target) {
|
||||
if (leaf->op == C2_L_OEXISTS) {
|
||||
return true;
|
||||
}
|
||||
if (leaf->op != C2_L_OEQ) {
|
||||
log_error("Unsupported operator %d for string comparison.", leaf->op);
|
||||
assert(leaf->op == C2_L_OEQ);
|
||||
return false;
|
||||
}
|
||||
if (leaf->match == C2_L_MPCRE) {
|
||||
#ifdef CONFIG_REGEX_PCRE
|
||||
assert(strlen(target) <= INT_MAX);
|
||||
assert(leaf->regex_pcre);
|
||||
return (pcre2_match(leaf->regex_pcre, (PCRE2_SPTR)target, strlen(target),
|
||||
0, 0, leaf->regex_pcre_match, NULL) > 0);
|
||||
#else
|
||||
log_error("PCRE regular expression support not compiled in.");
|
||||
assert(leaf->match != C2_L_MPCRE);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
if (leaf->match_ignorecase) {
|
||||
switch (leaf->match) {
|
||||
case C2_L_MEXACT: return !strcasecmp(target, leaf->ptnstr);
|
||||
case C2_L_MCONTAINS: return strcasestr(target, leaf->ptnstr);
|
||||
case C2_L_MSTART:
|
||||
return !strncasecmp(target, leaf->ptnstr, strlen(leaf->ptnstr));
|
||||
case C2_L_MWILDCARD: return !fnmatch(leaf->ptnstr, target, FNM_CASEFOLD);
|
||||
default: unreachable();
|
||||
}
|
||||
} else {
|
||||
switch (leaf->match) {
|
||||
case C2_L_MEXACT: return !strcmp(target, leaf->ptnstr);
|
||||
case C2_L_MCONTAINS: return strstr(target, leaf->ptnstr);
|
||||
case C2_L_MSTART:
|
||||
return !strncmp(target, leaf->ptnstr, strlen(leaf->ptnstr));
|
||||
case C2_L_MWILDCARD: return !fnmatch(leaf->ptnstr, target, 0);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
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;
|
||||
if (pleaf->predef != C2_L_PUNDEFINED) {
|
||||
switch (pleaf->predef) {
|
||||
case C2_L_PWINDOWTYPE:
|
||||
predef_target = WINTYPES[w->window_type];
|
||||
break;
|
||||
if (leaf->predef != C2_L_PUNDEFINED) {
|
||||
switch (leaf->predef) {
|
||||
case C2_L_PWINDOWTYPE: predef_target = WINTYPES[w->window_type]; break;
|
||||
case C2_L_PNAME: predef_target = w->name; break;
|
||||
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 (prop_info.type == XCB_ATOM_ATOM) {
|
||||
// An atom type property, convert it to string
|
||||
int word_count = 1;
|
||||
if (pleaf->index < 0) {
|
||||
// Get length of property in 32-bit multiples
|
||||
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
||||
if (!predef_target) {
|
||||
return false;
|
||||
}
|
||||
log_verbose("Matching against predefined target %s", predef_target);
|
||||
return c2_string_op(leaf, predef_target);
|
||||
}
|
||||
winprop_t prop = x_get_prop_with_offset(
|
||||
&ps->c, wid, pleaf->tgtatom, idx, word_count, XCB_ATOM_ATOM, 0);
|
||||
|
||||
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
||||
targets = targets_free = (const char **)ccalloc(2 * ntargets, char *);
|
||||
targets_free_inner = targets + ntargets;
|
||||
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 (values->type == C2_PROPERTY_TYPE_ATOM) {
|
||||
size_t ntargets = 0;
|
||||
auto targets = c2_values_get_number_targets(values, leaf->index, &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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
free_winprop(&prop);
|
||||
|
||||
if (values->type != C2_PROPERTY_TYPE_STRING) {
|
||||
log_verbose("Property %s is not a string", leaf->tgt);
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
size_t offset = 0;
|
||||
int index = leaf->index;
|
||||
while (offset < values->length && index != 0) {
|
||||
offset += strlen(values->string + offset) + 1;
|
||||
index -= 1;
|
||||
}
|
||||
if (index != 0 || values->length == 0) {
|
||||
// index is out of bounds
|
||||
return false;
|
||||
}
|
||||
return c2_string_op(leaf, values->string + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a window against a single leaf window condition.
|
||||
*
|
||||
* For internal use.
|
||||
*/
|
||||
static inline bool
|
||||
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);
|
||||
|
||||
// Return if wid is missing
|
||||
if (leaf->predef == C2_L_PUNDEFINED && !wid) {
|
||||
log_debug("Window ID missing.");
|
||||
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}));
|
||||
|
||||
auto pattern_type = leaf->ptntype;
|
||||
if (pattern_type == C2_L_PTUNDEFINED) {
|
||||
auto values = &w->c2_state.values[leaf->target_id];
|
||||
if (values->type == C2_PROPERTY_TYPE_STRING) {
|
||||
pattern_type = C2_L_PTSTRING;
|
||||
} else {
|
||||
// Not an atom type, just fetch the string list
|
||||
char **strlst = NULL;
|
||||
int nstr = 0;
|
||||
if (wid_get_text_prop(&ps->c, ps->atoms, 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;
|
||||
}
|
||||
}
|
||||
if (strlst) {
|
||||
targets_free = (const char **)strlst;
|
||||
pattern_type = C2_L_PTINT;
|
||||
}
|
||||
}
|
||||
|
||||
if (ntargets == 0) {
|
||||
goto fail_str;
|
||||
}
|
||||
for (size_t i = 0; i < ntargets; ++i) {
|
||||
if (!targets[i]) {
|
||||
goto fail_str;
|
||||
}
|
||||
}
|
||||
*perr = false;
|
||||
|
||||
// Actual matching
|
||||
bool res = false;
|
||||
for (size_t i = 0; i < ntargets; ++i) {
|
||||
const char *tgt = targets[i];
|
||||
switch (pleaf->op) {
|
||||
case C2_L_OEXISTS: res = true; break;
|
||||
case C2_L_OEQ:
|
||||
switch (pleaf->match) {
|
||||
case C2_L_MEXACT:
|
||||
if (pleaf->match_ignorecase) {
|
||||
res = !strcasecmp(tgt, pleaf->ptnstr);
|
||||
} else {
|
||||
res = !strcmp(tgt, pleaf->ptnstr);
|
||||
}
|
||||
break;
|
||||
case C2_L_MCONTAINS:
|
||||
if (pleaf->match_ignorecase) {
|
||||
res = strcasestr(tgt, pleaf->ptnstr);
|
||||
} else {
|
||||
res = strstr(tgt, pleaf->ptnstr);
|
||||
}
|
||||
break;
|
||||
case C2_L_MSTART:
|
||||
if (pleaf->match_ignorecase) {
|
||||
res = !strncasecmp(tgt, pleaf->ptnstr,
|
||||
strlen(pleaf->ptnstr));
|
||||
} else {
|
||||
res = !strncmp(tgt, pleaf->ptnstr,
|
||||
strlen(pleaf->ptnstr));
|
||||
}
|
||||
break;
|
||||
case C2_L_MWILDCARD: {
|
||||
int flags = 0;
|
||||
if (pleaf->match_ignorecase) {
|
||||
flags |= FNM_CASEFOLD;
|
||||
}
|
||||
res = !fnmatch(pleaf->ptnstr, tgt, flags);
|
||||
} break;
|
||||
case C2_L_MPCRE:
|
||||
#ifdef CONFIG_REGEX_PCRE
|
||||
assert(strlen(tgt) <= INT_MAX);
|
||||
assert(pleaf->regex_pcre);
|
||||
res = (pcre2_match(pleaf->regex_pcre, (PCRE2_SPTR)tgt,
|
||||
strlen(tgt), 0, 0,
|
||||
pleaf->regex_pcre_match, NULL) > 0);
|
||||
#else
|
||||
assert(0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default: *perr = true; assert(0);
|
||||
}
|
||||
if (res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*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;
|
||||
switch (pattern_type) {
|
||||
// Deal with integer patterns
|
||||
case C2_L_PTINT: return c2_match_once_leaf_int(w, leaf);
|
||||
// String patterns
|
||||
case C2_L_PTSTRING: return c2_match_once_leaf_string(state->atoms, w, leaf);
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
@ -1713,34 +1731,35 @@ 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(struct c2_state *state, const struct managed_win *w, const c2_ptr_t cond) {
|
||||
bool result = false;
|
||||
bool error = true;
|
||||
|
||||
// Handle a branch
|
||||
if (cond.isbranch) {
|
||||
// Handle a branch (and/or/xor operation)
|
||||
const c2_b_t *pb = cond.b;
|
||||
|
||||
if (!pb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = 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: error = true; assert(0);
|
||||
default: unreachable();
|
||||
}
|
||||
|
||||
log_debug("(%#010x): branch: result = %d, pattern = %s", w->base.id,
|
||||
|
@ -1753,13 +1772,7 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||
return false;
|
||||
}
|
||||
|
||||
c2_match_once_leaf(ps, w, pleaf, &result, &error);
|
||||
|
||||
// For EXISTS operator, no errors are fatal
|
||||
if (C2_L_OEXISTS == pleaf->op && error) {
|
||||
result = false;
|
||||
error = false;
|
||||
}
|
||||
result = c2_match_once_leaf(state, w, pleaf);
|
||||
|
||||
log_debug("(%#010x): leaf: result = %d, client = %#010x, "
|
||||
"pattern = %s",
|
||||
|
@ -1767,10 +1780,6 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||
}
|
||||
|
||||
// Postprocess the result
|
||||
if (error) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (cond.isbranch ? cond.b->neg : cond.l->neg) {
|
||||
result = !result;
|
||||
}
|
||||
|
@ -1785,12 +1794,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;
|
||||
}
|
||||
|
@ -1801,8 +1809,9 @@ bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condl
|
|||
return false;
|
||||
}
|
||||
|
||||
/// Iterate over all conditions in a condition linked list. Call the callback for each of
|
||||
/// the conditions. If the callback returns true, the iteration stops early.
|
||||
/// Iterate over all conditions in a condition linked list. Call the callback for
|
||||
/// each of the conditions. If the callback returns true, the iteration stops
|
||||
/// early.
|
||||
///
|
||||
/// Returns whether the iteration was stopped early.
|
||||
bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) {
|
||||
|
@ -1831,9 +1840,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 = {
|
||||
|
|
18
src/c2.h
18
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,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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, "");
|
||||
|
|
32
src/win.c
32
src/win.c
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue