picom upto date sync with yshui, full anim support

This commit is contained in:
Arda Atci
2022-10-04 00:24:05 +03:00
parent 2dae094981
commit 70c729d389
16 changed files with 1108 additions and 196 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ compton
build/
compile_commands.json
build.ninja
make.sh
# language servers
.ccls-cache

View File

@@ -1,29 +1,76 @@
#################################
# Animations #
# !These animations WILL NOT work correctly for any other wm other than phyOS-dwm fork!
# fly-in: Windows fly in from random directions to the screen
# maximize: Windows pop from center of the screen to their respective positions
# minimize: Windows minimize from their position to the center of the screen
# slide-in-center: Windows move from upper-center of the screen to their respective positions
# slide-out-center: Windows move to the upper-center of the screen
# slide-left: Windows are created from the right-most window position and slide leftwards
# slide right: Windows are created from the left-most window position and slide rightwards
# slide-down: Windows are moved from the top of the screen and slide downward
# slide-up: Windows are moved from their position to top of the screen
# squeeze: Windows are either closed or created to/from their center y-position (the animation is similar to a blinking eye)
# squeeze-bottom: Similar to squeeze, but the animation starts from bottom-most y-position
# zoom: Windows are either created or destroyed from/to their center (not the screen center)
#################################
#enable or disable animations
animations = true;
#change animation speed of windows in current tag e.g open window in current tag
animation-stiffness-in-tag = 125;
#change animation speed of windows when tag changes
animation-stiffness-tag-change = 90.0;
animation-window-mass = 0.4;
animation-dampening = 15;
animation-clamping = true;
#open windows
animation-for-open-window = "zoom";
#minimize or close windows
animation-for-unmap-window = "squeeze";
#popup windows
animation-for-transient-window = "slide-up"; #available options: slide-up, slide-down, slide-left, slide-right, squeeze, squeeze-bottom, zoom
#set animation for windows being transitioned out while changings tags
animation-for-prev-tag = "minimize";
#enables fading for windows being transitioned out while changings tags
enable-fading-prev-tag = true;
#set animation for windows being transitioned in while changings tags
animation-for-next-tag = "slide-in-center";
#enables fading for windows being transitioned in while changings tags
enable-fading-next-tag = true;
#################################
# Shadows #
#################################
# Enabled client-side shadows on windows. Note desktop windows
# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
# unless explicitly requested using the wintypes option.
#
# shadow = false
shadow = true;
shadow = false;
# The blur radius for shadows, in pixels. (defaults to 12)
# shadow-radius = 12
shadow-radius = 7;
shadow-radius = 60;
# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
# shadow-opacity = .75
# The left offset for shadows, in pixels. (defaults to -15)
# shadow-offset-x = -15
shadow-offset-x = -7;
shadow-offset-x = -20;
# The top offset for shadows, in pixels. (defaults to -15)
# shadow-offset-y = -15
shadow-offset-y = -7;
shadow-offset-y = -20;
# Red color value of shadow (0.0 - 1.0, defaults to 0).
# shadow-red = 0
@@ -48,6 +95,9 @@ shadow-exclude = [
"class_g = 'Conky'",
"class_g ?= 'Notify-osd'",
"class_g = 'Cairo-clock'",
"class_g = 'dwm'",
"class_g = 'chromium'",
"class_g *?= 'slop'",
"_GTK_FRAME_EXTENTS@:c"
];
@@ -72,19 +122,18 @@ shadow-exclude = [
# Fade windows in/out when opening/closing and when opacity changes,
# unless no-fading-openclose is used.
# fading = false
fading = true;
# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
# fade-in-step = 0.028
fade-in-step = 0.03;
fade-in-step = 0.023;
# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
# fade-out-step = 0.03
fade-out-step = 0.03;
fade-out-step = 0.035;
# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
# fade-delta = 10
fade-delta = 10
# Specify a list of conditions of windows that should not be faded.
# fade-exclude = []
@@ -103,15 +152,13 @@ fade-out-step = 0.03;
# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
# inactive-opacity = 1
inactive-opacity = 0.8;
# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
# frame-opacity = 1.0
frame-opacity = 0.7;
# Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows.
# inactive-opacity-override = true
inactive-opacity-override = false;
inactive-opacity-override = true;
# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
# active-opacity = 1.0
@@ -121,21 +168,13 @@ inactive-opacity-override = false;
# Specify a list of conditions of windows that should never be considered focused.
# focus-exclude = []
focus-exclude = [ "class_g = 'Cairo-clock'" ];
focus-exclude = [
"class_g = 'Cairo-clock'" ,
];
# Use fixed inactive dim value, instead of adjusting according to window opacity.
# inactive-dim-fixed = 1.0
# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
# like `50:name *= "Firefox"`. picom-trans is recommended over this.
# Note we don't make any guarantee about possible conflicts with other
# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
# example:
# opacity-rule = [ "80:class_g = 'URxvt'" ];
#
# opacity-rule = []
#################################
# Corners #
#################################
@@ -143,52 +182,21 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ];
# Sets the radius of rounded window corners. When > 0, the compositor will
# round the corners of windows. Does not interact well with
# `transparent-clipping`.
corner-radius = 0
corner-radius = 11;
# Exclude conditions for rounded corners.
rounded-corners-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'"
];
#rounded-corners-exclude = [
# "window_type = 'dock'",
# "window_type = 'desktop'"
#];
#################################
# Background-Blurring #
#################################
# Parameters for background blurring, see the *BLUR* section for more information.
# blur-method =
# blur-size = 12
#
# blur-deviation = false
#
# blur-strength = 5
# Blur background of semi-transparent / ARGB windows.
# Bad in performance, with driver-dependent behavior.
# The name of the switch may change without prior notifications.
#
# blur-background = false
# Blur background of windows when the window frame is not opaque.
# Implies:
# blur-background
# Bad in performance, with driver-dependent behavior. The name may change.
#
# blur-background-frame = false
# Use fixed blur strength rather than adjusting according to window opacity.
# blur-background-fixed = false
# Specify the blur convolution kernel, with the following format:
# example:
# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
#
# blur-kern = ""
blur-kern = "3x3box";
blur: {
method = "dual_kawase";
strength = 9;
background = true;
background-frame = false;
background-fixed = false;
}
# Exclude conditions for background blur.
@@ -196,28 +204,28 @@ blur-kern = "3x3box";
blur-background-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'",
"_GTK_FRAME_EXTENTS@:c"
"_GTK_FRAME_EXTENTS@:c",
"class_g = 'Chromium'",
"class_g = 'Discord'",
"class_g = 'Dunst'",
"class_g = 'Peek'",
"class_g *?= 'slop'",
];
#################################
# General Settings #
#################################
# Enable remote control via D-Bus. See the man page for more details.
# dbus = true
# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
# daemon = false
# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`.
# `xrender` is the default one.
#
# backend = "glx"
backend = "xrender";
backend = "glx"
# Enable/disable VSync.
# vsync = false
vsync = true;
# vsync = true
# Enable remote control via D-Bus. See the *D-BUS API* section below for more details.
# dbus = false
@@ -236,25 +244,25 @@ mark-ovredir-focused = true;
# shaped windows. The accuracy is not very high, unfortunately.
#
# detect-rounded-corners = false
detect-rounded-corners = true;
detect-rounded-corners = false;
# Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers
# not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows.
#
# detect-client-opacity = false
detect-client-opacity = true;
detect-client-opacity = false;
# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
# provided that the WM supports it.
#
# use-ewmh-active-win = false
use-ewmh-active-win = true;
# Unredirect all windows if a full-screen opaque window is detected,
# to maximize performance for full-screen windows. Known to cause flickering
# when redirecting/unredirecting windows.
#
# unredir-if-possible = false
unredir-if-possible = false;
# Delay before unredirecting the window, in milliseconds. Defaults to 0.
# unredir-if-possible-delay = 0
@@ -298,7 +306,7 @@ detect-transient = true;
# practically happened) and may not work with blur-background.
# My tests show a 15% performance boost. Recommended.
#
# glx-no-stencil = false
glx-no-stencil = true;
# GLX backend: Avoid rebinding pixmap on window damage.
# Probably could improve performance on rapid window content changes,
@@ -319,24 +327,18 @@ use-damage = true;
# calls are finished before picom starts drawing. Needed on nvidia-drivers
# with GLX backend for some users.
#
# xrender-sync-fence = false
xrender-sync-fence = true;
# GLX backend: Use specified GLSL fragment shader for rendering window
# contents. Read the man page for a detailed explanation of the interface.
# GLX backend: Use specified GLSL fragment shader for rendering window contents.
# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl`
# in the source tree for examples.
#
# window-shader-fg = "default"
# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
#
# window-shader-fg-rule = [
# "my_shader.frag:window_type != 'dock'"
# ]
window-shader-fg = "default";
# Force all windows to be painted with blending. Useful if you
# have a glx-fshader-win that could turn opaque pixels transparent.
#
# force-win-blend = false
# force-win-blend = true;
# Do not use EWMH to detect fullscreen windows.
# Reverts to checking if a window is fullscreen based only on its size and coordinates.
@@ -353,7 +355,7 @@ use-damage = true;
# Make transparent windows clip other windows like non-transparent windows do,
# instead of blending on top of them.
#
# transparent-clipping = false
transparent-clipping = false;
# Specify a list of conditions of windows that should never have transparent
# clipping applied. Useful for screenshot tools, where you need to be able to
@@ -426,3 +428,9 @@ wintypes:
popup_menu = { opacity = 0.8; }
dropdown_menu = { opacity = 0.8; }
};
opacity-rule = [
"100:class_g = 'St' && focused",
"50:class_g = 'St' && !focused",
"100:fullscreen",
];

