1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-25 14:06:08 -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:
Yuxuan Shui 2024-07-30 11:52:10 +01:00
parent 882f21b34a
commit 8384dcad0c
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
10 changed files with 230 additions and 64 deletions

View file

@ -44,6 +44,7 @@ typedef struct _c2_l c2_l_t;
/// Pointer to a condition tree. /// Pointer to a condition tree.
typedef struct { typedef struct {
bool isbranch : 1; bool isbranch : 1;
bool istrue : 1;
union { union {
c2_b_t *b; c2_b_t *b;
c2_l_t *l; c2_l_t *l;
@ -101,13 +102,11 @@ struct c2_property_value {
}; };
/// Initializer for c2_ptr_t. /// Initializer for c2_ptr_t.
#define C2_PTR_INIT \ static const c2_ptr_t C2_PTR_INIT = {
{ \ .isbranch = false,
.isbranch = false, \ .istrue = false,
.l = NULL, \ .l = NULL,
} };
static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
/// Operator of a branch element. /// Operator of a branch element.
typedef enum { typedef enum {
@ -200,24 +199,21 @@ struct _c2_l {
static const unsigned int C2_L_INVALID_TARGET_ID = UINT_MAX; static const unsigned int C2_L_INVALID_TARGET_ID = UINT_MAX;
/// Initializer for c2_l_t. /// Initializer for c2_l_t.
#define C2_L_INIT \ static const c2_l_t C2_L_INIT = {
{ \ .neg = false,
.neg = false, \ .op = C2_L_OEXISTS,
.op = C2_L_OEXISTS, \ .match = C2_L_MEXACT,
.match = C2_L_MEXACT, \ .match_ignorecase = false,
.match_ignorecase = false, \ .tgt = NULL,
.tgt = NULL, \ .tgtatom = 0,
.tgtatom = 0, \ .target_on_client = false,
.target_on_client = false, \ .predef = C2_L_PUNDEFINED,
.predef = C2_L_PUNDEFINED, \ .index = 0,
.index = 0, \ .ptntype = C2_L_PTUNDEFINED,
.ptntype = C2_L_PTUNDEFINED, \ .ptnstr = NULL,
.ptnstr = NULL, \ .ptnint = 0,
.ptnint = 0, \ .target_id = C2_L_INVALID_TARGET_ID,
.target_id = C2_L_INVALID_TARGET_ID, \ };
}
static const c2_l_t leaf_def = C2_L_INIT;
/// Linked list type of conditions. /// Linked list type of conditions.
struct _c2_lptr { 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) { static inline void c2_ptr_reset(c2_ptr_t *pp) {
if (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 // Otherwise, combine the second and the incoming one
else { 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); assert(eles[1].isbranch);
pele = &eles[1].b->opr2; 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); presult->l = cmalloc(c2_l_t);
c2_l_t *const pleaf = presult->l; c2_l_t *const pleaf = presult->l;
memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); *pleaf = C2_L_INIT;
// Parse negation marks // Parse negation marks
while ('!' == pattern[offset]) { 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); auto pleaf = cmalloc(c2_l_t);
presult->isbranch = false; presult->isbranch = false;
presult->l = pleaf; presult->l = pleaf;
memcpy(pleaf, &leaf_def, sizeof(c2_l_t)); *pleaf = C2_L_INIT;
pleaf->op = C2_L_OEQ; pleaf->op = C2_L_OEQ;
pleaf->ptntype = C2_L_PTSTRING; 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), log_debug("(%#010x): branch: result = %d, pattern = %s", win_id(w),
result, c2_condition_to_str2(cond)); result, c2_condition_to_str2(cond));
} else if (cond.istrue) {
return true;
} else { } else {
// A leaf // A leaf
const c2_l_t *pleaf = cond.l; 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; 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. * 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; 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) { struct c2_state *c2_state_new(struct atom *atoms) {
auto ret = ccalloc(1, struct c2_state); auto ret = ccalloc(1, struct c2_state);
ret->atoms = atoms; 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); HASH_FIND(hh, state->tracked_properties, &key, sizeof(key), p);
return p != NULL; 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;
}

View file

@ -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); bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data);
/// Return user data stored in a condition. /// Return user data stored in a condition.
void *c2_list_get_data(const c2_lptr_t *condlist); 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 /// 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. /// next call to this function, and should not be freed.
const char *c2_lptr_to_str(const c2_lptr_t *); 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. * Destroy a condition list.

View file

@ -717,7 +717,9 @@ bool parse_config(options_t *opt, const char *config_file) {
.track_leader = false, .track_leader = false,
.rounded_corners_blacklist = NULL .rounded_corners_blacklist = NULL,
.rules = NULL,
}; };
// clang-format on // clang-format on

