mirror of
https://github.com/yshui/picom.git
synced 2024-11-18 13:55:36 -05:00
core: parse and use universal rules
Design is described in #1284. Added a trivial test of universal rules Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
882f21b34a
commit
8384dcad0c
10 changed files with 230 additions and 64 deletions
74
src/c2.c
74
src/c2.c
|
@ -44,6 +44,7 @@ typedef struct _c2_l c2_l_t;
|
|||
/// Pointer to a condition tree.
|
||||
typedef struct {
|
||||
bool isbranch : 1;
|
||||
bool istrue : 1;
|
||||
union {
|
||||
c2_b_t *b;
|
||||
c2_l_t *l;
|
||||
|
@ -101,13 +102,11 @@ struct c2_property_value {
|
|||
};
|
||||
|
||||
/// Initializer for c2_ptr_t.
|
||||
#define C2_PTR_INIT \
|
||||
{ \
|
||||
.isbranch = false, \
|
||||
.l = NULL, \
|
||||
}
|
||||
|
||||
static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
|
||||
static const c2_ptr_t C2_PTR_INIT = {
|
||||
.isbranch = false,
|
||||
.istrue = false,
|
||||
.l = NULL,
|
||||
};
|
||||
|
||||
/// Operator of a branch element.
|
||||
typedef enum {
|
||||
|
@ -200,24 +199,21 @@ struct _c2_l {
|
|||
|
||||
static const unsigned int C2_L_INVALID_TARGET_ID = UINT_MAX;
|
||||
/// Initializer for c2_l_t.
|
||||
#define C2_L_INIT \
|
||||
{ \
|
||||
.neg = false, \
|
||||
.op = C2_L_OEXISTS, \
|
||||
.match = C2_L_MEXACT, \
|
||||
.match_ignorecase = false, \
|
||||
.tgt = NULL, \
|
||||
.tgtatom = 0, \
|
||||
.target_on_client = false, \
|
||||
.predef = C2_L_PUNDEFINED, \
|
||||
.index = 0, \
|
||||
.ptntype = C2_L_PTUNDEFINED, \
|
||||
.ptnstr = NULL, \
|
||||
.ptnint = 0, \
|
||||
.target_id = C2_L_INVALID_TARGET_ID, \
|
||||
}
|
||||
|
||||
static const c2_l_t leaf_def = C2_L_INIT;
|
||||
static const c2_l_t C2_L_INIT = {
|
||||
.neg = false,
|
||||
.op = C2_L_OEXISTS,
|
||||
.match = C2_L_MEXACT,
|
||||
.match_ignorecase = false,
|
||||
.tgt = NULL,
|
||||
.tgtatom = 0,
|
||||
.target_on_client = false,
|
||||
.predef = C2_L_PUNDEFINED,
|
||||
.index = 0,
|
||||
.ptntype = C2_L_PTUNDEFINED,
|
||||
.ptnstr = NULL,
|
||||
.ptnint = 0,
|
||||
.target_id = C2_L_INVALID_TARGET_ID,
|
||||
};
|
||||
|
||||
/// Linked list type of conditions.
|
||||
struct _c2_lptr {
|
||||
|
@ -301,7 +297,7 @@ static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) {
|
|||
*/
|
||||
static inline void c2_ptr_reset(c2_ptr_t *pp) {
|
||||
if (pp) {
|
||||
memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
|
||||
*pp = C2_PTR_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -699,7 +695,7 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||
}
|
||||
// Otherwise, combine the second and the incoming one
|
||||
else {
|
||||
eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL);
|
||||
eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_INIT);
|
||||
assert(eles[1].isbranch);
|
||||
pele = &eles[1].b->opr2;
|
||||
}
|
||||
|
@ -813,7 +809,7 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) {
|
|||
presult->l = cmalloc(c2_l_t);
|
||||
|
||||
c2_l_t *const pleaf = presult->l;
|
||||
memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
|
||||
*pleaf = C2_L_INIT;
|
||||
|
||||
// Parse negation marks
|
||||
while ('!' == pattern[offset]) {
|
||||
|
@ -1170,7 +1166,7 @@ static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) {
|
|||
auto pleaf = cmalloc(c2_l_t);
|
||||
presult->isbranch = false;
|
||||
presult->l = pleaf;
|
||||
memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
|
||||
*pleaf = C2_L_INIT;
|
||||
pleaf->op = C2_L_OEQ;
|
||||
pleaf->ptntype = C2_L_PTSTRING;
|
||||
|
||||
|
@ -1870,6 +1866,8 @@ static bool c2_match_once(struct c2_state *state, const struct win *w, const c2_
|
|||
|
||||
log_debug("(%#010x): branch: result = %d, pattern = %s", win_id(w),
|
||||
result, c2_condition_to_str2(cond));
|
||||
} else if (cond.istrue) {
|
||||
return true;
|
||||
} else {
|
||||
// A leaf
|
||||
const c2_l_t *pleaf = cond.l;
|
||||
|
@ -1893,6 +1891,12 @@ static bool c2_match_once(struct c2_state *state, const struct win *w, const c2_
|
|||
return result;
|
||||
}
|
||||
|
||||
c2_lptr_t *c2_new_true(void) {
|
||||
auto ret = ccalloc(1, c2_lptr_t);
|
||||
ret->ptr = (c2_ptr_t){.istrue = true};
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a window against a condition linked list.
|
||||
*
|
||||
|
@ -1949,6 +1953,12 @@ void *c2_list_get_data(const c2_lptr_t *condlist) {
|
|||
return condlist->data;
|
||||
}
|
||||
|
||||
void *c2_list_set_data(c2_lptr_t *condlist, void *data) {
|
||||
void *old = condlist->data;
|
||||
condlist->data = data;
|
||||
return old;
|
||||
}
|
||||
|
||||
struct c2_state *c2_state_new(struct atom *atoms) {
|
||||
auto ret = ccalloc(1, struct c2_state);
|
||||
ret->atoms = atoms;
|
||||
|
@ -2199,3 +2209,9 @@ bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property) {
|
|||
HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p);
|
||||
return p != NULL;
|
||||
}
|
||||
|
||||
void c2_condlist_insert(c2_lptr_t **pcondlst, c2_lptr_t *pnew) {
|
||||
c2_lptr_t *pcur = *pcondlst;
|
||||
pnew->next = pcur;
|
||||
*pcondlst = pnew;
|
||||
}
|
||||
|
|
5
src/c2.h
5
src/c2.h
|
@ -60,9 +60,14 @@ typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data);
|
|||
bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data);
|
||||
/// Return user data stored in a condition.
|
||||
void *c2_list_get_data(const c2_lptr_t *condlist);
|
||||
/// Set user data stored in a condition. Return the old user data.
|
||||
void *c2_list_set_data(c2_lptr_t *condlist, void *data);
|
||||
/// Convert a c2_lptr_t to string. The returned string is only valid until the
|
||||
/// next call to this function, and should not be freed.
|
||||
const char *c2_lptr_to_str(const c2_lptr_t *);
|
||||
void c2_condlist_insert(c2_lptr_t **pcondlst, c2_lptr_t *pnew);
|
||||
/// Create a new condition list with a single condition that is always true.
|
||||
c2_lptr_t *c2_new_true(void);
|
||||
|
||||
/**
|
||||
* Destroy a condition list.
|
||||
|
|
|
@ -717,7 +717,9 @@ bool parse_config(options_t *opt, const char *config_file) {
|
|||
|
||||
.track_leader = false,
|
||||
|
||||
.rounded_corners_blacklist = NULL
|
||||
.rounded_corners_blacklist = NULL,
|
||||
|
||||
.rules = NULL,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
12
src/config.h
12
src/config.h
|
@ -8,6 +8,7 @@
|
|||
/// Common functions and definitions for configuration parsing
|
||||
/// Used for command line arguments and config files
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
@ -166,6 +167,9 @@ struct window_maybe_options {
|
|||
enum tristate unredir_ignore;
|
||||
};
|
||||
|
||||
// Make sure `window_options` has no implicit padding.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic error "-Wpadded"
|
||||
/// Like `window_maybe_options`, but all fields are guaranteed to be set.
|
||||
struct window_options {
|
||||
double opacity;
|
||||
|
@ -181,17 +185,15 @@ struct window_options {
|
|||
bool paint;
|
||||
bool unredir_ignore;
|
||||
|
||||
char padding[2];
|
||||
char padding[4];
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
static inline bool
|
||||
win_options_eq(const struct window_options *a, const struct window_options *b) {
|
||||
return memcmp(a, b, offsetof(struct window_options, padding)) == 0;
|
||||
}
|
||||
|
||||
static_assert(offsetof(struct window_options, padding) == 36, "window_options has "
|
||||
"implicit padding");
|
||||
|
||||
extern struct shader_info null_shader;
|
||||
|
||||
/// Structure representing all options.
|
||||
|
@ -399,6 +401,8 @@ typedef struct options {
|
|||
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
|
||||
/// Array of all the scripts used in `animations`. This is a dynarr.
|
||||
struct script **all_scripts;
|
||||
|
||||
c2_lptr_t *rules;
|
||||
} options_t;
|
||||
|
||||
extern const char *const BACKEND_STRS[NUM_BKEND + 1];
|
||||
|
|
|
@ -529,6 +529,82 @@ void generate_fading_config(struct options *opt) {
|
|||
dynarr_extend_from(opt->all_scripts, scripts, number_of_scripts);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
} all_window_options[] = {
|
||||
{"fade", offsetof(struct window_maybe_options, fade)},
|
||||
{"paint", offsetof(struct window_maybe_options, paint)},
|
||||
{"shadow", offsetof(struct window_maybe_options, shadow)},
|
||||
{"invert-color", offsetof(struct window_maybe_options, invert_color)},
|
||||
{"blur-background", offsetof(struct window_maybe_options, blur_background)},
|
||||
{"clip-shadow-above", offsetof(struct window_maybe_options, clip_shadow_above)},
|
||||
{"transparent-clipping", offsetof(struct window_maybe_options, transparent_clipping)},
|
||||
};
|
||||
|
||||
static c2_lptr_t *parse_rule(config_setting_t *setting) {
|
||||
if (!config_setting_is_group(setting)) {
|
||||
log_error("Invalid rule at line %d. It must be a group.",
|
||||
config_setting_source_line(setting));
|
||||
return NULL;
|
||||
}
|
||||
int ival;
|
||||
double fval;
|
||||
const char *sval;
|
||||
c2_lptr_t *rule = NULL;
|
||||
if (config_setting_lookup_string(setting, "match", &sval)) {
|
||||
rule = c2_parse(NULL, sval, NULL);
|
||||
if (!rule) {
|
||||
log_error("Failed to parse rule at line %d.",
|
||||
config_setting_source_line(setting));
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
// If no match condition is specified, it matches all windows
|
||||
rule = c2_new_true();
|
||||
}
|
||||
|
||||
auto wopts = cmalloc(struct window_maybe_options);
|
||||
*wopts = (struct window_maybe_options){
|
||||
.opacity = NAN,
|
||||
.corner_radius = -1,
|
||||
};
|
||||
c2_list_set_data(rule, wopts);
|
||||
|
||||
for (size_t i = 0; i < ARR_SIZE(all_window_options); i++) {
|
||||
if (config_setting_lookup_bool(setting, all_window_options[i].name, &ival)) {
|
||||
void *ptr = (char *)wopts + all_window_options[i].offset;
|
||||
*(enum tristate *)ptr = tri_from_bool(ival);
|
||||
}
|
||||
}
|
||||
if (config_setting_lookup_float(setting, "opacity", &fval)) {
|
||||
wopts->opacity = normalize_d(fval);
|
||||
}
|
||||
if (config_setting_lookup_float(setting, "dim", &fval)) {
|
||||
wopts->dim = normalize_d(fval);
|
||||
}
|
||||
if (config_setting_lookup_int(setting, "corner-radius", &ival)) {
|
||||
wopts->corner_radius = ival;
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
static void parse_rules(config_setting_t *setting, c2_lptr_t **rules) {
|
||||
if (!config_setting_is_list(setting)) {
|
||||
log_error("Invalid value for \"rules\" at line %d. It must be a list.",
|
||||
config_setting_source_line(setting));
|
||||
return;
|
||||
}
|
||||
const auto length = (unsigned int)config_setting_length(setting);
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
auto sub = config_setting_get_elem(setting, i);
|
||||
auto rule = parse_rule(sub);
|
||||
if (rule != NULL) {
|
||||
c2_condlist_insert(rules, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char **
|
||||
resolve_include(config_t *cfg, const char *include_dir, const char *path, const char **err) {
|
||||
char *result = locate_auxiliary_file("include", path, include_dir);
|
||||
|
@ -977,6 +1053,11 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) {
|
|||
parse_animations(opt->animations, animations, &opt->all_scripts);
|
||||
}
|
||||
|
||||
config_setting_t *rules = config_lookup(&cfg, "rules");
|
||||
if (rules) {
|
||||
parse_rules(rules, &opt->rules);
|
||||
}
|
||||
|
||||
opt->config_file_path = path;
|
||||
path = NULL;
|
||||
succeeded = true;
|
||||
|
|
|
@ -954,6 +954,14 @@ bool get_cfg(options_t *opt, int argc, char *const *argv) {
|
|||
|
||||
void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c,
|
||||
struct options *option) {
|
||||
if (option->rules) {
|
||||
if (!c2_list_postprocess(state, c->c, option->rules)) {
|
||||
log_error("Post-processing of rules failed, some of your rules "
|
||||
"might not work");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(c2_list_postprocess(state, c->c, option->unredir_if_possible_blacklist) &&
|
||||
c2_list_postprocess(state, c->c, option->paint_blacklist) &&
|
||||
c2_list_postprocess(state, c->c, option->shadow_blacklist) &&
|
||||
|
@ -987,6 +995,7 @@ void options_destroy(struct options *options) {
|
|||
c2_list_free(&options->corner_radius_rules, NULL);
|
||||
c2_list_free(&options->window_shader_fg_rules, free);
|
||||
c2_list_free(&options->transparent_clipping_blacklist, NULL);
|
||||
c2_list_free(&options->rules, free);
|
||||
|
||||
free(options->config_file_path);
|
||||
free(options->write_pid_path);
|
||||
|
|
|
@ -2000,6 +2000,7 @@ static struct window_options win_options_from_config(const struct options *opts)
|
|||
.paint = true,
|
||||
.clip_shadow_above = false,
|
||||
.unredir_ignore = false,
|
||||
.opacity = 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
99
src/wm/win.c
99
src/wm/win.c
|
@ -998,6 +998,24 @@ void win_update_opacity_rule(session_t *ps, struct win *w) {
|
|||
w->options.opacity = opacity;
|
||||
}
|
||||
|
||||
struct win_update_rule_params {
|
||||
struct win *w;
|
||||
struct session *ps;
|
||||
struct window_maybe_options options;
|
||||
};
|
||||
|
||||
static bool win_update_rule(const c2_lptr_t *rule, void *args) {
|
||||
auto params = (struct win_update_rule_params *)args;
|
||||
void *pdata = NULL;
|
||||
if (!c2_match_one(params->ps->c2_state, params->w, rule, &pdata)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto wopts_next = (struct window_maybe_options *)pdata;
|
||||
params->options = win_maybe_options_fold(params->options, *wopts_next);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be called on window data changes.
|
||||
*
|
||||
|
@ -1009,39 +1027,60 @@ void win_on_factor_change(session_t *ps, struct win *w) {
|
|||
c2_window_state_update(ps->c2_state, &w->c2_state, ps->c.c, wid, win_id(w));
|
||||
// Focus and is_fullscreen needs to be updated first, as other rules might depend
|
||||
// on the focused state of the window
|
||||
bool focused = win_is_focused(ps, w);
|
||||
win_update_is_fullscreen(ps, w);
|
||||
|
||||
win_determine_shadow(ps, w);
|
||||
win_determine_clip_shadow_above(ps, w);
|
||||
win_determine_invert_color(ps, w);
|
||||
win_determine_blur_background(ps, w);
|
||||
win_determine_rounded_corners(ps, w);
|
||||
win_determine_fg_shader(ps, w);
|
||||
win_update_dim(ps, w, focused);
|
||||
if (ps->o.rules == NULL) {
|
||||
bool focused = win_is_focused(ps, w);
|
||||
// Universal rules take precedence over wintype_option and
|
||||
// other exclusion/inclusion lists. And it also supersedes
|
||||
// some of the "override" options.
|
||||
win_determine_shadow(ps, w);
|
||||
win_determine_clip_shadow_above(ps, w);
|
||||
win_determine_invert_color(ps, w);
|
||||
win_determine_blur_background(ps, w);
|
||||
win_determine_rounded_corners(ps, w);
|
||||
win_determine_fg_shader(ps, w);
|
||||
win_update_opacity_rule(ps, w);
|
||||
win_update_dim(ps, w, focused);
|
||||
w->mode = win_calc_mode(w);
|
||||
log_debug("Window mode changed to %d", w->mode);
|
||||
win_update_opacity_rule(ps, w);
|
||||
w->opacity = win_calc_opacity_target(ps, w, focused);
|
||||
w->options.paint = TRI_UNKNOWN;
|
||||
w->options.unredir_ignore = TRI_UNKNOWN;
|
||||
w->options.fade = TRI_UNKNOWN;
|
||||
w->options.transparent_clipping = TRI_UNKNOWN;
|
||||
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
|
||||
c2_match(ps->c2_state, w, ps->o.paint_blacklist, NULL)) {
|
||||
w->options.paint = TRI_FALSE;
|
||||
}
|
||||
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
|
||||
c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL)) {
|
||||
w->options.unredir_ignore = TRI_TRUE;
|
||||
}
|
||||
if (c2_match(ps->c2_state, w, ps->o.fade_blacklist, NULL)) {
|
||||
w->options.fade = TRI_FALSE;
|
||||
}
|
||||
if (c2_match(ps->c2_state, w, ps->o.transparent_clipping_blacklist, NULL)) {
|
||||
w->options.transparent_clipping = TRI_FALSE;
|
||||
}
|
||||
} else {
|
||||
struct win_update_rule_params params = {
|
||||
.w = w,
|
||||
.ps = ps,
|
||||
.options = WIN_MAYBE_OPTIONS_DEFAULT,
|
||||
};
|
||||
assert(w->state == WSTATE_MAPPED);
|
||||
c2_list_foreach(ps->o.rules, win_update_rule, ¶ms);
|
||||
w->options = params.options;
|
||||
if (safe_isnan(w->options.opacity) && w->has_opacity_prop) {
|
||||
w->options.opacity = ((double)w->opacity_prop) / OPAQUE;
|
||||
}
|
||||
w->opacity = win_options(w).opacity;
|
||||
}
|
||||
|
||||
w->mode = win_calc_mode(w);
|
||||
log_debug("Window mode changed to %d", w->mode);
|
||||
win_update_opacity_rule(ps, w);
|
||||
w->opacity = win_calc_opacity_target(ps, w, focused);
|
||||
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) {
|
||||
w->options.paint = c2_match(ps->c2_state, w, ps->o.paint_blacklist, NULL)
|
||||
? TRI_FALSE
|
||||
: TRI_UNKNOWN;
|
||||
}
|
||||
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) {
|
||||
w->options.unredir_ignore =
|
||||
c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL)
|
||||
? TRI_TRUE
|
||||
: TRI_UNKNOWN;
|
||||
}
|
||||
|
||||
w->options.fade =
|
||||
c2_match(ps->c2_state, w, ps->o.fade_blacklist, NULL) ? TRI_FALSE : TRI_UNKNOWN;
|
||||
|
||||
w->options.transparent_clipping =
|
||||
c2_match(ps->c2_state, w, ps->o.transparent_clipping_blacklist, NULL)
|
||||
? TRI_FALSE
|
||||
: TRI_UNKNOWN;
|
||||
|
||||
w->reg_ignore_valid = false;
|
||||
if (ps->debug_window != XCB_NONE &&
|
||||
|
@ -1132,7 +1171,7 @@ void win_on_client_update(session_t *ps, struct win *w) {
|
|||
win_update_role(&ps->c, ps->atoms, w);
|
||||
|
||||
// Update everything related to conditions
|
||||
win_on_factor_change(ps, w);
|
||||
win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
|
||||
|
||||
auto r = XCB_AWAIT(xcb_get_window_attributes, ps->c.c, client_win_id);
|
||||
if (!r) {
|
||||
|
|
8
tests/configs/issue239_universal.conf
Normal file
8
tests/configs/issue239_universal.conf
Normal file
|
@ -0,0 +1,8 @@
|
|||
fading = true;
|
||||
fade-in-step = 1;
|
||||
fade-out-step = 0.01;
|
||||
shadow = true;
|
||||
rules = ({
|
||||
match = "name = 'NoShadow'";
|
||||
shadow = false;
|
||||
})
|
|
@ -8,6 +8,7 @@ eval `dbus-launch --sh-syntax`
|
|||
./run_one_test.sh $exe configs/empty.conf testcases/basic.py
|
||||
./run_one_test.sh $exe configs/issue357.conf testcases/issue357.py
|
||||
./run_one_test.sh $exe configs/issue239.conf testcases/issue239.py
|
||||
./run_one_test.sh $exe configs/issue239_universal.conf testcases/issue239.py
|
||||
./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py
|
||||
./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py
|
||||
./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py
|
||||
|
|
Loading…
Reference in a new issue