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