picom upto date sync with yshui, full anim support

small changes

small changes

picom upto date sync with yshui, full anim support

small changes

small changes

rounded corner delay fix

fixed open window type anim, closes #12

AUR pkg, closes #15, thanks fxzzi

fix lerping on shadows, closes #4

picom upto date sync with yshui, full anim support

animations added, picom upstreamed yshui/next

shadow fix

shadow fix

randr extension

randr anim fix

randr_mon position fix

pos fix

multi monitor desktop switch fixed

fix for vertical stacked monitors

closes #26

revert opacity rule

index on next: 5a8c61da revert opacity rule

Add running section to README.md

Properly animates depending on wintype animation property in config

fixed window unmap crash

fixes animations on destroying/unmapping windows

xrender fix
This commit is contained in:
Arda Atci 2022-10-04 00:24:05 +03:00 committed by Yuxuan Shui
parent f7e538fe88
commit b37989b9ba
19 changed files with 1109 additions and 205 deletions

1
.gitignore vendored
View File

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

View File

@ -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=<path> 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!

View File

@ -1,29 +1,76 @@
#################################
# Animations #
# !These animations WILL NOT work correctly for any other wm other than phyOS-dwm fork!
# fly-in: Windows fly in from random directions to the screen
# maximize: Windows pop from center of the screen to their respective positions
# minimize: Windows minimize from their position to the center of the screen
# slide-in-center: Windows move from upper-center of the screen to their respective positions
# slide-out-center: Windows move to the upper-center of the screen
# slide-left: Windows are created from the right-most window position and slide leftwards
# slide right: Windows are created from the left-most window position and slide rightwards
# slide-down: Windows are moved from the top of the screen and slide downward
# slide-up: Windows are moved from their position to top of the screen
# squeeze: Windows are either closed or created to/from their center y-position (the animation is similar to a blinking eye)
# squeeze-bottom: Similar to squeeze, but the animation starts from bottom-most y-position
# zoom: Windows are either created or destroyed from/to their center (not the screen center)
#################################
#enable or disable animations
animations = true;
#change animation speed of windows in current tag e.g open window in current tag
animation-stiffness-in-tag = 125;
#change animation speed of windows when tag changes
animation-stiffness-tag-change = 90.0;
animation-window-mass = 0.4;
animation-dampening = 15;
animation-clamping = true;
#open windows
animation-for-open-window = "zoom";
#minimize or close windows
animation-for-unmap-window = "squeeze";
#popup windows
animation-for-transient-window = "slide-up"; #available options: slide-up, slide-down, slide-left, slide-right, squeeze, squeeze-bottom, zoom
#set animation for windows being transitioned out while changings tags
animation-for-prev-tag = "minimize";
#enables fading for windows being transitioned out while changings tags
enable-fading-prev-tag = true;
#set animation for windows being transitioned in while changings tags
animation-for-next-tag = "slide-in-center";
#enables fading for windows being transitioned in while changings tags
enable-fading-next-tag = true;
#################################
# Shadows #
#################################
# Enabled client-side shadows on windows. Note desktop windows
# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
# unless explicitly requested using the wintypes option.
#
# shadow = false
shadow = true;
shadow = false;
# The blur radius for shadows, in pixels. (defaults to 12)
# shadow-radius = 12
shadow-radius = 7;
shadow-radius = 60;
# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
# shadow-opacity = .75
# The left offset for shadows, in pixels. (defaults to -15)
# shadow-offset-x = -15
shadow-offset-x = -7;
shadow-offset-x = -20;
# The top offset for shadows, in pixels. (defaults to -15)
# shadow-offset-y = -15
shadow-offset-y = -7;
shadow-offset-y = -20;
# Red color value of shadow (0.0 - 1.0, defaults to 0).
# shadow-red = 0
@ -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",
];

View File

@ -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, \

View File

@ -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 <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local, reg_visible, reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x, -w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(&reg_visible_local, &reg_visible_local,
&reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image,
&reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
double alpha = additional_alpha * w->opacity;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img,
&reg_frame, &reg_visible_local,
(double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, dest_coord,
reg_paint_in_bound, reg_visible, true);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_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},
&reg_paint, &reg_visible);
(coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height},
&reg_paint, &reg_visible, true);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
&reg_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, &reg_shadow, &reg_visible);
inverted_mask, window_coord, &reg_shadow, &reg_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,
&reg_paint_in_bound, &reg_visible);
window_coord, NULL, dest_coord,
&reg_paint_in_bound, &reg_visible, true);
} else {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
if (is_animating && w->old_win_image) {
bool is_focused = win_is_focused_raw(ps, w);
if (!is_focused && w->focused && w->opacity_is_set)
is_focused = true;
assert(w->old_win_image);
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
&reg_visible, &reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x,
-w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
// Only animate opacity here if we are resizing
// a transparent window
process_window_for_painting(ps, w, w->win_image,
is_focused ? 1.0 : w->opacity >= 1 ? 1.0 : w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
// Only do this if size changes as otherwise moving
// transparent windows will flicker and if you just
// move so slightly they will keep flickering
if (resizing && (!is_focused || !w->opacity_is_set)) {
process_window_for_painting(ps, w, w->old_win_image,
1.0 - w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
} else {
process_window_for_painting(
ps, w, w->win_image, 1.0, &reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, &reg_frame,
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_bound_local);
}
skip:
pixman_region32_fini(&reg_bound);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 *);

View File

@ -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_;

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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");
}

View File

@ -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);

View File

@ -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))

348
src/win.c
View File

@ -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);
}

View File

@ -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);