diff --git a/.gitignore b/.gitignore index 20b39d0..7b28f94 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ compton build/ compile_commands.json build.ninja +make.sh # language servers .ccls-cache diff --git a/picom.sample.conf b/picom.sample.conf index 59255d0..950ad37 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 @@ -48,6 +95,9 @@ shadow-exclude = [ "class_g = 'Conky'", "class_g ?= 'Notify-osd'", "class_g = 'Cairo-clock'", + "class_g = 'dwm'", + "class_g = 'chromium'", + "class_g *?= 'slop'", "_GTK_FRAME_EXTENTS@:c" ]; @@ -72,19 +122,18 @@ shadow-exclude = [ # Fade windows in/out when opening/closing and when opacity changes, # unless no-fading-openclose is used. -# fading = false fading = true; # Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028) # fade-in-step = 0.028 -fade-in-step = 0.03; +fade-in-step = 0.023; # Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03) # fade-out-step = 0.03 -fade-out-step = 0.03; +fade-out-step = 0.035; # The time between steps in fade step, in milliseconds. (> 0, defaults to 10) -# fade-delta = 10 +fade-delta = 10 # Specify a list of conditions of windows that should not be faded. # fade-exclude = [] @@ -103,15 +152,13 @@ fade-out-step = 0.03; # Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0) # inactive-opacity = 1 -inactive-opacity = 0.8; # Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default) # frame-opacity = 1.0 -frame-opacity = 0.7; # Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows. # inactive-opacity-override = true -inactive-opacity-override = false; +inactive-opacity-override = true; # Default opacity for active windows. (0.0 - 1.0, defaults to 1.0) # active-opacity = 1.0 @@ -121,21 +168,13 @@ inactive-opacity-override = false; # Specify a list of conditions of windows that should never be considered focused. # focus-exclude = [] -focus-exclude = [ "class_g = 'Cairo-clock'" ]; +focus-exclude = [ +"class_g = 'Cairo-clock'" , +]; # Use fixed inactive dim value, instead of adjusting according to window opacity. # inactive-dim-fixed = 1.0 -# Specify a list of opacity rules, in the format `PERCENT:PATTERN`, -# like `50:name *= "Firefox"`. picom-trans is recommended over this. -# Note we don't make any guarantee about possible conflicts with other -# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows. -# example: -# opacity-rule = [ "80:class_g = 'URxvt'" ]; -# -# opacity-rule = [] - - ################################# # Corners # ################################# @@ -143,52 +182,21 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # Sets the radius of rounded window corners. When > 0, the compositor will # round the corners of windows. Does not interact well with # `transparent-clipping`. -corner-radius = 0 +corner-radius = 11; # Exclude conditions for rounded corners. -rounded-corners-exclude = [ - "window_type = 'dock'", - "window_type = 'desktop'" -]; +#rounded-corners-exclude = [ +# "window_type = 'dock'", +# "window_type = 'desktop'" +#]; - -################################# -# Background-Blurring # -################################# - - -# Parameters for background blurring, see the *BLUR* section for more information. -# blur-method = -# blur-size = 12 -# -# blur-deviation = false -# -# blur-strength = 5 - -# Blur background of semi-transparent / ARGB windows. -# Bad in performance, with driver-dependent behavior. -# The name of the switch may change without prior notifications. -# -# blur-background = false - -# Blur background of windows when the window frame is not opaque. -# Implies: -# blur-background -# Bad in performance, with driver-dependent behavior. The name may change. -# -# blur-background-frame = false - - -# Use fixed blur strength rather than adjusting according to window opacity. -# blur-background-fixed = false - - -# Specify the blur convolution kernel, with the following format: -# example: -# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"; -# -# blur-kern = "" -blur-kern = "3x3box"; +blur: { + method = "dual_kawase"; + strength = 9; + background = true; + background-frame = false; + background-fixed = false; +} # Exclude conditions for background blur. @@ -196,28 +204,28 @@ blur-kern = "3x3box"; blur-background-exclude = [ "window_type = 'dock'", "window_type = 'desktop'", - "_GTK_FRAME_EXTENTS@:c" + "_GTK_FRAME_EXTENTS@:c", + "class_g = 'Chromium'", + "class_g = 'Discord'", + "class_g = 'Dunst'", + "class_g = 'Peek'", + "class_g *?= 'slop'", ]; ################################# # General Settings # ################################# -# Enable remote control via D-Bus. See the man page for more details. -# dbus = true - # Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. # daemon = false # Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. # `xrender` is the default one. # -# backend = "glx" -backend = "xrender"; +backend = "glx" # Enable/disable VSync. -# vsync = false -vsync = true; +# vsync = true # Enable remote control via D-Bus. See the *D-BUS API* section below for more details. # dbus = false @@ -236,25 +244,25 @@ mark-ovredir-focused = true; # shaped windows. The accuracy is not very high, unfortunately. # # detect-rounded-corners = false -detect-rounded-corners = true; +detect-rounded-corners = false; # Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers # not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows. # # detect-client-opacity = false -detect-client-opacity = true; +detect-client-opacity = false; # Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window, # rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy, # provided that the WM supports it. # -# use-ewmh-active-win = false +use-ewmh-active-win = true; # Unredirect all windows if a full-screen opaque window is detected, # to maximize performance for full-screen windows. Known to cause flickering # when redirecting/unredirecting windows. # -# unredir-if-possible = false +unredir-if-possible = false; # Delay before unredirecting the window, in milliseconds. Defaults to 0. # unredir-if-possible-delay = 0 @@ -298,7 +306,7 @@ detect-transient = true; # practically happened) and may not work with blur-background. # My tests show a 15% performance boost. Recommended. # -# glx-no-stencil = false +glx-no-stencil = true; # GLX backend: Avoid rebinding pixmap on window damage. # Probably could improve performance on rapid window content changes, @@ -319,24 +327,18 @@ use-damage = true; # calls are finished before picom starts drawing. Needed on nvidia-drivers # with GLX backend for some users. # -# xrender-sync-fence = false +xrender-sync-fence = true; -# GLX backend: Use specified GLSL fragment shader for rendering window -# contents. Read the man page for a detailed explanation of the interface. +# GLX backend: Use specified GLSL fragment shader for rendering window contents. +# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` +# in the source tree for examples. # -# window-shader-fg = "default" - -# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar -# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg. -# -# window-shader-fg-rule = [ -# "my_shader.frag:window_type != 'dock'" -# ] +window-shader-fg = "default"; # Force all windows to be painted with blending. Useful if you # have a glx-fshader-win that could turn opaque pixels transparent. # -# force-win-blend = false +# force-win-blend = true; # Do not use EWMH to detect fullscreen windows. # Reverts to checking if a window is fullscreen based only on its size and coordinates. @@ -353,7 +355,7 @@ use-damage = true; # Make transparent windows clip other windows like non-transparent windows do, # instead of blending on top of them. # -# transparent-clipping = false +transparent-clipping = false; # Set the log level. Possible values are: # "trace", "debug", "info", "warn", "error" @@ -420,3 +422,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 6f4eae6..0a086e1 100644 --- a/src/atom.h +++ b/src/atom.h @@ -23,7 +23,8 @@ WM_CLIENT_MACHINE, \ _NET_ACTIVE_WINDOW, \ _COMPTON_SHADOW, \ - _NET_WM_WINDOW_TYPE + _NET_WM_WINDOW_TYPE, \ + _NET_CURRENT_MON_CENTER #define ATOM_LIST2 \ _NET_WM_WINDOW_TYPE_DESKTOP, \ diff --git a/src/backend/backend.c b/src/backend/backend.c index 032c301..6cfe7fb 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -55,6 +55,58 @@ region_t get_damage(session_t *ps, bool all_damage) { return region; } +static void process_window_for_painting(session_t *ps, struct managed_win *w, + void *win_image, double additional_alpha, + region_t *reg_bound, region_t *reg_visible, + region_t *reg_paint, region_t *reg_paint_in_bound) { + // For window image processing, we don't have to limit the process + // region to damage for correctness. (see for + // details) + + // The visible region, in window local coordinates Although we + // don't limit process region to damage, we provide that info in + // reg_visible as a hint. Since window image data outside of the + // damage region won't be painted onto target + coord_t window_coord = {.x = w->g.x, .y = w->g.y}; + coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb}; + + region_t reg_visible_local; + { + // The bounding shape, in window local coordinates + region_t reg_bound_local; + pixman_region32_init(®_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); + pixman_region32_fini(®_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); + ps->backend_data->ops->release_image(ps->backend_data, new_img); + pixman_region32_fini(®_visible_local); +} + void handle_device_reset(session_t *ps) { log_error("Device reset detected"); // Wait for reset to complete @@ -185,7 +237,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { if (ps->root_image) { ps->backend_data->ops->compose(ps->backend_data, ps->root_image, - (coord_t){0}, NULL, (coord_t){0}, + (coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height}, ®_paint, ®_visible); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, @@ -237,6 +289,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { * option */ auto real_win_mode = w->mode; coord_t window_coord = {.x = w->g.x, .y = w->g.y}; + coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb}; if (w->blur_background && (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || @@ -409,6 +462,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH, w->win_image, &border_width); + if (w->old_win_image) { + // TODO(dccsillag): explain why the following is + // "necessary" + double zero = 0.0; + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH, + w->old_win_image, &zero); + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, + w->old_win_image, &zero); + } } ps->backend_data->ops->set_image_property( @@ -431,53 +495,43 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { } // Draw window on target - if (w->frame_opacity == 1) { + bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0; + if (w->frame_opacity == 1 && !is_animating) { ps->backend_data->ops->compose(ps->backend_data, w->win_image, - window_coord, NULL, window_coord, + window_coord, NULL, dest_coord, ®_paint_in_bound, ®_visible); } 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 191e814..3d620f8 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -183,6 +183,11 @@ struct backend_operations { void *mask, coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible); + void (*_compose)(backend_t *backend_data, void *image_data, + int dst_x1, int dst_y1, int dst_x2, int dst_y2, + const region_t *reg_paint, const region_t *reg_visible); + + /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. void (*fill)(backend_t *backend_data, struct color, const region_t *clip); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 7ade233..ff6091c 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -412,7 +412,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe } glUniform1i(win_shader->uniform_mask_tex, 2); - glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x, + glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x , (float)mask_offset.y); if (mask != NULL) { glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted); @@ -494,7 +494,8 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int extent_height, int texture_height, int root_height, bool y_inverted, GLint *coord, GLuint *indices) { image_dst.y = root_height - image_dst.y; - image_dst.y -= extent_height; + image_dst.y -= extent_height; + for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 @@ -573,6 +574,11 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y}; x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height, gd->height, inner->y_inverted, coord, indices); + for (unsigned int i = 2; i < 16; i+=4) { + coord[i+0] = lerp_range(0, mask_offset.x, 0, inner->width, coord[i+0]); + coord[i+1] = lerp_range(0, mask_offset.y, 0, inner->height, coord[i+1]); + } + _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects); free(indices); diff --git a/src/common.h b/src/common.h index c06a30c..efc84b6 100644 --- a/src/common.h +++ b/src/common.h @@ -147,6 +147,8 @@ typedef struct session { ev_timer unredir_timer; /// Timer for fading ev_timer fade_timer; + /// Timer for animations + ev_timer animation_timer; /// Use an ev_idle callback for drawing /// So we only start drawing when events are processed ev_idle draw_idle; @@ -196,6 +198,8 @@ typedef struct session { int root_width; // Damage of root window. // Damage root_damage; + int selmon_center_x; + int selmon_center_y; /// X Composite overlay window. xcb_window_t overlay; /// The target window for debug mode @@ -255,6 +259,8 @@ typedef struct session { xcb_render_picture_t *alpha_picts; /// Time of last fading. In milliseconds. long long fade_time; + /// Time of last window animation step. In milliseconds. + long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time /// Head pointer of the error ignore linked list. ignore_t *ignore_head; /// Pointer to the next member of tail element of the error diff --git a/src/config.c b/src/config.c index d9b2dd9..e58cfbd 100644 --- a/src/config.c +++ b/src/config.c @@ -708,6 +708,10 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en // opacity logic is complicated, and needs an "unset" state opt->wintype_option[i].opacity = NAN; } + if (!mask[i].animation) { + mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID; + opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID; + } if (!mask[i].clip_shadow_above) { mask[i].clip_shadow_above = true; opt->wintype_option[i].clip_shadow_above = false; @@ -715,6 +719,40 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en } } +enum open_window_animation parse_open_window_animation(const char *src) { + if (strcmp(src, "none") == 0) { + return OPEN_WINDOW_ANIMATION_NONE; + } else if (strcmp(src, "fly-in") == 0) { + return OPEN_WINDOW_ANIMATION_FLYIN; + } else if (strcmp(src, "zoom") == 0) { + return OPEN_WINDOW_ANIMATION_ZOOM; + } else if (strcmp(src, "slide-up") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_UP; + } else if (strcmp(src, "slide-down") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_DOWN; + } else if (strcmp(src, "slide-left") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_LEFT; + } else if (strcmp(src, "slide-right") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT; + } else if (strcmp(src, "slide-out") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_OUT; + } else if (strcmp(src, "slide-in") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_IN; + } else if (strcmp(src, "slide-out-center") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER; + } else if (strcmp(src, "slide-in-center") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER; + } else if (strcmp(src, "minimize") == 0 || strcmp(src, "maximize") == 0) { + return OPEN_WINDOW_ANIMATION_MINIMIZE; + } else if (strcmp(src, "squeeze") == 0) { + return OPEN_WINDOW_ANIMATION_SQUEEZE; + } else if (strcmp(src, "squeeze-bottom") == 0) { + return OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM; + } + + return OPEN_WINDOW_ANIMATION_INVALID; +} + char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) { // clang-format off @@ -759,6 +797,18 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .no_fading_destroyed_argb = false, .fade_blacklist = NULL, + .animations = false, + .animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_unmap_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_next_tag = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_prev_tag = OPEN_WINDOW_ANIMATION_NONE, + .animation_stiffness = 200.0, + .animation_stiffness_tag_change = 200.0, + .animation_window_mass = 1.0, + .animation_dampening = 25, + .animation_clamping = true, + .inactive_opacity = 1.0, .inactive_opacity_override = false, .active_opacity = 1.0, @@ -790,7 +840,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .track_leader = false, - .rounded_corners_blacklist = NULL + .rounded_corners_blacklist = NULL, + .animation_blacklist = NULL }; // clang-format on diff --git a/src/config.h b/src/config.h index e1dc2dd..a58321f 100644 --- a/src/config.h +++ b/src/config.h @@ -40,6 +40,25 @@ enum backend { NUM_BKEND, }; +enum open_window_animation { + OPEN_WINDOW_ANIMATION_NONE = 0, + OPEN_WINDOW_ANIMATION_FLYIN, + OPEN_WINDOW_ANIMATION_SLIDE_UP, + OPEN_WINDOW_ANIMATION_SLIDE_DOWN, + OPEN_WINDOW_ANIMATION_SLIDE_LEFT, + OPEN_WINDOW_ANIMATION_SLIDE_RIGHT, + OPEN_WINDOW_ANIMATION_SLIDE_IN, + OPEN_WINDOW_ANIMATION_SLIDE_OUT, + OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER, + OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER, + OPEN_WINDOW_ANIMATION_ZOOM, + OPEN_WINDOW_ANIMATION_MINIMIZE, + OPEN_WINDOW_ANIMATION_MAXIMIZE, + OPEN_WINDOW_ANIMATION_SQUEEZE, + OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM, + OPEN_WINDOW_ANIMATION_INVALID, +}; + typedef struct win_option_mask { bool shadow : 1; bool fade : 1; @@ -49,6 +68,7 @@ typedef struct win_option_mask { bool redir_ignore : 1; bool opacity : 1; bool clip_shadow_above : 1; + enum open_window_animation animation; } win_option_mask_t; typedef struct win_option { @@ -60,6 +80,7 @@ typedef struct win_option { bool redir_ignore; double opacity; bool clip_shadow_above; + enum open_window_animation animation; } win_option_t; enum blur_method { @@ -172,6 +193,33 @@ typedef struct options { /// Fading blacklist. A linked list of conditions. c2_lptr_t *fade_blacklist; + // === Animations === + /// Whether to do window animations + bool animations; + /// Which animation to run when opening a window + enum open_window_animation animation_for_open_window; + /// Which animation to run when opening a transient window + enum open_window_animation animation_for_transient_window; + /// Which animation to run when unmapping a window + enum open_window_animation animation_for_unmap_window; + /// Which animation to run when swapping to new tag + enum open_window_animation animation_for_next_tag; + /// Which animation to run for old tag + enum open_window_animation animation_for_prev_tag; + /// Spring stiffness for animation + double animation_stiffness; + /// Spring stiffness for current tag animation + double animation_stiffness_tag_change; + /// Window mass for animation + double animation_window_mass; + /// Animation dampening + double animation_dampening; + /// Whether to clamp animations + bool animation_clamping; + /// Animation blacklist. A linked list of conditions. + c2_lptr_t *animation_blacklist; + /// TODO: open/close animations + // === Opacity === /// Default opacity for inactive windows. /// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY. @@ -253,6 +301,12 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + + // Enable fading for next tag + bool enable_fading_next_tag; + + // Enable fading for prev tag + bool enable_fading_prev_tag; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; @@ -266,6 +320,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 e3c70ad..21e8896 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -226,6 +226,15 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ o->clip_shadow_above = ival; mask->clip_shadow_above = true; } + const char *sval = NULL; + if (config_setting_lookup_string(setting, "animation", &sval)) { + enum open_window_animation animation = parse_open_window_animation(sval); + if (animation >= OPEN_WINDOW_ANIMATION_INVALID) + animation = OPEN_WINDOW_ANIMATION_NONE; + + o->animation = animation; + mask->animation = OPEN_WINDOW_ANIMATION_INVALID; + } double fval; if (config_setting_lookup_float(setting, "opacity", &fval)) { @@ -458,6 +467,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/event.c b/src/event.c index e6052f1..f84b595 100644 --- a/src/event.c +++ b/src/event.c @@ -449,6 +449,15 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } if (ps->root == ev->window) { + if (ev->atom == ps->atoms->a_NET_CURRENT_MON_CENTER) { + winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32); + if (prop.nitems == 2) { + ps->selmon_center_x = prop.p32[0]; + ps->selmon_center_y = prop.p32[1]; + } + free_winprop(&prop); + } + if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) { // to update focus ps->pending_updates = true; diff --git a/src/options.c b/src/options.c index 3186ddc..381330e 100644 --- a/src/options.c +++ b/src/options.c @@ -71,6 +71,36 @@ static void usage(const char *argv0, int ret) { "-F\n" " Equals to -f. Deprecated.\n" "\n" + "--animations\n" + " Run animations for window geometry changes (movement and scaling).\n" + "\n" + "--animation-for-open-window\n" + " Which animation to run when opening a window.\n" + " Must be one of `none`, `fly-in`, `zoom`,\n" + " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" + " (default: none).\n" + "\n" + "--animation-for-transient-window\n" + " Which animation to run when opening a transient window.\n" + " Must be one of `none`, `fly-in`, `zoom`,\n" + " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" + " (default: none).\n" + "\n" + "--animation-stiffness\n" + " Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n" + "\n" + "--animation-dampening\n" + " Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n" + "\n" + "--animation-window-mass\n" + " Mass parameter for animation (default: 1.0).\n" + "\n" + "--animation-clamping\n" + " Whether to clamp animations (default: true)\n" + "\n" + "--animation-exclude condition\n" + " Exclude conditions for animation.\n" + "\n" "-i opacity\n" " Opacity of inactive windows. (0.1 - 1.0)\n" "\n" @@ -460,6 +490,14 @@ static const struct option longopts[] = { {"diagnostics", no_argument, NULL, 801}, {"debug-mode", no_argument, NULL, 802}, {"no-ewmh-fullscreen", no_argument, NULL, 803}, + {"animations", no_argument, NULL, 804}, + {"animation-stiffness-in-tag", required_argument, NULL, 805}, + {"animation-stiffness-tag-change", required_argument, NULL, 806}, + {"animation-dampening", required_argument, NULL, 807}, + {"animation-window-mass", required_argument, NULL, 808}, + {"animation-clamping", no_argument, NULL, 809}, + {"animation-for-open-window", required_argument, NULL, 810}, + {"animation-for-transient-window", required_argument, NULL, 811}, // Must terminate with a NULL entry {NULL, 0, NULL, 0}, }; @@ -881,6 +919,52 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; P_CASEBOOL(802, debug_mode); P_CASEBOOL(803, no_ewmh_fullscreen); + P_CASEBOOL(804, animations); + case 805: + // --animation-stiffness + opt->animation_stiffness = atof(optarg); + break; + case 806: + // --animation-stiffness-for-tags + opt->animation_stiffness_tag_change = atof(optarg); + break; + case 807: + // --animation-dampening + opt->animation_dampening = atof(optarg); + break; + case 808: + // --animation-window-masss + opt->animation_window_mass = atof(optarg); + break; + case 809: + // --animation-clamping + opt->animation_clamping = true; + break; + case 810: { + // --animation-for-open-window + enum open_window_animation animation = parse_open_window_animation(optarg); + if (animation >= OPEN_WINDOW_ANIMATION_INVALID) { + log_warn("Invalid open-window animation %s, ignoring.", optarg); + } else { + opt->animation_for_open_window = animation; + } + break; + } + case 811: { + // --animation-for-transient-window + enum open_window_animation animation = parse_open_window_animation(optarg); + if (animation >= OPEN_WINDOW_ANIMATION_INVALID) { + log_warn("Invalid transient-window animation %s, ignoring.", optarg); + } else { + opt->animation_for_transient_window = animation; + } + break; + } + case 812: { + // --animation-exclude + condlst_add(&opt->animation_blacklist, optarg); + break; + } default: usage(argv[0], 1); break; #undef P_CASEBOOL } diff --git a/src/picom.c b/src/picom.c index bac03b0..0f24b00 100644 --- a/src/picom.c +++ b/src/picom.c @@ -668,78 +668,250 @@ static void handle_root_flags(session_t *ps) { } static struct managed_win * -paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { +paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { // XXX need better, more general name for `fade_running`. It really - // means if fade is still ongoing after the current frame is rendered + // means if fade is still ongoing after the current frame is rendered. + // Same goes for `animation_running`. struct managed_win *bottom = NULL; *fade_running = false; - *animation = false; + *animation_running = false; + auto now = get_time_ms(); // Fading step calculation long long steps = 0L; - auto now = get_time_ms(); if (ps->fade_time) { assert(now >= ps->fade_time); steps = (now - ps->fade_time) / ps->o.fade_delta; } else { // Reset fade_time if unset - ps->fade_time = get_time_ms(); + ps->fade_time = now; steps = 0L; } ps->fade_time += steps * ps->o.fade_delta; - // First, let's process fading, and animated shaders - // TODO(yshui) check if a window is fully obscured, and if we don't need to - // process fading or animation for it. + if (ps->o.animations && !ps->animation_time) + ps->animation_time = now; + + double delta_secs = (double)(now - ps->animation_time) / 1000; + + // First, let's process fading win_stack_foreach_managed_safe(w, &ps->window_stack) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; const double opacity_old = w->opacity; + // IMPORTANT: These window animation steps must happen before any other + // [pre]processing. This is because it changes the window's geometry. + if (ps->o.animations && + !isnan(w->animation_progress) && w->animation_progress != 1.0 && + ps->o.wintype_option[w->window_type].animation != 0 && + win_is_mapped_in_x(w)) + { + double neg_displacement_x = + w->animation_dest_center_x - w->animation_center_x; + double neg_displacement_y = + w->animation_dest_center_y - w->animation_center_y; + double neg_displacement_w = w->animation_dest_w - w->animation_w; + double neg_displacement_h = w->animation_dest_h - w->animation_h; + double animation_stiffness = ps->o.animation_stiffness; + if (!(w->animation_is_tag & ANIM_IN_TAG)) { + if (w->animation_is_tag & ANIM_SLOW) + animation_stiffness = ps->o.animation_stiffness_tag_change; + else if (w->animation_is_tag & ANIM_FAST) + animation_stiffness = ps->o.animation_stiffness_tag_change * 1.5; + } + if (w->state == WSTATE_FADING && !(w->animation_is_tag & ANIM_FADE)) + w->opacity_target = win_calc_opacity_target(ps, w); + double acceleration_x = + (animation_stiffness * neg_displacement_x - + ps->o.animation_dampening * w->animation_velocity_x) / + ps->o.animation_window_mass; + double acceleration_y = + (animation_stiffness * neg_displacement_y - + ps->o.animation_dampening * w->animation_velocity_y) / + ps->o.animation_window_mass; + double acceleration_w = + (animation_stiffness * neg_displacement_w - + ps->o.animation_dampening * w->animation_velocity_w) / + ps->o.animation_window_mass; + double acceleration_h = + (animation_stiffness * neg_displacement_h - + ps->o.animation_dampening * w->animation_velocity_h) / + ps->o.animation_window_mass; + w->animation_velocity_x += acceleration_x * delta_secs; + w->animation_velocity_y += acceleration_y * delta_secs; + w->animation_velocity_w += acceleration_w * delta_secs; + w->animation_velocity_h += acceleration_h * delta_secs; + + // Animate window geometry + double new_animation_x = + w->animation_center_x + w->animation_velocity_x * delta_secs; + double new_animation_y = + w->animation_center_y + w->animation_velocity_y * delta_secs; + double new_animation_w = + w->animation_w + w->animation_velocity_w * delta_secs; + double new_animation_h = + w->animation_h + w->animation_velocity_h * delta_secs; + + // Negative new width/height causes segfault and it can happen + // when clamping disabled and shading a window + if (new_animation_h < 0) + new_animation_h = 0; + + if (new_animation_w < 0) + new_animation_w = 0; + + if (ps->o.animation_clamping) { + w->animation_center_x = clamp( + new_animation_x, + min2(w->animation_center_x, w->animation_dest_center_x), + max2(w->animation_center_x, w->animation_dest_center_x)); + w->animation_center_y = clamp( + new_animation_y, + min2(w->animation_center_y, w->animation_dest_center_y), + max2(w->animation_center_y, w->animation_dest_center_y)); + w->animation_w = + clamp(new_animation_w, + min2(w->animation_w, w->animation_dest_w), + max2(w->animation_w, w->animation_dest_w)); + w->animation_h = + clamp(new_animation_h, + min2(w->animation_h, w->animation_dest_h), + max2(w->animation_h, w->animation_dest_h)); + } else { + w->animation_center_x = new_animation_x; + w->animation_center_y = new_animation_y; + w->animation_w = new_animation_w; + w->animation_h = new_animation_h; + } + + // Now we are done doing the math; we just need to submit our + // changes (if there are any). + + struct win_geometry old_g = w->g; + double old_animation_progress = w->animation_progress; + new_animation_x = round(w->animation_center_x - w->animation_w * 0.5); + new_animation_y = round(w->animation_center_y - w->animation_h * 0.5); + new_animation_w = round(w->animation_w); + new_animation_h = round(w->animation_h); + + bool position_changed = + new_animation_x != old_g.x || new_animation_y != old_g.y; + bool size_changed = + new_animation_w != old_g.width || new_animation_h != old_g.height; + bool geometry_changed = position_changed || size_changed; + + // Mark past window region with damage + if (was_painted && geometry_changed) + add_damage_from_win(ps, w); + + double x_dist = w->animation_dest_center_x - w->animation_center_x; + double y_dist = w->animation_dest_center_y - w->animation_center_y; + double w_dist = w->animation_dest_w - w->animation_w; + double h_dist = w->animation_dest_h - w->animation_h; + w->animation_progress = + 1.0 - w->animation_inv_og_distance * + sqrt(x_dist * x_dist + y_dist * y_dist + + w_dist * w_dist + h_dist * h_dist); + + // When clamping disabled we don't want the overlayed image to + // fade in again because process is moving to negative value + if (w->animation_progress < old_animation_progress) + w->animation_progress = old_animation_progress; + + w->g.x = (int16_t)new_animation_x; + w->g.y = (int16_t)new_animation_y; + w->g.width = (uint16_t)new_animation_w; + w->g.height = (uint16_t)new_animation_h; + + if (w->animation_is_tag > ANIM_IN_TAG && (((w->animation_is_tag & ANIM_FADE) && w->opacity_target == w->opacity) || ((w->g.width == 0 || w->g.height == 0) && (w->animation_dest_w == 0 || w->animation_dest_h == 0)))) { + w->g.x = w->pending_g.x; + w->g.y = w->pending_g.y; + if (ps->o.animation_for_next_tag < OPEN_WINDOW_ANIMATION_ZOOM) { + w->g.width = w->pending_g.width; + w->g.height = w->pending_g.height; + } else { + w->g.width = 0; + w->g.height = 0; + } + } + + // Submit window size change + if (size_changed) { + win_on_win_size_change(ps, w); + + pixman_region32_clear(&w->bounding_shape); + pixman_region32_fini(&w->bounding_shape); + pixman_region32_init_rect(&w->bounding_shape, 0, 0, + (uint)w->widthb, (uint)w->heightb); + + win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE); + win_process_image_flags(ps, w); + } + // Mark new window region with damage + if (was_painted && geometry_changed) { + add_damage_from_win(ps, w); + w->reg_ignore_valid = false; + } + + // We can't check for 1 here as sometimes 1 = 0.999999999999999 + // in case of floating numbers + if (w->animation_progress >= 0.999999999) { + w->animation_progress = 1; + w->animation_velocity_x = 0.0; + w->animation_velocity_y = 0.0; + w->animation_velocity_w = 0.0; + w->animation_velocity_h = 0.0; + w->opacity = win_calc_opacity_target(ps, w); + } + + *animation_running = true; + } + if (win_should_dim(ps, w) != w->dim) { w->dim = win_should_dim(ps, w); add_damage_from_win(ps, w); } - if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) { - add_damage_from_win(ps, w); - *animation = true; - } + if (w->opacity != w->opacity_target) { + // Run fading + if (run_fade(ps, &w, steps)) { + *fade_running = true; + } - // 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_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_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } + // Update window mode + w->mode = win_calc_mode(w); - // 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; + // 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(); @@ -1406,6 +1578,11 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_ queue_redraw(ps); } +static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + session_t *ps = session_ptr(w, animation_timer); + queue_redraw(ps); +} + static void handle_pending_updates(EV_P_ struct session *ps) { if (ps->pending_updates) { log_debug("Delayed handling of events, entering critical section"); @@ -1490,9 +1667,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { * screen is not redirected. its sole purpose should be to decide whether the * screen should be redirected. */ bool fade_running = false; - bool animation = false; + bool animation_running = false; bool was_redirected = ps->redirected; - auto bottom = paint_preprocess(ps, &fade_running, &animation); + auto bottom = paint_preprocess(ps, &fade_running, &animation_running); ps->tmout_unredir_hit = false; if (!was_redirected && ps->redirected) { @@ -1514,6 +1691,13 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0); ev_timer_start(EV_A_ & ps->fade_timer); } + // Start/stop animation timer depends on whether windows are animating + if (!animation_running && ev_is_active(&ps->animation_timer)) { + ev_timer_stop(EV_A_ & ps->animation_timer); + } else if (animation_running && !ev_is_active(&ps->animation_timer)) { + ev_timer_set(&ps->animation_timer, 0, 0); + ev_timer_start(EV_A_ & ps->animation_timer); + } // If the screen is unredirected, free all_damage to stop painting if (ps->redirected && ps->o.stoppaint_force != ON) { @@ -1537,11 +1721,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { if (!fade_running) { ps->fade_time = 0L; } + if (!animation_running) { + ps->animation_time = 0L; + } // TODO(yshui) Investigate how big the X critical section needs to be. There are // suggestions that rendering should be in the critical section as well. - ps->redraw_needed = animation; + ps->redraw_needed = false; } static void draw_callback(EV_P_ ev_idle *w, int revents) { @@ -1549,9 +1736,8 @@ static void draw_callback(EV_P_ ev_idle *w, int revents) { draw_callback_impl(EV_A_ ps, revents); - // Don't do painting non-stop unless we are in benchmark mode, or if - // draw_callback_impl thinks we should continue painting. - if (!ps->o.benchmark && !ps->redraw_needed) { + // Don't do painting non-stop unless we are in benchmark mode + if (!ps->o.benchmark) { ev_idle_stop(EV_A_ & ps->draw_idle); } } @@ -1677,6 +1863,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, + .animation_time = 0L, .ignore_head = NULL, .ignore_tail = NULL, .quit = false, @@ -1925,7 +2112,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.window_shader_fg_rules) && c2_list_postprocess(ps, ps->o.opacity_rules) && c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && - c2_list_postprocess(ps, ps->o.focus_blacklist))) { + c2_list_postprocess(ps, ps->o.focus_blacklist) && + c2_list_postprocess(ps, ps->o.animation_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); } @@ -2154,6 +2342,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_idle_init(&ps->draw_idle, draw_callback); ev_init(&ps->fade_timer, fade_timer_callback); + ev_init(&ps->animation_timer, animation_timer_callback); // Set up SIGUSR1 signal handler to reset program ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1); @@ -2244,6 +2433,16 @@ static session_t *session_init(int argc, char **argv, Display *dpy, write_pid(ps); + // When picom starts, fetch monitor positions first. Because it won't fetch the data unless property changes. + if (!ps->selmon_center_x && !ps->selmon_center_y) { + winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32); + if (prop.nitems == 2) { + ps->selmon_center_x = prop.p32[0]; + ps->selmon_center_y = prop.p32[1]; + } + free_winprop(&prop); + } + if (fork && stderr_logger) { // Remove the stderr logger if we will fork log_remove_target_tls(stderr_logger); @@ -2443,6 +2642,7 @@ static void session_destroy(session_t *ps) { // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); + ev_timer_stop(ps->loop, &ps->animation_timer); ev_idle_stop(ps->loop, &ps->draw_idle); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); diff --git a/src/utils.h b/src/utils.h index a35bfa8..a632107 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,6 +20,7 @@ #include "types.h" #define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#define CLEAR_MASK(x) x = 0; #ifdef __FAST_MATH__ #warning Use of -ffast-math can cause rendering error or artifacts, \ @@ -132,6 +133,19 @@ static inline int attr_const normalize_i_range(int i, int min, int max) { return i; } +/** + * Linearly interpolate from a range into another. + * + * @param a,b first range + * @param c,d second range + * @param value value to interpolate, should be in range [a,b] + * @return interpolated value in range [c,d] + */ +static inline int attr_const lerp_range(int a, int b, int c, int d, int value) { + ASSERT_IN_RANGE(value, a, b); + return (d-c)*(value-a)/(b-a) + c; +} + #define min2(a, b) ((a) > (b) ? (b) : (a)) #define max2(a, b) ((a) > (b) ? (a) : (b)) #define min3(a, b, c) min2(a, min2(b, c)) diff --git a/src/win.c b/src/win.c index d97597e..7cabdea 100644 --- a/src/win.c +++ b/src/win.c @@ -303,6 +303,13 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { w->flags |= WIN_FLAGS_PIXMAP_NONE; } } +static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) { + log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name); + if (w->old_win_image) { + base->ops->release_image(base, w->old_win_image); + w->old_win_image = NULL; + } +} static inline void win_release_shadow(backend_t *base, struct managed_win *w) { log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name); assert(w->shadow_image); @@ -392,6 +399,7 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) { if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)); win_release_pixmap(backend, w); + win_release_oldpixmap(backend, w); } if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { @@ -459,6 +467,170 @@ static void win_update_properties(session_t *ps, struct managed_win *w) { win_clear_all_properties_stale(w); } +static void init_animation(session_t *ps, struct managed_win *w) { + CLEAR_MASK(w->animation_is_tag) + static double *anim_x, *anim_y, *anim_w, *anim_h; + enum open_window_animation animation = ps->o.animation_for_open_window; + + if (w->window_type != WINTYPE_TOOLTIP && + wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) { + animation = ps->o.animation_for_transient_window; + } + + anim_x = &w->animation_center_x, anim_y = &w->animation_center_y; + anim_w = &w->animation_w, anim_h = &w->animation_h; + if (w->dwm_mask & ANIM_PREV_TAG) { + animation = ps->o.animation_for_prev_tag; + + if (ps->o.enable_fading_prev_tag) { + w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old); + w->state = WSTATE_FADING; + w->animation_is_tag |= ANIM_FADE; + } + if (ps->o.animation_for_prev_tag >= OPEN_WINDOW_ANIMATION_ZOOM) { + w->animation_is_tag |= ANIM_FAST; + w->dwm_mask |= ANIM_SPECIAL_MINIMIZE; + goto revert; + } + w->animation_is_tag |= ANIM_SLOW; + } else if (w->dwm_mask & ANIM_NEXT_TAG) { + animation = ps->o.animation_for_next_tag; + w->animation_is_tag |= animation >= OPEN_WINDOW_ANIMATION_ZOOM ? ANIM_FAST : ANIM_SLOW; + if (ps->o.enable_fading_next_tag) { + w->opacity = 0.0; + w->state = WSTATE_FADING; + } + } else if (w->dwm_mask & ANIM_UNMAP) { + animation = ps->o.animation_for_unmap_window; +revert: + anim_x = &w->animation_dest_center_x, anim_y = &w->animation_dest_center_y; + anim_w = &w->animation_dest_w, anim_h = &w->animation_dest_h; + } + + switch (animation) { + case OPEN_WINDOW_ANIMATION_NONE: { // No animation + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_w = w->pending_g.width; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-in from a random point outside the screen + // Compute random point off screen + double angle = 2 * M_PI * ((double)rand() / RAND_MAX); + const double radius = + sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); + + // Set animation + *anim_x = ps->selmon_center_x + radius * cos(angle); + *anim_y = ps->selmon_center_y + radius * sin(angle); + *anim_w = 0; + *anim_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location + *anim_x = w->pending_g.x + w->pending_g.width * 0.5; + *anim_y = w->pending_g.y + w->pending_g.height; + *anim_w = w->pending_g.width; + *anim_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location + *anim_x = w->pending_g.x + w->pending_g.width * 0.5; + *anim_y = w->pending_g.y; + *anim_w = w->pending_g.width; + *anim_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location + *anim_x = w->pending_g.x + w->pending_g.width; + *anim_y = w->pending_g.y + w->pending_g.height * 0.5; + *anim_w = 0; + *anim_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location + *anim_x = w->pending_g.x; + *anim_y = w->pending_g.y + w->pending_g.height * 0.5; + *anim_w = 0; + *anim_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_IN: { + *anim_x = w->pending_g.x + w->pending_g.width * 0.5; + *anim_y = w->pending_g.y + w->pending_g.height * 0.5; + *anim_w = w->pending_g.width; + *anim_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER: { + *anim_x = ps->selmon_center_x; + *anim_y = w->g.y + w->pending_g.height * 0.5; + *anim_w = w->pending_g.width; + *anim_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_OUT: { + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER: { + w->animation_dest_center_x = ps->selmon_center_x; + w->animation_dest_center_y = w->pending_g.y; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-in the image, without changing its location + if (w->dwm_mask & ANIM_SPECIAL_MINIMIZE) { + *anim_x = w->g.x + w->g.width * 0.5; + *anim_y = w->g.y + w->g.height * 0.5; + } else { + *anim_x = w->pending_g.x + w->pending_g.width * 0.5; + *anim_y = w->pending_g.y + w->pending_g.height * 0.5; + } + *anim_w = 0; + *anim_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_MINIMIZE: { + *anim_x = ps->selmon_center_x; + *anim_y = ps->selmon_center_y; + *anim_w = 0; + *anim_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SQUEEZE: { + if (w->dwm_mask & ANIM_PREV_TAG) { + *anim_h = 0; + } else { + *anim_x = w->pending_g.x + w->pending_g.width * 0.5; + *anim_y = w->pending_g.y + w->pending_g.height * 0.5; + *anim_w = w->pending_g.width; + *anim_h = 0; + } + break; + } + case OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM: { + if (w->dwm_mask & ANIM_PREV_TAG) { + *anim_y = w->g.y + w->g.height; + *anim_h = 0; + } else { + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height; + w->animation_w = w->pending_g.width; + *anim_h = 0; + *anim_y = w->pending_g.y + w->pending_g.height; + } + break; + } + case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break; + } +} + /// Handle non-image flags. This phase might set IMAGES_STALE flags void win_process_update_flags(session_t *ps, struct managed_win *w) { // Whether the window was visible before we process the mapped flag. i.e. @@ -499,8 +671,78 @@ 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; + // Determine if a window should animate + if (win_should_animate(ps, w)) { + if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height) + w->dwm_mask = ANIM_PREV_TAG; + else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height) + w->dwm_mask = ANIM_NEXT_TAG; + + if (!was_visible || w->dwm_mask || (w->window_type == WINTYPE_NOTIFICATION && w->state == WSTATE_MAPPING)) { + // Set window-open animation + init_animation(ps, w); + + if (!(w->dwm_mask & ANIM_SPECIAL_MINIMIZE)) { + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + w->g.x = (int16_t)round(w->animation_center_x - + w->animation_w * 0.5); + w->g.y = (int16_t)round(w->animation_center_y - + w->animation_h * 0.5); + w->g.width = (uint16_t)round(w->animation_w); + w->g.height = (uint16_t)round(w->animation_h); + } + } else { + w->animation_is_tag = ANIM_IN_TAG; + w->animation_dest_center_x = + w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = + w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + } + CLEAR_MASK(w->dwm_mask) + + w->g.border_width = w->pending_g.border_width; + + double x_dist = w->animation_dest_center_x - w->animation_center_x; + double y_dist = w->animation_dest_center_y - w->animation_center_y; + double w_dist = w->animation_dest_w - w->animation_w; + double h_dist = w->animation_dest_h - w->animation_h; + w->animation_inv_og_distance = + 1.0 / sqrt(x_dist * x_dist + y_dist * y_dist + + w_dist * w_dist + h_dist * h_dist); + + if (isinf(w->animation_inv_og_distance)) + w->animation_inv_og_distance = 0; + + // We only grab images if w->reg_ignore_valid is true as + // there's an ev_shape_notify() event fired quickly on new windows + // for e.g. in case of Firefox main menu and ev_shape_notify() + // sets the win_set_flags(w, WIN_FLAGS_SIZE_STALE); which + // brakes the new image captured and because this same event + // also sets w->reg_ignore_valid = false; too we check for it + if (w->reg_ignore_valid) { + if (w->old_win_image) { + ps->backend_data->ops->release_image(ps->backend_data, + w->old_win_image); + w->old_win_image = NULL; + } + + // We only grab + if (w->win_image) { + w->old_win_image = ps->backend_data->ops->clone_image( + ps->backend_data, w->win_image, &w->bounding_shape); + } + } + + w->animation_progress = 0.0; + + } else { + w->g = w->pending_g; + } if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { win_on_win_size_change(ps, w); @@ -812,6 +1054,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; @@ -876,6 +1122,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. * @@ -1505,14 +1769,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_IMAGES_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_IMAGES_NONE, // updated by property/attributes/etc + // change .stale_props = NULL, .stale_props_capacity = 0, @@ -1538,6 +1807,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, @@ -2094,17 +2364,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). @@ -2372,6 +2656,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) { @@ -2417,7 +2725,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; } @@ -2849,5 +3157,5 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { /// Return whether this window is mapped on the X server side bool win_is_mapped_in_x(const struct managed_win *w) { return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING || - w->state == WSTATE_MAPPED || (w->flags & WIN_FLAGS_MAPPED); + w->state == WSTATE_MAPPED || w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED); } diff --git a/src/win.h b/src/win.h index 8b79cb1..63a0572 100644 --- a/src/win.h +++ b/src/win.h @@ -100,11 +100,25 @@ struct win_geometry { uint16_t border_width; }; +enum { + // dwm_mask + ANIM_PREV_TAG = 1, + ANIM_NEXT_TAG = (1 << 1), + ANIM_UNMAP = (1 << 2), + ANIM_SPECIAL_MINIMIZE = (1 << 3), + // animation_is_tag + ANIM_IN_TAG = 1, + ANIM_SLOW = (1 << 1), + ANIM_FAST = (1 << 2), + ANIM_FADE = (1 << 3), +}; + struct managed_win { struct win base; /// backend data attached to this window. Only available when /// `state` is not UNMAPPED void *win_image; + void *old_win_image; // Old window image for interpolating window contents during animations void *shadow_image; void *mask_image; /// Pointer to the next higher window to paint. @@ -156,6 +170,8 @@ struct managed_win { /// opacity state, window geometry, window mapped/unmapped state, /// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW. /// NULL means reg_ignore has not been calculated for this window. + /// 1 = tag prev , 2 = tag next, 4 = unmap + uint32_t dwm_mask; rc_region_t *reg_ignore; /// Whether the reg_ignore of all windows beneath this window are valid bool reg_ignore_valid; @@ -173,6 +189,24 @@ struct managed_win { bool unredir_if_possible_excluded; /// Whether this window is in open/close state. bool in_openclose; + /// Current position and destination, for animation + double animation_center_x, animation_center_y; + double animation_dest_center_x, animation_dest_center_y; + double animation_w, animation_h; + double animation_dest_w, animation_dest_h; + /// Spring animation velocity + double animation_velocity_x, animation_velocity_y; + double animation_velocity_w, animation_velocity_h; + /// Track animation progress; goes from 0 to 1 + double animation_progress; + /// Inverse of the window distance at the start of animation, for + /// tracking animation progress + double animation_inv_og_distance; + /// Animation info if it is a tag change & check if its changing window sizes + /// 0: no tag change + /// 1: normal tag change animation + /// 2: tag change animation that effects window size + uint16_t animation_is_tag; // Client window related members /// ID of the top-level client window of the window. @@ -458,6 +492,9 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags); /// Mark properties as stale for a window void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops); +/// Determine if a window should animate +bool attr_pure win_should_animate(session_t *ps, const struct managed_win *w); + static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) { return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1); }