View file

@ -8,6 +8,7 @@
/// Common functions and definitions for configuration parsing /// Common functions and definitions for configuration parsing
/// Used for command line arguments and config files /// Used for command line arguments and config files
#include <stdalign.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
@ -166,6 +167,9 @@ struct window_maybe_options {
enum tristate unredir_ignore; 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. /// Like `window_maybe_options`, but all fields are guaranteed to be set.
struct window_options { struct window_options {
double opacity; double opacity;
@ -181,17 +185,15 @@ struct window_options {
bool paint; bool paint;
bool unredir_ignore; bool unredir_ignore;
char padding[2]; char padding[4];
}; };
#pragma GCC diagnostic pop
static inline bool static inline bool
win_options_eq(const struct window_options *a, const struct window_options *b) { win_options_eq(const struct window_options *a, const struct window_options *b) {
return memcmp(a, b, offsetof(struct window_options, padding)) == 0; 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; extern struct shader_info null_shader;
/// Structure representing all options. /// Structure representing all options.
@ -399,6 +401,8 @@ typedef struct options {
struct win_script animations[ANIMATION_TRIGGER_LAST + 1]; struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
/// Array of all the scripts used in `animations`. This is a dynarr. /// Array of all the scripts used in `animations`. This is a dynarr.
struct script **all_scripts; struct script **all_scripts;
c2_lptr_t *rules;
} options_t; } options_t;
extern const char *const BACKEND_STRS[NUM_BKEND + 1]; extern const char *const BACKEND_STRS[NUM_BKEND + 1];

View file

@ -529,6 +529,82 @@ void generate_fading_config(struct options *opt) {
dynarr_extend_from(opt->all_scripts, scripts, number_of_scripts); 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 ** static const char **
resolve_include(config_t *cfg, const char *include_dir, const char *path, const char **err) { resolve_include(config_t *cfg, const char *include_dir, const char *path, const char **err) {
char *result = locate_auxiliary_file("include", path, include_dir); 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); 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; opt->config_file_path = path;
path = NULL; path = NULL;
succeeded = true; succeeded = true;

View file

@ -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, void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c,
struct options *option) { 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) && 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->paint_blacklist) &&
c2_list_postprocess(state, c->c, option->shadow_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->corner_radius_rules, NULL);
c2_list_free(&options->window_shader_fg_rules, free); c2_list_free(&options->window_shader_fg_rules, free);
c2_list_free(&options->transparent_clipping_blacklist, NULL); c2_list_free(&options->transparent_clipping_blacklist, NULL);
c2_list_free(&options->rules, free);
free(options->config_file_path); free(options->config_file_path);
free(options->write_pid_path); free(options->write_pid_path);

View file

@ -2000,6 +2000,7 @@ static struct window_options win_options_from_config(const struct options *opts)
.paint = true, .paint = true,
.clip_shadow_above = false, .clip_shadow_above = false,
.unredir_ignore = false, .unredir_ignore = false,
.opacity = 1,
}; };
} }

View file

@ -998,6 +998,24 @@ void win_update_opacity_rule(session_t *ps, struct win *w) {
w->options.opacity = opacity; 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. * 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)); 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 // 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
bool focused = win_is_focused(ps, w);
win_update_is_fullscreen(ps, w); win_update_is_fullscreen(ps, w);
win_determine_shadow(ps, w); if (ps->o.rules == NULL) {
win_determine_clip_shadow_above(ps, w); bool focused = win_is_focused(ps, w);
win_determine_invert_color(ps, w); // Universal rules take precedence over wintype_option and
win_determine_blur_background(ps, w); // other exclusion/inclusion lists. And it also supersedes
win_determine_rounded_corners(ps, w); // some of the "override" options.
win_determine_fg_shader(ps, w); win_determine_shadow(ps, w);
win_update_dim(ps, w, focused); 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, &params);
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); w->mode = win_calc_mode(w);
log_debug("Window mode changed to %d", w->mode); 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; w->reg_ignore_valid = false;
if (ps->debug_window != XCB_NONE && 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); win_update_role(&ps->c, ps->atoms, w);
// Update everything related to conditions // 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); auto r = XCB_AWAIT(xcb_get_window_attributes, ps->c.c, client_win_id);
if (!r) { if (!r) {

View file

@ -0,0 +1,8 @@
fading = true;
fade-in-step = 1;
fade-out-step = 0.01;
shadow = true;
rules = ({
match = "name = 'NoShadow'";
shadow = false;
})

View file

@ -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/empty.conf testcases/basic.py
./run_one_test.sh $exe configs/issue357.conf testcases/issue357.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.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_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.py
./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py