View File

@@ -23,7 +23,8 @@
WM_CLIENT_MACHINE, \
_NET_ACTIVE_WINDOW, \
_COMPTON_SHADOW, \
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE, \
_NET_CURRENT_MON_CENTER
#define ATOM_LIST2 \
_NET_WM_WINDOW_TYPE_DESKTOP, \

View File

@@ -55,6 +55,58 @@ region_t get_damage(session_t *ps, bool all_damage) {
return region;
}
static void process_window_for_painting(session_t *ps, struct managed_win *w,
void *win_image, double additional_alpha,
region_t *reg_bound, region_t *reg_visible,
region_t *reg_paint, region_t *reg_paint_in_bound) {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
region_t reg_visible_local;
{
// The bounding shape, in window local coordinates
region_t reg_bound_local;
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local, reg_visible, reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x, -w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(&reg_visible_local, &reg_visible_local,
&reg_bound_local);
pixman_region32_fini(&reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image,
&reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
double alpha = additional_alpha * w->opacity;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img,
&reg_frame, &reg_visible_local,
(double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, dest_coord,
reg_paint_in_bound, reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
}
void handle_device_reset(session_t *ps) {
log_error("Device reset detected");
// Wait for reset to complete
@@ -185,7 +237,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
(coord_t){0}, NULL, (coord_t){0},
(coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height},
&reg_paint, &reg_visible);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
@@ -237,6 +289,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
* option */
auto real_win_mode = w->mode;
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
@@ -409,6 +462,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->win_image, &border_width);
if (w->old_win_image) {
// TODO(dccsillag): explain why the following is
// "necessary"
double zero = 0.0;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->old_win_image, &zero);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS,
w->old_win_image, &zero);
}
}
ps->backend_data->ops->set_image_property(
@@ -431,53 +495,43 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
}
// Draw window on target
if (w->frame_opacity == 1) {
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
if (w->frame_opacity == 1 && !is_animating) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
window_coord, NULL, window_coord,
window_coord, NULL, dest_coord,
&reg_paint_in_bound, &reg_visible);
} else {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
if (is_animating && w->old_win_image) {
bool is_focused = win_is_focused_raw(ps, w);
if (!is_focused && w->focused && w->opacity_is_set)
is_focused = true;
assert(w->old_win_image);
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
&reg_visible, &reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x,
-w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
// Only animate opacity here if we are resizing
// a transparent window
process_window_for_painting(ps, w, w->win_image,
is_focused ? 1.0 : w->opacity >= 1 ? 1.0 : w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
// Only do this if size changes as otherwise moving
// transparent windows will flicker and if you just
// move so slightly they will keep flickering
if (resizing && (!is_focused || !w->opacity_is_set)) {
process_window_for_painting(ps, w, w->old_win_image,
1.0 - w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
} else {
process_window_for_painting(
ps, w, w->win_image, 1.0, &reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, &reg_frame,
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_bound_local);
}
skip:
pixman_region32_fini(&reg_bound);

View File

@@ -183,6 +183,11 @@ struct backend_operations {
void *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible);
void (*_compose)(backend_t *backend_data, void *image_data,
int dst_x1, int dst_y1, int dst_x2, int dst_y2,
const region_t *reg_paint, const region_t *reg_visible);
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);

View File

@@ -412,7 +412,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
}
glUniform1i(win_shader->uniform_mask_tex, 2);
glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x,
glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x ,
(float)mask_offset.y);
if (mask != NULL) {
glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted);
@@ -496,6 +496,7 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
image_dst.y = root_height - image_dst.y;
image_dst.y -= extent_height;
for (int i = 0; i < nrects; i++) {
// Y-flip. Note after this, crect.y1 > crect.y2
rect_t crect = rects[i];
@@ -573,6 +574,11 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask
coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y};
x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height,
gd->height, inner->y_inverted, coord, indices);
for (unsigned int i = 2; i < 16; i+=4) {
coord[i+0] = lerp_range(0, mask_offset.x, 0, inner->width, coord[i+0]);
coord[i+1] = lerp_range(0, mask_offset.y, 0, inner->height, coord[i+1]);
}
_gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);

