Compare commits
135 Commits
4574977287
...
d4f3ffb258
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4f3ffb258 | ||
|
|
7b2fa9d09e | ||
|
|
cd203473fe | ||
|
|
a5b2ceb55c | ||
|
|
c9840c1a7b | ||
|
|
bd1c6e600e | ||
|
|
4080a8d254 | ||
|
|
5ee7b09542 | ||
|
|
75f89caf04 | ||
|
|
057c8265b0 | ||
|
|
c178be2874 | ||
|
|
7da885e6c0 | ||
|
|
ff691e6295 | ||
|
|
fc62f6a4ed | ||
|
|
0b45b3415b | ||
|
|
d7a2f8ade6 | ||
|
|
ffaa42a019 | ||
|
|
e7c00108d1 | ||
|
|
72693b7550 | ||
|
|
6986ba919a | ||
|
|
79bf7cd5c3 | ||
|
|
91667d7747 | ||
|
|
c6b48d7cbc | ||
|
|
8384890d3a | ||
|
|
f7b578dd54 | ||
|
|
40ca6d7146 | ||
|
|
b3c12b8724 | ||
|
|
70ea36bedd | ||
|
|
fd9c52d0ee | ||
|
|
5a8c61daed | ||
|
|
b1fce63a26 | ||
|
|
b73e6a0c45 | ||
|
|
98e842e83b | ||
|
|
2241c7d87a | ||
|
|
21c2da549a | ||
|
|
2b74392ec7 | ||
|
|
367643e98a | ||
|
|
a5c4095082 | ||
|
|
15aa5014a6 | ||
|
|
4d624bbee5 | ||
|
|
dc9d1fe248 | ||
|
|
1c7466c07c | ||
|
|
44cfa0b0ef | ||
|
|
bb2b4801f7 | ||
|
|
36e6b73cde | ||
|
|
90f57e9b1a | ||
|
|
157ecd2077 | ||
|
|
3a791ab559 | ||
|
|
f5e8351507 | ||
|
|
9f14c43989 | ||
|
|
97d9c960e0 | ||
|
|
d41a291fd6 | ||
|
|
c3a7cf29ab | ||
|
|
3b1930d2c6 | ||
|
|
92a32808ae | ||
|
|
d3c467f4c0 | ||
|
|
4be96a92c7 | ||
|
|
ab766b6386 | ||
|
|
7e975cdc5d | ||
|
|
349f2f37db | ||
|
|
7846b17c54 | ||
|
|
a667886959 | ||
|
|
6c08650f3c | ||
|
|
7f533c0b23 | ||
|
|
efa3d9c227 | ||
|
|
0b965b2e5a | ||
|
|
ba2e24af3e | ||
|
|
0278a1fb0b | ||
|
|
931c1b8bf6 | ||
|
|
6906e6694a | ||
|
|
f1bff49b1c | ||
|
|
fe3f53f3a4 | ||
|
|
57956fb219 | ||
|
|
c51020aef7 | ||
|
|
eec8bf79d2 | ||
|
|
227cb55ca5 | ||
|
|
e9ff18e1bd | ||
|
|
43d8d3ed5d | ||
|
|
bf0832cd4c | ||
|
|
9c3204cc72 | ||
|
|
bc1f99f2ae | ||
|
|
d21fb34e55 | ||
|
|
7f18e74b8f | ||
|
|
2173654fbd | ||
|
|
123ef51210 | ||
|
|
4ecc3e65a9 | ||
|
|
4a7a9e8079 | ||
|
|
dacadb9fc3 | ||
|
|
144e78fd5d | ||
|
|
d647ccca16 | ||
|
|
9fe7e65e3e | ||
|
|
ad5a042803 | ||
|
|
0a82f460e0 | ||
|
|
49490ab99f | ||
|
|
9bf39b8d1d | ||
|
|
11a195747a | ||
|
|
89690c9843 | ||
|
|
935885d396 | ||
|
|
611f8b80c2 | ||
|
|
8f848c2b1d | ||
|
|
a10a64f984 | ||
|
|
1434881567 | ||
|
|
fa21c44ee7 | ||
|
|
dbb81b5116 | ||
|
|
f7596fd43a | ||
|
|
f7a950a638 | ||
|
|
fbe7ed5699 | ||
|
|
0004173ecd | ||
|
|
1e5de4067b | ||
|
|
04b80760d8 | ||
|
|
a88393a7df | ||
|
|
8e8a62ae29 | ||
|
|
223533acec | ||
|
|
bcc7d37546 | ||
|
|
c9aee893d2 | ||
|
|
a1dcadf81b | ||
|
|
c86652a5c4 | ||
|
|
157cb57ea7 | ||
|
|
ad8feaad12 | ||
|
|
f54315b51c | ||
|
|
fd18476f4b | ||
|
|
8e3ff3d63b | ||
|
|
04b027d495 | ||
|
|
b93ad16e5a | ||
|
|
68b48617c7 | ||
|
|
59d5b95483 | ||
|
|
0a49da1b91 | ||
|
|
260adcdc15 | ||
|
|
4fbc9b33ad | ||
|
|
9e397ef4e9 | ||
|
|
70c729d389 | ||
|
|
23c151c8dc | ||
|
|
b7434c7b76 | ||
|
|
5a8da46952 | ||
|
|
f6b0b04f5b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ compton
|
||||
build/
|
||||
compile_commands.json
|
||||
build.ninja
|
||||
make.sh
|
||||
|
||||
# language servers
|
||||
.ccls-cache
|
||||
|
||||
@@ -85,6 +85,11 @@ $ ninja -C build
|
||||
|
||||
### To install
|
||||
|
||||
#### AUR (arch)
|
||||
- picom-ftlabs-git
|
||||
Thanks to @Fxzzi for maintaining the package.
|
||||
|
||||
|
||||
``` bash
|
||||
$ ninja -C build install
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
];
|
||||
|
||||
@@ -73,19 +123,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 = []
|
||||
@@ -104,15 +153,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
|
||||
@@ -122,21 +169,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 #
|
||||
#################################
|
||||
@@ -144,52 +183,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.
|
||||
@@ -197,24 +205,25 @@ 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`, `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
|
||||
@@ -222,8 +231,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.
|
||||
@@ -239,25 +247,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
|
||||
@@ -301,7 +309,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,
|
||||
@@ -322,24 +330,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.
|
||||
@@ -356,7 +358,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
|
||||
@@ -429,3 +431,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",
|
||||
];
|
||||
|
||||
@@ -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(®_bound_local);
|
||||
pixman_region32_copy(®_bound_local, reg_bound);
|
||||
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
|
||||
|
||||
pixman_region32_init(®_visible_local);
|
||||
pixman_region32_intersect(®_visible_local, reg_visible, reg_paint);
|
||||
pixman_region32_translate(®_visible_local, -w->g.x, -w->g.y);
|
||||
// Data outside of the bounding shape won't be visible,
|
||||
// but it is not necessary to limit the image operations
|
||||
// to the bounding shape yet. So pass that as the visible
|
||||
// region, not the clip region.
|
||||
pixman_region32_intersect(®_visible_local, ®_visible_local,
|
||||
®_bound_local);
|
||||
}
|
||||
|
||||
auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image,
|
||||
®_visible_local);
|
||||
auto reg_frame = win_get_region_frame_local_by_val(w);
|
||||
double alpha = additional_alpha * w->opacity;
|
||||
ps->backend_data->ops->set_image_property(
|
||||
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
|
||||
ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img,
|
||||
®_frame, ®_visible_local,
|
||||
(double[]){w->frame_opacity});
|
||||
pixman_region32_fini(®_frame);
|
||||
ps->backend_data->ops->compose(ps->backend_data, new_img,
|
||||
window_coord, NULL, dest_coord,
|
||||
reg_paint_in_bound, reg_visible, true);
|
||||
ps->backend_data->ops->release_image(ps->backend_data, new_img);
|
||||
pixman_region32_fini(®_visible_local);
|
||||
pixman_region32_fini(®_bound_local);
|
||||
}
|
||||
|
||||
void handle_device_reset(session_t *ps) {
|
||||
log_error("Device reset detected");
|
||||
// Wait for reset to complete
|
||||
@@ -210,8 +262,8 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
|
||||
|
||||
if (ps->root_image) {
|
||||
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
|
||||
(coord_t){0}, NULL, (coord_t){0},
|
||||
®_paint, ®_visible);
|
||||
(coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height},
|
||||
®_paint, ®_visible, true);
|
||||
} else {
|
||||
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
|
||||
®_paint);
|
||||
@@ -262,6 +314,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
|
||||
* option */
|
||||
auto real_win_mode = w->mode;
|
||||
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
|
||||
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
|
||||
|
||||
if (w->blur_background &&
|
||||
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
|
||||
@@ -390,7 +443,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
|
||||
}
|
||||
ps->backend_data->ops->compose(
|
||||
ps->backend_data, w->shadow_image, shadow_coord,
|
||||
inverted_mask, window_coord, ®_shadow, ®_visible);
|
||||
inverted_mask, window_coord, ®_shadow, ®_visible, false);
|
||||
if (inverted_mask) {
|
||||
ps->backend_data->ops->set_image_property(
|
||||
ps->backend_data, IMAGE_PROPERTY_INVERTED,
|
||||
@@ -434,6 +487,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(
|
||||
@@ -456,53 +520,43 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
|
||||
}
|
||||
|
||||
// Draw window on target
|
||||
if (w->frame_opacity == 1) {
|
||||
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
|
||||
if (w->frame_opacity == 1 && !is_animating) {
|
||||
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
|
||||
window_coord, NULL, window_coord,
|
||||
®_paint_in_bound, ®_visible);
|
||||
window_coord, NULL, dest_coord,
|
||||
®_paint_in_bound, ®_visible, true);
|
||||
} else {
|
||||
// For window image processing, we don't have to limit the process
|
||||
// region to damage for correctness. (see <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(®_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);
|
||||
|
||||
@@ -176,7 +176,12 @@ struct backend_operations {
|
||||
*/
|
||||
void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst,
|
||||
void *mask, coord_t mask_dst, const region_t *reg_paint,
|
||||
const region_t *reg_visible);
|
||||
const region_t *reg_visible, bool lerp);
|
||||
|
||||
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);
|
||||
|
||||
@@ -19,6 +19,13 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) {
|
||||
}
|
||||
}
|
||||
|
||||
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) {
|
||||
if (driver & DRIVER_NVIDIA) {
|
||||
return VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||
}
|
||||
return VBLANK_SCHEDULER_PRESENT;
|
||||
}
|
||||
|
||||
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
|
||||
enum driver ret = 0;
|
||||
// First we try doing backend agnostic detection using RANDR
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdio.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
|
||||
struct session;
|
||||
@@ -41,6 +42,8 @@ enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_
|
||||
|
||||
/// Apply driver specified global workarounds. It's safe to call this multiple times.
|
||||
void apply_driver_workarounds(struct session *ps, enum driver);
|
||||
/// Choose a vblank scheduler based on the driver.
|
||||
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver);
|
||||
|
||||
// Print driver names to stdout, for diagnostics
|
||||
static inline void print_drivers(enum driver drivers) {
|
||||
|
||||
@@ -67,7 +67,7 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag
|
||||
void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused,
|
||||
void *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 || mask == &dummy->mask);
|
||||
|
||||
@@ -499,6 +499,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];
|
||||
@@ -550,7 +551,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, void *image_data, coord_t image_dst, void *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;
|
||||
struct backend_image *img = image_data;
|
||||
auto inner = (struct gl_texture *)img->inner;
|
||||
@@ -576,6 +577,14 @@ 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);
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 *, void *image_data, coord_t image_dst, void *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_resize(struct gl_data *, int width, int height);
|
||||
|
||||
|
||||
@@ -250,10 +250,16 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
||||
}
|
||||
if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
|
||||
// Apply image properties using a temporary image, because the source
|
||||
// image is transparent. Otherwise the properties can be applied directly
|
||||
// on the target image.
|
||||
// image is transparent or will get transparent corners. Otherwise the
|
||||
// properties can be applied directly on the target image.
|
||||
// Also force a 32-bit ARGB visual for transparent corners, otherwise the
|
||||
// corners become black.
|
||||
auto visual =
|
||||
(img->corner_radius != 0 && inner->depth != 32)
|
||||
? x_get_visual_for_standard(xd->base.c, XCB_PICT_STANDARD_ARGB_32)
|
||||
: inner->visual;
|
||||
auto tmp_pict = x_create_picture_with_visual(
|
||||
xd->base.c, inner->width, inner->height, inner->visual, 0, NULL);
|
||||
xd->base.c, inner->width, inner->height, visual, 0, NULL);
|
||||
|
||||
// Set clip region translated to source coordinate
|
||||
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
|
||||
@@ -350,7 +356,7 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
||||
}
|
||||
|
||||
static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst,
|
||||
const region_t *reg_paint, const region_t *reg_visible) {
|
||||
const region_t *reg_paint, const region_t *reg_visible, bool lerp attr_unused) {
|
||||
struct _xrender_data *xd = (void *)base;
|
||||
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
|
||||
xd->back[2]);
|
||||
|
||||
13
src/common.h
13
src/common.h
@@ -143,6 +143,11 @@ 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;
|
||||
/// Use an ev_timer callback for drawing
|
||||
ev_timer draw_timer;
|
||||
/// Called every time we have timeouts or new data on socket,
|
||||
@@ -175,7 +180,6 @@ typedef struct session {
|
||||
bool server_grabbed;
|
||||
/// Width of root window.
|
||||
int root_width;
|
||||
/// Height of root window.
|
||||
int root_height;
|
||||
/// X Composite overlay window.
|
||||
xcb_window_t overlay;
|
||||
@@ -264,6 +268,13 @@ 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.
|
||||
pending_reply_t *pending_reply_head;
|
||||
/// Pointer to the <code>next</code> member of tail element of the error
|
||||
/// ignore linked list.
|
||||
pending_reply_t **pending_reply_tail;
|
||||
// Cached blur convolution kernels.
|
||||
struct x_convolution_kernel **blur_kerns_cache;
|
||||
/// If we should quit
|
||||
|
||||
71
src/config.c
71
src/config.c
@@ -623,11 +623,17 @@ struct debug_options_entry {
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
static const struct debug_options_entry debug_options_entries[] = {
|
||||
"smart_frame_pacing",
|
||||
NULL,
|
||||
offsetof(struct debug_options, smart_frame_pacing),
|
||||
// clang-format off
|
||||
const char *vblank_scheduler_str[] = {
|
||||
[VBLANK_SCHEDULER_PRESENT] = "present",
|
||||
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] = "sgi_video_sync",
|
||||
[LAST_VBLANK_SCHEDULER] = NULL
|
||||
};
|
||||
static const struct debug_options_entry debug_options_entries[] = {
|
||||
{"smart_frame_pacing", NULL, offsetof(struct debug_options, smart_frame_pacing)},
|
||||
{"force_vblank_sched", vblank_scheduler_str, offsetof(struct debug_options, force_vblank_scheduler)},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
void parse_debug_option_single(char *setting, struct debug_options *debug_options) {
|
||||
char *equal = strchr(setting, '=');
|
||||
@@ -670,7 +676,9 @@ void parse_debug_option_single(char *setting, struct debug_options *debug_option
|
||||
/// Parse debug options from environment variable `PICOM_DEBUG`.
|
||||
void parse_debug_options(struct debug_options *debug_options) {
|
||||
const char *debug = getenv("PICOM_DEBUG");
|
||||
const struct debug_options default_debug_options = {};
|
||||
const struct debug_options default_debug_options = {
|
||||
.force_vblank_scheduler = LAST_VBLANK_SCHEDULER,
|
||||
};
|
||||
|
||||
*debug_options = default_debug_options;
|
||||
if (!debug) {
|
||||
@@ -789,6 +797,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;
|
||||
@@ -796,6 +808,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
|
||||
@@ -841,6 +887,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,
|
||||
@@ -872,7 +930,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
|
||||
|
||||
|
||||
59
src/config.h
59
src/config.h
@@ -40,6 +40,24 @@ enum backend {
|
||||
NUM_BKEND,
|
||||
};
|
||||
|
||||
enum open_window_animation {
|
||||
OPEN_WINDOW_ANIMATION_NONE = 0,
|
||||
OPEN_WINDOW_ANIMATION_FLYIN,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_UP,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_IN,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_OUT,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER,
|
||||
OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER,
|
||||
OPEN_WINDOW_ANIMATION_ZOOM,
|
||||
OPEN_WINDOW_ANIMATION_MINIMIZE,
|
||||
OPEN_WINDOW_ANIMATION_SQUEEZE,
|
||||
OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM,
|
||||
OPEN_WINDOW_ANIMATION_INVALID,
|
||||
};
|
||||
|
||||
typedef struct win_option_mask {
|
||||
bool shadow : 1;
|
||||
bool fade : 1;
|
||||
@@ -49,6 +67,7 @@ typedef struct win_option_mask {
|
||||
bool redir_ignore : 1;
|
||||
bool opacity : 1;
|
||||
bool clip_shadow_above : 1;
|
||||
enum open_window_animation animation;
|
||||
} win_option_mask_t;
|
||||
|
||||
typedef struct win_option {
|
||||
@@ -60,6 +79,7 @@ typedef struct win_option {
|
||||
bool redir_ignore;
|
||||
double opacity;
|
||||
bool clip_shadow_above;
|
||||
enum open_window_animation animation;
|
||||
} win_option_t;
|
||||
|
||||
enum blur_method {
|
||||
@@ -83,11 +103,15 @@ enum vblank_scheduler_type {
|
||||
LAST_VBLANK_SCHEDULER,
|
||||
};
|
||||
|
||||
extern const char *vblank_scheduler_str[];
|
||||
|
||||
/// Internal, private options for debugging and development use.
|
||||
struct debug_options {
|
||||
/// Try to reduce frame latency by using vblank interval and render time
|
||||
/// estimates. Right now it's not working well across drivers.
|
||||
int smart_frame_pacing;
|
||||
/// Override the vblank scheduler chosen by the compositor.
|
||||
int force_vblank_scheduler;
|
||||
};
|
||||
|
||||
/// Structure representing all options.
|
||||
@@ -191,6 +215,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.
|
||||
@@ -274,6 +325,13 @@ 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;
|
||||
@@ -294,6 +352,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.
|
||||
|
||||
@@ -262,6 +262,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)) {
|
||||
@@ -511,6 +520,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
|
||||
|
||||
@@ -59,7 +59,7 @@ endif
|
||||
|
||||
if get_option('opengl')
|
||||
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
|
||||
deps += [dependency('gl', required: true), dependency('egl', required: true)]
|
||||
deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)]
|
||||
srcs += [ 'opengl.c' ]
|
||||
endif
|
||||
|
||||
|
||||
@@ -184,6 +184,15 @@ static const struct picom_option picom_options[] = {
|
||||
"you want to attach a debugger to picom"},
|
||||
{"no-ewmh-fullscreen" , no_argument , 803, NULL , "Do not use EWMH to detect fullscreen windows. Reverts to checking if a "
|
||||
"window is fullscreen based only on its size and coordinates."},
|
||||
{"animations" ,no_argument , 804, NULL , "Enable/disable animations."},
|
||||
{"animation-stiffness-in-tag" , required_argument, 805, NULL , "Animation speed in current tag (float)."},
|
||||
{"animation-stiffness-tag-change", required_argument, 806, NULL , "Animation speed when tag changes (change to a new desktop)."},
|
||||
{"animation-dampening" , required_argument, 807, NULL , "Animation dampening ratio (spring dampening as an example)."},
|
||||
{"animation-window-mass" , required_argument, 808, NULL , "Animation window mass (lower mass makes animations bumpy)."},
|
||||
{"animation-clamping" , no_argument , 809, NULL , "Enable/disable animation clamping. Disabling increases performance"},
|
||||
{"animation-for-open-window" , required_argument, 810, NULL , "Set animation for opening window (Check sample.conf for options)."},
|
||||
{"animation-for-transient-window", required_argument, 811, NULL , "Set animation for transient (child) windows."},
|
||||
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -750,6 +759,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
|
||||
}
|
||||
|
||||
366
src/picom.c
366
src/picom.c
@@ -144,6 +144,38 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t
|
||||
return w;
|
||||
}
|
||||
|
||||
void check_render_finish(struct vblank_event *e attr_unused, void *ud) {
|
||||
auto ps = (session_t *)ud;
|
||||
if (!ps->backend_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec render_time;
|
||||
bool completed =
|
||||
ps->backend_data->ops->last_render_time(ps->backend_data, &render_time);
|
||||
if (!completed) {
|
||||
// Render hasn't completed yet, we can't start another render.
|
||||
// Check again at the next vblank.
|
||||
log_debug("Last render did not complete during vblank, msc: "
|
||||
"%" PRIu64,
|
||||
ps->last_msc);
|
||||
vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ud);
|
||||
return;
|
||||
}
|
||||
|
||||
// The frame has been finished and presented, record its render time.
|
||||
if (ps->o.debug_options.smart_frame_pacing) {
|
||||
int render_time_us =
|
||||
(int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L);
|
||||
render_statistics_add_render_time_sample(
|
||||
&ps->render_stats, render_time_us + (int)ps->last_schedule_delay);
|
||||
log_verbose("Last render call took: %d (gpu) + %d (cpu) us, "
|
||||
"last_msc: %" PRIu64,
|
||||
render_time_us, (int)ps->last_schedule_delay, ps->last_msc);
|
||||
}
|
||||
ps->backend_busy = false;
|
||||
}
|
||||
|
||||
void collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
|
||||
auto ps = (session_t *)ud;
|
||||
assert(ps->frame_pacing);
|
||||
@@ -207,36 +239,14 @@ void reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
|
||||
log_verbose("Rescheduling render at vblank, msc: %" PRIu64, e->msc);
|
||||
|
||||
collect_vblank_interval_statistics(e, ud);
|
||||
check_render_finish(e, ud);
|
||||
|
||||
if (ps->backend_busy) {
|
||||
struct timespec render_time;
|
||||
bool completed =
|
||||
ps->backend_data->ops->last_render_time(ps->backend_data, &render_time);
|
||||
if (!completed) {
|
||||
// Render hasn't completed yet, we can't start another render.
|
||||
// Check again at the next vblank.
|
||||
log_debug("Last render did not complete during vblank, msc: "
|
||||
"%" PRIu64,
|
||||
ps->last_msc);
|
||||
vblank_scheduler_schedule(ps->vblank_scheduler,
|
||||
reschedule_render_at_vblank, ud);
|
||||
return;
|
||||
}
|
||||
|
||||
// The frame has been finished and presented, record its render time.
|
||||
if (ps->o.debug_options.smart_frame_pacing) {
|
||||
int render_time_us = (int)(render_time.tv_sec * 1000000L +
|
||||
render_time.tv_nsec / 1000L);
|
||||
render_statistics_add_render_time_sample(
|
||||
&ps->render_stats, render_time_us + (int)ps->last_schedule_delay);
|
||||
log_verbose("Last render call took: %d (gpu) + %d (cpu) us, "
|
||||
"last_msc: %" PRIu64,
|
||||
render_time_us, (int)ps->last_schedule_delay,
|
||||
ps->last_msc);
|
||||
}
|
||||
ps->backend_busy = false;
|
||||
}
|
||||
|
||||
schedule_render(ps, false);
|
||||
}
|
||||
|
||||
@@ -324,29 +334,39 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) {
|
||||
ps->next_render = now_us;
|
||||
|
||||
if (!ps->frame_pacing || !ps->redirected) {
|
||||
// If not doing frame pacing, schedule a render immediately unless it's
|
||||
// already scheduled; if not redirected, we schedule immediately to have a
|
||||
// chance to redirect. We won't have frame or render timing information
|
||||
// If not doing frame pacing, schedule a render immediately; if
|
||||
// not redirected, we schedule immediately to have a chance to
|
||||
// redirect. We won't have frame or render timing information
|
||||
// anyway.
|
||||
if (!ev_is_active(&ps->draw_timer)) {
|
||||
assert(!ev_is_active(&ps->draw_timer));
|
||||
goto schedule;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// if ps->o.debug_options.smart_frame_pacing is false, we won't have any render
|
||||
// time or vblank interval estimates, so we would naturally fallback to schedule
|
||||
// render immediately.
|
||||
auto render_budget = render_statistics_get_budget(&ps->render_stats, &divisor);
|
||||
auto render_budget = render_statistics_get_budget(&ps->render_stats);
|
||||
auto frame_time = render_statistics_get_vblank_time(&ps->render_stats);
|
||||
if (frame_time == 0) {
|
||||
// We don't have enough data for render time estimates, maybe there's
|
||||
// no frame rendered yet, or the backend doesn't support render timing
|
||||
// information, schedule render immediately.
|
||||
log_verbose("Not enough data for render time estimates.");
|
||||
goto schedule;
|
||||
}
|
||||
|
||||
auto const deadline = ps->last_msc_instant + (unsigned long)divisor * frame_time;
|
||||
if (render_budget >= frame_time) {
|
||||
// If the estimated render time is already longer than the estimated
|
||||
// vblank interval, there is no way we can make it. Instead of always
|
||||
// dropping frames, we try desperately to catch up and schedule a
|
||||
// render immediately.
|
||||
log_verbose("Render budget: %u us >= frame time: %" PRIu32 " us",
|
||||
render_budget, frame_time);
|
||||
goto schedule;
|
||||
}
|
||||
|
||||
auto target_frame = (now_us + render_budget - ps->last_msc_instant) / frame_time + 1;
|
||||
auto const deadline = ps->last_msc_instant + target_frame * frame_time;
|
||||
unsigned int available = 0;
|
||||
if (deadline > now_us) {
|
||||
available = (unsigned int)(deadline - now_us);
|
||||
@@ -389,18 +409,8 @@ void queue_redraw(session_t *ps) {
|
||||
return;
|
||||
}
|
||||
ps->render_queued = true;
|
||||
if (ps->o.debug_options.smart_frame_pacing && ps->vblank_scheduler) {
|
||||
// Make we schedule_render call is synced with vblank events.
|
||||
// See the comment on schedule_render for more details.
|
||||
if (!vblank_scheduler_schedule(ps->vblank_scheduler,
|
||||
reschedule_render_at_vblank, ps)) {
|
||||
// TODO(yshui): handle error here
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
schedule_render(ps, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a region of the screen size.
|
||||
@@ -851,51 +861,219 @@ static void handle_root_flags(session_t *ps) {
|
||||
}
|
||||
}
|
||||
|
||||
static struct managed_win *
|
||||
paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
|
||||
/**
|
||||
* Go through the window stack and calculate some parameters for rendering.
|
||||
*
|
||||
* @return whether the operation succeeded
|
||||
*/
|
||||
static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
|
||||
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;
|
||||
*fade_running = false;
|
||||
*animation = false;
|
||||
*out_bottom = NULL;
|
||||
*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;
|
||||
@@ -909,6 +1087,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
|
||||
add_damage_from_win(ps, w);
|
||||
}
|
||||
|
||||
|
||||
if (win_check_fade_finished(ps, w)) {
|
||||
// the window has been destroyed because fading finished
|
||||
continue;
|
||||
@@ -929,6 +1108,10 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
|
||||
w->reg_ignore_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (animation_running)
|
||||
ps->animation_time = now;
|
||||
|
||||
// Opacity will not change, from now on.
|
||||
rc_region_t *last_reg_ignore = rc_region_new();
|
||||
@@ -1116,8 +1299,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
|
||||
}
|
||||
}
|
||||
|
||||
*out_bottom = bottom;
|
||||
return true;
|
||||
return bottom;
|
||||
}
|
||||
|
||||
void root_damaged(session_t *ps) {
|
||||
@@ -1467,6 +1649,7 @@ static bool redirect_start(session_t *ps) {
|
||||
}
|
||||
|
||||
ps->frame_pacing = !ps->o.no_frame_pacing;
|
||||
// if ((ps->o.legacy_backends || ps->o.animations || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
|
||||
if ((ps->o.legacy_backends || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
|
||||
ps->frame_pacing) {
|
||||
// Disable frame pacing if we are using a legacy backend or if we are in
|
||||
@@ -1475,6 +1658,10 @@ static bool redirect_start(session_t *ps) {
|
||||
ps->frame_pacing = false;
|
||||
}
|
||||
|
||||
// Re-detect driver since we now have a backend
|
||||
ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root);
|
||||
apply_driver_workarounds(ps, ps->drivers);
|
||||
|
||||
if (ps->present_exists && ps->frame_pacing) {
|
||||
// Initialize rendering and frame timing statistics, and frame pacing
|
||||
// states.
|
||||
@@ -1482,8 +1669,15 @@ static bool redirect_start(session_t *ps) {
|
||||
ps->last_msc = 0;
|
||||
ps->last_schedule_delay = 0;
|
||||
render_statistics_reset(&ps->render_stats);
|
||||
ps->vblank_scheduler =
|
||||
vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps));
|
||||
enum vblank_scheduler_type scheduler_type =
|
||||
choose_vblank_scheduler(ps->drivers);
|
||||
if (ps->o.debug_options.force_vblank_scheduler != LAST_VBLANK_SCHEDULER) {
|
||||
scheduler_type =
|
||||
(enum vblank_scheduler_type)ps->o.debug_options.force_vblank_scheduler;
|
||||
}
|
||||
log_info("Using vblank scheduler: %s.", vblank_scheduler_str[scheduler_type]);
|
||||
ps->vblank_scheduler = vblank_scheduler_new(
|
||||
ps->loop, &ps->c, session_get_target_window(ps), scheduler_type);
|
||||
if (!ps->vblank_scheduler) {
|
||||
return false;
|
||||
}
|
||||
@@ -1500,10 +1694,6 @@ static bool redirect_start(session_t *ps) {
|
||||
ps->redirected = true;
|
||||
ps->first_frame = true;
|
||||
|
||||
// Re-detect driver since we now have a backend
|
||||
ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root);
|
||||
apply_driver_workarounds(ps, ps->drivers);
|
||||
|
||||
root_damaged(ps);
|
||||
|
||||
// Repaint the whole screen
|
||||
@@ -1622,6 +1812,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");
|
||||
@@ -1727,14 +1922,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;
|
||||
struct managed_win *bottom = NULL;
|
||||
if (!paint_preprocess(ps, &fade_running, &animation, &bottom)) {
|
||||
log_fatal("Pre-render preparation has failed, exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto bottom = paint_preprocess(ps, &fade_running, &animation_running);
|
||||
ps->tmout_unredir_hit = false;
|
||||
|
||||
if (!was_redirected && ps->redirected) {
|
||||
@@ -1756,6 +1946,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);
|
||||
@@ -1796,16 +1993,17 @@ 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;
|
||||
|
||||
// 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) {
|
||||
queue_redraw(ps);
|
||||
if (ps->vblank_scheduler) {
|
||||
// Even if we might not want to render during next vblank, we want to keep
|
||||
// `backend_busy` up to date, so when the next render comes, we can
|
||||
// immediately know if we can render.
|
||||
vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1937,6 +2135,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
|
||||
.redirected = false,
|
||||
.alpha_picts = NULL,
|
||||
.fade_time = 0L,
|
||||
.animation_time = 0L,
|
||||
.pending_reply_head = NULL,
|
||||
.pending_reply_tail = NULL,
|
||||
.quit = false,
|
||||
|
||||
.expose_rects = NULL,
|
||||
@@ -2108,8 +2309,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
|
||||
ext_info = xcb_get_extension_data(ps->c.c, &xcb_dpms_id);
|
||||
ps->dpms_exists = ext_info && ext_info->present;
|
||||
if (!ps->dpms_exists) {
|
||||
log_fatal("No DPMS extension");
|
||||
exit(1);
|
||||
log_warn("No DPMS extension");
|
||||
}
|
||||
|
||||
// Parse configuration file
|
||||
@@ -2186,7 +2386,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");
|
||||
}
|
||||
@@ -2382,7 +2583,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);
|
||||
@@ -2411,6 +2612,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);
|
||||
@@ -2717,6 +2920,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);
|
||||
|
||||
@@ -55,26 +55,15 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int
|
||||
/// A `divisor` is also returned, indicating the target framerate. The divisor is
|
||||
/// the number of vblanks we should wait between each frame. A divisor of 1 means
|
||||
/// full framerate, 2 means half framerate, etc.
|
||||
unsigned int
|
||||
render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor) {
|
||||
unsigned int render_statistics_get_budget(struct render_statistics *rs) {
|
||||
if (rs->render_times.nelem < rs->render_times.window_size) {
|
||||
// No valid render time estimates yet. Assume maximum budget.
|
||||
*divisor = 1;
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
// N-th percentile of render times, see render_statistics_init for N.
|
||||
auto render_time_percentile =
|
||||
rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times);
|
||||
auto vblank_time_us = render_statistics_get_vblank_time(rs);
|
||||
if (vblank_time_us == 0) {
|
||||
// We don't have a good estimate of the vblank time yet, so we
|
||||
// assume we can finish in one vblank.
|
||||
*divisor = 1;
|
||||
} else {
|
||||
*divisor =
|
||||
(unsigned int)(render_time_percentile / rs->vblank_time_us.mean + 1);
|
||||
}
|
||||
return (unsigned int)render_time_percentile;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,7 @@ void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int
|
||||
void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us);
|
||||
|
||||
/// How much time budget we should give to the backend for rendering, in microseconds.
|
||||
///
|
||||
/// A `divisor` is also returned, indicating the target framerate. The divisor is
|
||||
/// the number of vblanks we should wait between each frame. A divisor of 1 means
|
||||
/// full framerate, 2 means half framerate, etc.
|
||||
unsigned int
|
||||
render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor);
|
||||
unsigned int render_statistics_get_budget(struct render_statistics *rs);
|
||||
|
||||
/// Return the measured vblank interval in microseconds. Returns 0 if not enough
|
||||
/// samples have been collected yet.
|
||||
|
||||
14
src/utils.h
14
src/utils.h
@@ -21,6 +21,7 @@
|
||||
#include "types.h"
|
||||
|
||||
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
#define CLEAR_MASK(x) x = 0;
|
||||
|
||||
#ifdef __FAST_MATH__
|
||||
#warning Use of -ffast-math can cause rendering error or artifacts, \
|
||||
@@ -135,6 +136,19 @@ static inline int attr_const attr_unused normalize_i_range(int i, int min, int m
|
||||
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;
|
||||
}
|
||||
|
||||
/// Generic integer abs()
|
||||
#define iabs(val) \
|
||||
({ \
|
||||
|
||||
281
src/vblank.c
281
src/vblank.c
@@ -2,12 +2,26 @@
|
||||
|
||||
#include <ev.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xproto.h>
|
||||
#include "config.h"
|
||||
|
||||
#ifdef CONFIG_OPENGL
|
||||
// Enable sgi_video_sync_vblank_scheduler
|
||||
#include <GL/glx.h>
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib-xcb.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "backend/gl/glx.h"
|
||||
#endif
|
||||
|
||||
#include "compiler.h"
|
||||
#include "config.h"
|
||||
#include "list.h" // for container_of
|
||||
#include "log.h"
|
||||
#include "vblank.h"
|
||||
@@ -48,6 +62,7 @@ struct present_vblank_scheduler {
|
||||
};
|
||||
|
||||
struct vblank_scheduler_ops {
|
||||
size_t size;
|
||||
void (*init)(struct vblank_scheduler *self);
|
||||
void (*deinit)(struct vblank_scheduler *self);
|
||||
void (*schedule)(struct vblank_scheduler *self);
|
||||
@@ -57,6 +72,242 @@ struct vblank_scheduler_ops {
|
||||
static void
|
||||
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event);
|
||||
|
||||
#ifdef CONFIG_OPENGL
|
||||
struct sgi_video_sync_vblank_scheduler {
|
||||
struct vblank_scheduler base;
|
||||
|
||||
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
|
||||
// ... and all the thread shenanigans that come with it.
|
||||
_Atomic unsigned int last_msc;
|
||||
_Atomic uint64_t last_ust;
|
||||
ev_async notify;
|
||||
pthread_t sync_thread;
|
||||
bool running, error;
|
||||
|
||||
/// Protects `running`, `error` and `base.vblank_event_requested`
|
||||
pthread_mutex_t vblank_requested_mtx;
|
||||
pthread_cond_t vblank_requested_cnd;
|
||||
};
|
||||
|
||||
struct sgi_video_sync_thread_args {
|
||||
struct sgi_video_sync_vblank_scheduler *self;
|
||||
int start_status;
|
||||
pthread_mutex_t start_mtx;
|
||||
pthread_cond_t start_cnd;
|
||||
};
|
||||
|
||||
static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
|
||||
const char *glx_ext = glXQueryExtensionsString(dpy, screen);
|
||||
const char *needle = "GLX_SGI_video_sync";
|
||||
char *found = strstr(glx_ext, needle);
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
if (found != glx_ext && found[-1] != ' ') {
|
||||
return false;
|
||||
}
|
||||
if (found[strlen(needle)] != ' ' && found[strlen(needle)] != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
|
||||
(const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
if (!glXWaitVideoSyncSGI) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void *sgi_video_sync_thread(void *data) {
|
||||
auto args = (struct sgi_video_sync_thread_args *)data;
|
||||
auto self = args->self;
|
||||
Display *dpy = XOpenDisplay(NULL);
|
||||
int error_code = 0;
|
||||
if (!dpy) {
|
||||
error_code = 1;
|
||||
goto start_failed;
|
||||
}
|
||||
Window root = DefaultRootWindow(dpy), dummy = None;
|
||||
int screen = DefaultScreen(dpy);
|
||||
int ncfg = 0;
|
||||
GLXFBConfig *cfg_ = glXChooseFBConfig(
|
||||
dpy, screen,
|
||||
(int[]){GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 0},
|
||||
&ncfg);
|
||||
GLXContext ctx = NULL;
|
||||
GLXDrawable drawable = None;
|
||||
|
||||
if (!cfg_) {
|
||||
error_code = 2;
|
||||
goto start_failed;
|
||||
}
|
||||
GLXFBConfig cfg = cfg_[0];
|
||||
XFree(cfg_);
|
||||
|
||||
XVisualInfo *vi = glXGetVisualFromFBConfig(dpy, cfg);
|
||||
if (!vi) {
|
||||
error_code = 3;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
Visual *visual = vi->visual;
|
||||
const int depth = vi->depth;
|
||||
XFree(vi);
|
||||
|
||||
Colormap colormap = XCreateColormap(dpy, root, visual, AllocNone);
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.colormap = colormap;
|
||||
|
||||
dummy = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual,
|
||||
CWColormap, &attributes);
|
||||
XFreeColormap(dpy, colormap);
|
||||
if (dummy == None) {
|
||||
error_code = 4;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
drawable = glXCreateWindow(dpy, cfg, dummy, NULL);
|
||||
if (drawable == None) {
|
||||
error_code = 5;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
ctx = glXCreateNewContext(dpy, cfg, GLX_RGBA_TYPE, 0, true);
|
||||
if (ctx == NULL) {
|
||||
error_code = 6;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
if (!glXMakeContextCurrent(dpy, drawable, drawable, ctx)) {
|
||||
error_code = 7;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
if (!check_sgi_video_sync_extension(dpy, screen)) {
|
||||
error_code = 8;
|
||||
goto start_failed;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&args->start_mtx);
|
||||
args->start_status = 0;
|
||||
pthread_cond_signal(&args->start_cnd);
|
||||
pthread_mutex_unlock(&args->start_mtx);
|
||||
|
||||
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||
while (self->running) {
|
||||
if (!self->base.vblank_event_requested) {
|
||||
pthread_cond_wait(&self->vblank_requested_cnd,
|
||||
&self->vblank_requested_mtx);
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||
|
||||
unsigned int last_msc;
|
||||
glXWaitVideoSyncSGI(1, 0, &last_msc);
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
atomic_store(&self->last_msc, last_msc);
|
||||
atomic_store(&self->last_ust,
|
||||
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
|
||||
ev_async_send(self->base.loop, &self->notify);
|
||||
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||
}
|
||||
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||
goto cleanup;
|
||||
|
||||
start_failed:
|
||||
pthread_mutex_lock(&args->start_mtx);
|
||||
args->start_status = error_code;
|
||||
pthread_cond_signal(&args->start_cnd);
|
||||
pthread_mutex_unlock(&args->start_mtx);
|
||||
|
||||
cleanup:
|
||||
if (dpy) {
|
||||
glXMakeCurrent(dpy, None, NULL);
|
||||
if (ctx) {
|
||||
glXDestroyContext(dpy, ctx);
|
||||
}
|
||||
if (drawable) {
|
||||
glXDestroyWindow(dpy, drawable);
|
||||
}
|
||||
if (dummy) {
|
||||
XDestroyWindow(dpy, dummy);
|
||||
}
|
||||
XCloseDisplay(dpy);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
|
||||
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||
log_verbose("Requesting vblank event for msc %d", self->last_msc + 1);
|
||||
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||
assert(!base->vblank_event_requested);
|
||||
base->vblank_event_requested = true;
|
||||
pthread_cond_signal(&self->vblank_requested_cnd);
|
||||
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||
}
|
||||
|
||||
static void
|
||||
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
|
||||
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
|
||||
auto event = (struct vblank_event){
|
||||
.msc = atomic_load(&sched->last_msc),
|
||||
.ust = atomic_load(&sched->last_ust),
|
||||
};
|
||||
sched->base.vblank_event_requested = false;
|
||||
log_verbose("Received vblank event for msc %lu", event.msc);
|
||||
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
||||
}
|
||||
|
||||
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
|
||||
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||
auto args = (struct sgi_video_sync_thread_args){
|
||||
.self = self,
|
||||
.start_status = -1,
|
||||
};
|
||||
pthread_mutex_init(&args.start_mtx, NULL);
|
||||
pthread_cond_init(&args.start_cnd, NULL);
|
||||
|
||||
base->type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||
ev_async_init(&self->notify, sgi_video_sync_scheduler_callback);
|
||||
ev_async_start(base->loop, &self->notify);
|
||||
pthread_mutex_init(&self->vblank_requested_mtx, NULL);
|
||||
pthread_cond_init(&self->vblank_requested_cnd, NULL);
|
||||
|
||||
self->running = true;
|
||||
pthread_create(&self->sync_thread, NULL, sgi_video_sync_thread, &args);
|
||||
|
||||
pthread_mutex_lock(&args.start_mtx);
|
||||
while (args.start_status == -1) {
|
||||
pthread_cond_wait(&args.start_cnd, &args.start_mtx);
|
||||
}
|
||||
if (args.start_status != 0) {
|
||||
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
|
||||
args.start_status);
|
||||
abort();
|
||||
}
|
||||
pthread_mutex_destroy(&args.start_mtx);
|
||||
pthread_cond_destroy(&args.start_cnd);
|
||||
log_info("Started sgi_video_sync_thread");
|
||||
}
|
||||
|
||||
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
|
||||
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||
ev_async_stop(base->loop, &self->notify);
|
||||
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||
self->running = false;
|
||||
pthread_cond_signal(&self->vblank_requested_cnd);
|
||||
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||
|
||||
pthread_join(self->sync_thread, NULL);
|
||||
|
||||
pthread_mutex_destroy(&self->vblank_requested_mtx);
|
||||
pthread_cond_destroy(&self->vblank_requested_cnd);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
|
||||
auto self = (struct present_vblank_scheduler *)base;
|
||||
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
|
||||
@@ -170,11 +421,22 @@ static bool handle_present_events(struct vblank_scheduler *base) {
|
||||
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
|
||||
[VBLANK_SCHEDULER_PRESENT] =
|
||||
{
|
||||
.size = sizeof(struct present_vblank_scheduler),
|
||||
.init = present_vblank_scheduler_init,
|
||||
.deinit = present_vblank_scheduler_deinit,
|
||||
.schedule = present_vblank_scheduler_schedule,
|
||||
.handle_x_events = handle_present_events,
|
||||
},
|
||||
#ifdef CONFIG_OPENGL
|
||||
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] =
|
||||
{
|
||||
.size = sizeof(struct sgi_video_sync_vblank_scheduler),
|
||||
.init = sgi_video_sync_scheduler_init,
|
||||
.deinit = sgi_video_sync_scheduler_deinit,
|
||||
.schedule = sgi_video_sync_scheduler_schedule,
|
||||
.handle_x_events = NULL,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
|
||||
@@ -239,13 +501,22 @@ void vblank_scheduler_free(struct vblank_scheduler *self) {
|
||||
free(self);
|
||||
}
|
||||
|
||||
struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||
xcb_window_t target_window) {
|
||||
struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler));
|
||||
struct vblank_scheduler *
|
||||
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||
xcb_window_t target_window, enum vblank_scheduler_type type) {
|
||||
size_t object_size = vblank_scheduler_ops[type].size;
|
||||
auto init_fn = vblank_scheduler_ops[type].init;
|
||||
if (!object_size || !init_fn) {
|
||||
log_error("Unsupported or invalid vblank scheduler type: %d", type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(object_size >= sizeof(struct vblank_scheduler));
|
||||
struct vblank_scheduler *self = calloc(1, object_size);
|
||||
self->target_window = target_window;
|
||||
self->c = c;
|
||||
self->loop = loop;
|
||||
vblank_scheduler_ops[VBLANK_SCHEDULER_PRESENT].init(self);
|
||||
init_fn(self);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <ev.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "x.h"
|
||||
|
||||
/// An object that schedule vblank events.
|
||||
@@ -31,7 +32,8 @@ typedef void (*vblank_callback_t)(struct vblank_event *event, void *user_data);
|
||||
bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb,
|
||||
void *user_data);
|
||||
struct vblank_scheduler *
|
||||
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, xcb_window_t target_window);
|
||||
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||
xcb_window_t target_window, enum vblank_scheduler_type type);
|
||||
void vblank_scheduler_free(struct vblank_scheduler *);
|
||||
|
||||
bool vblank_handle_x_events(struct vblank_scheduler *self);
|
||||
|
||||
329
src/win.c
329
src/win.c
@@ -302,6 +302,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);
|
||||
@@ -398,6 +405,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)) {
|
||||
@@ -465,6 +473,172 @@ 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;
|
||||
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;
|
||||
}
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -505,8 +679,79 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
|
||||
add_damage_from_win(ps, w);
|
||||
}
|
||||
|
||||
// Update window geometry
|
||||
|
||||
|
||||
// Determine if a window should animate
|
||||
if (win_should_animate(ps, w)) {
|
||||
if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
||||
w->dwm_mask = ANIM_PREV_TAG;
|
||||
else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
||||
w->dwm_mask = ANIM_NEXT_TAG;
|
||||
|
||||
if (!was_visible || w->dwm_mask) {
|
||||
|
||||
// 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);
|
||||
@@ -818,6 +1063,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;
|
||||
@@ -882,6 +1131,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.
|
||||
*
|
||||
@@ -1530,9 +1797,14 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
||||
.state = WSTATE_UNMAPPED, // updated by window state changes
|
||||
.in_openclose = true, // set to false after first map is done,
|
||||
// true here because window is just created
|
||||
.animation_velocity_x = 0.0, // updated by window geometry changes
|
||||
.animation_velocity_y = 0.0, // updated by window geometry changes
|
||||
.animation_velocity_w = 0.0, // updated by window geometry changes
|
||||
.animation_velocity_h = 0.0, // updated by window geometry changes
|
||||
.animation_progress = 1.0, // updated by window geometry changes
|
||||
.animation_inv_og_distance = NAN, // updated by window geometry changes
|
||||
.reg_ignore_valid = false, // set to true when damaged
|
||||
.flags = WIN_FLAGS_IMAGES_NONE, // updated by
|
||||
// property/attributes/etc
|
||||
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
|
||||
// change
|
||||
.stale_props = NULL,
|
||||
.stale_props_capacity = 0,
|
||||
@@ -1559,6 +1831,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,
|
||||
@@ -2121,17 +2394,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).
|
||||
@@ -2400,6 +2687,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) {
|
||||
@@ -2445,7 +2756,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;
|
||||
}
|
||||
@@ -2457,11 +2768,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)",
|
||||
@@ -2470,6 +2782,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);
|
||||
}
|
||||
@@ -2869,5 +3182,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);
|
||||
}
|
||||
|
||||
37
src/win.h
37
src/win.h
@@ -96,11 +96,25 @@ struct win_geometry {
|
||||
uint16_t border_width;
|
||||
};
|
||||
|
||||
enum {
|
||||
// dwm_mask
|
||||
ANIM_PREV_TAG = 1,
|
||||
ANIM_NEXT_TAG = (1 << 1),
|
||||
ANIM_UNMAP = (1 << 2),
|
||||
ANIM_SPECIAL_MINIMIZE = (1 << 3),
|
||||
// animation_is_tag
|
||||
ANIM_IN_TAG = 1,
|
||||
ANIM_SLOW = (1 << 1),
|
||||
ANIM_FAST = (1 << 2),
|
||||
ANIM_FADE = (1 << 3),
|
||||
};
|
||||
|
||||
struct managed_win {
|
||||
struct win base;
|
||||
/// backend data attached to this window. Only available when
|
||||
/// `state` is not UNMAPPED
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -459,6 +493,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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user