From b37989b9bad76a4cd0ae28ac5812705f5cdf8a50 Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Tue, 4 Oct 2022 00:24:05 +0300 Subject: [PATCH] picom upto date sync with yshui, full anim support small changes small changes picom upto date sync with yshui, full anim support small changes small changes rounded corner delay fix fixed open window type anim, closes #12 AUR pkg, closes #15, thanks fxzzi fix lerping on shadows, closes #4 picom upto date sync with yshui, full anim support animations added, picom upstreamed yshui/next shadow fix shadow fix randr extension randr anim fix randr_mon position fix pos fix multi monitor desktop switch fixed fix for vertical stacked monitors closes #26 revert opacity rule index on next: 5a8c61da revert opacity rule Add running section to README.md Properly animates depending on wintype animation property in config fixed window unmap crash fixes animations on destroying/unmapping windows xrender fix --- .gitignore | 1 + README.md | 11 ++ picom.sample.conf | 182 +++++++++--------- src/atom.h | 3 +- src/backend/backend.c | 148 ++++++++++----- src/backend/backend.h | 7 +- src/backend/dummy/dummy.c | 2 +- src/backend/gl/gl_common.c | 13 +- src/backend/gl/gl_common.h | 2 +- src/backend/xrender/xrender.c | 3 +- src/common.h | 6 + src/config.c | 53 +++++- src/config.h | 54 ++++++ src/config_libconfig.c | 73 +++++++ src/options.c | 80 +++++++- src/picom.c | 282 +++++++++++++++++++++++---- src/utils.h | 14 ++ src/win.c | 348 ++++++++++++++++++++++++++++++++-- src/win.h | 36 ++++ 19 files changed, 1111 insertions(+), 207 deletions(-) diff --git a/.gitignore b/.gitignore index a8330cb8cb..0cac7892bf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ compton build/ compile_commands.json build.ninja +make.sh # language servers .ccls-cache diff --git a/README.md b/README.md index 28790eed45..1ba8ffd282 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,23 @@ $ ninja -C build ### To install +#### AUR (arch) +- picom-ftlabs-git +Thanks to @Fxzzi for maintaining the package. + + ``` bash $ ninja -C build install ``` Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix= build` +## Running +To launch with all animations as a background process you can use: +`picom --animations -b` + +To only have specific animations, enable them with cli flags (see `picom --help`) or add them to your picom config. + ## How to Contribute All contributions are welcome! diff --git a/picom.sample.conf b/picom.sample.conf index abe8284047..3bb5f8c801 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -1,29 +1,76 @@ ################################# -# Shadows # +# 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 @@ -69,19 +116,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 = [] @@ -100,15 +146,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 @@ -118,21 +162,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 # ################################# @@ -140,52 +176,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. @@ -200,17 +205,13 @@ blur-background-exclude = [ # 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`, `egl` or `xr_glx_hybrid`. # `xrender` is the default one. # -# backend = "glx" -backend = "xrender"; +backend = "glx" # Use higher precision during rendering, and apply dither when presenting the # rendered screen. Reduces banding artifacts, but might cause performance @@ -218,8 +219,7 @@ backend = "xrender"; dithered-present = false; # Enable/disable VSync. -# vsync = false -vsync = true; +# vsync = true # Try to detect WM windows (a non-override-redirect window with no # child that has 'WM_STATE') and mark them as active. @@ -235,25 +235,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 @@ -297,7 +297,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, @@ -318,24 +318,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. @@ -352,7 +346,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 @@ -425,3 +419,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", +]; diff --git a/src/atom.h b/src/atom.h index a24dcd4425..161388035e 100644 --- a/src/atom.h +++ b/src/atom.h @@ -25,7 +25,8 @@ _NET_WM_WINDOW_TYPE, \ _XROOTPMAP_ID, \ ESETROOT_PMAP_ID, \ - _XSETROOT_ID + _XSETROOT_ID \ + _NET_WM_WINDOW_TYPE, #define ATOM_LIST2 \ _NET_WM_WINDOW_TYPE_DESKTOP, \ diff --git a/src/backend/backend.c b/src/backend/backend.c index 697c273a6f..750bf71b13 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -56,6 +56,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 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; + region_t reg_bound_local; + { + // The bounding shape, in window local coordinates + pixman_region32_init(®_bound_local); + pixman_region32_copy(®_bound_local, reg_bound); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + + pixman_region32_init(®_visible_local); + pixman_region32_intersect(®_visible_local, reg_visible, reg_paint); + pixman_region32_translate(®_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(®_visible_local, ®_visible_local, + ®_bound_local); + } + + auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image, + ®_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, + ®_frame, ®_visible_local, + (double[]){w->frame_opacity}); + pixman_region32_fini(®_frame); + ps->backend_data->ops->compose(ps->backend_data, new_img, + window_coord, NULL, dest_coord, + reg_paint_in_bound, reg_visible, true); + ps->backend_data->ops->release_image(ps->backend_data, new_img); + pixman_region32_fini(®_visible_local); + pixman_region32_fini(®_bound_local); +} + void handle_device_reset(session_t *ps) { log_error("Device reset detected"); // Wait for reset to complete @@ -210,8 +262,8 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { if (ps->root_image) { ps->backend_data->ops->compose(ps->backend_data, ps->root_image, - (coord_t){0}, NULL, (coord_t){0}, - ®_paint, ®_visible); + (coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height}, + ®_paint, ®_visible, true); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, ®_paint); @@ -262,6 +314,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { * 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 || @@ -399,7 +452,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { } ps->backend_data->ops->compose( ps->backend_data, w->shadow_image, shadow_coord, - inverted_mask, window_coord, ®_shadow, ®_visible); + inverted_mask, window_coord, ®_shadow, ®_visible, false); if (inverted_mask) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_INVERTED, @@ -443,6 +496,17 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { 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( @@ -465,53 +529,43 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { } // 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, - ®_paint_in_bound, ®_visible); + window_coord, NULL, dest_coord, + ®_paint_in_bound, ®_visible, true); } else { - // For window image processing, we don't have to limit the process - // region to damage for correctness. (see 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 - region_t reg_visible_local; - region_t reg_bound_local; - { - // The bounding shape, in window local coordinates - pixman_region32_init(®_bound_local); - pixman_region32_copy(®_bound_local, ®_bound); - pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); - - pixman_region32_init(®_visible_local); - pixman_region32_intersect(®_visible_local, - ®_visible, ®_paint); - pixman_region32_translate(®_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( - ®_visible_local, ®_visible_local, ®_bound_local); + 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); + + bool resizing = + w->g.width != w->pending_g.width || + w->g.height != w->pending_g.height; + + // 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, + ®_bound, ®_visible, + ®_paint, ®_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, + ®_bound, ®_visible, + ®_paint, ®_paint_in_bound); + } + } else { + process_window_for_painting( + ps, w, w->win_image, 1.0, ®_bound, ®_visible, + ®_paint, ®_paint_in_bound); } - - auto new_img = ps->backend_data->ops->clone_image( - ps->backend_data, w->win_image, ®_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, ®_frame, - ®_visible_local, (double[]){w->frame_opacity}); - pixman_region32_fini(®_frame); - ps->backend_data->ops->compose(ps->backend_data, new_img, - window_coord, NULL, window_coord, - ®_paint_in_bound, ®_visible); - ps->backend_data->ops->release_image(ps->backend_data, new_img); - pixman_region32_fini(®_visible_local); - pixman_region32_fini(®_bound_local); } skip: pixman_region32_fini(®_bound); diff --git a/src/backend/backend.h b/src/backend/backend.h index eb3d5997b0..c84fe2cdd7 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -180,7 +180,12 @@ struct backend_operations { */ void (*compose)(backend_t *backend_data, image_handle image, coord_t image_dst, image_handle mask, coord_t mask_dst, const region_t *reg_paint, - const region_t *reg_visible) attr_nonnull(1, 2, 6, 7); + const region_t *reg_visible, bool lerp) attr_nonnull(1, 2, 6, 7); + + 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); diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index bab7c931e4..92fcdb525c 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -68,7 +68,7 @@ static void dummy_check_image(struct backend_base *base, image_handle image) { void dummy_compose(struct backend_base *base, image_handle image, coord_t dst attr_unused, image_handle mask attr_unused, coord_t mask_dst attr_unused, const region_t *reg_paint attr_unused, - const region_t *reg_visible attr_unused) { + const region_t *reg_visible attr_unused, bool lerp attr_unused) { auto dummy attr_unused = (struct dummy_data *)base; dummy_check_image(base, image); assert(mask == NULL || (struct backend_image *)mask == &dummy->mask); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 9661622ecd..a6204dd72a 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -420,7 +420,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); @@ -502,6 +502,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]; @@ -553,7 +554,7 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, // TODO(yshui) make use of reg_visible void gl_compose(backend_t *base, image_handle image, coord_t image_dst, image_handle mask_, coord_t mask_dst, const region_t *reg_tgt, - const region_t *reg_visible attr_unused) { + const region_t *reg_visible attr_unused, bool lerp) { auto gd = (struct gl_data *)base; auto img = (struct backend_image *)image; auto mask = (struct backend_image *)mask_; @@ -580,6 +581,14 @@ void gl_compose(backend_t *base, image_handle image, coord_t image_dst, 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); + + if (lerp) { + 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); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index c898a4a294..3f98377c27 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -151,7 +151,7 @@ bool gl_last_render_time(backend_t *backend_data, struct timespec *time); * @brief Render a region with texture data. */ void gl_compose(backend_t *, image_handle image, coord_t image_dst, image_handle mask, - coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible); + coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp); void gl_root_change(backend_t *base, session_t *); diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 9e053ab212..d56b6a8085 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -14,6 +14,7 @@ #include "backend/backend.h" #include "backend/backend_common.h" #include "common.h" +#include "compiler.h" #include "config.h" #include "kernel.h" #include "log.h" @@ -362,7 +363,7 @@ static void xrender_compose_impl(struct xrender_data *xd, struct xrender_image * static void xrender_compose(backend_t *base, image_handle image_, coord_t dst, image_handle mask_, - coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible) { + coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible, bool lerp attr_unused) { auto xd = (struct xrender_data *)base; auto image = (struct xrender_image *)image_; auto mask = (struct xrender_image *)mask_; diff --git a/src/common.h b/src/common.h index aff25e67ca..d0fd2541ae 100644 --- a/src/common.h +++ b/src/common.h @@ -143,6 +143,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_timer callback for drawing ev_timer draw_timer; /// Called every time we have timeouts or new data on socket, @@ -177,6 +179,8 @@ typedef struct session { int root_width; /// Height of root window. int root_height; + int selmon_center_x; + int selmon_center_y; /// X Composite overlay window. xcb_window_t overlay; /// The target window for debug mode @@ -266,6 +270,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 // Cached blur convolution kernels. struct x_convolution_kernel **blur_kerns_cache; /// If we should quit diff --git a/src/config.c b/src/config.c index 63b8fb0c98..fb60a4d070 100644 --- a/src/config.c +++ b/src/config.c @@ -796,6 +796,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; @@ -803,6 +807,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 @@ -848,6 +886,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, @@ -879,7 +929,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 diff --git a/src/config.h b/src/config.h index dd9baf5c00..d676bbeaae 100644 --- a/src/config.h +++ b/src/config.h @@ -40,6 +40,24 @@ 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_SQUEEZE, + OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM, + OPEN_WINDOW_ANIMATION_INVALID, +}; + typedef struct win_option_mask { bool shadow : 1; bool fade : 1; @@ -49,6 +67,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 +79,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 { @@ -197,6 +217,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. @@ -280,6 +327,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; @@ -300,6 +353,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. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index f719e50e6e..0d7a05a694 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -264,6 +264,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 = animation; + } double fval; if (config_setting_lookup_float(setting, "opacity", &fval)) { @@ -514,6 +523,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 diff --git a/src/options.c b/src/options.c index 1c5785b6d8..e4cd6e638c 100644 --- a/src/options.c +++ b/src/options.c @@ -187,6 +187,19 @@ static const struct picom_option picom_options[] = { "window is fullscreen based only on its size and coordinates."}, {"realtime" , no_argument , 804, NULL , "Enable realtime scheduling. This might reduce latency, but might also cause " "other issues. Disable this if you see the compositor being killed."}, + + {"animation-stiffness-in-tag" , required_argument, 805, NULL , "Stiffness (a.k.a. tension) parameter for animation (default: 200.0)."}, + {"animation-stiffness-tag-change" , required_argument, 806, NULL , "Stiffness (a.k.a. tension) parameter for animation (default: 200.0). ??"}, + {"animation-dampening" , required_argument, 807, NULL , "Dampening (a.k.a. friction) parameter for animation (default: 25.0)."}, + {"animation-window-mass" , required_argument, 808, NULL , "Mass parameter for animation (default: 1.0)."}, + {"animation-clamping" , no_argument , 809, NULL , "Whether to clamp animations (default: true)."}, + {"animation-for-open-window" , required_argument, 810, NULL , "Which animation to run when opening a window. Must be one of `none`, `fade`, " + "`zoom`, `slide-down`, `slide-up`, `slide-left`, `slide-right`. (default: none)."}, + {"animation-for-transient-window" , required_argument, 811, NULL , "Which animation to run when opening a transient window. Must be one of `none`, " + "`fly-in`, `zoom`, `slide-down`, `slide-up`, `slide-left`, `slide-right`. " + "(default: none)."}, + {"animation-exclude" , required_argument, 812, "COND" , "Exclude conditions for animation."}, + {"animations" , no_argument , 813, NULL , "Run animations for window geometry changes (movement and scaling)."}, }; // clang-format on @@ -376,6 +389,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // Parse command line arguments. Range checking will be done later. bool failed = false; + char *endptr = NULL; const char *deprecation_message attr_unused = "has been removed. If you encounter problems " "without this feature, please feel free to " @@ -584,7 +598,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(298, glx_no_rebind_pixmap); case 299: { // --glx-swap-method - char *endptr; long tmpval = strtol(optarg, &endptr, 10); bool should_remove = true; if (*endptr || !(*optarg)) { @@ -753,6 +766,68 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(802, debug_mode); P_CASEBOOL(803, no_ewmh_fullscreen); P_CASEBOOL(804, use_realtime_scheduling); + case 805: + // --animation-stiffness + opt->animation_stiffness = strtod(optarg, &endptr); + if (*endptr != '\0' || opt->animation_stiffness < 0) { + log_error("Invalid animation-stiffness value: %s", optarg); + failed = true; + } + break; + case 806: + // --animation-stiffness-for-tags + opt->animation_stiffness_tag_change = strtod(optarg, &endptr); + if (*endptr != '\0' || opt->animation_stiffness_tag_change < 0) { + log_error("Invalid animation-stiffness-for-tags value: %s", optarg); + failed = true; + } + break; + case 807: + // --animation-dampening + opt->animation_dampening = strtod(optarg, &endptr); + if (*endptr != '\0' || opt->animation_dampening < 0) { + log_error("Invalid animation-dampening value: %s", optarg); + failed = true; + } + break; + case 808: + // --animation-window-masss + opt->animation_window_mass = strtod(optarg, &endptr); + if (*endptr != '\0' || opt->animation_window_mass < 0) { + log_error("Invalid animation-window-mass value: %s", optarg); + failed = true; + } + 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; + } + P_CASEBOOL(813, animations); default: usage(argv[0], 1); break; #undef P_CASEBOOL } @@ -912,7 +987,8 @@ void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c c2_list_postprocess(state, c->c, option->opacity_rules) && c2_list_postprocess(state, c->c, option->rounded_corners_blacklist) && c2_list_postprocess(state, c->c, option->corner_radius_rules) && - c2_list_postprocess(state, c->c, option->focus_blacklist))) { + c2_list_postprocess(state, c->c, option->focus_blacklist) && + c2_list_postprocess(state, c->c, option->animation_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); } diff --git a/src/picom.c b/src/picom.c index aae5258b8e..7120abfa6c 100644 --- a/src/picom.c +++ b/src/picom.c @@ -906,80 +906,256 @@ static void handle_root_flags(session_t *ps) { * * @return whether the operation succeeded */ -static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation, +static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running, struct managed_win **out_bottom) { // 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; + auto now = get_time_ms(); *fade_running = false; - *animation = false; + *animation_running = false; *out_bottom = NULL; // 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); + + if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING) { + 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; - } - // Run fading - if (run_fade(ps, &w, steps)) { - *fade_running = true; - } + if (w->opacity != w->opacity_target) { + // Run fading + if (run_fade(ps, &w, steps)) { + *fade_running = 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->opacity != opacity_old) { - add_damage_from_win(ps, w); - } + // 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->opacity != opacity_old) { + add_damage_from_win(ps, w); + } - if (win_check_fade_finished(ps, w)) { - // the window has been destroyed because fading finished - continue; - } - if (win_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } + if (win_check_fade_finished(ps, w)) { + // the window has been destroyed because fading finished + continue; + } - // Update window mode - w->mode = win_calc_mode(w); + if (win_has_frame(w)) { + w->frame_opacity = ps->o.frame_opacity; + } else { + w->frame_opacity = 1.0; + } - // Destroy all reg_ignore above when frame opaque state changes on - // SOLID mode - if (was_painted && w->mode != mode_old) { - w->reg_ignore_valid = false; + // Update window mode + w->mode = win_calc_mode(w); + + // Destroy all reg_ignore above when frame opaque state changes on + // SOLID mode + if (was_painted && w->mode != mode_old) { + 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(); @@ -1722,6 +1898,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"); @@ -1827,10 +2008,10 @@ 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; struct managed_win *bottom = NULL; - if (!paint_preprocess(ps, &fade_running, &animation, &bottom)) { + if (!paint_preprocess(ps, &fade_running, &animation_running, &bottom)) { log_fatal("Pre-render preparation has failed, exiting..."); exit(1); } @@ -1856,6 +2037,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); + } int64_t after_preprocess_us; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1896,15 +2084,21 @@ 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; + } ps->render_queued = false; + 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. // Queue redraw if animation is running. This should be picked up by next present // event. - if (animation) { + if (animation_running) { queue_redraw(ps); } if (ps->vblank_scheduler) { @@ -2043,6 +2237,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, + .animation_time = 0L, .quit = false, .expose_rects = NULL, @@ -2454,7 +2649,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // Monitor screen changes if vsync_sw is enabled and we are using // an auto-detected refresh rate, or when X RandR features are enabled - if (ps->randr_exists && ps->o.crop_shadow_to_monitor) { + if (ps->randr_exists) { xcb_randr_select_input(ps->c.c, ps->c.screen_info->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); x_update_monitors(&ps->c, &ps->monitors); @@ -2483,6 +2678,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_init(&ps->draw_timer, 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); @@ -2727,6 +2924,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_timer_stop(ps->loop, &ps->draw_timer); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); diff --git a/src/utils.h b/src/utils.h index 4392738157..270a8a0c9e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -21,6 +21,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, \ @@ -167,6 +168,19 @@ static inline int attr_const attr_unused normalize_i_range(int i, int min, int m __auto_type __tmp = (val); \ __tmp > 0 ? __tmp : -__tmp; \ }) +/** + * 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)) diff --git a/src/win.c b/src/win.c index 146d2537d0..c4ebc11c99 100644 --- a/src/win.c +++ b/src/win.c @@ -324,6 +324,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); if (w->shadow_image) { @@ -416,6 +423,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); } win_release_shadow(backend, w); @@ -500,6 +508,173 @@ 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 int32_t randr_mon_center_x, randr_mon_center_y; + if (w->randr_monitor != -1) { + auto e = pixman_region32_extents(&ps->monitors.regions[w->randr_monitor]); + randr_mon_center_x = (e->x2 + e->x1) / 2, randr_mon_center_y = (e->y2 + e->y1) / 2; + } else { + randr_mon_center_x = ps->root_width / 2, randr_mon_center_y = ps->root_height / 2; + } + static double *anim_x, *anim_y, *anim_w, *anim_h; + enum open_window_animation 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; + } + + if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID + && !w->dwm_mask) { + animation = ps->o.wintype_option[w->window_type].animation; + } + + 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; + } + + double angle; + 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 + 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 = randr_mon_center_x + radius * cos(angle); + *anim_y = randr_mon_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 = randr_mon_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 = randr_mon_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 = randr_mon_center_x; + *anim_y = randr_mon_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. @@ -545,11 +720,79 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { add_damage_from_win(ps, w); } - // Update window geometry - w->g = w->pending_g; - // Whether a window is fullscreen changes based on its geometry - win_update_is_fullscreen(ps, w); + + // 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) { + + // 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(w, ps->o.shadow_offset_x, @@ -856,6 +1099,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; @@ -920,6 +1167,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. * @@ -1285,6 +1550,9 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { w->transparent_clipping_excluded = c2_match(ps->c2_state, w, ps->o.transparent_clipping_blacklist, NULL); + w->transparent_clipping_excluded = + c2_match(ps, w, ps->o.transparent_clipping_blacklist, NULL); + win_update_opacity_target(ps, w); w->reg_ignore_valid = false; @@ -1558,14 +1826,19 @@ struct win *fill_win(session_t *ps, struct win *w) { .blur_background = false, .reg_ignore = NULL, // The following ones are updated for other reasons - .pixmap_damaged = false, // updated by damage events - .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 - .reg_ignore_valid = false, // set to true when damaged - .flags = WIN_FLAGS_PIXMAP_NONE, // updated by - // property/attributes/etc - // change + .pixmap_damaged = false, // updated by damage events + .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_PIXMAP_NONE, // updated by property/attributes/etc + // change .stale_props = NULL, .stale_props_capacity = 0, @@ -1591,6 +1864,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, @@ -2119,17 +2393,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). @@ -2401,6 +2689,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) { @@ -2446,7 +2758,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; } @@ -2458,11 +2770,12 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) { // TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to // the x.c. void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) { - mw->randr_monitor = -1; for (int i = 0; i < monitors->count; i++) { auto e = pixman_region32_extents(&monitors->regions[i]); - if (e->x1 <= mw->g.x && e->y1 <= mw->g.y && - e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) { + if (((e->x1 <= mw->g.x || e->x1 <= mw->pending_g.x) && + (e->x2 >= mw->g.x + mw->widthb || e->x2 >= mw->pending_g.x + mw->widthb)) && + (e->y1 <= mw->g.y || e->y1 <= mw->pending_g.y) && + (e->y2 >= mw->g.y + mw->heightb || e->y2 >= mw->pending_g.y + mw->heightb)) { mw->randr_monitor = i; log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the " "monitor %d (%dx%d+%dx%d)", @@ -2471,6 +2784,7 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) { return; } } + mw->randr_monitor = -1; log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor", mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb); } @@ -2837,5 +3151,5 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *w) { /// 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); } diff --git a/src/win.h b/src/win.h index 024ab4f114..1c27c0e2a8 100644 --- a/src/win.h +++ b/src/win.h @@ -96,11 +96,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 image_handle win_image; + image_handle old_win_image; // Old window image for interpolating window contents during animations image_handle shadow_image; image_handle mask_image; /// Pointer to the next higher window to paint. @@ -152,6 +166,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; @@ -169,6 +185,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. @@ -479,6 +513,8 @@ void win_update_bounding_shape(struct x_connection *c, struct managed_win *w, bool shape_exists, bool detect_rounded_corners); bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms, struct managed_win *w); +/// 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);