View File

@@ -147,6 +147,8 @@ typedef struct session {
ev_timer unredir_timer;
/// Timer for fading
ev_timer fade_timer;
/// Timer for animations
ev_timer animation_timer;
/// Use an ev_idle callback for drawing
/// So we only start drawing when events are processed
ev_idle draw_idle;
@@ -196,6 +198,8 @@ typedef struct session {
int root_width;
// Damage of root window.
// Damage root_damage;
int selmon_center_x;
int selmon_center_y;
/// X Composite overlay window.
xcb_window_t overlay;
/// The target window for debug mode
@@ -255,6 +259,8 @@ typedef struct session {
xcb_render_picture_t *alpha_picts;
/// Time of last fading. In milliseconds.
long long fade_time;
/// Time of last window animation step. In milliseconds.
long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time
/// Head pointer of the error ignore linked list.
ignore_t *ignore_head;
/// Pointer to the <code>next</code> member of tail element of the error

View File

@@ -708,6 +708,10 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
// opacity logic is complicated, and needs an "unset" state
opt->wintype_option[i].opacity = NAN;
}
if (!mask[i].animation) {
mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].clip_shadow_above) {
mask[i].clip_shadow_above = true;
opt->wintype_option[i].clip_shadow_above = false;
@@ -715,6 +719,40 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
}
}
enum open_window_animation parse_open_window_animation(const char *src) {
if (strcmp(src, "none") == 0) {
return OPEN_WINDOW_ANIMATION_NONE;
} else if (strcmp(src, "fly-in") == 0) {
return OPEN_WINDOW_ANIMATION_FLYIN;
} else if (strcmp(src, "zoom") == 0) {
return OPEN_WINDOW_ANIMATION_ZOOM;
} else if (strcmp(src, "slide-up") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_UP;
} else if (strcmp(src, "slide-down") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_DOWN;
} else if (strcmp(src, "slide-left") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_LEFT;
} else if (strcmp(src, "slide-right") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT;
} else if (strcmp(src, "slide-out") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT;
} else if (strcmp(src, "slide-in") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN;
} else if (strcmp(src, "slide-out-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER;
} else if (strcmp(src, "slide-in-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER;
} else if (strcmp(src, "minimize") == 0 || strcmp(src, "maximize") == 0) {
return OPEN_WINDOW_ANIMATION_MINIMIZE;
} else if (strcmp(src, "squeeze") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE;
} else if (strcmp(src, "squeeze-bottom") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM;
}
return OPEN_WINDOW_ANIMATION_INVALID;
}
char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
// clang-format off
@@ -759,6 +797,18 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.no_fading_destroyed_argb = false,
.fade_blacklist = NULL,
.animations = false,
.animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_unmap_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_next_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_prev_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_stiffness = 200.0,
.animation_stiffness_tag_change = 200.0,
.animation_window_mass = 1.0,
.animation_dampening = 25,
.animation_clamping = true,
.inactive_opacity = 1.0,
.inactive_opacity_override = false,
.active_opacity = 1.0,
@@ -790,7 +840,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.track_leader = false,
.rounded_corners_blacklist = NULL
.rounded_corners_blacklist = NULL,
.animation_blacklist = NULL
};
// clang-format on

View File

@@ -40,6 +40,25 @@ enum backend {
NUM_BKEND,
};
enum open_window_animation {
OPEN_WINDOW_ANIMATION_NONE = 0,
OPEN_WINDOW_ANIMATION_FLYIN,
OPEN_WINDOW_ANIMATION_SLIDE_UP,
OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
OPEN_WINDOW_ANIMATION_SLIDE_IN,
OPEN_WINDOW_ANIMATION_SLIDE_OUT,
OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER,
OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER,
OPEN_WINDOW_ANIMATION_ZOOM,
OPEN_WINDOW_ANIMATION_MINIMIZE,
OPEN_WINDOW_ANIMATION_MAXIMIZE,
OPEN_WINDOW_ANIMATION_SQUEEZE,
OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM,
OPEN_WINDOW_ANIMATION_INVALID,
};
typedef struct win_option_mask {
bool shadow : 1;
bool fade : 1;
@@ -49,6 +68,7 @@ typedef struct win_option_mask {
bool redir_ignore : 1;
bool opacity : 1;
bool clip_shadow_above : 1;
enum open_window_animation animation;
} win_option_mask_t;
typedef struct win_option {
@@ -60,6 +80,7 @@ typedef struct win_option {
bool redir_ignore;
double opacity;
bool clip_shadow_above;
enum open_window_animation animation;
} win_option_t;
enum blur_method {
@@ -172,6 +193,33 @@ typedef struct options {
/// Fading blacklist. A linked list of conditions.
c2_lptr_t *fade_blacklist;
// === Animations ===
/// Whether to do window animations
bool animations;
/// Which animation to run when opening a window
enum open_window_animation animation_for_open_window;
/// Which animation to run when opening a transient window
enum open_window_animation animation_for_transient_window;
/// Which animation to run when unmapping a window
enum open_window_animation animation_for_unmap_window;
/// Which animation to run when swapping to new tag
enum open_window_animation animation_for_next_tag;
/// Which animation to run for old tag
enum open_window_animation animation_for_prev_tag;
/// Spring stiffness for animation
double animation_stiffness;
/// Spring stiffness for current tag animation
double animation_stiffness_tag_change;
/// Window mass for animation
double animation_window_mass;
/// Animation dampening
double animation_dampening;
/// Whether to clamp animations
bool animation_clamping;
/// Animation blacklist. A linked list of conditions.
c2_lptr_t *animation_blacklist;
/// TODO: open/close animations
// === Opacity ===
/// Default opacity for inactive windows.
/// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY.
@@ -253,6 +301,12 @@ typedef struct options {
// Make transparent windows clip other windows, instead of blending on top of
// them
bool transparent_clipping;
// Enable fading for next tag
bool enable_fading_next_tag;
// Enable fading for prev tag
bool enable_fading_prev_tag;
/// A list of conditions of windows to which transparent clipping
/// should not apply
c2_lptr_t *transparent_clipping_blacklist;
@@ -269,6 +323,7 @@ bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *)
char *must_use locate_auxiliary_file(const char *scope, const char *path,
const char *include_dir);
enum blur_method must_use parse_blur_method(const char *src);
enum open_window_animation must_use parse_open_window_animation(const char *src);
/**
* Add a pattern to a condition linked list.

View File

@@ -226,6 +226,15 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
o->clip_shadow_above = ival;
mask->clip_shadow_above = true;
}
const char *sval = NULL;
if (config_setting_lookup_string(setting, "animation", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation = animation;
mask->animation = OPEN_WINDOW_ANIMATION_INVALID;
}
double fval;
if (config_setting_lookup_float(setting, "opacity", &fval)) {
@@ -461,6 +470,70 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above");
// --fade-exclude
parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude");
// --animations
lcfg_lookup_bool(&cfg, "animations", &opt->animations);
// --animation-for-open-window
if (config_lookup_string(&cfg, "animation-for-open-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_open_window = animation;
}
// --animation-for-transient-window
if (config_lookup_string(&cfg, "animation-for-transient-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_transient_window = animation;
}
// --animation-for-unmap-window
if (config_lookup_string(&cfg, "animation-for-unmap-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid unmap-window animation %s", sval);
goto err;
}
opt->animation_for_unmap_window = animation;
}
// --animation-for-next-tag
if (config_lookup_string(&cfg, "animation-for-next-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid next-tag animation %s", sval);
goto err;
}
opt->animation_for_next_tag = animation;
}
// --animation-for-prev-tag
if (config_lookup_string(&cfg, "animation-for-prev-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid prev-tag animation %s", sval);
goto err;
}
opt->animation_for_prev_tag = animation;
}
// --animations-exclude
parse_cfg_condlst(&cfg, &opt->animation_blacklist, "animation-exclude");
// --animation-stiffness
config_lookup_float(&cfg, "animation-stiffness-in-tag", &opt->animation_stiffness);
// --animation-stiffness-tag-change
config_lookup_float(&cfg, "animation-stiffness-tag-change", &opt->animation_stiffness_tag_change);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-next-tag", &opt->enable_fading_next_tag);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-prev-tag", &opt->enable_fading_prev_tag);
// --animation-window-mass
config_lookup_float(&cfg, "animation-window-mass", &opt->animation_window_mass);
// --animation-dampening
config_lookup_float(&cfg, "animation-dampening", &opt->animation_dampening);
// --animation-clamping
lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
// --focus-exclude
parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude");
// --invert-color-include

View File

@@ -449,6 +449,15 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
if (ps->root == ev->window) {
if (ev->atom == ps->atoms->a_NET_CURRENT_MON_CENTER) {
winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32);
if (prop.nitems == 2) {
ps->selmon_center_x = prop.p32[0];
ps->selmon_center_y = prop.p32[1];
}
free_winprop(&prop);
}
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
// to update focus
ps->pending_updates = true;

View File

@@ -71,6 +71,36 @@ static void usage(const char *argv0, int ret) {
"-F\n"
" Equals to -f. Deprecated.\n"
"\n"
"--animations\n"
" Run animations for window geometry changes (movement and scaling).\n"
"\n"
"--animation-for-open-window\n"
" Which animation to run when opening a window.\n"
" Must be one of `none`, `fly-in`, `zoom`,\n"
" `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
" (default: none).\n"
"\n"
"--animation-for-transient-window\n"
" Which animation to run when opening a transient window.\n"
" Must be one of `none`, `fly-in`, `zoom`,\n"
" `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
" (default: none).\n"
"\n"
"--animation-stiffness\n"
" Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n"
"\n"
"--animation-dampening\n"
" Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n"
"\n"
"--animation-window-mass\n"
" Mass parameter for animation (default: 1.0).\n"
"\n"
"--animation-clamping\n"
" Whether to clamp animations (default: true)\n"
"\n"
"--animation-exclude condition\n"
" Exclude conditions for animation.\n"
"\n"
"-i opacity\n"
" Opacity of inactive windows. (0.1 - 1.0)\n"
"\n"
@@ -466,6 +496,14 @@ static const struct option longopts[] = {
{"diagnostics", no_argument, NULL, 801},
{"debug-mode", no_argument, NULL, 802},
{"no-ewmh-fullscreen", no_argument, NULL, 803},
{"animations", no_argument, NULL, 804},
{"animation-stiffness-in-tag", required_argument, NULL, 805},
{"animation-stiffness-tag-change", required_argument, NULL, 806},
{"animation-dampening", required_argument, NULL, 807},
{"animation-window-mass", required_argument, NULL, 808},
{"animation-clamping", no_argument, NULL, 809},
{"animation-for-open-window", required_argument, NULL, 810},
{"animation-for-transient-window", required_argument, NULL, 811},
// Must terminate with a NULL entry
{NULL, 0, NULL, 0},
};
@@ -892,6 +930,52 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
break;
P_CASEBOOL(802, debug_mode);
P_CASEBOOL(803, no_ewmh_fullscreen);
P_CASEBOOL(804, animations);
case 805:
// --animation-stiffness
opt->animation_stiffness = atof(optarg);
break;
case 806:
// --animation-stiffness-for-tags
opt->animation_stiffness_tag_change = atof(optarg);
break;
case 807:
// --animation-dampening
opt->animation_dampening = atof(optarg);
break;
case 808:
// --animation-window-masss
opt->animation_window_mass = atof(optarg);
break;
case 809:
// --animation-clamping
opt->animation_clamping = true;
break;
case 810: {
// --animation-for-open-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid open-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_open_window = animation;
}
break;
}
case 811: {
// --animation-for-transient-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid transient-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_transient_window = animation;
}
break;
}
case 812: {
// --animation-exclude
condlst_add(&opt->animation_blacklist, optarg);
break;
}
default: usage(argv[0], 1); break;
#undef P_CASEBOOL
}

View File

@@ -668,44 +668,212 @@ static void handle_root_flags(session_t *ps) {
}
static struct managed_win *
paint_preprocess(session_t *ps, bool *fade_running, bool *animation) {
paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered
// means if fade is still ongoing after the current frame is rendered.
// Same goes for `animation_running`.
struct managed_win *bottom = NULL;
*fade_running = false;
*animation = false;
*animation_running = false;
auto now = get_time_ms();
// Fading step calculation
long long steps = 0L;
auto now = get_time_ms();
if (ps->fade_time) {
assert(now >= ps->fade_time);
steps = (now - ps->fade_time) / ps->o.fade_delta;
} else {
// Reset fade_time if unset
ps->fade_time = get_time_ms();
ps->fade_time = now;
steps = 0L;
}
ps->fade_time += steps * ps->o.fade_delta;
// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it.
if (ps->o.animations && !ps->animation_time)
ps->animation_time = now;
double delta_secs = (double)(now - ps->animation_time) / 1000;
// First, let's process fading
win_stack_foreach_managed_safe(w, &ps->window_stack) {
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
const double opacity_old = w->opacity;
// IMPORTANT: These window animation steps must happen before any other
// [pre]processing. This is because it changes the window's geometry.
if (ps->o.animations &&
!isnan(w->animation_progress) && w->animation_progress != 1.0 &&
ps->o.wintype_option[w->window_type].animation != 0 &&
win_is_mapped_in_x(w))
{
double neg_displacement_x =
w->animation_dest_center_x - w->animation_center_x;
double neg_displacement_y =
w->animation_dest_center_y - w->animation_center_y;
double neg_displacement_w = w->animation_dest_w - w->animation_w;
double neg_displacement_h = w->animation_dest_h - w->animation_h;
double animation_stiffness = ps->o.animation_stiffness;
if (!(w->animation_is_tag & ANIM_IN_TAG)) {
if (w->animation_is_tag & ANIM_SLOW)
animation_stiffness = ps->o.animation_stiffness_tag_change;
else if (w->animation_is_tag & ANIM_FAST)
animation_stiffness = ps->o.animation_stiffness_tag_change * 1.5;
}
if (w->state == WSTATE_FADING && !(w->animation_is_tag & ANIM_FADE))
w->opacity_target = win_calc_opacity_target(ps, w);
double acceleration_x =
(animation_stiffness * neg_displacement_x -
ps->o.animation_dampening * w->animation_velocity_x) /
ps->o.animation_window_mass;
double acceleration_y =
(animation_stiffness * neg_displacement_y -
ps->o.animation_dampening * w->animation_velocity_y) /
ps->o.animation_window_mass;
double acceleration_w =
(animation_stiffness * neg_displacement_w -
ps->o.animation_dampening * w->animation_velocity_w) /
ps->o.animation_window_mass;
double acceleration_h =
(animation_stiffness * neg_displacement_h -
ps->o.animation_dampening * w->animation_velocity_h) /
ps->o.animation_window_mass;
w->animation_velocity_x += acceleration_x * delta_secs;
w->animation_velocity_y += acceleration_y * delta_secs;
w->animation_velocity_w += acceleration_w * delta_secs;
w->animation_velocity_h += acceleration_h * delta_secs;
// Animate window geometry
double new_animation_x =
w->animation_center_x + w->animation_velocity_x * delta_secs;
double new_animation_y =
w->animation_center_y + w->animation_velocity_y * delta_secs;
double new_animation_w =
w->animation_w + w->animation_velocity_w * delta_secs;
double new_animation_h =
w->animation_h + w->animation_velocity_h * delta_secs;
// Negative new width/height causes segfault and it can happen
// when clamping disabled and shading a window
if (new_animation_h < 0)
new_animation_h = 0;
if (new_animation_w < 0)
new_animation_w = 0;
if (ps->o.animation_clamping) {
w->animation_center_x = clamp(
new_animation_x,
min2(w->animation_center_x, w->animation_dest_center_x),
max2(w->animation_center_x, w->animation_dest_center_x));
w->animation_center_y = clamp(
new_animation_y,
min2(w->animation_center_y, w->animation_dest_center_y),
max2(w->animation_center_y, w->animation_dest_center_y));
w->animation_w =
clamp(new_animation_w,
min2(w->animation_w, w->animation_dest_w),
max2(w->animation_w, w->animation_dest_w));
w->animation_h =
clamp(new_animation_h,
min2(w->animation_h, w->animation_dest_h),
max2(w->animation_h, w->animation_dest_h));
} else {
w->animation_center_x = new_animation_x;
w->animation_center_y = new_animation_y;
w->animation_w = new_animation_w;
w->animation_h = new_animation_h;
}
// Now we are done doing the math; we just need to submit our
// changes (if there are any).
struct win_geometry old_g = w->g;
double old_animation_progress = w->animation_progress;
new_animation_x = round(w->animation_center_x - w->animation_w * 0.5);
new_animation_y = round(w->animation_center_y - w->animation_h * 0.5);
new_animation_w = round(w->animation_w);
new_animation_h = round(w->animation_h);
bool position_changed =
new_animation_x != old_g.x || new_animation_y != old_g.y;
bool size_changed =
new_animation_w != old_g.width || new_animation_h != old_g.height;
bool geometry_changed = position_changed || size_changed;
// Mark past window region with damage
if (was_painted && geometry_changed)
add_damage_from_win(ps, w);
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_progress =
1.0 - w->animation_inv_og_distance *
sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
// When clamping disabled we don't want the overlayed image to
// fade in again because process is moving to negative value
if (w->animation_progress < old_animation_progress)
w->animation_progress = old_animation_progress;
w->g.x = (int16_t)new_animation_x;
w->g.y = (int16_t)new_animation_y;
w->g.width = (uint16_t)new_animation_w;
w->g.height = (uint16_t)new_animation_h;
if (w->animation_is_tag > ANIM_IN_TAG && (((w->animation_is_tag & ANIM_FADE) && w->opacity_target == w->opacity) || ((w->g.width == 0 || w->g.height == 0) && (w->animation_dest_w == 0 || w->animation_dest_h == 0)))) {
w->g.x = w->pending_g.x;
w->g.y = w->pending_g.y;
if (ps->o.animation_for_next_tag < OPEN_WINDOW_ANIMATION_ZOOM) {
w->g.width = w->pending_g.width;
w->g.height = w->pending_g.height;
} else {
w->g.width = 0;
w->g.height = 0;
}
}
// Submit window size change
if (size_changed) {
win_on_win_size_change(ps, w);
pixman_region32_clear(&w->bounding_shape);
pixman_region32_fini(&w->bounding_shape);
pixman_region32_init_rect(&w->bounding_shape, 0, 0,
(uint)w->widthb, (uint)w->heightb);
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
win_process_image_flags(ps, w);
}
// Mark new window region with damage
if (was_painted && geometry_changed) {
add_damage_from_win(ps, w);
w->reg_ignore_valid = false;
}
// We can't check for 1 here as sometimes 1 = 0.999999999999999
// in case of floating numbers
if (w->animation_progress >= 0.999999999) {
w->animation_progress = 1;
w->animation_velocity_x = 0.0;
w->animation_velocity_y = 0.0;
w->animation_velocity_w = 0.0;
w->animation_velocity_h = 0.0;
w->opacity = win_calc_opacity_target(ps, w);
}
*animation_running = true;
}
if (win_should_dim(ps, w) != w->dim) {
w->dim = win_should_dim(ps, w);
add_damage_from_win(ps, w);
}
if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
add_damage_from_win(ps, w);
*animation = true;
}
if (w->opacity != w->opacity_target) {
// Run fading
if (run_fade(ps, &w, steps)) {
*fade_running = true;
@@ -739,6 +907,10 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) {
w->reg_ignore_valid = false;
}
}
}
if (animation_running)
ps->animation_time = now;
// Opacity will not change, from now on.
rc_region_t *last_reg_ignore = rc_region_new();
@@ -1406,6 +1578,11 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_
queue_redraw(ps);
}
static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
session_t *ps = session_ptr(w, animation_timer);
queue_redraw(ps);
}
static void handle_pending_updates(EV_P_ struct session *ps) {
if (ps->pending_updates) {
log_debug("Delayed handling of events, entering critical section");
@@ -1490,9 +1667,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
* screen is not redirected. its sole purpose should be to decide whether the
* screen should be redirected. */
bool fade_running = false;
bool animation = false;
bool animation_running = false;
bool was_redirected = ps->redirected;
auto bottom = paint_preprocess(ps, &fade_running, &animation);
auto bottom = paint_preprocess(ps, &fade_running, &animation_running);
ps->tmout_unredir_hit = false;
if (!was_redirected && ps->redirected) {
@@ -1514,6 +1691,13 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0);
ev_timer_start(EV_A_ & ps->fade_timer);
}
// Start/stop animation timer depends on whether windows are animating
if (!animation_running && ev_is_active(&ps->animation_timer)) {
ev_timer_stop(EV_A_ & ps->animation_timer);
} else if (animation_running && !ev_is_active(&ps->animation_timer)) {
ev_timer_set(&ps->animation_timer, 0, 0);
ev_timer_start(EV_A_ & ps->animation_timer);
}
// If the screen is unredirected, free all_damage to stop painting
if (ps->redirected && ps->o.stoppaint_force != ON) {
@@ -1537,11 +1721,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
if (!fade_running) {
ps->fade_time = 0L;
}
if (!animation_running) {
ps->animation_time = 0L;
}
// TODO(yshui) Investigate how big the X critical section needs to be. There are
// suggestions that rendering should be in the critical section as well.
ps->redraw_needed = animation;
ps->redraw_needed = false;
}
static void draw_callback(EV_P_ ev_idle *w, int revents) {
@@ -1549,9 +1736,8 @@ static void draw_callback(EV_P_ ev_idle *w, int revents) {
draw_callback_impl(EV_A_ ps, revents);
// Don't do painting non-stop unless we are in benchmark mode, or if
// draw_callback_impl thinks we should continue painting.
if (!ps->o.benchmark && !ps->redraw_needed) {
// Don't do painting non-stop unless we are in benchmark mode
if (!ps->o.benchmark) {
ev_idle_stop(EV_A_ & ps->draw_idle);
}
}
@@ -1677,6 +1863,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
.redirected = false,
.alpha_picts = NULL,
.fade_time = 0L,
.animation_time = 0L,
.ignore_head = NULL,
.ignore_tail = NULL,
.quit = false,
@@ -1925,7 +2112,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
c2_list_postprocess(ps, ps->o.window_shader_fg_rules) &&
c2_list_postprocess(ps, ps->o.opacity_rules) &&
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
c2_list_postprocess(ps, ps->o.focus_blacklist))) {
c2_list_postprocess(ps, ps->o.focus_blacklist) &&
c2_list_postprocess(ps, ps->o.animation_blacklist))) {
log_error("Post-processing of conditionals failed, some of your rules "
"might not work");
}
@@ -2154,6 +2342,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ev_idle_init(&ps->draw_idle, draw_callback);
ev_init(&ps->fade_timer, fade_timer_callback);
ev_init(&ps->animation_timer, animation_timer_callback);
// Set up SIGUSR1 signal handler to reset program
ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1);
@@ -2244,6 +2433,16 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
write_pid(ps);
// When picom starts, fetch monitor positions first. Because it won't fetch the data unless property changes.
if (!ps->selmon_center_x && !ps->selmon_center_y) {
winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32);
if (prop.nitems == 2) {
ps->selmon_center_x = prop.p32[0];
ps->selmon_center_y = prop.p32[1];
}
free_winprop(&prop);
}
if (fork && stderr_logger) {
// Remove the stderr logger if we will fork
log_remove_target_tls(stderr_logger);
@@ -2443,6 +2642,7 @@ static void session_destroy(session_t *ps) {
// Stop libev event handlers
ev_timer_stop(ps->loop, &ps->unredir_timer);
ev_timer_stop(ps->loop, &ps->fade_timer);
ev_timer_stop(ps->loop, &ps->animation_timer);
ev_idle_stop(ps->loop, &ps->draw_idle);
ev_prepare_stop(ps->loop, &ps->event_check);
ev_signal_stop(ps->loop, &ps->usr1_signal);

View File

@@ -20,6 +20,7 @@
#include "types.h"
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define CLEAR_MASK(x) x = 0;
#ifdef __FAST_MATH__
#warning Use of -ffast-math can cause rendering error or artifacts, \
@@ -132,6 +133,19 @@ static inline int attr_const normalize_i_range(int i, int min, int max) {
return i;
}
/**
* Linearly interpolate from a range into another.
*
* @param a,b first range
* @param c,d second range
* @param value value to interpolate, should be in range [a,b]
* @return interpolated value in range [c,d]
*/
static inline int attr_const lerp_range(int a, int b, int c, int d, int value) {
ASSERT_IN_RANGE(value, a, b);
return (d-c)*(value-a)/(b-a) + c;
}
#define min2(a, b) ((a) > (b) ? (b) : (a))
#define max2(a, b) ((a) > (b) ? (a) : (b))
#define min3(a, b, c) min2(a, min2(b, c))

318
src/win.c
View File

@@ -303,6 +303,13 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) {
w->flags |= WIN_FLAGS_PIXMAP_NONE;
}
}
static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) {
log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name);
if (w->old_win_image) {
base->ops->release_image(base, w->old_win_image);
w->old_win_image = NULL;
}
}
static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name);
assert(w->shadow_image);
@@ -394,6 +401,7 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
win_release_pixmap(backend, w);
win_release_oldpixmap(backend, w);
}
if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) {
@@ -461,6 +469,170 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_clear_all_properties_stale(w);
}
static void init_animation(session_t *ps, struct managed_win *w) {
CLEAR_MASK(w->animation_is_tag)
static double *anim_x, *anim_y, *anim_w, *anim_h;
enum open_window_animation animation = ps->o.animation_for_open_window;
if (w->window_type != WINTYPE_TOOLTIP &&
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
animation = ps->o.animation_for_transient_window;
}
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
anim_w = &w->animation_w, anim_h = &w->animation_h;
if (w->dwm_mask & ANIM_PREV_TAG) {
animation = ps->o.animation_for_prev_tag;
if (ps->o.enable_fading_prev_tag) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->state = WSTATE_FADING;
w->animation_is_tag |= ANIM_FADE;
}
if (ps->o.animation_for_prev_tag >= OPEN_WINDOW_ANIMATION_ZOOM) {
w->animation_is_tag |= ANIM_FAST;
w->dwm_mask |= ANIM_SPECIAL_MINIMIZE;
goto revert;
}
w->animation_is_tag |= ANIM_SLOW;
} else if (w->dwm_mask & ANIM_NEXT_TAG) {
animation = ps->o.animation_for_next_tag;
w->animation_is_tag |= animation >= OPEN_WINDOW_ANIMATION_ZOOM ? ANIM_FAST : ANIM_SLOW;
if (ps->o.enable_fading_next_tag) {
w->opacity = 0.0;
w->state = WSTATE_FADING;
}
} else if (w->dwm_mask & ANIM_UNMAP) {
animation = ps->o.animation_for_unmap_window;
revert:
anim_x = &w->animation_dest_center_x, anim_y = &w->animation_dest_center_y;
anim_w = &w->animation_dest_w, anim_h = &w->animation_dest_h;
}
switch (animation) {
case OPEN_WINDOW_ANIMATION_NONE: { // No animation
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5;
w->animation_w = w->pending_g.width;
w->animation_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-in from a random point outside the screen
// Compute random point off screen
double angle = 2 * M_PI * ((double)rand() / RAND_MAX);
const double radius =
sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height);
// Set animation
*anim_x = ps->selmon_center_x + radius * cos(angle);
*anim_y = ps->selmon_center_y + radius * sin(angle);
*anim_w = 0;
*anim_h = 0;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location
*anim_x = w->pending_g.x;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_IN: {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER: {
*anim_x = ps->selmon_center_x;
*anim_y = w->g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_OUT: {
w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER: {
w->animation_dest_center_x = ps->selmon_center_x;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
}
case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-in the image, without changing its location
if (w->dwm_mask & ANIM_SPECIAL_MINIMIZE) {
*anim_x = w->g.x + w->g.width * 0.5;
*anim_y = w->g.y + w->g.height * 0.5;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
}
*anim_w = 0;
*anim_h = 0;
break;
}
case OPEN_WINDOW_ANIMATION_MINIMIZE: {
*anim_x = ps->selmon_center_x;
*anim_y = ps->selmon_center_y;
*anim_w = 0;
*anim_h = 0;
break;
}
case OPEN_WINDOW_ANIMATION_SQUEEZE: {
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_h = 0;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = 0;
}
break;
}
case OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM: {
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_y = w->g.y + w->g.height;
*anim_h = 0;
} else {
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height;
w->animation_w = w->pending_g.width;
*anim_h = 0;
*anim_y = w->pending_g.y + w->pending_g.height;
}
break;
}
case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break;
}
}
/// Handle non-image flags. This phase might set IMAGES_STALE flags
void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Whether the window was visible before we process the mapped flag. i.e.
@@ -501,8 +673,78 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
add_damage_from_win(ps, w);
}
// Update window geometry
// Determine if a window should animate
if (win_should_animate(ps, w)) {
if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
w->dwm_mask = ANIM_PREV_TAG;
else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
w->dwm_mask = ANIM_NEXT_TAG;
if (!was_visible || w->dwm_mask || (w->window_type == WINTYPE_NOTIFICATION && w->state == WSTATE_MAPPING)) {
// Set window-open animation
init_animation(ps, w);
if (!(w->dwm_mask & ANIM_SPECIAL_MINIMIZE)) {
w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
w->g.x = (int16_t)round(w->animation_center_x -
w->animation_w * 0.5);
w->g.y = (int16_t)round(w->animation_center_y -
w->animation_h * 0.5);
w->g.width = (uint16_t)round(w->animation_w);
w->g.height = (uint16_t)round(w->animation_h);
}
} else {
w->animation_is_tag = ANIM_IN_TAG;
w->animation_dest_center_x =
w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y =
w->pending_g.y + w->pending_g.height * 0.5;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
}
CLEAR_MASK(w->dwm_mask)
w->g.border_width = w->pending_g.border_width;
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_inv_og_distance =
1.0 / sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
if (isinf(w->animation_inv_og_distance))
w->animation_inv_og_distance = 0;
// We only grab images if w->reg_ignore_valid is true as
// there's an ev_shape_notify() event fired quickly on new windows
// for e.g. in case of Firefox main menu and ev_shape_notify()
// sets the win_set_flags(w, WIN_FLAGS_SIZE_STALE); which
// brakes the new image captured and because this same event
// also sets w->reg_ignore_valid = false; too we check for it
if (w->reg_ignore_valid) {
if (w->old_win_image) {
ps->backend_data->ops->release_image(ps->backend_data,
w->old_win_image);
w->old_win_image = NULL;
}
// We only grab
if (w->win_image) {
w->old_win_image = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &w->bounding_shape);
}
}
w->animation_progress = 0.0;
} else {
w->g = w->pending_g;
}
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
win_on_win_size_change(ps, w);
@@ -814,6 +1056,10 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) {
return 0;
}
if ((w->state == WSTATE_FADING && (w->animation_is_tag & ANIM_FADE))) {
return 0;
}
// Try obeying opacity property and window type opacity firstly
if (w->has_opacity_prop) {
opacity = ((double)w->opacity_prop) / OPAQUE;
@@ -878,6 +1124,24 @@ bool win_should_fade(session_t *ps, const struct managed_win *w) {
return ps->o.wintype_option[w->window_type].fade;
}
/**
* Determine if a window should animate.
*/
bool win_should_animate(session_t *ps, const struct managed_win *w) {
if (!ps->o.animations) {
return false;
}
if (ps->o.wintype_option[w->window_type].animation == 0) {
log_debug("Animation disabled by window_type");
return false;
}
if (c2_match(ps, w, ps->o.animation_blacklist, NULL)) {
log_debug("Animation disabled by animation_exclude");
return false;
}
return true;
}
/**
* Reread _COMPTON_SHADOW property from a window.
*
@@ -1514,9 +1778,14 @@ struct win *fill_win(session_t *ps, struct win *w) {
.state = WSTATE_UNMAPPED, // updated by window state changes
.in_openclose = true, // set to false after first map is done,
// true here because window is just created
.animation_velocity_x = 0.0, // updated by window geometry changes
.animation_velocity_y = 0.0, // updated by window geometry changes
.animation_velocity_w = 0.0, // updated by window geometry changes
.animation_velocity_h = 0.0, // updated by window geometry changes
.animation_progress = 1.0, // updated by window geometry changes
.animation_inv_og_distance = NAN, // updated by window geometry changes
.reg_ignore_valid = false, // set to true when damaged
.flags = WIN_FLAGS_IMAGES_NONE, // updated by
// property/attributes/etc
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
// change
.stale_props = NULL,
.stale_props_capacity = 0,
@@ -1543,6 +1812,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
// have no meaning or have no use until the window
// is mapped
.win_image = NULL,
.old_win_image = NULL,
.shadow_image = NULL,
.mask_image = NULL,
.prev_trans = NULL,
@@ -2100,17 +2370,31 @@ static void unmap_win_finish(session_t *ps, struct managed_win *w) {
// Shadow image can be preserved.
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
win_release_pixmap(ps->backend_data, w);
win_release_oldpixmap(ps->backend_data, w);
}
} else {
assert(!w->win_image);
assert(!w->old_win_image);
assert(!w->shadow_image);
}
// Force animation to completed position
w->animation_velocity_x = 0;
w->animation_velocity_y = 0;
w->animation_velocity_w = 0;
w->animation_velocity_h = 0;
w->animation_progress = 1.0;
free_paint(ps, &w->paint);
free_paint(ps, &w->shadow_paint);
// Try again at binding images when the window is mapped next time
win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR);
// Flag window so that it gets animated when it reapears
// in case it wasn't destroyed
win_set_flags(w, WIN_FLAGS_POSITION_STALE);
win_set_flags(w, WIN_FLAGS_SIZE_STALE);
}
/// Finish the destruction of a window (e.g. after fading has finished).
@@ -2378,6 +2662,30 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->opacity_target = win_calc_opacity_target(ps, w);
if (ps->o.animations && ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && ps->o.wintype_option[w->window_type].animation) {
w->dwm_mask = ANIM_UNMAP;
init_animation(ps, w);
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_inv_og_distance =
1.0 / sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
if (isinf(w->animation_inv_og_distance))
w->animation_inv_og_distance = 0;
w->animation_progress = 0.0;
if (w->old_win_image) {
ps->backend_data->ops->release_image(ps->backend_data,
w->old_win_image);
w->old_win_image = NULL;
}
}
#ifdef CONFIG_DBUS
// Send D-Bus signal
if (ps->o.dbus) {
@@ -2423,7 +2731,7 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
///
/// @return whether the window is destroyed and freed
bool win_skip_fading(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
if ((w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED)) {
assert(w->opacity_target == w->opacity);
return false;
}
@@ -2855,5 +3163,5 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *i) {
/// Return whether this window is mapped on the X server side
bool win_is_mapped_in_x(const struct managed_win *w) {
return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING ||
w->state == WSTATE_MAPPED || (w->flags & WIN_FLAGS_MAPPED);
w->state == WSTATE_MAPPED || w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED);
}

View File

@@ -100,11 +100,25 @@ struct win_geometry {
uint16_t border_width;
};
enum {
// dwm_mask
ANIM_PREV_TAG = 1,
ANIM_NEXT_TAG = (1 << 1),
ANIM_UNMAP = (1 << 2),
ANIM_SPECIAL_MINIMIZE = (1 << 3),
// animation_is_tag
ANIM_IN_TAG = 1,
ANIM_SLOW = (1 << 1),
ANIM_FAST = (1 << 2),
ANIM_FADE = (1 << 3),
};
struct managed_win {
struct win base;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
void *win_image;
void *old_win_image; // Old window image for interpolating window contents during animations
void *shadow_image;
void *mask_image;
/// Pointer to the next higher window to paint.
@@ -156,6 +170,8 @@ struct managed_win {
/// opacity state, window geometry, window mapped/unmapped state,
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
/// NULL means reg_ignore has not been calculated for this window.
/// 1 = tag prev , 2 = tag next, 4 = unmap
uint32_t dwm_mask;
rc_region_t *reg_ignore;
/// Whether the reg_ignore of all windows beneath this window are valid
bool reg_ignore_valid;
@@ -173,6 +189,24 @@ struct managed_win {
bool unredir_if_possible_excluded;
/// Whether this window is in open/close state.
bool in_openclose;
/// Current position and destination, for animation
double animation_center_x, animation_center_y;
double animation_dest_center_x, animation_dest_center_y;
double animation_w, animation_h;
double animation_dest_w, animation_dest_h;
/// Spring animation velocity
double animation_velocity_x, animation_velocity_y;
double animation_velocity_w, animation_velocity_h;
/// Track animation progress; goes from 0 to 1
double animation_progress;
/// Inverse of the window distance at the start of animation, for
/// tracking animation progress
double animation_inv_og_distance;
/// Animation info if it is a tag change & check if its changing window sizes
/// 0: no tag change
/// 1: normal tag change animation
/// 2: tag change animation that effects window size
uint16_t animation_is_tag;
// Client window related members
/// ID of the top-level client window of the window.
@@ -461,6 +495,9 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags);
/// Mark properties as stale for a window
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
/// Determine if a window should animate
bool attr_pure win_should_animate(session_t *ps, const struct managed_win *w);
static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}