Skip to content

Commit

Permalink
core: parse and use universal rules
Browse files Browse the repository at this point in the history
Design is described in #1284.

Added a trivial test of universal rules

Signed-off-by: Yuxuan Shui <[email protected]>
  • Loading branch information
yshui committed Jul 31, 2024
1 parent 6cbf88f commit e067afa
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 87 deletions.
126 changes: 74 additions & 52 deletions src/c2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -173,6 +172,7 @@ struct _c2_l {
C2_L_POVREDIR,
C2_L_PARGB,
C2_L_PFOCUSED,
C2_L_PGROUPFOCUSED,
C2_L_PWMWIN,
C2_L_PBSHAPED,
C2_L_PROUNDED,
Expand All @@ -198,23 +198,20 @@ struct _c2_l {
};

/// 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, \
}

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,
};

/// Linked list type of conditions.
struct _c2_lptr {
Expand All @@ -235,30 +232,31 @@ static struct {
bool is_string;
bool deprecated;
} C2_PREDEFS[] = {
[C2_L_PID] = { "id", false, true },
[C2_L_PX] = { "x", false, },
[C2_L_PY] = { "y", false, },
[C2_L_PX2] = { "x2", false, },
[C2_L_PY2] = { "y2", false, },
[C2_L_PWIDTH] = { "width", false, },
[C2_L_PHEIGHT] = { "height", false, },
[C2_L_PWIDTHB] = { "widthb", false, },
[C2_L_PHEIGHTB] = { "heightb", false, },
[C2_L_PBDW] = { "border_width", false, },
[C2_L_PFULLSCREEN] = { "fullscreen", false, },
[C2_L_POVREDIR] = { "override_redirect", false, },
[C2_L_PARGB] = { "argb", false, },
[C2_L_PFOCUSED] = { "focused", false, },
[C2_L_PWMWIN] = { "wmwin", false, },
[C2_L_PBSHAPED] = { "bounding_shaped", false, },
[C2_L_PROUNDED] = { "rounded_corners", false, },
[C2_L_PCLIENT] = { "client", false, true },
[C2_L_PWINDOWTYPE] = { "window_type", true, },
[C2_L_PLEADER] = { "leader", false, true },
[C2_L_PNAME] = { "name", true, },
[C2_L_PCLASSG] = { "class_g", true, },
[C2_L_PCLASSI] = { "class_i", true, },
[C2_L_PROLE] = { "role", true, },
[C2_L_PID] = { "id", false, true },
[C2_L_PX] = { "x", false, },
[C2_L_PY] = { "y", false, },
[C2_L_PX2] = { "x2", false, },
[C2_L_PY2] = { "y2", false, },
[C2_L_PWIDTH] = { "width", false, },
[C2_L_PHEIGHT] = { "height", false, },
[C2_L_PWIDTHB] = { "widthb", false, },
[C2_L_PHEIGHTB] = { "heightb", false, },
[C2_L_PBDW] = { "border_width", false, },
[C2_L_PFULLSCREEN] = { "fullscreen", false, },
[C2_L_POVREDIR] = { "override_redirect", false, },
[C2_L_PARGB] = { "argb", false, },
[C2_L_PFOCUSED] = { "focused", false, },
[C2_L_PGROUPFOCUSED] = { "group_focused", false, },
[C2_L_PWMWIN] = { "wmwin", false, },
[C2_L_PBSHAPED] = { "bounding_shaped", false, },
[C2_L_PROUNDED] = { "rounded_corners", false, },
[C2_L_PCLIENT] = { "client", false, true },
[C2_L_PWINDOWTYPE] = { "window_type", true, },
[C2_L_PLEADER] = { "leader", false, true },
[C2_L_PNAME] = { "name", true, },
[C2_L_PCLASSG] = { "class_g", true, },
[C2_L_PCLASSI] = { "class_i", true, },
[C2_L_PROLE] = { "role", true, },
};
// clang-format on

Expand Down Expand Up @@ -297,7 +295,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;
}
}

Expand Down Expand Up @@ -695,7 +693,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;
}
Expand Down Expand Up @@ -809,7 +807,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]) {
Expand Down Expand Up @@ -1166,7 +1164,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;

Expand Down Expand Up @@ -1610,6 +1608,10 @@ static bool c2_match_once_leaf_int(const struct win *w, const c2_l_t *leaf) {
predef_target =
w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused;
break;
case C2_L_PGROUPFOCUSED:
predef_target = w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
w->is_group_focused;
break;
case C2_L_PWMWIN: predef_target = win_is_wmwin(w); break;
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
Expand Down Expand Up @@ -1847,6 +1849,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;
Expand All @@ -1870,6 +1874,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.
*
Expand Down Expand Up @@ -1926,6 +1936,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;
Expand Down Expand Up @@ -2176,3 +2192,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 changes: 5 additions & 0 deletions src/c2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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];
Expand Down
80 changes: 80 additions & 0 deletions src/config_libconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,81 @@ 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)},
{"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);
Expand Down Expand Up @@ -977,6 +1052,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;
Expand Down
1 change: 1 addition & 0 deletions src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,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);
Expand Down
Loading

0 comments on commit e067afa

Please sign in to comment.