diff --git a/.gitignore b/.gitignore index a8330cb8..0cac7892 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 28790eed..1ba8ffd2 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 abe82840..3bb5f8c8 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -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 @@ -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 a24dcd44..16138803 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 697c273a..750bf71b 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) + 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(®_bound_local); - pixman_region32_copy(®_bound_local, ®_bound); - pixman_region32_translate(®_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(®_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); + // 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 eb3d5997..c84fe2cd 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 bab7c931..92fcdb52 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 9661622e..a6204dd7 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 c898a4a2..3f98377c 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 9e053ab2..d56b6a80 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 aff25e67..d0fd2541 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 63b8fb0c..fb60a4d0 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 dd9baf5c..d676bbea 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 f719e50e..0d7a05a6 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 1c5785b6..e4cd6e63 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 aae5258b..7120abfa 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 43927381..270a8a0c 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 146d2537..c4ebc11c 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 024ab4f1..1c27c0e2 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);