Skip to content

Commit

Permalink
Merge pull request #1303 from yshui/animation-rules
Browse files Browse the repository at this point in the history
Animation rules
  • Loading branch information
yshui committed Aug 5, 2024
2 parents 567c0ba + 4777327 commit 0efe479
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 53 deletions.
13 changes: 10 additions & 3 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ static const char *animation_trigger_names[] attr_unused = {

struct script;
struct win_script {
int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS];
/// A running animation can be configured to prevent other animations from
/// starting.
uint64_t suppressions;
struct script *script;
/// true if this script is generated by us, false if this is a user choice.
int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS];
bool is_generated;
};

Expand Down Expand Up @@ -192,6 +192,9 @@ struct window_maybe_options {
enum window_unredir_option unredir;
/// Whether shadow should be rendered beneath this window.
enum tristate full_shadow;

/// Window specific animations
struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
};

// Make sure `window_options` has no implicit padding.
Expand All @@ -212,12 +215,16 @@ struct window_options {
bool clip_shadow_above;
bool paint;
bool full_shadow;

struct win_script animations[ANIMATION_TRIGGER_LAST + 1];
};
#pragma GCC diagnostic pop

static inline bool
win_options_eq(const struct window_options *a, const struct window_options *b) {
return memcmp(a, b, sizeof(struct window_options)) == 0;
win_options_no_damage(const struct window_options *a, const struct window_options *b) {
// Animation changing does not immediately change how window is rendered, so
// they don't cause damage.
return memcmp(a, b, offsetof(struct window_options, animations)) == 0;
}

extern struct shader_info null_shader;
Expand Down
21 changes: 12 additions & 9 deletions src/config_libconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ static const struct {
{"transparent-clipping", offsetof(struct window_maybe_options, transparent_clipping)},
};

static c2_lptr_t *parse_rule(config_setting_t *setting) {
static c2_lptr_t *parse_rule(config_setting_t *setting, struct script ***out_scripts) {
if (!config_setting_is_group(setting)) {
log_error("Invalid rule at line %d. It must be a group.",
config_setting_source_line(setting));
Expand All @@ -603,10 +603,7 @@ static c2_lptr_t *parse_rule(config_setting_t *setting) {
}

auto wopts = cmalloc(struct window_maybe_options);
*wopts = (struct window_maybe_options){
.opacity = NAN,
.corner_radius = -1,
};
*wopts = WIN_MAYBE_OPTIONS_DEFAULT;
c2_list_set_data(rule, wopts);

for (size_t i = 0; i < ARR_SIZE(all_window_options); i++) {
Expand All @@ -625,14 +622,20 @@ static c2_lptr_t *parse_rule(config_setting_t *setting) {
wopts->corner_radius = ival;
}

auto unredir_setting = config_setting_lookup(setting, "unredir-if-possible");
auto unredir_setting = config_setting_lookup(setting, "unredir");
if (unredir_setting) {
wopts->unredir = parse_unredir_option(unredir_setting);
}

auto animations = config_setting_lookup(setting, "animations");
if (animations) {
parse_animations(wopts->animations, animations, out_scripts);
}
return rule;
}

static void parse_rules(config_setting_t *setting, c2_lptr_t **rules) {
static void
parse_rules(config_setting_t *setting, struct script ***out_scripts, 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));
Expand All @@ -641,7 +644,7 @@ static void parse_rules(config_setting_t *setting, c2_lptr_t **rules) {
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);
auto rule = parse_rule(sub, out_scripts);
if (rule != NULL) {
c2_condlist_insert(rules, rule);
}
Expand Down Expand Up @@ -767,7 +770,7 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) {

config_setting_t *rules = config_lookup(&cfg, "rules");
if (rules) {
parse_rules(rules, &opt->rules);
parse_rules(rules, &opt->all_scripts, &opt->rules);
}

// --dbus
Expand Down
26 changes: 14 additions & 12 deletions src/picom.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,10 @@ static void destroy_backend(session_t *ps) {
// `win_process_animation_and_state_change` hasn't been called.)
// TBH, this assertion is probably too complex than what it's worth.
assert(!w->win_image || w->state == WSTATE_MAPPED ||
w->running_animation != NULL || w->previous.state != w->state);
w->running_animation_instance != NULL || w->previous.state != w->state);
// Wrapping up animation in progress
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;

if (ps->backend_data) {
// Unmapped windows could still have shadow images.
Expand Down Expand Up @@ -751,15 +751,15 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;

if (w->running_animation != NULL) {
if (w->running_animation_instance != NULL) {
*animation = true;
}

// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->running_animation != NULL) {
if (was_painted && w->running_animation_instance != NULL) {
add_damage_from_win(ps, w);
}

Expand Down Expand Up @@ -817,7 +817,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if ((w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYED) &&
w->running_animation == NULL) {
w->running_animation_instance == NULL) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
Expand Down Expand Up @@ -929,7 +929,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid;
w->reg_ignore_valid = true;

if (w->state == WSTATE_DESTROYED && w->running_animation == NULL) {
if (w->state == WSTATE_DESTROYED && w->running_animation_instance == NULL) {
// the window should be destroyed because it was destroyed
// by X server and now its animations are finished
win_destroy_finish(ps, w);
Expand Down Expand Up @@ -1662,8 +1662,8 @@ static void handle_pending_updates(struct session *ps, double delta_t) {
// Window might be freed by this function, if it's destroyed and its
// animation finished
if (w != NULL && win_process_animation_and_state_change(ps, w, delta_t)) {
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;
w->in_openclose = false;
if (w->state == WSTATE_UNMAPPED) {
unmap_win_finish(ps, w);
Expand Down Expand Up @@ -1738,8 +1738,8 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
if (w == NULL) {
continue;
}
free(w->running_animation);
w->running_animation = NULL;
free(w->running_animation_instance);
w->running_animation_instance = NULL;
if (w->state == WSTATE_DESTROYED) {
win_destroy_finish(ps, w);
}
Expand Down Expand Up @@ -1991,7 +1991,7 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data)
}

static struct window_options win_options_from_config(const struct options *opts) {
return (struct window_options){
struct window_options ret = {
.blur_background = opts->blur_method != BLUR_METHOD_NONE,
.full_shadow = false,
.shadow = opts->shadow_enable,
Expand All @@ -2006,6 +2006,8 @@ static struct window_options win_options_from_config(const struct options *opts)
.unredir = WINDOW_UNREDIR_WHEN_POSSIBLE_ELSE_TERMINATE,
.opacity = 1,
};
memcpy(ret.animations, opts->animations, sizeof(ret.animations));
return ret;
}

/**
Expand Down
49 changes: 25 additions & 24 deletions src/wm/win.c
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ void win_process_secondary_flags(session_t *ps, struct win *w) {
}

auto new_options = win_options(w);
if (win_options_eq(&old_options, &new_options)) {
if (win_options_no_damage(&old_options, &new_options)) {
pixman_region32_fini(&extents);
return;
}
Expand Down Expand Up @@ -792,7 +792,7 @@ void unmap_win_finish(session_t *ps, struct win *w) {
if (w->state != WSTATE_DESTROYED) {
win_clear_flags(w, WIN_FLAGS_PIXMAP_ERROR);
}
assert(w->running_animation == NULL);
assert(w->running_animation_instance == NULL);
}

/**
Expand Down Expand Up @@ -1715,8 +1715,9 @@ struct win_script_context win_script_context_prepare(struct session *ps, struct
}

double win_animatable_get(const struct win *w, enum win_script_output output) {
if (w->running_animation && w->running_animation_outputs[output] >= 0) {
return w->running_animation->memory[w->running_animation_outputs[output]];
if (w->running_animation_instance && w->running_animation.output_indices[output] >= 0) {
return w->running_animation_instance
->memory[w->running_animation.output_indices[output]];
}
switch (output) {
case WIN_SCRIPT_BLUR_OPACITY: return w->state == WSTATE_MAPPED ? 1.0 : 0.0;
Expand Down Expand Up @@ -1758,23 +1759,23 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
bool state_changed = w->previous.state != w->state;
w->previous.state = w->state;
w->previous.opacity = w->opacity;
return state_changed || (w->running_animation != NULL);
return state_changed || (w->running_animation_instance != NULL);
}

auto win_ctx = win_script_context_prepare(ps, w);
w->previous.opacity = w->opacity;
if (w->previous.state == w->state && win_ctx.opacity_before == win_ctx.opacity) {
advance_animation:
// No state changes, if there's a animation running, we just continue it.
if (w->running_animation == NULL) {
if (w->running_animation_instance == NULL) {
return false;
}
log_verbose("Advance animation for %#010x (%s) %f seconds", win_id(w),
w->name, delta_t);
if (!script_instance_is_finished(w->running_animation)) {
w->running_animation->elapsed += delta_t;
auto result =
script_instance_evaluate(w->running_animation, &win_ctx);
if (!script_instance_is_finished(w->running_animation_instance)) {
w->running_animation_instance->elapsed += delta_t;
auto result = script_instance_evaluate(
w->running_animation_instance, &win_ctx);
if (result != SCRIPT_EVAL_OK) {
log_error("Failed to run animation script: %d", result);
return true;
Expand Down Expand Up @@ -1824,7 +1825,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
break;
case WSTATE_PAIR(WSTATE_UNMAPPED, WSTATE_DESTROYED):
if ((!ps->o.no_fading_destroyed_argb || !win_has_alpha(w)) &&
w->running_animation != NULL) {
w->running_animation_instance != NULL) {
trigger = ANIMATION_TRIGGER_CLOSE;
}
break;
Expand All @@ -1847,19 +1848,20 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
}
}

if (trigger != ANIMATION_TRIGGER_INVALID && w->running_animation &&
(w->running_animation_suppressions & (1 << trigger)) != 0) {
if (trigger != ANIMATION_TRIGGER_INVALID && w->running_animation_instance &&
(w->running_animation.suppressions & (1 << trigger)) != 0) {
log_debug("Not starting animation %s for window %#010x (%s) because it "
"is being suppressed.",
animation_trigger_names[trigger], win_id(w), w->name);
goto advance_animation;
}

if (trigger == ANIMATION_TRIGGER_INVALID || ps->o.animations[trigger].script == NULL) {
auto wopts = win_options(w);
if (trigger == ANIMATION_TRIGGER_INVALID || wopts.animations[trigger].script == NULL) {
return true;
}

if (ps->o.animations[trigger].is_generated && !win_options(w).fade) {
if (wopts.animations[trigger].is_generated && !wopts.fade) {
// Window's animation is fading (as signified by the fact that it's
// generated), but the user has disabled fading for this window.
return true;
Expand All @@ -1868,16 +1870,15 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
log_debug("Starting animation %s for window %#010x (%s)",
animation_trigger_names[trigger], win_id(w), w->name);

auto new_animation = script_instance_new(ps->o.animations[trigger].script);
if (w->running_animation) {
script_instance_resume_from(w->running_animation, new_animation);
free(w->running_animation);
auto new_animation = script_instance_new(wopts.animations[trigger].script);
if (w->running_animation_instance) {
script_instance_resume_from(w->running_animation_instance, new_animation);
free(w->running_animation_instance);
}
w->running_animation = new_animation;
w->running_animation_outputs = ps->o.animations[trigger].output_indices;
w->running_animation_suppressions = ps->o.animations[trigger].suppressions;
script_instance_evaluate(w->running_animation, &win_ctx);
return false;
w->running_animation_instance = new_animation;
w->running_animation = wopts.animations[trigger];
script_instance_evaluate(w->running_animation_instance, &win_ctx);
return script_instance_is_finished(w->running_animation_instance);
}

#undef WSTATE_PAIR
Expand Down
20 changes: 15 additions & 5 deletions src/wm/win.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,8 @@ struct win {
/// by `win_process_animation_and_state_change` to trigger appropriate
/// animations.
struct win_state_change previous;
struct script_instance *running_animation;
const int *running_animation_outputs;
uint64_t running_animation_suppressions;
struct script_instance *running_animation_instance;
struct win_script running_animation;
};

struct win_script_context {
Expand Down Expand Up @@ -310,12 +309,19 @@ static const struct window_maybe_options WIN_MAYBE_OPTIONS_DEFAULT = {
.unredir = WINDOW_UNREDIR_INVALID,
};

static inline void win_script_fold(const struct win_script *upper,
const struct win_script *lower, struct win_script *output) {
for (size_t i = 0; i <= ANIMATION_TRIGGER_LAST; i++) {
output[i] = upper[i].script ? upper[i] : lower[i];
}
}

/// Combine two window options. The `upper` value has higher priority, the `lower` value
/// will only be used if the corresponding value in `upper` is not set (e.g. it is
/// TRI_UNKNOWN for tristate values, NaN for opacity, -1 for corner_radius).
static inline struct window_maybe_options __attribute__((always_inline))
win_maybe_options_fold(struct window_maybe_options upper, struct window_maybe_options lower) {
return (struct window_maybe_options){
struct window_maybe_options ret = {
.unredir = upper.unredir == WINDOW_UNREDIR_INVALID ? lower.unredir : upper.unredir,
.blur_background = tri_or(upper.blur_background, lower.blur_background),
.clip_shadow_above = tri_or(upper.clip_shadow_above, lower.clip_shadow_above),
Expand All @@ -329,14 +335,16 @@ win_maybe_options_fold(struct window_maybe_options upper, struct window_maybe_op
.shader = upper.shader ? upper.shader : lower.shader,
.corner_radius = upper.corner_radius >= 0 ? upper.corner_radius : lower.corner_radius,
};
win_script_fold(upper.animations, lower.animations, ret.animations);
return ret;
}

/// Unwrap a `window_maybe_options` to a `window_options`, using the default value for
/// values that are not set in the `window_maybe_options`.
static inline struct window_options __attribute__((always_inline))
win_maybe_options_or(struct window_maybe_options maybe, struct window_options def) {
assert(def.unredir != WINDOW_UNREDIR_INVALID);
return (struct window_options){
struct window_options ret = {
.unredir = maybe.unredir == WINDOW_UNREDIR_INVALID ? def.unredir : maybe.unredir,
.blur_background = tri_or_bool(maybe.blur_background, def.blur_background),
.clip_shadow_above = tri_or_bool(maybe.clip_shadow_above, def.clip_shadow_above),
Expand All @@ -351,6 +359,8 @@ win_maybe_options_or(struct window_maybe_options maybe, struct window_options de
.dim = !safe_isnan(maybe.dim) ? maybe.dim : def.dim,
.shader = maybe.shader ? maybe.shader : def.shader,
};
win_script_fold(maybe.animations, def.animations, ret.animations);
return ret;
}

static inline struct window_options __attribute__((always_inline))
Expand Down

0 comments on commit 0efe479

Please sign in to comment.