From 8a373c38a631e0344c38d3b19ab673aacfbaf1f5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 10 Oct 2022 13:55:05 +0100 Subject: [PATCH 01/64] win: fix leak in win_bind_mask Related: #905 Signed-off-by: Yuxuan Shui --- src/win.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/win.c b/src/win.c index d97597e..2ad6e86 100644 --- a/src/win.c +++ b/src/win.c @@ -350,6 +350,8 @@ bool win_bind_mask(struct backend_base *b, struct managed_win *w) { pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); w->mask_image = b->ops->make_mask( b, (geometry_t){.width = w->g.width, .height = w->g.height}, ®_bound_local); + pixman_region32_fini(®_bound_local); + if (!w->mask_image) { return false; } From 2328b97c2329270fa04b89f8a4bcd046f8a1a4b6 Mon Sep 17 00:00:00 2001 From: Stefan Radziuk Date: Wed, 1 Dec 2021 22:51:57 +0000 Subject: [PATCH 02/64] Exclude transparent-clipping-excluded windows from updating the ignored region Transparent clipping interacts poorly with programs whose transparent interface elements must show windows below them for functionality, for example screenshot utilities. --- man/picom.1.asciidoc | 3 +++ picom.sample.conf | 6 ++++++ src/config.h | 3 +++ src/config_libconfig.c | 3 +++ src/options.c | 11 +++++++++++ src/picom.c | 2 +- src/win.c | 4 ++++ src/win.h | 3 +++ 8 files changed, 34 insertions(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 82085f2..17de2c8 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -259,6 +259,9 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--transparent-clipping*:: Make transparent windows clip other windows like non-transparent windows do, instead of blending on top of them. +*--transparent-clipping-exclude* 'CONDITION':: + 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 see through transparent parts of the window. + *--window-shader-fg* 'SHADER':: Specify GLSL fragment shader path for rendering window contents. Does not work when *--legacy-backends* is enabled. Shader is searched first relative to the directory the configuration file is in, then in the usual places for a configuration file. See section *SHADER INTERFACE* below for more details on the interface. diff --git a/picom.sample.conf b/picom.sample.conf index 59255d0..071a994 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -355,6 +355,12 @@ use-damage = true; # # 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 +# see through transparent parts of the window. +# +# transparent-clipping-exclude = [] + # Set the log level. Possible values are: # "trace", "debug", "info", "warn", "error" # in increasing level of importance. Case doesn't matter. diff --git a/src/config.h b/src/config.h index e1dc2dd..7259dc1 100644 --- a/src/config.h +++ b/src/config.h @@ -253,6 +253,9 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + /// A list of conditions of windows to which transparent clipping + /// should not apply + c2_lptr_t *transparent_clipping_blacklist; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index e3c70ad..461fff3 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -452,6 +452,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen); // --transparent-clipping lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping); + // --transparent-clipping-exclude + parse_cfg_condlst(&cfg, &opt->transparent_clipping_blacklist, + "transparent-clipping-exclude"); // --shadow-exclude parse_cfg_condlst(&cfg, &opt->shadow_blacklist, "shadow-exclude"); // --clip-shadow-above diff --git a/src/options.c b/src/options.c index 3186ddc..9f6888f 100644 --- a/src/options.c +++ b/src/options.c @@ -348,6 +348,11 @@ static void usage(const char *argv0, int ret) { " Make transparent windows clip other windows like non-transparent windows\n" " do, instead of blending on top of them\n" "\n" + "--transparent-clipping-exclude condition\n" + " Specify a list of conditions of windows that should never have\n" + " transparent clipping applied. Useful for screenshot tools, where you\n" + " need to be able to see through transparent parts of the window.\n" + "\n" "--window-shader-fg shader\n" " Specify GLSL fragment shader path for rendering window contents. Does\n" " not work when `--legacy-backends` is enabled.\n" @@ -455,6 +460,7 @@ static const struct option longopts[] = { {"clip-shadow-above", required_argument, NULL, 335}, {"window-shader-fg", required_argument, NULL, 336}, {"window-shader-fg-rule", required_argument, NULL, 337}, + {"transparent-clipping-exclude", required_argument, NULL, 338}, {"legacy-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -823,6 +829,11 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } break; } + case 338: { + // --transparent-clipping-exclude + condlst_add(&opt->transparent_clipping_blacklist, optarg); + break; + } case 321: { enum log_level tmp_level = string_to_log_level(optarg); if (tmp_level == LOG_LEVEL_INVALID) { diff --git a/src/picom.c b/src/picom.c index bac03b0..e4a5607 100644 --- a/src/picom.c +++ b/src/picom.c @@ -831,7 +831,7 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { // we add the window region to the ignored region // Otherwise last_reg_ignore shouldn't change if ((w->mode != WMODE_TRANS && !ps->o.force_win_blend) || - ps->o.transparent_clipping) { + (ps->o.transparent_clipping && !w->transparent_clipping_excluded)) { // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS region_t *tmp = rc_region_new(); if (w->mode == WMODE_SOLID) { diff --git a/src/win.c b/src/win.c index 2ad6e86..370fbfd 100644 --- a/src/win.c +++ b/src/win.c @@ -1225,6 +1225,9 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { w->fade_excluded = c2_match(ps, w, ps->o.fade_blacklist, NULL); + w->transparent_clipping_excluded = + c2_match(ps, w, ps->o.transparent_clipping_blacklist, NULL); + win_update_opacity_target(ps, w); w->reg_ignore_valid = false; @@ -1567,6 +1570,7 @@ struct win *fill_win(session_t *ps, struct win *w) { .rounded_corners = false, .paint_excluded = false, .fade_excluded = false, + .transparent_clipping_excluded = false, .unredir_if_possible_excluded = false, .prop_shadow = -1, // following 4 are set in win_mark_client diff --git a/src/win.h b/src/win.h index 8b79cb1..abc03d9 100644 --- a/src/win.h +++ b/src/win.h @@ -232,6 +232,9 @@ struct managed_win { /// Whether fading is excluded by the rules. Calculated. bool fade_excluded; + /// Whether transparent clipping is excluded by the rules. + bool transparent_clipping_excluded; + // Frame-opacity-related members /// Current window frame opacity. Affected by window opacity. double frame_opacity; From b7434c7b76f855349deddb74b03c246a09663fee Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Mon, 24 Oct 2022 17:29:52 +0300 Subject: [PATCH 03/64] small changes --- src/config.h | 1 - src/win.c | 29 +++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/config.h b/src/config.h index a58321f..b91a5ef 100644 --- a/src/config.h +++ b/src/config.h @@ -53,7 +53,6 @@ enum open_window_animation { 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, diff --git a/src/win.c b/src/win.c index 5c6f197..0b0fd45 100644 --- a/src/win.c +++ b/src/win.c @@ -673,8 +673,17 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { add_damage_from_win(ps, w); } + + // Determine if a window should animate if (win_should_animate(ps, w)) { + if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + win_on_win_size_change(ps, w); + win_update_bounding_shape(ps, w); + damaged = true; + win_clear_flags(w, WIN_FLAGS_SIZE_STALE); + } + 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) @@ -746,12 +755,12 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { w->g = w->pending_g; } - if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { - win_on_win_size_change(ps, w); - win_update_bounding_shape(ps, w); - damaged = true; - win_clear_flags(w, WIN_FLAGS_SIZE_STALE); - } + if (!win_should_animate(ps, w) && win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + win_on_win_size_change(ps, w); + win_update_bounding_shape(ps, w); + damaged = true; + win_clear_flags(w, WIN_FLAGS_SIZE_STALE); + } if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) { damaged = true; @@ -3027,6 +3036,10 @@ win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_windo void win_set_flags(struct managed_win *w, uint64_t flags) { log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); if (unlikely(w->state == WSTATE_DESTROYING)) { + if (w->animation_progress != 1.0) { + // Return because animation will trigger some of the flags + return; + } log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name); return; } @@ -3039,6 +3052,10 @@ void win_clear_flags(struct managed_win *w, uint64_t flags) { log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id, w->name); if (unlikely(w->state == WSTATE_DESTROYING)) { + if (w->animation_progress != 1.0) { + // Return because animation will trigger some of the flags + return; + } log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id, w->name); return; From 23c151c8dc87bb59d8489a9b8e0448c051b380ac Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Tue, 25 Oct 2022 01:24:44 +0300 Subject: [PATCH 04/64] small changes --- src/win.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win.c b/src/win.c index 0b0fd45..7553c6b 100644 --- a/src/win.c +++ b/src/win.c @@ -689,7 +689,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { 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)) { + if (!was_visible || w->dwm_mask) { // Set window-open animation init_animation(ps, w); From 70c729d3899e64e72259b7fecd2d4187dfae6069 Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Tue, 4 Oct 2022 00:24:05 +0300 Subject: [PATCH 05/64] picom upto date sync with yshui, full anim support --- .gitignore | 1 + picom.sample.conf | 192 +++++++++++---------- src/atom.h | 3 +- src/backend/backend.c | 138 ++++++++++----- src/backend/backend.h | 5 + src/backend/gl/gl_common.c | 10 +- src/common.h | 6 + src/config.c | 53 +++++- src/config.h | 55 ++++++ src/config_libconfig.c | 73 ++++++++ src/event.c | 9 + src/options.c | 84 ++++++++++ src/picom.c | 292 +++++++++++++++++++++++++++----- src/utils.h | 14 ++ src/win.c | 332 +++++++++++++++++++++++++++++++++++-- src/win.h | 37 +++++ 16 files changed, 1108 insertions(+), 196 deletions(-) diff --git a/.gitignore b/.gitignore index 20b39d0..7b28f94 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ compton build/ compile_commands.json build.ninja +make.sh # language servers .ccls-cache diff --git a/picom.sample.conf b/picom.sample.conf index 071a994..f357a26 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -1,29 +1,76 @@ +################################# +# Animations # + +# !These animations WILL NOT work correctly for any other wm other than phyOS-dwm fork! + +# fly-in: Windows fly in from random directions to the screen +# maximize: Windows pop from center of the screen to their respective positions +# minimize: Windows minimize from their position to the center of the screen +# slide-in-center: Windows move from upper-center of the screen to their respective positions +# slide-out-center: Windows move to the upper-center of the screen +# slide-left: Windows are created from the right-most window position and slide leftwards +# slide right: Windows are created from the left-most window position and slide rightwards +# slide-down: Windows are moved from the top of the screen and slide downward +# slide-up: Windows are moved from their position to top of the screen +# squeeze: Windows are either closed or created to/from their center y-position (the animation is similar to a blinking eye) +# squeeze-bottom: Similar to squeeze, but the animation starts from bottom-most y-position +# zoom: Windows are either created or destroyed from/to their center (not the screen center) + +################################# + +#enable or disable animations +animations = true; +#change animation speed of windows in current tag e.g open window in current tag +animation-stiffness-in-tag = 125; +#change animation speed of windows when tag changes +animation-stiffness-tag-change = 90.0; + +animation-window-mass = 0.4; +animation-dampening = 15; +animation-clamping = true; + +#open windows +animation-for-open-window = "zoom"; +#minimize or close windows +animation-for-unmap-window = "squeeze"; +#popup windows +animation-for-transient-window = "slide-up"; #available options: slide-up, slide-down, slide-left, slide-right, squeeze, squeeze-bottom, zoom + +#set animation for windows being transitioned out while changings tags +animation-for-prev-tag = "minimize"; +#enables fading for windows being transitioned out while changings tags +enable-fading-prev-tag = true; + +#set animation for windows being transitioned in while changings tags +animation-for-next-tag = "slide-in-center"; +#enables fading for windows being transitioned in while changings tags +enable-fading-next-tag = true; + ################################# # Shadows # ################################# - # Enabled client-side shadows on windows. Note desktop windows # (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow, # unless explicitly requested using the wintypes option. # # shadow = false -shadow = true; +shadow = false; # The blur radius for shadows, in pixels. (defaults to 12) # shadow-radius = 12 -shadow-radius = 7; +shadow-radius = 60; # The opacity of shadows. (0.0 - 1.0, defaults to 0.75) # shadow-opacity = .75 # The left offset for shadows, in pixels. (defaults to -15) # shadow-offset-x = -15 -shadow-offset-x = -7; +shadow-offset-x = -20; # The top offset for shadows, in pixels. (defaults to -15) # shadow-offset-y = -15 -shadow-offset-y = -7; +shadow-offset-y = -20; # Red color value of shadow (0.0 - 1.0, defaults to 0). # shadow-red = 0 @@ -48,6 +95,9 @@ shadow-exclude = [ "class_g = 'Conky'", "class_g ?= 'Notify-osd'", "class_g = 'Cairo-clock'", + "class_g = 'dwm'", + "class_g = 'chromium'", + "class_g *?= 'slop'", "_GTK_FRAME_EXTENTS@:c" ]; @@ -72,19 +122,18 @@ shadow-exclude = [ # Fade windows in/out when opening/closing and when opacity changes, # unless no-fading-openclose is used. -# fading = false fading = true; # Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028) # fade-in-step = 0.028 -fade-in-step = 0.03; +fade-in-step = 0.023; # Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03) # fade-out-step = 0.03 -fade-out-step = 0.03; +fade-out-step = 0.035; # The time between steps in fade step, in milliseconds. (> 0, defaults to 10) -# fade-delta = 10 +fade-delta = 10 # Specify a list of conditions of windows that should not be faded. # fade-exclude = [] @@ -103,15 +152,13 @@ fade-out-step = 0.03; # Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0) # inactive-opacity = 1 -inactive-opacity = 0.8; # Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default) # frame-opacity = 1.0 -frame-opacity = 0.7; # Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows. # inactive-opacity-override = true -inactive-opacity-override = false; +inactive-opacity-override = true; # Default opacity for active windows. (0.0 - 1.0, defaults to 1.0) # active-opacity = 1.0 @@ -121,21 +168,13 @@ inactive-opacity-override = false; # Specify a list of conditions of windows that should never be considered focused. # focus-exclude = [] -focus-exclude = [ "class_g = 'Cairo-clock'" ]; +focus-exclude = [ +"class_g = 'Cairo-clock'" , +]; # Use fixed inactive dim value, instead of adjusting according to window opacity. # inactive-dim-fixed = 1.0 -# Specify a list of opacity rules, in the format `PERCENT:PATTERN`, -# like `50:name *= "Firefox"`. picom-trans is recommended over this. -# Note we don't make any guarantee about possible conflicts with other -# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows. -# example: -# opacity-rule = [ "80:class_g = 'URxvt'" ]; -# -# opacity-rule = [] - - ################################# # Corners # ################################# @@ -143,52 +182,21 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # Sets the radius of rounded window corners. When > 0, the compositor will # round the corners of windows. Does not interact well with # `transparent-clipping`. -corner-radius = 0 +corner-radius = 11; # Exclude conditions for rounded corners. -rounded-corners-exclude = [ - "window_type = 'dock'", - "window_type = 'desktop'" -]; +#rounded-corners-exclude = [ +# "window_type = 'dock'", +# "window_type = 'desktop'" +#]; - -################################# -# Background-Blurring # -################################# - - -# Parameters for background blurring, see the *BLUR* section for more information. -# blur-method = -# blur-size = 12 -# -# blur-deviation = false -# -# blur-strength = 5 - -# Blur background of semi-transparent / ARGB windows. -# Bad in performance, with driver-dependent behavior. -# The name of the switch may change without prior notifications. -# -# blur-background = false - -# Blur background of windows when the window frame is not opaque. -# Implies: -# blur-background -# Bad in performance, with driver-dependent behavior. The name may change. -# -# blur-background-frame = false - - -# Use fixed blur strength rather than adjusting according to window opacity. -# blur-background-fixed = false - - -# Specify the blur convolution kernel, with the following format: -# example: -# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"; -# -# blur-kern = "" -blur-kern = "3x3box"; +blur: { + method = "dual_kawase"; + strength = 9; + background = true; + background-frame = false; + background-fixed = false; +} # Exclude conditions for background blur. @@ -196,28 +204,28 @@ blur-kern = "3x3box"; blur-background-exclude = [ "window_type = 'dock'", "window_type = 'desktop'", - "_GTK_FRAME_EXTENTS@:c" + "_GTK_FRAME_EXTENTS@:c", + "class_g = 'Chromium'", + "class_g = 'Discord'", + "class_g = 'Dunst'", + "class_g = 'Peek'", + "class_g *?= 'slop'", ]; ################################# # General Settings # ################################# -# Enable remote control via D-Bus. See the man page for more details. -# dbus = true - # Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. # daemon = false # Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. # `xrender` is the default one. # -# backend = "glx" -backend = "xrender"; +backend = "glx" # Enable/disable VSync. -# vsync = false -vsync = true; +# vsync = true # Enable remote control via D-Bus. See the *D-BUS API* section below for more details. # dbus = false @@ -236,25 +244,25 @@ mark-ovredir-focused = true; # shaped windows. The accuracy is not very high, unfortunately. # # detect-rounded-corners = false -detect-rounded-corners = true; +detect-rounded-corners = false; # Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers # not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows. # # detect-client-opacity = false -detect-client-opacity = true; +detect-client-opacity = false; # Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window, # rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy, # provided that the WM supports it. # -# use-ewmh-active-win = false +use-ewmh-active-win = true; # Unredirect all windows if a full-screen opaque window is detected, # to maximize performance for full-screen windows. Known to cause flickering # when redirecting/unredirecting windows. # -# unredir-if-possible = false +unredir-if-possible = false; # Delay before unredirecting the window, in milliseconds. Defaults to 0. # unredir-if-possible-delay = 0 @@ -298,7 +306,7 @@ detect-transient = true; # practically happened) and may not work with blur-background. # My tests show a 15% performance boost. Recommended. # -# glx-no-stencil = false +glx-no-stencil = true; # GLX backend: Avoid rebinding pixmap on window damage. # Probably could improve performance on rapid window content changes, @@ -319,24 +327,18 @@ use-damage = true; # calls are finished before picom starts drawing. Needed on nvidia-drivers # with GLX backend for some users. # -# xrender-sync-fence = false +xrender-sync-fence = true; -# GLX backend: Use specified GLSL fragment shader for rendering window -# contents. Read the man page for a detailed explanation of the interface. +# GLX backend: Use specified GLSL fragment shader for rendering window contents. +# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` +# in the source tree for examples. # -# window-shader-fg = "default" - -# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar -# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg. -# -# window-shader-fg-rule = [ -# "my_shader.frag:window_type != 'dock'" -# ] +window-shader-fg = "default"; # Force all windows to be painted with blending. Useful if you # have a glx-fshader-win that could turn opaque pixels transparent. # -# force-win-blend = false +# force-win-blend = true; # Do not use EWMH to detect fullscreen windows. # Reverts to checking if a window is fullscreen based only on its size and coordinates. @@ -353,7 +355,7 @@ use-damage = true; # Make transparent windows clip other windows like non-transparent windows do, # instead of blending on top of them. # -# transparent-clipping = false +transparent-clipping = false; # 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", +]; diff --git a/src/atom.h b/src/atom.h index 6f4eae6..0a086e1 100644 --- a/src/atom.h +++ b/src/atom.h @@ -23,7 +23,8 @@ WM_CLIENT_MACHINE, \ _NET_ACTIVE_WINDOW, \ _COMPTON_SHADOW, \ - _NET_WM_WINDOW_TYPE + _NET_WM_WINDOW_TYPE, \ + _NET_CURRENT_MON_CENTER #define ATOM_LIST2 \ _NET_WM_WINDOW_TYPE_DESKTOP, \ diff --git a/src/backend/backend.c b/src/backend/backend.c index 032c301..6cfe7fb 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -55,6 +55,58 @@ region_t get_damage(session_t *ps, bool all_damage) { return region; } +static void process_window_for_painting(session_t *ps, struct managed_win *w, + void *win_image, double additional_alpha, + region_t *reg_bound, region_t *reg_visible, + region_t *reg_paint, region_t *reg_paint_in_bound) { + // For window image processing, we don't have to limit the process + // region to damage for correctness. (see for + // details) + + // The visible region, in window local coordinates Although we + // don't limit process region to damage, we provide that info in + // reg_visible as a hint. Since window image data outside of the + // damage region won't be painted onto target + coord_t window_coord = {.x = w->g.x, .y = w->g.y}; + coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb}; + + region_t reg_visible_local; + { + // The bounding shape, in window local coordinates + region_t reg_bound_local; + pixman_region32_init(®_bound_local); + pixman_region32_copy(®_bound_local, reg_bound); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + + pixman_region32_init(®_visible_local); + pixman_region32_intersect(®_visible_local, reg_visible, reg_paint); + pixman_region32_translate(®_visible_local, -w->g.x, -w->g.y); + // Data outside of the bounding shape won't be visible, + // but it is not necessary to limit the image operations + // to the bounding shape yet. So pass that as the visible + // region, not the clip region. + pixman_region32_intersect(®_visible_local, ®_visible_local, + ®_bound_local); + pixman_region32_fini(®_bound_local); + } + + auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image, + ®_visible_local); + auto reg_frame = win_get_region_frame_local_by_val(w); + double alpha = additional_alpha * w->opacity; + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha); + ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, + ®_frame, ®_visible_local, + (double[]){w->frame_opacity}); + pixman_region32_fini(®_frame); + ps->backend_data->ops->compose(ps->backend_data, new_img, + window_coord, NULL, dest_coord, + reg_paint_in_bound, reg_visible); + ps->backend_data->ops->release_image(ps->backend_data, new_img); + pixman_region32_fini(®_visible_local); +} + void handle_device_reset(session_t *ps) { log_error("Device reset detected"); // Wait for reset to complete @@ -185,7 +237,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { if (ps->root_image) { ps->backend_data->ops->compose(ps->backend_data, ps->root_image, - (coord_t){0}, NULL, (coord_t){0}, + (coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height}, ®_paint, ®_visible); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, @@ -237,6 +289,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { * option */ auto real_win_mode = w->mode; coord_t window_coord = {.x = w->g.x, .y = w->g.y}; + coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb}; if (w->blur_background && (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || @@ -409,6 +462,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH, w->win_image, &border_width); + if (w->old_win_image) { + // TODO(dccsillag): explain why the following is + // "necessary" + double zero = 0.0; + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH, + w->old_win_image, &zero); + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, + w->old_win_image, &zero); + } } ps->backend_data->ops->set_image_property( @@ -431,53 +495,43 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { } // Draw window on target - if (w->frame_opacity == 1) { + bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0; + if (w->frame_opacity == 1 && !is_animating) { ps->backend_data->ops->compose(ps->backend_data, w->win_image, - window_coord, NULL, window_coord, + window_coord, NULL, dest_coord, ®_paint_in_bound, ®_visible); } else { - // For window image processing, we don't have to limit the process - // region to damage for correctness. (see for - // details) + if (is_animating && w->old_win_image) { + bool is_focused = win_is_focused_raw(ps, w); + if (!is_focused && w->focused && w->opacity_is_set) + is_focused = true; + assert(w->old_win_image); - // The visible region, in window local coordinates Although we - // don't limit process region to damage, we provide that info in - // reg_visible as a hint. Since window image data outside of the - // damage region won't be painted onto target - region_t reg_visible_local; - region_t reg_bound_local; - { - // The bounding shape, in window local coordinates - pixman_region32_init(®_bound_local); - pixman_region32_copy(®_bound_local, ®_bound); - pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + bool resizing = + w->g.width != w->pending_g.width || + w->g.height != w->pending_g.height; - pixman_region32_init(®_visible_local); - pixman_region32_intersect(®_visible_local, - ®_visible, ®_paint); - pixman_region32_translate(®_visible_local, -w->g.x, - -w->g.y); - // Data outside of the bounding shape won't be visible, - // but it is not necessary to limit the image operations - // to the bounding shape yet. So pass that as the visible - // region, not the clip region. - pixman_region32_intersect( - ®_visible_local, ®_visible_local, ®_bound_local); + // Only animate opacity here if we are resizing + // a transparent window + process_window_for_painting(ps, w, w->win_image, + is_focused ? 1.0 : w->opacity >= 1 ? 1.0 : w->animation_progress, + ®_bound, ®_visible, + ®_paint, ®_paint_in_bound); + + // Only do this if size changes as otherwise moving + // transparent windows will flicker and if you just + // move so slightly they will keep flickering + if (resizing && (!is_focused || !w->opacity_is_set)) { + process_window_for_painting(ps, w, w->old_win_image, + 1.0 - w->animation_progress, + ®_bound, ®_visible, + ®_paint, ®_paint_in_bound); + } + } else { + process_window_for_painting( + ps, w, w->win_image, 1.0, ®_bound, ®_visible, + ®_paint, ®_paint_in_bound); } - - auto new_img = ps->backend_data->ops->clone_image( - ps->backend_data, w->win_image, ®_visible_local); - auto reg_frame = win_get_region_frame_local_by_val(w); - ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, - ®_visible_local, (double[]){w->frame_opacity}); - pixman_region32_fini(®_frame); - ps->backend_data->ops->compose(ps->backend_data, new_img, - window_coord, NULL, window_coord, - ®_paint_in_bound, ®_visible); - ps->backend_data->ops->release_image(ps->backend_data, new_img); - pixman_region32_fini(®_visible_local); - pixman_region32_fini(®_bound_local); } skip: pixman_region32_fini(®_bound); diff --git a/src/backend/backend.h b/src/backend/backend.h index 191e814..3d620f8 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -183,6 +183,11 @@ struct backend_operations { void *mask, coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible); + void (*_compose)(backend_t *backend_data, void *image_data, + int dst_x1, int dst_y1, int dst_x2, int dst_y2, + const region_t *reg_paint, const region_t *reg_visible); + + /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. void (*fill)(backend_t *backend_data, struct color, const region_t *clip); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 7ade233..ff6091c 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -412,7 +412,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe } glUniform1i(win_shader->uniform_mask_tex, 2); - glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x, + glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x , (float)mask_offset.y); if (mask != NULL) { glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted); @@ -494,7 +494,8 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int extent_height, int texture_height, int root_height, bool y_inverted, GLint *coord, GLuint *indices) { image_dst.y = root_height - image_dst.y; - image_dst.y -= extent_height; + image_dst.y -= extent_height; + for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 @@ -573,6 +574,11 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y}; x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height, gd->height, inner->y_inverted, coord, indices); + for (unsigned int i = 2; i < 16; i+=4) { + coord[i+0] = lerp_range(0, mask_offset.x, 0, inner->width, coord[i+0]); + coord[i+1] = lerp_range(0, mask_offset.y, 0, inner->height, coord[i+1]); + } + _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects); free(indices); diff --git a/src/common.h b/src/common.h index c06a30c..efc84b6 100644 --- a/src/common.h +++ b/src/common.h @@ -147,6 +147,8 @@ typedef struct session { ev_timer unredir_timer; /// Timer for fading ev_timer fade_timer; + /// Timer for animations + ev_timer animation_timer; /// Use an ev_idle callback for drawing /// So we only start drawing when events are processed ev_idle draw_idle; @@ -196,6 +198,8 @@ typedef struct session { int root_width; // Damage of root window. // Damage root_damage; + int selmon_center_x; + int selmon_center_y; /// X Composite overlay window. xcb_window_t overlay; /// The target window for debug mode @@ -255,6 +259,8 @@ typedef struct session { xcb_render_picture_t *alpha_picts; /// Time of last fading. In milliseconds. long long fade_time; + /// Time of last window animation step. In milliseconds. + long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time /// Head pointer of the error ignore linked list. ignore_t *ignore_head; /// Pointer to the next member of tail element of the error diff --git a/src/config.c b/src/config.c index d9b2dd9..e58cfbd 100644 --- a/src/config.c +++ b/src/config.c @@ -708,6 +708,10 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en // opacity logic is complicated, and needs an "unset" state opt->wintype_option[i].opacity = NAN; } + if (!mask[i].animation) { + mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID; + opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID; + } if (!mask[i].clip_shadow_above) { mask[i].clip_shadow_above = true; opt->wintype_option[i].clip_shadow_above = false; @@ -715,6 +719,40 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en } } +enum open_window_animation parse_open_window_animation(const char *src) { + if (strcmp(src, "none") == 0) { + return OPEN_WINDOW_ANIMATION_NONE; + } else if (strcmp(src, "fly-in") == 0) { + return OPEN_WINDOW_ANIMATION_FLYIN; + } else if (strcmp(src, "zoom") == 0) { + return OPEN_WINDOW_ANIMATION_ZOOM; + } else if (strcmp(src, "slide-up") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_UP; + } else if (strcmp(src, "slide-down") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_DOWN; + } else if (strcmp(src, "slide-left") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_LEFT; + } else if (strcmp(src, "slide-right") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT; + } else if (strcmp(src, "slide-out") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_OUT; + } else if (strcmp(src, "slide-in") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_IN; + } else if (strcmp(src, "slide-out-center") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER; + } else if (strcmp(src, "slide-in-center") == 0) { + return OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER; + } else if (strcmp(src, "minimize") == 0 || strcmp(src, "maximize") == 0) { + return OPEN_WINDOW_ANIMATION_MINIMIZE; + } else if (strcmp(src, "squeeze") == 0) { + return OPEN_WINDOW_ANIMATION_SQUEEZE; + } else if (strcmp(src, "squeeze-bottom") == 0) { + return OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM; + } + + return OPEN_WINDOW_ANIMATION_INVALID; +} + char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) { // clang-format off @@ -759,6 +797,18 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .no_fading_destroyed_argb = false, .fade_blacklist = NULL, + .animations = false, + .animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_unmap_window = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_next_tag = OPEN_WINDOW_ANIMATION_NONE, + .animation_for_prev_tag = OPEN_WINDOW_ANIMATION_NONE, + .animation_stiffness = 200.0, + .animation_stiffness_tag_change = 200.0, + .animation_window_mass = 1.0, + .animation_dampening = 25, + .animation_clamping = true, + .inactive_opacity = 1.0, .inactive_opacity_override = false, .active_opacity = 1.0, @@ -790,7 +840,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .track_leader = false, - .rounded_corners_blacklist = NULL + .rounded_corners_blacklist = NULL, + .animation_blacklist = NULL }; // clang-format on diff --git a/src/config.h b/src/config.h index 7259dc1..f028302 100644 --- a/src/config.h +++ b/src/config.h @@ -40,6 +40,25 @@ enum backend { NUM_BKEND, }; +enum open_window_animation { + OPEN_WINDOW_ANIMATION_NONE = 0, + OPEN_WINDOW_ANIMATION_FLYIN, + OPEN_WINDOW_ANIMATION_SLIDE_UP, + OPEN_WINDOW_ANIMATION_SLIDE_DOWN, + OPEN_WINDOW_ANIMATION_SLIDE_LEFT, + OPEN_WINDOW_ANIMATION_SLIDE_RIGHT, + OPEN_WINDOW_ANIMATION_SLIDE_IN, + OPEN_WINDOW_ANIMATION_SLIDE_OUT, + OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER, + OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER, + OPEN_WINDOW_ANIMATION_ZOOM, + OPEN_WINDOW_ANIMATION_MINIMIZE, + OPEN_WINDOW_ANIMATION_MAXIMIZE, + OPEN_WINDOW_ANIMATION_SQUEEZE, + OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM, + OPEN_WINDOW_ANIMATION_INVALID, +}; + typedef struct win_option_mask { bool shadow : 1; bool fade : 1; @@ -49,6 +68,7 @@ typedef struct win_option_mask { bool redir_ignore : 1; bool opacity : 1; bool clip_shadow_above : 1; + enum open_window_animation animation; } win_option_mask_t; typedef struct win_option { @@ -60,6 +80,7 @@ typedef struct win_option { bool redir_ignore; double opacity; bool clip_shadow_above; + enum open_window_animation animation; } win_option_t; enum blur_method { @@ -172,6 +193,33 @@ typedef struct options { /// Fading blacklist. A linked list of conditions. c2_lptr_t *fade_blacklist; + // === Animations === + /// Whether to do window animations + bool animations; + /// Which animation to run when opening a window + enum open_window_animation animation_for_open_window; + /// Which animation to run when opening a transient window + enum open_window_animation animation_for_transient_window; + /// Which animation to run when unmapping a window + enum open_window_animation animation_for_unmap_window; + /// Which animation to run when swapping to new tag + enum open_window_animation animation_for_next_tag; + /// Which animation to run for old tag + enum open_window_animation animation_for_prev_tag; + /// Spring stiffness for animation + double animation_stiffness; + /// Spring stiffness for current tag animation + double animation_stiffness_tag_change; + /// Window mass for animation + double animation_window_mass; + /// Animation dampening + double animation_dampening; + /// Whether to clamp animations + bool animation_clamping; + /// Animation blacklist. A linked list of conditions. + c2_lptr_t *animation_blacklist; + /// TODO: open/close animations + // === Opacity === /// Default opacity for inactive windows. /// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY. @@ -253,6 +301,12 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + + // Enable fading for next tag + bool enable_fading_next_tag; + + // Enable fading for prev tag + bool enable_fading_prev_tag; /// 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. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 461fff3..c6d5aa8 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -226,6 +226,15 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ o->clip_shadow_above = ival; mask->clip_shadow_above = true; } + const char *sval = NULL; + if (config_setting_lookup_string(setting, "animation", &sval)) { + enum open_window_animation animation = parse_open_window_animation(sval); + if (animation >= OPEN_WINDOW_ANIMATION_INVALID) + animation = OPEN_WINDOW_ANIMATION_NONE; + + o->animation = animation; + mask->animation = OPEN_WINDOW_ANIMATION_INVALID; + } double fval; if (config_setting_lookup_float(setting, "opacity", &fval)) { @@ -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 diff --git a/src/event.c b/src/event.c index e6052f1..f84b595 100644 --- a/src/event.c +++ b/src/event.c @@ -449,6 +449,15 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } if (ps->root == ev->window) { + if (ev->atom == ps->atoms->a_NET_CURRENT_MON_CENTER) { + winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32); + if (prop.nitems == 2) { + ps->selmon_center_x = prop.p32[0]; + ps->selmon_center_y = prop.p32[1]; + } + free_winprop(&prop); + } + if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) { // to update focus ps->pending_updates = true; diff --git a/src/options.c b/src/options.c index 9f6888f..9bc3991 100644 --- a/src/options.c +++ b/src/options.c @@ -71,6 +71,36 @@ static void usage(const char *argv0, int ret) { "-F\n" " Equals to -f. Deprecated.\n" "\n" + "--animations\n" + " Run animations for window geometry changes (movement and scaling).\n" + "\n" + "--animation-for-open-window\n" + " Which animation to run when opening a window.\n" + " Must be one of `none`, `fly-in`, `zoom`,\n" + " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" + " (default: none).\n" + "\n" + "--animation-for-transient-window\n" + " Which animation to run when opening a transient window.\n" + " Must be one of `none`, `fly-in`, `zoom`,\n" + " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n" + " (default: none).\n" + "\n" + "--animation-stiffness\n" + " Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n" + "\n" + "--animation-dampening\n" + " Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n" + "\n" + "--animation-window-mass\n" + " Mass parameter for animation (default: 1.0).\n" + "\n" + "--animation-clamping\n" + " Whether to clamp animations (default: true)\n" + "\n" + "--animation-exclude condition\n" + " Exclude conditions for animation.\n" + "\n" "-i opacity\n" " Opacity of inactive windows. (0.1 - 1.0)\n" "\n" @@ -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 } diff --git a/src/picom.c b/src/picom.c index e4a5607..170f89e 100644 --- a/src/picom.c +++ b/src/picom.c @@ -668,78 +668,250 @@ static void handle_root_flags(session_t *ps) { } static struct managed_win * -paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { +paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { // XXX need better, more general name for `fade_running`. It really - // means if fade is still ongoing after the current frame is rendered + // means if fade is still ongoing after the current frame is rendered. + // Same goes for `animation_running`. struct managed_win *bottom = NULL; *fade_running = false; - *animation = false; + *animation_running = false; + auto now = get_time_ms(); // Fading step calculation long long steps = 0L; - auto now = get_time_ms(); if (ps->fade_time) { assert(now >= ps->fade_time); steps = (now - ps->fade_time) / ps->o.fade_delta; } else { // Reset fade_time if unset - ps->fade_time = get_time_ms(); + ps->fade_time = now; steps = 0L; } ps->fade_time += steps * ps->o.fade_delta; - // First, let's process fading, and animated shaders - // TODO(yshui) check if a window is fully obscured, and if we don't need to - // process fading or animation for it. + if (ps->o.animations && !ps->animation_time) + ps->animation_time = now; + + double delta_secs = (double)(now - ps->animation_time) / 1000; + + // First, let's process fading win_stack_foreach_managed_safe(w, &ps->window_stack) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; const double opacity_old = w->opacity; + // IMPORTANT: These window animation steps must happen before any other + // [pre]processing. This is because it changes the window's geometry. + if (ps->o.animations && + !isnan(w->animation_progress) && w->animation_progress != 1.0 && + ps->o.wintype_option[w->window_type].animation != 0 && + win_is_mapped_in_x(w)) + { + double neg_displacement_x = + w->animation_dest_center_x - w->animation_center_x; + double neg_displacement_y = + w->animation_dest_center_y - w->animation_center_y; + double neg_displacement_w = w->animation_dest_w - w->animation_w; + double neg_displacement_h = w->animation_dest_h - w->animation_h; + double animation_stiffness = ps->o.animation_stiffness; + if (!(w->animation_is_tag & ANIM_IN_TAG)) { + if (w->animation_is_tag & ANIM_SLOW) + animation_stiffness = ps->o.animation_stiffness_tag_change; + else if (w->animation_is_tag & ANIM_FAST) + animation_stiffness = ps->o.animation_stiffness_tag_change * 1.5; + } + if (w->state == WSTATE_FADING && !(w->animation_is_tag & ANIM_FADE)) + w->opacity_target = win_calc_opacity_target(ps, w); + double acceleration_x = + (animation_stiffness * neg_displacement_x - + ps->o.animation_dampening * w->animation_velocity_x) / + ps->o.animation_window_mass; + double acceleration_y = + (animation_stiffness * neg_displacement_y - + ps->o.animation_dampening * w->animation_velocity_y) / + ps->o.animation_window_mass; + double acceleration_w = + (animation_stiffness * neg_displacement_w - + ps->o.animation_dampening * w->animation_velocity_w) / + ps->o.animation_window_mass; + double acceleration_h = + (animation_stiffness * neg_displacement_h - + ps->o.animation_dampening * w->animation_velocity_h) / + ps->o.animation_window_mass; + w->animation_velocity_x += acceleration_x * delta_secs; + w->animation_velocity_y += acceleration_y * delta_secs; + w->animation_velocity_w += acceleration_w * delta_secs; + w->animation_velocity_h += acceleration_h * delta_secs; + + // Animate window geometry + double new_animation_x = + w->animation_center_x + w->animation_velocity_x * delta_secs; + double new_animation_y = + w->animation_center_y + w->animation_velocity_y * delta_secs; + double new_animation_w = + w->animation_w + w->animation_velocity_w * delta_secs; + double new_animation_h = + w->animation_h + w->animation_velocity_h * delta_secs; + + // Negative new width/height causes segfault and it can happen + // when clamping disabled and shading a window + if (new_animation_h < 0) + new_animation_h = 0; + + if (new_animation_w < 0) + new_animation_w = 0; + + if (ps->o.animation_clamping) { + w->animation_center_x = clamp( + new_animation_x, + min2(w->animation_center_x, w->animation_dest_center_x), + max2(w->animation_center_x, w->animation_dest_center_x)); + w->animation_center_y = clamp( + new_animation_y, + min2(w->animation_center_y, w->animation_dest_center_y), + max2(w->animation_center_y, w->animation_dest_center_y)); + w->animation_w = + clamp(new_animation_w, + min2(w->animation_w, w->animation_dest_w), + max2(w->animation_w, w->animation_dest_w)); + w->animation_h = + clamp(new_animation_h, + min2(w->animation_h, w->animation_dest_h), + max2(w->animation_h, w->animation_dest_h)); + } else { + w->animation_center_x = new_animation_x; + w->animation_center_y = new_animation_y; + w->animation_w = new_animation_w; + w->animation_h = new_animation_h; + } + + // Now we are done doing the math; we just need to submit our + // changes (if there are any). + + struct win_geometry old_g = w->g; + double old_animation_progress = w->animation_progress; + new_animation_x = round(w->animation_center_x - w->animation_w * 0.5); + new_animation_y = round(w->animation_center_y - w->animation_h * 0.5); + new_animation_w = round(w->animation_w); + new_animation_h = round(w->animation_h); + + bool position_changed = + new_animation_x != old_g.x || new_animation_y != old_g.y; + bool size_changed = + new_animation_w != old_g.width || new_animation_h != old_g.height; + bool geometry_changed = position_changed || size_changed; + + // Mark past window region with damage + if (was_painted && geometry_changed) + add_damage_from_win(ps, w); + + double x_dist = w->animation_dest_center_x - w->animation_center_x; + double y_dist = w->animation_dest_center_y - w->animation_center_y; + double w_dist = w->animation_dest_w - w->animation_w; + double h_dist = w->animation_dest_h - w->animation_h; + w->animation_progress = + 1.0 - w->animation_inv_og_distance * + sqrt(x_dist * x_dist + y_dist * y_dist + + w_dist * w_dist + h_dist * h_dist); + + // When clamping disabled we don't want the overlayed image to + // fade in again because process is moving to negative value + if (w->animation_progress < old_animation_progress) + w->animation_progress = old_animation_progress; + + w->g.x = (int16_t)new_animation_x; + w->g.y = (int16_t)new_animation_y; + w->g.width = (uint16_t)new_animation_w; + w->g.height = (uint16_t)new_animation_h; + + if (w->animation_is_tag > ANIM_IN_TAG && (((w->animation_is_tag & ANIM_FADE) && w->opacity_target == w->opacity) || ((w->g.width == 0 || w->g.height == 0) && (w->animation_dest_w == 0 || w->animation_dest_h == 0)))) { + w->g.x = w->pending_g.x; + w->g.y = w->pending_g.y; + if (ps->o.animation_for_next_tag < OPEN_WINDOW_ANIMATION_ZOOM) { + w->g.width = w->pending_g.width; + w->g.height = w->pending_g.height; + } else { + w->g.width = 0; + w->g.height = 0; + } + } + + // Submit window size change + if (size_changed) { + win_on_win_size_change(ps, w); + + pixman_region32_clear(&w->bounding_shape); + pixman_region32_fini(&w->bounding_shape); + pixman_region32_init_rect(&w->bounding_shape, 0, 0, + (uint)w->widthb, (uint)w->heightb); + + win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE); + win_process_image_flags(ps, w); + } + // Mark new window region with damage + if (was_painted && geometry_changed) { + add_damage_from_win(ps, w); + w->reg_ignore_valid = false; + } + + // We can't check for 1 here as sometimes 1 = 0.999999999999999 + // in case of floating numbers + if (w->animation_progress >= 0.999999999) { + w->animation_progress = 1; + w->animation_velocity_x = 0.0; + w->animation_velocity_y = 0.0; + w->animation_velocity_w = 0.0; + w->animation_velocity_h = 0.0; + w->opacity = win_calc_opacity_target(ps, w); + } + + *animation_running = true; + } + if (win_should_dim(ps, w) != w->dim) { w->dim = win_should_dim(ps, w); add_damage_from_win(ps, w); } - if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) { - add_damage_from_win(ps, w); - *animation = true; - } + if (w->opacity != w->opacity_target) { + // Run fading + if (run_fade(ps, &w, steps)) { + *fade_running = true; + } - // Run fading - if (run_fade(ps, &w, steps)) { - *fade_running = true; - } + // Add window to damaged area if its opacity changes + // If was_painted == false, and to_paint is also false, we don't care + // If was_painted == false, but to_paint is true, damage will be added in + // the loop below + if (was_painted && w->opacity != opacity_old) { + add_damage_from_win(ps, w); + } - // Add window to damaged area if its opacity changes - // If was_painted == false, and to_paint is also false, we don't care - // If was_painted == false, but to_paint is true, damage will be added in - // the loop below - if (was_painted && w->opacity != opacity_old) { - add_damage_from_win(ps, w); - } + if (win_check_fade_finished(ps, w)) { + // the window has been destroyed because fading finished + continue; + } - if (win_check_fade_finished(ps, w)) { - // the window has been destroyed because fading finished - continue; - } + if (win_has_frame(w)) { + w->frame_opacity = ps->o.frame_opacity; + } else { + w->frame_opacity = 1.0; + } - if (win_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } + // Update window mode + w->mode = win_calc_mode(w); - // Update window mode - w->mode = win_calc_mode(w); - - // Destroy all reg_ignore above when frame opaque state changes on - // SOLID mode - if (was_painted && w->mode != mode_old) { - w->reg_ignore_valid = false; + // Destroy all reg_ignore above when frame opaque state changes on + // SOLID mode + if (was_painted && w->mode != mode_old) { + w->reg_ignore_valid = false; + } } } + if (animation_running) + ps->animation_time = now; + // Opacity will not change, from now on. rc_region_t *last_reg_ignore = rc_region_new(); @@ -1406,6 +1578,11 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_ queue_redraw(ps); } +static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + session_t *ps = session_ptr(w, animation_timer); + queue_redraw(ps); +} + static void handle_pending_updates(EV_P_ struct session *ps) { if (ps->pending_updates) { log_debug("Delayed handling of events, entering critical section"); @@ -1490,9 +1667,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { * screen is not redirected. its sole purpose should be to decide whether the * screen should be redirected. */ bool fade_running = false; - bool animation = false; + bool animation_running = false; bool was_redirected = ps->redirected; - auto bottom = paint_preprocess(ps, &fade_running, &animation); + auto bottom = paint_preprocess(ps, &fade_running, &animation_running); ps->tmout_unredir_hit = false; if (!was_redirected && ps->redirected) { @@ -1514,6 +1691,13 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0); ev_timer_start(EV_A_ & ps->fade_timer); } + // Start/stop animation timer depends on whether windows are animating + if (!animation_running && ev_is_active(&ps->animation_timer)) { + ev_timer_stop(EV_A_ & ps->animation_timer); + } else if (animation_running && !ev_is_active(&ps->animation_timer)) { + ev_timer_set(&ps->animation_timer, 0, 0); + ev_timer_start(EV_A_ & ps->animation_timer); + } // If the screen is unredirected, free all_damage to stop painting if (ps->redirected && ps->o.stoppaint_force != ON) { @@ -1537,11 +1721,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { if (!fade_running) { ps->fade_time = 0L; } + if (!animation_running) { + ps->animation_time = 0L; + } // TODO(yshui) Investigate how big the X critical section needs to be. There are // suggestions that rendering should be in the critical section as well. - ps->redraw_needed = animation; + ps->redraw_needed = false; } static void draw_callback(EV_P_ ev_idle *w, int revents) { @@ -1549,9 +1736,8 @@ static void draw_callback(EV_P_ ev_idle *w, int revents) { draw_callback_impl(EV_A_ ps, revents); - // Don't do painting non-stop unless we are in benchmark mode, or if - // draw_callback_impl thinks we should continue painting. - if (!ps->o.benchmark && !ps->redraw_needed) { + // Don't do painting non-stop unless we are in benchmark mode + if (!ps->o.benchmark) { ev_idle_stop(EV_A_ & ps->draw_idle); } } @@ -1677,6 +1863,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, + .animation_time = 0L, .ignore_head = NULL, .ignore_tail = NULL, .quit = false, @@ -1925,7 +2112,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.window_shader_fg_rules) && c2_list_postprocess(ps, ps->o.opacity_rules) && c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && - c2_list_postprocess(ps, ps->o.focus_blacklist))) { + c2_list_postprocess(ps, ps->o.focus_blacklist) && + c2_list_postprocess(ps, ps->o.animation_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); } @@ -2154,6 +2342,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_idle_init(&ps->draw_idle, draw_callback); ev_init(&ps->fade_timer, fade_timer_callback); + ev_init(&ps->animation_timer, animation_timer_callback); // Set up SIGUSR1 signal handler to reset program ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1); @@ -2244,6 +2433,16 @@ static session_t *session_init(int argc, char **argv, Display *dpy, write_pid(ps); + // When picom starts, fetch monitor positions first. Because it won't fetch the data unless property changes. + if (!ps->selmon_center_x && !ps->selmon_center_y) { + winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32); + if (prop.nitems == 2) { + ps->selmon_center_x = prop.p32[0]; + ps->selmon_center_y = prop.p32[1]; + } + free_winprop(&prop); + } + if (fork && stderr_logger) { // Remove the stderr logger if we will fork log_remove_target_tls(stderr_logger); @@ -2443,6 +2642,7 @@ static void session_destroy(session_t *ps) { // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); + ev_timer_stop(ps->loop, &ps->animation_timer); ev_idle_stop(ps->loop, &ps->draw_idle); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); diff --git a/src/utils.h b/src/utils.h index a35bfa8..a632107 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,6 +20,7 @@ #include "types.h" #define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#define CLEAR_MASK(x) x = 0; #ifdef __FAST_MATH__ #warning Use of -ffast-math can cause rendering error or artifacts, \ @@ -132,6 +133,19 @@ static inline int attr_const normalize_i_range(int i, int min, int max) { return i; } +/** + * Linearly interpolate from a range into another. + * + * @param a,b first range + * @param c,d second range + * @param value value to interpolate, should be in range [a,b] + * @return interpolated value in range [c,d] + */ +static inline int attr_const lerp_range(int a, int b, int c, int d, int value) { + ASSERT_IN_RANGE(value, a, b); + return (d-c)*(value-a)/(b-a) + c; +} + #define min2(a, b) ((a) > (b) ? (b) : (a)) #define max2(a, b) ((a) > (b) ? (a) : (b)) #define min3(a, b, c) min2(a, min2(b, c)) diff --git a/src/win.c b/src/win.c index 370fbfd..fd97125 100644 --- a/src/win.c +++ b/src/win.c @@ -303,6 +303,13 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { w->flags |= WIN_FLAGS_PIXMAP_NONE; } } +static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) { + log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name); + if (w->old_win_image) { + base->ops->release_image(base, w->old_win_image); + w->old_win_image = NULL; + } +} static inline void win_release_shadow(backend_t *base, struct managed_win *w) { log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name); assert(w->shadow_image); @@ -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); } diff --git a/src/win.h b/src/win.h index abc03d9..619341f 100644 --- a/src/win.h +++ b/src/win.h @@ -100,11 +100,25 @@ struct win_geometry { uint16_t border_width; }; +enum { + // dwm_mask + ANIM_PREV_TAG = 1, + ANIM_NEXT_TAG = (1 << 1), + ANIM_UNMAP = (1 << 2), + ANIM_SPECIAL_MINIMIZE = (1 << 3), + // animation_is_tag + ANIM_IN_TAG = 1, + ANIM_SLOW = (1 << 1), + ANIM_FAST = (1 << 2), + ANIM_FADE = (1 << 3), +}; + struct managed_win { struct win base; /// backend data attached to this window. Only available when /// `state` is not UNMAPPED void *win_image; + void *old_win_image; // Old window image for interpolating window contents during animations void *shadow_image; void *mask_image; /// Pointer to the next higher window to paint. @@ -156,6 +170,8 @@ struct managed_win { /// opacity state, window geometry, window mapped/unmapped state, /// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW. /// NULL means reg_ignore has not been calculated for this window. + /// 1 = tag prev , 2 = tag next, 4 = unmap + uint32_t dwm_mask; rc_region_t *reg_ignore; /// Whether the reg_ignore of all windows beneath this window are valid bool reg_ignore_valid; @@ -173,6 +189,24 @@ struct managed_win { bool unredir_if_possible_excluded; /// Whether this window is in open/close state. bool in_openclose; + /// Current position and destination, for animation + double animation_center_x, animation_center_y; + double animation_dest_center_x, animation_dest_center_y; + double animation_w, animation_h; + double animation_dest_w, animation_dest_h; + /// Spring animation velocity + double animation_velocity_x, animation_velocity_y; + double animation_velocity_w, animation_velocity_h; + /// Track animation progress; goes from 0 to 1 + double animation_progress; + /// Inverse of the window distance at the start of animation, for + /// tracking animation progress + double animation_inv_og_distance; + /// Animation info if it is a tag change & check if its changing window sizes + /// 0: no tag change + /// 1: normal tag change animation + /// 2: tag change animation that effects window size + uint16_t animation_is_tag; // Client window related members /// ID of the top-level client window of the window. @@ -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); } From 9e397ef4e92c4081fc081af55425760a4398f01d Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Mon, 24 Oct 2022 17:29:52 +0300 Subject: [PATCH 06/64] small changes --- src/config.h | 1 - src/win.c | 29 +++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/config.h b/src/config.h index f028302..1bb7288 100644 --- a/src/config.h +++ b/src/config.h @@ -53,7 +53,6 @@ enum open_window_animation { 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, diff --git a/src/win.c b/src/win.c index fd97125..457943e 100644 --- a/src/win.c +++ b/src/win.c @@ -673,8 +673,17 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { add_damage_from_win(ps, w); } + + // Determine if a window should animate if (win_should_animate(ps, w)) { + if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + win_on_win_size_change(ps, w); + win_update_bounding_shape(ps, w); + damaged = true; + win_clear_flags(w, WIN_FLAGS_SIZE_STALE); + } + 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) @@ -746,12 +755,12 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { w->g = w->pending_g; } - if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { - win_on_win_size_change(ps, w); - win_update_bounding_shape(ps, w); - damaged = true; - win_clear_flags(w, WIN_FLAGS_SIZE_STALE); - } + if (!win_should_animate(ps, w) && win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + win_on_win_size_change(ps, w); + win_update_bounding_shape(ps, w); + damaged = true; + win_clear_flags(w, WIN_FLAGS_SIZE_STALE); + } if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) { damaged = true; @@ -3031,6 +3040,10 @@ win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_windo void win_set_flags(struct managed_win *w, uint64_t flags) { log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); if (unlikely(w->state == WSTATE_DESTROYING)) { + if (w->animation_progress != 1.0) { + // Return because animation will trigger some of the flags + return; + } log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name); return; } @@ -3043,6 +3056,10 @@ void win_clear_flags(struct managed_win *w, uint64_t flags) { log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id, w->name); if (unlikely(w->state == WSTATE_DESTROYING)) { + if (w->animation_progress != 1.0) { + // Return because animation will trigger some of the flags + return; + } log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id, w->name); return; From 4fbc9b33adbae84b37f4fe2b0137f2bf110c833b Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Tue, 25 Oct 2022 01:24:44 +0300 Subject: [PATCH 07/64] small changes --- src/win.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win.c b/src/win.c index 457943e..aad7a38 100644 --- a/src/win.c +++ b/src/win.c @@ -689,7 +689,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { 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)) { + if (!was_visible || w->dwm_mask) { // Set window-open animation init_animation(ps, w); From 9f04d824b78fb39d03d00dfc68860216857c7d97 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 21 Aug 2022 14:57:39 +0100 Subject: [PATCH 08/64] Rename COMPTON_VERSION to PICOM_VERSION Signed-off-by: Yuxuan Shui --- meson.build | 2 +- src/dbus.c | 2 +- src/diagnostic.c | 2 +- src/options.c | 4 ++-- src/picom.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/meson.build b/meson.build index e333c31..0150a37 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ if git.found() endif endif -add_global_arguments('-DCOMPTON_VERSION="'+version+'"', language: 'c') +add_global_arguments('-DPICOM_VERSION="'+version+'"', language: 'c') if get_option('buildtype') == 'release' add_global_arguments('-DNDEBUG', language: 'c') diff --git a/src/dbus.c b/src/dbus.c index e037167..8b17b30 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1153,7 +1153,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { // version if (!strcmp("version", target)) { - cdbus_reply_string(ps, msg, COMPTON_VERSION); + cdbus_reply_string(ps, msg, PICOM_VERSION); return true; } diff --git a/src/diagnostic.c b/src/diagnostic.c index d275b1a..79ac5ac 100644 --- a/src/diagnostic.c +++ b/src/diagnostic.c @@ -12,7 +12,7 @@ #include "common.h" void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { - printf("**Version:** " COMPTON_VERSION "\n"); + printf("**Version:** " PICOM_VERSION "\n"); //printf("**CFLAGS:** %s\n", "??"); printf("\n### Extensions:\n\n"); printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No"); diff --git a/src/options.c b/src/options.c index 9f6888f..9ec6e81 100644 --- a/src/options.c +++ b/src/options.c @@ -26,7 +26,7 @@ static void usage(const char *argv0, int ret) { #define WARNING_DISABLED " (DISABLED AT COMPILE TIME)" static const char *usage_text = - "picom (" COMPTON_VERSION ")\n" + "picom (" PICOM_VERSION ")\n" "Please report bugs to https://github.com/yshui/picom\n\n" "usage: %s [options]\n" "Options:\n" @@ -495,7 +495,7 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all } else if (o == 314) { *all_xerrors = true; } else if (o == 318) { - printf("%s\n", COMPTON_VERSION); + printf("%s\n", PICOM_VERSION); return true; } else if (o == '?' || o == ':') { usage(argv[0], 1); diff --git a/src/picom.c b/src/picom.c index e4a5607..fd693fc 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1092,7 +1092,7 @@ static int register_cm(session_t *ps) { ps->c, xcb_change_property_checked( ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8, - (uint32_t)strlen(COMPTON_VERSION), COMPTON_VERSION)); + (uint32_t)strlen(PICOM_VERSION), PICOM_VERSION)); if (e) { log_error_x_error(e, "Failed to set COMPTON_VERSION."); free(e); From 59de702ea8b1d4e7376c1fe01a9bbf7e841c9684 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 21 Aug 2022 15:07:44 +0100 Subject: [PATCH 09/64] options: improve usage message printing Laying the usage message out by hand is tedious, also error prone because the option names are duplicated at 2 places and have to be consistent. Create a struct to hold the option names and help messages, and do layout programmatically. Signed-off-by: Yuxuan Shui --- src/options.c | 713 +++++++++++++++++++------------------------------- 1 file changed, 272 insertions(+), 441 deletions(-) diff --git a/src/options.c b/src/options.c index 9ec6e81..ba7485d 100644 --- a/src/options.c +++ b/src/options.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include // for xcb_render_fixed_t, XXX @@ -20,460 +22,289 @@ #pragma GCC diagnostic error "-Wunused-parameter" +struct picom_option { + const char *long_name; + int has_arg; + int val; + const char *arg_name; + const char *help; +}; + +// clang-format off +static const struct option *longopts = NULL; +static const struct picom_option picom_options[] = { +#ifdef CONFIG_LIBCONFIG + {"config" , required_argument, 256, NULL , "Path to the configuration file."}, +#endif + {"help" , no_argument , 'h', NULL , "Print this help message and exit."}, + {"shadow-radius" , required_argument, 'r', NULL , "The blur radius for shadows. (default 12)"}, + {"shadow-opacity" , required_argument, 'o', NULL , "The translucency for shadows. (default .75)"}, + {"shadow-offset-x" , required_argument, 'l', NULL , "The left offset for shadows. (default -15)"}, + {"shadow-offset-y" , required_argument, 't', NULL , "The top offset for shadows. (default -15)"}, + {"fade-in-step" , required_argument, 'I', NULL , "Opacity change between steps while fading in. (default 0.028)"}, + {"fade-out-step" , required_argument, 'O', NULL , "Opacity change between steps while fading out. (default 0.03)"}, + {"fade-delta" , required_argument, 'D', NULL , "The time between steps in a fade in milliseconds. (default 10)"}, + {"menu-opacity" , required_argument, 'm', NULL , "The opacity for menus. (default 1.0)"}, + {"shadow" , no_argument , 'c', NULL , "Enabled client-side shadows on windows."}, + {"clear-shadow" , no_argument , 'z', NULL , "Don't dreaw shadow behind the window."}, + {"fading" , no_argument , 'f', NULL , "Fade windows in/out when opening/closing and when opacity changes, " + "unless --no-fading-openclose is used."}, + {"inactive-opacity" , required_argument, 'i', NULL , "Opacity of inactive windows. (0.1 - 1.0)"}, + {"frame-opacity" , required_argument, 'e', NULL , "Opacity of window titlebars and borders. (0.1 - 1.0)"}, + {"daemon" , no_argument , 'b', NULL , "Daemonize process."}, + {"shadow-red" , required_argument, 257, NULL , "Red color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"shadow-green" , required_argument, 258, NULL , "Green color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"shadow-blue" , required_argument, 259, NULL , "Blue color value of shadow (0.0 - 1.0, defaults to 0)."}, + {"inactive-opacity-override" , no_argument , 260, NULL , "Inactive opacity set by -i overrides value of _NET_WM_WINDOW_OPACITY."}, + {"inactive-dim" , required_argument, 261, NULL , "Dim inactive windows. (0.0 - 1.0, defaults to 0)"}, + {"mark-wmwin-focused" , no_argument , 262, NULL , "Try to detect WM windows and mark them as active."}, + {"shadow-exclude" , required_argument, 263, NULL , "Exclude conditions for shadows."}, + {"mark-ovredir-focused" , no_argument , 264, NULL , "Mark windows that have no WM frame as active."}, + {"no-fading-openclose" , no_argument , 265, NULL , "Do not fade on window open/close."}, + {"shadow-ignore-shaped" , no_argument , 266, NULL , "Do not paint shadows on shaped windows. (Deprecated, use --shadow-exclude " + "\'bounding_shaped\' or --shadow-exclude \'bounding_shaped && " + "!rounded_corners\' instead.)"}, + {"detect-rounded-corners" , no_argument , 267, NULL , "Try to detect windows with rounded corners and don't consider them shaped " + "windows. Affects --shadow-ignore-shaped, --unredir-if-possible, and " + "possibly others. You need to turn this on manually if you want to match " + "against rounded_corners in conditions."}, + {"detect-client-opacity" , no_argument , 268, NULL , "Detect _NET_WM_WINDOW_OPACITY on client windows, useful for window " + "managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"}, + {"refresh-rate" , required_argument, 269, NULL , NULL}, + {"vsync" , optional_argument, 270, NULL , "Enable VSync"}, + {"sw-opti" , no_argument , 274, NULL , NULL}, + {"vsync-aggressive" , no_argument , 275, NULL , NULL}, + {"use-ewmh-active-win" , no_argument , 276, NULL , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is " + "focused instead of using FocusIn/Out events"}, + {"respect-prop-shadow" , no_argument , 277, NULL , NULL}, + {"unredir-if-possible" , no_argument , 278, NULL , "Unredirect all windows if a full-screen opaque window is detected, to " + "maximize performance for full-screen applications."}, + {"focus-exclude" , required_argument, 279, "COND" , "Specify a list of conditions of windows that should always be considered focused."}, + {"inactive-dim-fixed" , no_argument , 280, NULL , "Use fixed inactive dim value."}, + {"detect-transient" , no_argument , 281, NULL , "Use WM_TRANSIENT_FOR to group windows, and consider windows in the same " + "group focused at the same time."}, + {"detect-client-leader" , no_argument , 282, NULL , "Use WM_CLIENT_LEADER to group windows, and consider windows in the same group " + "focused at the same time. This usually means windows from the same application " + "will be considered focused or unfocused at the same time. WM_TRANSIENT_FOR has " + "higher priority if --detect-transient is enabled, too."}, + {"blur-background" , no_argument , 283, NULL , "Blur background of semi-transparent / ARGB windows. May impact performance"}, + {"blur-background-frame" , no_argument , 284, NULL , "Blur background of windows when the window frame is not opaque. Implies " + "--blur-background."}, + {"blur-background-fixed" , no_argument , 285, NULL , "Use fixed blur strength instead of adjusting according to window opacity."}, +#ifdef CONFIG_DBUS + {"dbus" , no_argument , 286, NULL , "Enable remote control via D-Bus. See the D-BUS API section in the man page " + "for more details."}, +#endif + {"logpath" , required_argument, 287, NULL , NULL}, + {"invert-color-include" , required_argument, 288, "COND" , "Specify a list of conditions of windows that should be painted with " + "inverted color."}, + {"opengl" , no_argument , 289, NULL , NULL}, + {"backend" , required_argument, 290, NULL , "Backend. Possible values are: xrender" +#ifdef CONFIG_OPENGL + ", glx" +#endif + }, + {"glx-no-stencil" , no_argument , 291, NULL , NULL}, + {"benchmark" , required_argument, 293, NULL , "Benchmark mode. Repeatedly paint until reaching the specified cycles."}, + {"benchmark-wid" , required_argument, 294, NULL , "Specify window ID to repaint in benchmark mode. If omitted or is 0, the whole" + " screen is repainted."}, + {"blur-background-exclude" , required_argument, 296, "COND" , "Exclude conditions for background blur."}, + {"active-opacity" , required_argument, 297, NULL , "Default opacity for active windows. (0.0 - 1.0)"}, + {"glx-no-rebind-pixmap" , no_argument , 298, NULL , NULL}, + {"glx-swap-method" , required_argument, 299, NULL , NULL}, + {"fade-exclude" , required_argument, 300, "COND" , "Exclude conditions for fading."}, + {"blur-kern" , required_argument, 301, NULL , "Specify the blur convolution kernel, see man page for more details"}, + {"resize-damage" , required_argument, 302, NULL , NULL}, // only used by legacy backends + {"glx-use-gpushader4" , no_argument , 303, NULL , NULL}, + {"opacity-rule" , required_argument, 304, "OPACITY:COND", "Specify a list of opacity rules, see man page for more details"}, + {"shadow-exclude-reg" , required_argument, 305, NULL , NULL}, + {"paint-exclude" , required_argument, 306, NULL , NULL}, + {"xinerama-shadow-crop" , no_argument , 307, NULL , "Crop shadow of a window fully on a particular Xinerama screen to the screen."}, + {"unredir-if-possible-exclude" , required_argument, 308, "COND" , "Conditions of windows that shouldn't be considered full-screen for " + "unredirecting screen."}, + {"unredir-if-possible-delay" , required_argument, 309, NULL, "Delay before unredirecting the window, in milliseconds. Defaults to 0."}, + {"write-pid-path" , required_argument, 310, "PATH" , "Write process ID to a file."}, + {"vsync-use-glfinish" , no_argument , 311, NULL , NULL}, + {"xrender-sync-fence" , no_argument , 313, NULL , "Additionally use X Sync fence to sync clients' draw calls. Needed on " + "nvidia-drivers with GLX backend for some users."}, + {"show-all-xerrors" , no_argument , 314, NULL , NULL}, + {"no-fading-destroyed-argb" , no_argument , 315, NULL , "Do not fade destroyed ARGB windows with WM frame. Workaround bugs in Openbox, " + "Fluxbox, etc."}, + {"force-win-blend" , no_argument , 316, NULL , "Force all windows to be painted with blending. Useful if you have a custom " + "shader that could turn opaque pixels transparent."}, + {"glx-fshader-win" , required_argument, 317, NULL , NULL}, + {"version" , no_argument , 318, NULL , "Print version number and exit."}, + {"no-x-selection" , no_argument , 319, NULL , NULL}, + {"log-level" , required_argument, 321, NULL , "Log level, possible values are: trace, debug, info, warn, error"}, + {"log-file" , required_argument, 322, NULL , "Path to the log file."}, + {"use-damage" , no_argument , 323, NULL , "Render only the damaged (changed) part of the screen"}, + {"no-use-damage" , no_argument , 324, NULL , "Disable the use of damage information. This cause the whole screen to be" + "redrawn everytime, instead of the part of the screen that has actually " + "changed. Potentially degrades the performance, but might fix some artifacts."}, + {"no-vsync" , no_argument , 325, NULL , "Disable VSync"}, + {"max-brightness" , required_argument, 326, NULL , "Dims windows which average brightness is above this threshold. Requires " + "--no-use-damage. (default: 1.0, meaning no dimming)"}, + {"transparent-clipping" , no_argument , 327, NULL , "Make transparent windows clip other windows like non-transparent windows do, " + "instead of blending on top of them"}, + {"transparent-clipping-exclude", required_argument, 338, "COND" , "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 see through transparent parts of the window."}, + {"blur-method" , required_argument, 328, NULL , "The algorithm used for background bluring. Available choices are: 'none' to " + "disable, 'gaussian', 'box' or 'kernel' for custom convolution blur with " + "--blur-kern. Note: 'gaussian' and 'box' is not supported by --legacy-backends."}, + {"blur-size" , required_argument, 329, NULL , "The radius of the blur kernel for 'box' and 'gaussian' blur method."}, + {"blur-deviation" , required_argument, 330, NULL , "The standard deviation for the 'gaussian' blur method."}, + {"blur-strength" , required_argument, 331, NULL , "The strength level of the 'dual_kawase' blur method."}, + {"shadow-color" , required_argument, 332, NULL , "Color of shadow, as a hex RGB string (defaults to #000000)"}, + {"corner-radius" , required_argument, 333, NULL , "Sets the radius of rounded window corners. When > 0, the compositor will " + "round the corners of windows. (defaults to 0)."}, + {"rounded-corners-exclude" , required_argument, 334, "COND" , "Exclude conditions for rounded corners."}, + {"clip-shadow-above" , required_argument, 335, NULL , "Specify a list of conditions of windows to not paint a shadow over, such " + "as a dock window."}, + {"window-shader-fg" , required_argument, 336, "PATH" , "Specify GLSL fragment shader path for rendering window contents. Does not" + " work when `--legacy-backends` is enabled. See man page for more details."}, + {"window-shader-fg-rule" , required_argument, 337, "PATH:COND" , "Specify GLSL fragment shader path for rendering window contents using " + "patterns. Pattern should be in the format of SHADER_PATH:PATTERN, " + "similar to --opacity-rule. SHADER_PATH can be \"default\", in which case " + "the default shader will be used. Does not work when --legacy-backends is " + "enabled. See man page for more details"}, + {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, + {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, + {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, + {"debug-mode" , no_argument , 802, NULL , "Render into a separate window, and don't take over the screen. Useful when " + "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."}, +}; +// clang-format on + +static void setup_longopts(void) { + auto opts = ccalloc(ARR_SIZE(picom_options) + 1, struct option); + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + opts[i].name = picom_options[i].long_name; + opts[i].has_arg = picom_options[i].has_arg; + opts[i].flag = NULL; + opts[i].val = picom_options[i].val; + } + longopts = opts; +} + +void print_help(const char *help, size_t indent, size_t curr_indent, size_t line_wrap, + FILE *f) { + if (curr_indent > indent) { + fputs("\n", f); + curr_indent = 0; + } + + if (line_wrap - indent <= 1) { + line_wrap = indent + 2; + } + + size_t pos = 0; + size_t len = strlen(help); + while (pos < len) { + fprintf(f, "%*s", (int)(indent - curr_indent), ""); + curr_indent = 0; + size_t towrite = line_wrap - indent; + while (help[pos] == ' ') { + pos++; + } + if (pos + towrite > len) { + towrite = len - pos; + fwrite(help + pos, 1, towrite, f); + } else { + auto space_break = towrite; + while (space_break > 0 && help[pos + space_break - 1] != ' ') { + space_break--; + } + + bool print_hyphen = false; + if (space_break == 0) { + print_hyphen = true; + towrite--; + } else { + towrite = space_break; + } + + fwrite(help + pos, 1, towrite, f); + + if (print_hyphen) { + fputs("-", f); + } + } + + fputs("\n", f); + pos += towrite; + } +} + /** * Print usage text. */ static void usage(const char *argv0, int ret) { -#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)" - static const char *usage_text = - "picom (" PICOM_VERSION ")\n" - "Please report bugs to https://github.com/yshui/picom\n\n" - "usage: %s [options]\n" - "Options:\n" - "\n" - "-r radius\n" - " The blur radius for shadows. (default 12)\n" - "\n" - "-o opacity\n" - " The translucency for shadows. (default .75)\n" - "\n" - "-l left-offset\n" - " The left offset for shadows. (default -15)\n" - "\n" - "-t top-offset\n" - " The top offset for shadows. (default -15)\n" - "\n" - "-I fade-in-step\n" - " Opacity change between steps while fading in. (default 0.028)\n" - "\n" - "-O fade-out-step\n" - " Opacity change between steps while fading out. (default 0.03)\n" - "\n" - "-D fade-delta-time\n" - " The time between steps in a fade in milliseconds. (default 10)\n" - "\n" - "-m opacity\n" - " The opacity for menus. (default 1.0)\n" - "\n" - "-c\n" - " Enabled client-side shadows on windows.\n" - "\n" - "-C\n" - " Avoid drawing shadows on dock/panel windows.\n" - "\n" - "-z\n" - " Zero the part of the shadow's mask behind the window.\n" - "\n" - "-f\n" - " Fade windows in/out when opening/closing and when opacity\n" - " changes, unless --no-fading-openclose is used.\n" - "\n" - "-F\n" - " Equals to -f. Deprecated.\n" - "\n" - "-i opacity\n" - " Opacity of inactive windows. (0.1 - 1.0)\n" - "\n" - "-e opacity\n" - " Opacity of window titlebars and borders. (0.1 - 1.0)\n" - "\n" - "-G\n" - " Don't draw shadows on DND windows\n" - "\n" - "-b\n" - " Daemonize process.\n" - "\n" - "--show-all-xerrors\n" - " Show all X errors (for debugging).\n" - "\n" - "--config path\n" - " Look for configuration file at the path. Use /dev/null to avoid\n" - " loading configuration file." -#ifndef CONFIG_LIBCONFIG - WARNING_DISABLED -#endif - "\n\n" - "--write-pid-path path\n" - " Write process ID to a file.\n" - "\n" - "--shadow-color color\n" - " Color of shadow, as a hex RGB string (defaults to #000000)\n" - "\n" - "--shadow-red value\n" - " Red color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-green value\n" - " Green color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--shadow-blue value\n" - " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n" - "\n" - "--inactive-opacity-override\n" - " Inactive opacity set by -i overrides value of _NET_WM_WINDOW_OPACITY.\n" - "\n" - "--inactive-dim value\n" - " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n" - "\n" - "--active-opacity opacity\n" - " Default opacity for active windows. (0.0 - 1.0)\n" - "\n" - "--corner-radius value\n" - " Sets the radius of rounded window corners. When > 0, the compositor\n" - " will round the corners of windows. (defaults to 0).\n" - "\n" - "--rounded-corners-exclude condition\n" - " Exclude conditions for rounded corners.\n" - "\n" - "--mark-wmwin-focused\n" - " Try to detect WM windows and mark them as active.\n" - "\n" - "--shadow-exclude condition\n" - " Exclude conditions for shadows.\n" - "\n" - "--fade-exclude condition\n" - " Exclude conditions for fading.\n" - "\n" - "--mark-ovredir-focused\n" - " Mark windows that have no WM frame as active.\n" - "\n" - "--no-fading-openclose\n" - " Do not fade on window open/close.\n" - "\n" - "--no-fading-destroyed-argb\n" - " Do not fade destroyed ARGB windows with WM frame. Workaround of bugs\n" - " in Openbox, Fluxbox, etc.\n" - "\n" - "--shadow-ignore-shaped\n" - " Do not paint shadows on shaped windows. (Deprecated, use\n" - " --shadow-exclude \'bounding_shaped\' or\n" - " --shadow-exclude \'bounding_shaped && !rounded_corners\' instead.)\n" - "\n" - "--detect-rounded-corners\n" - " Try to detect windows with rounded corners and don't consider\n" - " them shaped windows. Affects --shadow-ignore-shaped,\n" - " --unredir-if-possible, and possibly others. You need to turn this\n" - " on manually if you want to match against rounded_corners in\n" - " conditions.\n" - "\n" - "--detect-client-opacity\n" - " Detect _NET_WM_WINDOW_OPACITY on client windows, useful for window\n" - " managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame\n" - " windows.\n" - "\n" - "--vsync\n" - " Enable VSync\n" - "\n" - "--use-ewmh-active-win\n" - " Use _NET_WM_ACTIVE_WINDOW on the root window to determine which\n" - " window is focused instead of using FocusIn/Out events.\n" - "\n" - "--unredir-if-possible\n" - " Unredirect all windows if a full-screen opaque window is\n" - " detected, to maximize performance for full-screen windows.\n" - "\n" - "--unredir-if-possible-delay ms\n" - " Delay before unredirecting the window, in milliseconds.\n" - " Defaults to 0.\n" - "\n" - "--unredir-if-possible-exclude condition\n" - " Conditions of windows that shouldn't be considered full-screen\n" - " for unredirecting screen.\n" - "\n" - "--focus-exclude condition\n" - " Specify a list of conditions of windows that should always be\n" - " considered focused.\n" - "\n" - "--inactive-dim-fixed\n" - " Use fixed inactive dim value.\n" - "\n" - "--max-brightness\n" - " Dims windows which average brightness is above this threshold.\n" - " Requires --no-use-damage.\n" - " Default: 1.0 or no dimming.\n" - "\n" - "--detect-transient\n" - " Use WM_TRANSIENT_FOR to group windows, and consider windows in\n" - " the same group focused at the same time.\n" - "\n" - "--detect-client-leader\n" - " Use WM_CLIENT_LEADER to group windows, and consider windows in\n" - " the same group focused at the same time. This usually means windows\n" - " from the same application will be considered focused or unfocused at\n" - " the same time. WM_TRANSIENT_FOR has higher priority if\n" - " --detect-transient is enabled, too.\n" - "\n" - "--blur-method\n" - " The algorithm used for background bluring. Available choices are:\n" - " 'none' to disable, 'gaussian', 'box' or 'kernel' for custom\n" - " convolution blur with --blur-kern.\n" - " Note: 'gaussian' and 'box' is not supported by --legacy-backends.\n" - "\n" - "--blur-size\n" - " The radius of the blur kernel for 'box' and 'gaussian' blur method.\n" - "\n" - "--blur-deviation\n" - " The standard deviation for the 'gaussian' blur method.\n" - "\n" - "--blur-strength\n" - " The strength level of the 'dual_kawase' blur method.\n" - "\n" - "--blur-background\n" - " Blur background of semi-transparent / ARGB windows. Bad in\n" - " performance. The switch name may change without prior\n" - " notifications.\n" - "\n" - "--blur-background-frame\n" - " Blur background of windows when the window frame is not opaque.\n" - " Implies --blur-background. Bad in performance. The switch name\n" - " may change.\n" - "\n" - "--blur-background-fixed\n" - " Use fixed blur strength instead of adjusting according to window\n" - " opacity.\n" - "\n" - "--blur-kern matrix\n" - " Specify the blur convolution kernel, with the following format:\n" - " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n" - " The element in the center must not be included, it will be forever\n" - " 1.0 or changing based on opacity, depending on whether you have\n" - " --blur-background-fixed.\n" - " A 7x7 Gaussian blur kernel looks like:\n" - " --blur-kern " - "'7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0." - "000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0." - "029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0." - "493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0." - "243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0." - "003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0." - "000003'\n" - " Up to 4 blur kernels may be specified, separated with semicolon, for\n" - " multi-pass blur.\n" - " May also be one the predefined kernels: 3x3box (default), 5x5box,\n" - " 7x7box, 3x3gaussian, 5x5gaussian, 7x7gaussian, 9x9gaussian,\n" - " 11x11gaussian.\n" - "\n" - "--blur-background-exclude condition\n" - " Exclude conditions for background blur.\n" - "\n" - "--resize-damage integer\n" - " Resize damaged region by a specific number of pixels. A positive\n" - " value enlarges it while a negative one shrinks it. Useful for\n" - " fixing the line corruption issues of blur. May or may not\n" - " work with --glx-no-stencil. Shrinking doesn't function correctly.\n" - "\n" - "--invert-color-include condition\n" - " Specify a list of conditions of windows that should be painted with\n" - " inverted color. Resource-hogging, and is not well tested.\n" - "\n" - "--opacity-rule opacity:condition\n" - " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n" - " like \'50:name *= \"Firefox\"'. picom-trans is recommended over\n" - " this. Note we do not distinguish 100%% and unset, and we don't make\n" - " any guarantee about possible conflicts with other programs that set\n" - " _NET_WM_WINDOW_OPACITY on frame or client windows.\n" - "\n" - "--shadow-exclude-reg geometry\n" - " Specify a X geometry that describes the region in which shadow\n" - " should not be painted in, such as a dock window region.\n" - " Use --shadow-exclude-reg \'x10+0-0\', for example, if the 10 pixels\n" - " on the bottom of the screen should not have shadows painted on.\n" - "\n" - "--clip-shadow-above condition\n" - " Specify a list of conditions of windows to not paint a shadow over,\n" - " such as a dock window.\n" - "\n" - "--xinerama-shadow-crop\n" - " Crop shadow of a window fully on a particular Xinerama screen to the\n" - " screen.\n" - "\n" - "--backend backend\n" - " Choose backend. Possible choices are xrender, glx, and\n" - " xr_glx_hybrid." -#ifndef CONFIG_OPENGL - " (GLX BACKENDS DISABLED AT COMPILE TIME)" -#endif - "\n\n" - "--glx-no-stencil\n" - " GLX backend: Avoid using stencil buffer. Might cause issues\n" - " when rendering transparent content. My tests show a 15%% performance\n" - " boost.\n" - "\n" - "--glx-no-rebind-pixmap\n" - " GLX backend: Avoid rebinding pixmap on window damage. Probably\n" - " could improve performance on rapid window content changes, but is\n" - " known to break things on some drivers (LLVMpipe, xf86-video-intel,\n" - " etc.).\n" - "\n" - "--no-use-damage\n" - " Disable the use of damage information. This cause the whole screen to\n" - " be redrawn everytime, instead of the part of the screen that has\n" - " actually changed. Potentially degrades the performance, but might fix\n" - " some artifacts.\n" - "\n" - "--xrender-sync-fence\n" - " Additionally use X Sync fence to sync clients' draw calls. Needed\n" - " on nvidia-drivers with GLX backend for some users.\n" - "\n" - "--force-win-blend\n" - " Force all windows to be painted with blending. Useful if you have a\n" - " --glx-fshader-win that could turn opaque pixels transparent.\n" - "\n" - "--dbus\n" - " Enable remote control via D-Bus. See the D-BUS API section in the\n" - " man page for more details." -#ifndef CONFIG_DBUS - WARNING_DISABLED -#endif - "\n\n" - "--benchmark cycles\n" - " Benchmark mode. Repeatedly paint until reaching the specified cycles.\n" - "\n" - "--benchmark-wid window-id\n" - " Specify window ID to repaint in benchmark mode. If omitted or is 0,\n" - " the whole screen is repainted.\n" - "\n" - "--monitor-repaint\n" - " Highlight the updated area of the screen. For debugging the xrender\n" - " backend only.\n" - "\n" - "--debug-mode\n" - " Render into a separate window, and don't take over the screen. Useful\n" - " when you want to attach a debugger to picom\n" - "\n" - "--no-ewmh-fullscreen\n" - " Do not use EWMH to detect fullscreen windows. Reverts to checking\n" - " if a window is fullscreen based only on its size and coordinates.\n" - "\n" - "--transparent-clipping\n" - " Make transparent windows clip other windows like non-transparent windows\n" - " do, instead of blending on top of them\n" - "\n" - "--transparent-clipping-exclude condition\n" - " Specify a list of conditions of windows that should never have\n" - " transparent clipping applied. Useful for screenshot tools, where you\n" - " need to be able to see through transparent parts of the window.\n" - "\n" - "--window-shader-fg shader\n" - " Specify GLSL fragment shader path for rendering window contents. Does\n" - " not work when `--legacy-backends` is enabled.\n" - "\n" - "--window-shader-fg-rule shader:condition\n" - " Specify GLSL fragment shader path for rendering window contents using\n" - " patterns. Pattern should be in the format of `SHADER_PATH:PATTERN`,\n" - " similar to `--opacity-rule`. `SHADER_PATH` can be \"default\", in which\n" - " case the default shader will be used. Does not work when\n" - " `--legacy-backends` is enabled.\n"; FILE *f = (ret ? stderr : stdout); - fprintf(f, usage_text, argv0); -#undef WARNING_DISABLED + fprintf(f, "picom (%s)\n", PICOM_VERSION); + fprintf(f, "Standalone X11 compositor\n"); + fprintf(f, "Please report bugs to https://github.com/yshui/picom\n\n"); + + fprintf(f, "Usage: %s [OPTION]...\n\n", argv0); + fprintf(f, "OPTIONS:\n"); + + int line_wrap = 80; + struct winsize window_size = {0}; + if (ioctl(fileno(f), TIOCGWINSZ, &window_size) != -1) { + line_wrap = window_size.ws_col; + } + + size_t help_indent = 0; + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + if (picom_options[i].help == NULL) { + // Hide options with no help message. + continue; + } + auto option_len = strlen(picom_options[i].long_name) + 2 + 4; + if (picom_options[i].arg_name) { + option_len += strlen(picom_options[i].arg_name) + 1; + } + if (option_len > help_indent && option_len < 30) { + help_indent = option_len; + } + } + help_indent += 6; + + for (size_t i = 0; i < ARR_SIZE(picom_options); i++) { + if (picom_options[i].help == NULL) { + continue; + } + size_t option_len = 8; + fprintf(f, " "); + if ((picom_options[i].val > 'a' && picom_options[i].val < 'z') || + (picom_options[i].val > 'A' && picom_options[i].val < 'Z')) { + fprintf(f, "-%c, ", picom_options[i].val); + } else { + fprintf(f, " "); + } + fprintf(f, "--%s", picom_options[i].long_name); + option_len += strlen(picom_options[i].long_name) + 2; + if (picom_options[i].arg_name) { + fprintf(f, "=%s", picom_options[i].arg_name); + option_len += strlen(picom_options[i].arg_name) + 1; + } + fprintf(f, " "); + option_len += 2; + print_help(picom_options[i].help, help_indent, option_len, + (size_t)line_wrap, f); + } } static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb"; -static const struct option longopts[] = { - {"help", no_argument, NULL, 'h'}, - {"config", required_argument, NULL, 256}, - {"shadow-radius", required_argument, NULL, 'r'}, - {"shadow-opacity", required_argument, NULL, 'o'}, - {"shadow-offset-x", required_argument, NULL, 'l'}, - {"shadow-offset-y", required_argument, NULL, 't'}, - {"fade-in-step", required_argument, NULL, 'I'}, - {"fade-out-step", required_argument, NULL, 'O'}, - {"fade-delta", required_argument, NULL, 'D'}, - {"menu-opacity", required_argument, NULL, 'm'}, - {"shadow", no_argument, NULL, 'c'}, - {"clear-shadow", no_argument, NULL, 'z'}, - {"fading", no_argument, NULL, 'f'}, - {"inactive-opacity", required_argument, NULL, 'i'}, - {"frame-opacity", required_argument, NULL, 'e'}, - {"daemon", no_argument, NULL, 'b'}, - {"shadow-red", required_argument, NULL, 257}, - {"shadow-green", required_argument, NULL, 258}, - {"shadow-blue", required_argument, NULL, 259}, - {"inactive-opacity-override", no_argument, NULL, 260}, - {"inactive-dim", required_argument, NULL, 261}, - {"mark-wmwin-focused", no_argument, NULL, 262}, - {"shadow-exclude", required_argument, NULL, 263}, - {"mark-ovredir-focused", no_argument, NULL, 264}, - {"no-fading-openclose", no_argument, NULL, 265}, - {"shadow-ignore-shaped", no_argument, NULL, 266}, - {"detect-rounded-corners", no_argument, NULL, 267}, - {"detect-client-opacity", no_argument, NULL, 268}, - {"refresh-rate", required_argument, NULL, 269}, - {"vsync", optional_argument, NULL, 270}, - {"sw-opti", no_argument, NULL, 274}, - {"vsync-aggressive", no_argument, NULL, 275}, - {"use-ewmh-active-win", no_argument, NULL, 276}, - {"respect-prop-shadow", no_argument, NULL, 277}, - {"unredir-if-possible", no_argument, NULL, 278}, - {"focus-exclude", required_argument, NULL, 279}, - {"inactive-dim-fixed", no_argument, NULL, 280}, - {"detect-transient", no_argument, NULL, 281}, - {"detect-client-leader", no_argument, NULL, 282}, - {"blur-background", no_argument, NULL, 283}, - {"blur-background-frame", no_argument, NULL, 284}, - {"blur-background-fixed", no_argument, NULL, 285}, - {"dbus", no_argument, NULL, 286}, - {"logpath", required_argument, NULL, 287}, - {"invert-color-include", required_argument, NULL, 288}, - {"opengl", no_argument, NULL, 289}, - {"backend", required_argument, NULL, 290}, - {"glx-no-stencil", no_argument, NULL, 291}, - {"benchmark", required_argument, NULL, 293}, - {"benchmark-wid", required_argument, NULL, 294}, - {"blur-background-exclude", required_argument, NULL, 296}, - {"active-opacity", required_argument, NULL, 297}, - {"glx-no-rebind-pixmap", no_argument, NULL, 298}, - {"glx-swap-method", required_argument, NULL, 299}, - {"fade-exclude", required_argument, NULL, 300}, - {"blur-kern", required_argument, NULL, 301}, - {"resize-damage", required_argument, NULL, 302}, - {"glx-use-gpushader4", no_argument, NULL, 303}, - {"opacity-rule", required_argument, NULL, 304}, - {"shadow-exclude-reg", required_argument, NULL, 305}, - {"paint-exclude", required_argument, NULL, 306}, - {"xinerama-shadow-crop", no_argument, NULL, 307}, - {"unredir-if-possible-exclude", required_argument, NULL, 308}, - {"unredir-if-possible-delay", required_argument, NULL, 309}, - {"write-pid-path", required_argument, NULL, 310}, - {"vsync-use-glfinish", no_argument, NULL, 311}, - {"xrender-sync-fence", no_argument, NULL, 313}, - {"show-all-xerrors", no_argument, NULL, 314}, - {"no-fading-destroyed-argb", no_argument, NULL, 315}, - {"force-win-blend", no_argument, NULL, 316}, - {"glx-fshader-win", required_argument, NULL, 317}, - {"version", no_argument, NULL, 318}, - {"no-x-selection", no_argument, NULL, 319}, - {"log-level", required_argument, NULL, 321}, - {"log-file", required_argument, NULL, 322}, - {"use-damage", no_argument, NULL, 323}, - {"no-use-damage", no_argument, NULL, 324}, - {"no-vsync", no_argument, NULL, 325}, - {"max-brightness", required_argument, NULL, 326}, - {"transparent-clipping", no_argument, NULL, 327}, - {"blur-method", required_argument, NULL, 328}, - {"blur-size", required_argument, NULL, 329}, - {"blur-deviation", required_argument, NULL, 330}, - {"blur-strength", required_argument, NULL, 331}, - {"shadow-color", required_argument, NULL, 332}, - {"corner-radius", required_argument, NULL, 333}, - {"rounded-corners-exclude", required_argument, NULL, 334}, - {"clip-shadow-above", required_argument, NULL, 335}, - {"window-shader-fg", required_argument, NULL, 336}, - {"window-shader-fg-rule", required_argument, NULL, 337}, - {"transparent-clipping-exclude", required_argument, NULL, 338}, - {"legacy-backends", no_argument, NULL, 733}, - {"monitor-repaint", no_argument, NULL, 800}, - {"diagnostics", no_argument, NULL, 801}, - {"debug-mode", no_argument, NULL, 802}, - {"no-ewmh-fullscreen", no_argument, NULL, 803}, - // Must terminate with a NULL entry - {NULL, 0, NULL, 0}, -}; /// Get config options that are needed to parse the rest of the options /// Return true if we should quit bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors, bool *fork, int *exit_code) { + setup_longopts(); + int o = 0, longopt_idx = -1; // Pre-parse the commandline arguments to check for --config and invalid From d59ec6a34ae7435e8d01d85412a5dfaf18f90f68 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 29 Oct 2022 20:44:02 +0100 Subject: [PATCH 10/64] Bump version number Signed-off-by: Yuxuan Shui --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0150a37..11da327 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('picom', 'c', version: '9', +project('picom', 'c', version: '10', default_options: ['c_std=c11', 'warning_level=1']) cc = meson.get_compiler('c') From abea823f5dede234e7f7c2a142c907a22c80aeb8 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 29 Oct 2022 20:59:28 +0100 Subject: [PATCH 11/64] doc: remove mention of raw string pattern Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 17de2c8..7dc070f 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -302,7 +302,7 @@ With greater-than/less-than operators it looks like: 'OPERATOR' is one of `=` (equals), `<`, `>`, `<=`, `=>`, or nothing (exists). Exists operator checks whether a property exists on a window (but for predefined targets, exists means != 0 then). -'PATTERN' is either an integer or a string enclosed by single or double quotes. Python-3-style escape sequences and raw string are supported in the string format. +'PATTERN' is either an integer or a string enclosed by single or double quotes. Python-3-style escape sequences are supported in the string format. Supported logical operators are `&&` (and) and `||` (or). `&&` has higher precedence than `||`, left-to-right associativity. Use parentheses to change precedence. From 59d5b9548342d039499aa6802ce39aae1d509f8c Mon Sep 17 00:00:00 2001 From: Alp Date: Mon, 31 Oct 2022 22:12:16 +0300 Subject: [PATCH 12/64] rounded corner delay fix --- src/win.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/win.c b/src/win.c index aad7a38..119093c 100644 --- a/src/win.c +++ b/src/win.c @@ -677,13 +677,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { // Determine if a window should animate if (win_should_animate(ps, w)) { - if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { - win_on_win_size_change(ps, w); - win_update_bounding_shape(ps, w); - damaged = true; - win_clear_flags(w, WIN_FLAGS_SIZE_STALE); - } - + win_update_bounding_shape(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) @@ -749,13 +743,12 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { } } - w->animation_progress = 0.0; - + w->animation_progress = 0.0; } else { w->g = w->pending_g; } - if (!win_should_animate(ps, w) && win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { win_on_win_size_change(ps, w); win_update_bounding_shape(ps, w); damaged = true; From 7d0d693ca7a4f07f94dbd66a1b22ddf29bd61c49 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 11 Nov 2022 15:51:11 +0000 Subject: [PATCH 13/64] backend: gl: fix shadow from mask The intermediate texture used for shadow from mask calculation did not properly set the min/mag filter to linear, which is required by the blur methods. Because they use texture interpolation to accelerate the convolution calculation. Fixes #916 Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 7ade233..88078e6 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1213,6 +1213,8 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, auto source_texture = gl_new_texture(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); From 9d7cbe49f16cf33db378c73627a080c78182d5eb Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 15 Nov 2022 18:03:31 +0000 Subject: [PATCH 14/64] win: assert we won't clobber existing mask Signed-off-by: Yuxuan Shui --- src/win.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win.c b/src/win.c index 370fbfd..2525d89 100644 --- a/src/win.c +++ b/src/win.c @@ -346,6 +346,7 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w } bool win_bind_mask(struct backend_base *b, struct managed_win *w) { + assert(!w->mask_image); auto reg_bound_local = win_get_bounding_shape_global_by_val(w); pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); w->mask_image = b->ops->make_mask( From 7233601be3f1ba291d5fe2be3347c6f1003a6d0e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 16 Nov 2022 15:39:17 +0000 Subject: [PATCH 15/64] Fix typo Fixes #922 Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 2 +- picom.sample.conf | 4 ++-- src/common.h | 4 ++-- src/options.c | 2 +- tests/configs/parsing_test.conf | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 7dc070f..61af5a2 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -230,7 +230,7 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box GLX backend: Avoid rebinding pixmap on window damage. Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.). Recommended if it works. *--no-use-damage*:: - Disable the use of damage information. This cause the whole screen to be redrawn everytime, instead of the part of the screen has actually changed. Potentially degrades the performance, but might fix some artifacts. + Disable the use of damage information. This cause the whole screen to be redrawn every time, instead of the part of the screen has actually changed. Potentially degrades the performance, but might fix some artifacts. *--xrender-sync-fence*:: Use X Sync fence to sync clients' draw calls, to make sure all draw calls are finished before picom starts drawing. Needed on nvidia-drivers with GLX backend for some users. diff --git a/picom.sample.conf b/picom.sample.conf index 071a994..a8ba5c7 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -308,7 +308,7 @@ detect-transient = true; # glx-no-rebind-pixmap = false # Disable the use of damage information. -# This cause the whole screen to be redrawn everytime, instead of the part of the screen +# This cause the whole screen to be redrawn every time, instead of the part of the screen # has actually changed. Potentially degrades the performance, but might fix some artifacts. # The opposing option is use-damage # @@ -409,7 +409,7 @@ log-level = "warn"; # transparent, and you want shadows in those areas. # # clip-shadow-above::: -# Controls wether shadows that would have been drawn above the window should +# Controls whether shadows that would have been drawn above the window should # be clipped. Useful for dock windows that should have no shadow painted on top. # # redir-ignore::: diff --git a/src/common.h b/src/common.h index c06a30c..c773187 100644 --- a/src/common.h +++ b/src/common.h @@ -150,7 +150,7 @@ typedef struct session { /// Use an ev_idle callback for drawing /// So we only start drawing when events are processed ev_idle draw_idle; - /// Called everytime we have timeouts or new data on socket, + /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue ev_prepare event_check; @@ -240,7 +240,7 @@ typedef struct session { /// Whether we need to redraw the screen bool redraw_needed; - /// Cache a xfixes region so we don't need to allocate it everytime. + /// Cache a xfixes region so we don't need to allocate it every time. /// A workaround for yshui/picom#301 xcb_xfixes_region_t damaged_region; /// The region needs to painted on next paint. diff --git a/src/options.c b/src/options.c index ba7485d..0226714 100644 --- a/src/options.c +++ b/src/options.c @@ -139,7 +139,7 @@ static const struct picom_option picom_options[] = { {"log-file" , required_argument, 322, NULL , "Path to the log file."}, {"use-damage" , no_argument , 323, NULL , "Render only the damaged (changed) part of the screen"}, {"no-use-damage" , no_argument , 324, NULL , "Disable the use of damage information. This cause the whole screen to be" - "redrawn everytime, instead of the part of the screen that has actually " + "redrawn every time, instead of the part of the screen that has actually " "changed. Potentially degrades the performance, but might fix some artifacts."}, {"no-vsync" , no_argument , 325, NULL , "Disable VSync"}, {"max-brightness" , required_argument, 326, NULL , "Dims windows which average brightness is above this threshold. Requires " diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index 5269e6e..7236017 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -311,7 +311,7 @@ detect-transient = true; # glx-no-rebind-pixmap = false # Disable the use of damage information. -# This cause the whole screen to be redrawn everytime, instead of the part of the screen +# This cause the whole screen to be redrawn every time, instead of the part of the screen # has actually changed. Potentially degrades the performance, but might fix some artifacts. # The opposing option is use-damage # @@ -400,7 +400,7 @@ log-level = "warn"; # transparent, and you want shadows in those areas. # # clip-shadow-above::: -# Controls wether shadows that would have been drawn above the window should +# Controls whether shadows that would have been drawn above the window should # be clipped. Useful for dock windows that should have no shadow painted on top. # # redir-ignore::: From e61b3ea7a303f090c00b6bc88a6945ff76f89255 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 17 Nov 2022 22:07:59 +0000 Subject: [PATCH 16/64] backend: gl: fix crash when shadow radius is 0 Fixes #927 Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 65 +++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 88078e6..f4f277d 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1174,18 +1174,23 @@ struct gl_shadow_context { struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) { auto ctx = ccalloc(1, struct gl_shadow_context); ctx->radius = radius; + ctx->blur_context = NULL; - struct gaussian_blur_args args = { - .size = (int)radius, - .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), - }; - ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + if (radius > 0) { + struct gaussian_blur_args args = { + .size = (int)radius, + .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), + }; + ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + } return (struct backend_shadow_context *)ctx; } void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) { auto ctx_ = (struct gl_shadow_context *)ctx; - gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + if (ctx_->blur_context) { + gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + } free(ctx_); } @@ -1246,27 +1251,32 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, gl_check_err(); - glActiveTexture(GL_TEXTURE0); - auto tmp_texture = gl_new_texture(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, tmp_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, - GL_RED, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); + auto tmp_texture = source_texture; + if (gsctx->blur_context != NULL) { + glActiveTexture(GL_TEXTURE0); + tmp_texture = gl_new_texture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, + new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - tmp_texture, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tmp_texture, 0); - region_t reg_blur; - pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, - (unsigned int)new_inner->height); - // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), but we - // are covering the whole texture so we don't need to worry about that. - gl_blur_impl(1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, - source_texture, - (geometry_t){.width = new_inner->width, .height = new_inner->height}, - fbo, gd->default_mask_texture); - pixman_region32_fini(®_blur); + region_t reg_blur; + pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, + (unsigned int)new_inner->height); + // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), + // but we are covering the whole texture so we don't need to worry about + // that. + gl_blur_impl( + 1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, + source_texture, + (geometry_t){.width = new_inner->width, .height = new_inner->height}, + fbo, gd->default_mask_texture); + pixman_region32_fini(®_blur); + } // Colorize the shadow with color. log_debug("Colorize shadow"); @@ -1318,7 +1328,10 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); - glDeleteTextures(1, (GLuint[]){source_texture, tmp_texture}); + glDeleteTextures(1, (GLuint[]){source_texture}); + if (tmp_texture != source_texture) { + glDeleteTextures(1, (GLuint[]){tmp_texture}); + } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); gl_check_err(); From 706acb78b7c5a1236fa727292e83baf3d7803d1b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 17 Nov 2022 22:16:27 +0000 Subject: [PATCH 17/64] backend: gl: handle blur context creation failure Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index f4f277d..a7d2aab 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1182,6 +1182,11 @@ struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), }; ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + if (!ctx->blur_context) { + log_error("Failed to create shadow context"); + free(ctx); + return NULL; + } } return (struct backend_shadow_context *)ctx; } From 5580f461dcf3465ce48dfce335b7c4661d67207b Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Fri, 18 Nov 2022 10:13:49 +0000 Subject: [PATCH 18/64] fix log_debug call --- src/picom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/picom.c b/src/picom.c index fd693fc..ecd0907 100644 --- a/src/picom.c +++ b/src/picom.c @@ -539,8 +539,8 @@ static bool initialize_backend(session_t *ps) { } else { shader->attributes = 0; } - log_debug("Shader %s has attributes %ld", shader->key, - shader->attributes); + log_debug("Shader %s has attributes %" PRIu64, + shader->key, shader->attributes); } } From a9db7ab41eeaa8e1040550d23981a6edb0f71b61 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 21 Nov 2022 15:18:55 +0000 Subject: [PATCH 19/64] win: fix leaking of the mask image destroy_win_finish doesn't call win_release_images to free the images, so we need to add a release_mask call there. Related: #892 Signed-off-by: Yuxuan Shui --- src/win.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win.c b/src/win.c index 2525d89..fb61191 100644 --- a/src/win.c +++ b/src/win.c @@ -2140,6 +2140,7 @@ static void destroy_win_finish(session_t *ps, struct win *w) { assert(mw->shadow_image != NULL); win_release_shadow(ps->backend_data, mw); } + win_release_mask(ps->backend_data, mw); // Invalidate reg_ignore of windows below this one // TODO(yshui) what if next_w is not mapped?? From 2d35f78dc369906464dcb9f4470aed999f0e848e Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Fri, 18 Nov 2022 14:54:18 +0000 Subject: [PATCH 20/64] backend: egl: don't assume glEGLImageTargetTexStorage exists Use eglGetProcAddress instead. Fixes #932 --- src/backend/gl/egl.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 0c3c0d4..5c3b553 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -36,6 +36,8 @@ struct egl_data { EGLContext ctx; }; +static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL; + /** * Free a glx_texture_t. */ @@ -202,6 +204,14 @@ static backend_t *egl_init(session_t *ps) { goto end; } + glEGLImageTargetTexStorage = + (PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS" + "torageEXT"); + if (glEGLImageTargetTexStorage == NULL) { + log_error("Failed to get glEGLImageTargetTexStorageEXT."); + goto end; + } + gd->gl.decouple_texture_user_data = egl_decouple_user_data; gd->gl.release_user_data = egl_release_image; @@ -270,7 +280,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b wd->dim = 0; wd->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, inner->texture); - glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL); + glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); From e2b8c3fd1e5eb06c137daf03dcb6c11f93aa2cd4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 22 Nov 2022 16:12:36 +0000 Subject: [PATCH 21/64] c2: replace pcre with pcre2 Because pcre has been deprecated.[1] There are subtle changes from pcre to pcre2, so this could be a breaking change. Closes #895 [1]: https://www.pcre.org/original/changelog.txt Signed-off-by: Yuxuan Shui --- src/c2.c | 155 ++++++++++++++++++++++++------------------------ src/meson.build | 5 +- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/src/c2.c b/src/c2.c index 80ecb24..f8af699 100644 --- a/src/c2.c +++ b/src/c2.c @@ -17,15 +17,8 @@ // libpcre #ifdef CONFIG_REGEX_PCRE -#include - -// For compatibility with #endif @@ -89,49 +82,52 @@ struct _c2_b { /// Structure for leaf element in a window condition struct _c2_l { bool neg : 1; - enum { C2_L_OEXISTS, - C2_L_OEQ, - C2_L_OGT, - C2_L_OGTEQ, - C2_L_OLT, - C2_L_OLTEQ, + enum { + C2_L_OEXISTS, + C2_L_OEQ, + C2_L_OGT, + C2_L_OGTEQ, + C2_L_OLT, + C2_L_OLTEQ, } op : 3; - enum { C2_L_MEXACT, - C2_L_MSTART, - C2_L_MCONTAINS, - C2_L_MWILDCARD, - C2_L_MPCRE, + enum { + C2_L_MEXACT, + C2_L_MSTART, + C2_L_MCONTAINS, + C2_L_MWILDCARD, + C2_L_MPCRE, } match : 3; bool match_ignorecase : 1; char *tgt; xcb_atom_t tgtatom; bool tgt_onframe; int index; - enum { C2_L_PUNDEFINED = -1, - C2_L_PID = 0, - C2_L_PX, - C2_L_PY, - C2_L_PX2, - C2_L_PY2, - C2_L_PWIDTH, - C2_L_PHEIGHT, - C2_L_PWIDTHB, - C2_L_PHEIGHTB, - C2_L_PBDW, - C2_L_PFULLSCREEN, - C2_L_POVREDIR, - C2_L_PARGB, - C2_L_PFOCUSED, - C2_L_PWMWIN, - C2_L_PBSHAPED, - C2_L_PROUNDED, - C2_L_PCLIENT, - C2_L_PWINDOWTYPE, - C2_L_PLEADER, - C2_L_PNAME, - C2_L_PCLASSG, - C2_L_PCLASSI, - C2_L_PROLE, + enum { + C2_L_PUNDEFINED = -1, + C2_L_PID = 0, + C2_L_PX, + C2_L_PY, + C2_L_PX2, + C2_L_PY2, + C2_L_PWIDTH, + C2_L_PHEIGHT, + C2_L_PWIDTHB, + C2_L_PHEIGHTB, + C2_L_PBDW, + C2_L_PFULLSCREEN, + C2_L_POVREDIR, + C2_L_PARGB, + C2_L_PFOCUSED, + C2_L_PWMWIN, + C2_L_PBSHAPED, + C2_L_PROUNDED, + C2_L_PCLIENT, + C2_L_PWINDOWTYPE, + C2_L_PLEADER, + C2_L_PNAME, + C2_L_PCLASSG, + C2_L_PCLASSI, + C2_L_PROLE, } predef; enum c2_l_type { C2_L_TUNDEFINED, @@ -142,15 +138,16 @@ struct _c2_l { C2_L_TDRAWABLE, } type; int format; - enum { C2_L_PTUNDEFINED, - C2_L_PTSTRING, - C2_L_PTINT, + enum { + C2_L_PTUNDEFINED, + C2_L_PTSTRING, + C2_L_PTINT, } ptntype; char *ptnstr; long ptnint; #ifdef CONFIG_REGEX_PCRE - pcre *regex_pcre; - pcre_extra *regex_pcre_extra; + pcre2_code *regex_pcre; + pcre2_match_data *regex_pcre_match; #endif }; @@ -1059,32 +1056,31 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { // PCRE patterns if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) { #ifdef CONFIG_REGEX_PCRE - const char *error = NULL; - int erroffset = 0; - int options = 0; + int errorcode = 0; + PCRE2_SIZE erroffset = 0; + unsigned int options = 0; // Ignore case flag - if (pleaf->match_ignorecase) - options |= PCRE_CASELESS; + if (pleaf->match_ignorecase) { + options |= PCRE2_CASELESS; + } // Compile PCRE expression pleaf->regex_pcre = - pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL); - if (!pleaf->regex_pcre) { - log_error("Pattern \"%s\": PCRE regular expression parsing " + pcre2_compile((PCRE2_SPTR)pleaf->ptnstr, PCRE2_ZERO_TERMINATED, + options, &errorcode, &erroffset, NULL); + if (pleaf->regex_pcre == NULL) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errorcode, buffer, sizeof(buffer)); + log_error("Pattern \"%s\": PCRE regular expression " + "parsing " "failed on " - "offset %d: %s", - pleaf->ptnstr, erroffset, error); + "offset %zu: %s", + pleaf->ptnstr, erroffset, buffer); return false; } -#ifdef CONFIG_REGEX_PCRE_JIT - pleaf->regex_pcre_extra = - pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); - if (!pleaf->regex_pcre_extra) { - printf("Pattern \"%s\": PCRE regular expression study failed: %s", - pleaf->ptnstr, error); - } -#endif + pleaf->regex_pcre_match = + pcre2_match_data_create_from_pattern(pleaf->regex_pcre, NULL); // Free the target string // free(pleaf->tgt); @@ -1102,16 +1098,18 @@ static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) { if (!node.isbranch) { return c2_l_postprocess(ps, node.l); } - if (!c2_tree_postprocess(ps, node.b->opr1)) + if (!c2_tree_postprocess(ps, node.b->opr1)) { return false; + } return c2_tree_postprocess(ps, node.b->opr2); } bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) { c2_lptr_t *head = list; while (head) { - if (!c2_tree_postprocess(ps, head->ptr)) + if (!c2_tree_postprocess(ps, head->ptr)) { return false; + } head = head->next; } return true; @@ -1124,8 +1122,9 @@ static void c2_free(c2_ptr_t p) { if (p.isbranch) { c2_b_t *const pbranch = p.b; - if (!pbranch) + if (!pbranch) { return; + } c2_free(pbranch->opr1); c2_free(pbranch->opr2); @@ -1135,14 +1134,15 @@ static void c2_free(c2_ptr_t p) { else { c2_l_t *const pleaf = p.l; - if (!pleaf) + if (!pleaf) { return; + } free(pleaf->tgt); free(pleaf->ptnstr); #ifdef CONFIG_REGEX_PCRE - pcre_free(pleaf->regex_pcre); - LPCRE_FREE_STUDY(pleaf->regex_pcre_extra); + pcre2_code_free(pleaf->regex_pcre); + pcre2_match_data_free(pleaf->regex_pcre_match); #endif free(pleaf); } @@ -1550,9 +1550,10 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w case C2_L_MPCRE: #ifdef CONFIG_REGEX_PCRE assert(strlen(tgt) <= INT_MAX); - res = (pcre_exec(pleaf->regex_pcre, - pleaf->regex_pcre_extra, tgt, - (int)strlen(tgt), 0, 0, NULL, 0) >= 0); + assert(pleaf->regex_pcre); + res = (pcre2_match(pleaf->regex_pcre, (PCRE2_SPTR)tgt, + strlen(tgt), 0, 0, + pleaf->regex_pcre_match, NULL) > 0); #else assert(0); #endif diff --git a/src/meson.build b/src/meson.build index 60d83a8..09eb07b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,11 +44,8 @@ if get_option('config_file') srcs += [ 'config_libconfig.c' ] endif if get_option('regex') - pcre = dependency('libpcre', required: true) + pcre = dependency('libpcre2-8', required: true) cflags += ['-DCONFIG_REGEX_PCRE'] - if pcre.version().version_compare('>=8.20') - cflags += ['-DCONFIG_REGEX_PCRE_JIT'] - endif deps += [pcre] endif From 7360522e315d7bfa85666f4e73a629d0ec060754 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Thu, 24 Nov 2022 00:08:37 +0100 Subject: [PATCH 22/64] picom.sample.conf: Add egl to backend option doc --- picom.sample.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picom.sample.conf b/picom.sample.conf index a8ba5c7..7c74eb8 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -209,7 +209,7 @@ blur-background-exclude = [ # 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`. +# Specify the backend to use: `xrender`, `glx`, `egl` or `xr_glx_hybrid`. # `xrender` is the default one. # # backend = "glx" From 552bf77d0ebb4f61204eb4c7c03ea27cf1c03f46 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 27 Nov 2022 17:37:26 +0000 Subject: [PATCH 23/64] backend: egl: fix undefined symbols on old systems Users with an old EGL version won't be able to use the egl backend. OTOH we shouldn't prevent them from running picom because of a feature they won't even use. Don't assume the existence of EGL 1.5 symbols. Fixes #945 Signed-off-by: Yuxuan Shui --- src/backend/gl/egl.c | 46 ++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 5c3b553..5c40b00 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -37,6 +37,10 @@ struct egl_data { }; static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL; +static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL; +static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL; +static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL; +static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL; /** * Free a glx_texture_t. @@ -46,7 +50,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) { struct egl_pixmap *p = tex->user_data; // Release binding if (p->image != EGL_NO_IMAGE) { - eglDestroyImage(gd->display, p->image); + eglDestroyImageProc(gd->display, p->image); p->image = EGL_NO_IMAGE; } @@ -103,6 +107,20 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { * Initialize OpenGL. */ static backend_t *egl_init(session_t *ps) { + bool success = false; + +#define get_proc(name, type) \ + name##Proc = (type)eglGetProcAddress(#name); \ + if (!name##Proc) { \ + log_error("Failed to get EGL function " #name); \ + goto end; \ + } + get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC); + get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC); + get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC); + get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC); +#undef get_proc + // Check if we have the X11 platform const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (strstr(exts, "EGL_EXT_platform_x11") == NULL) { @@ -110,14 +128,13 @@ static backend_t *egl_init(session_t *ps) { return NULL; } - bool success = false; auto gd = ccalloc(1, struct egl_data); - gd->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_EXT, ps->dpy, - (EGLAttrib[]){ - EGL_PLATFORM_X11_SCREEN_EXT, - ps->scr, - EGL_NONE, - }); + gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, + (EGLAttrib[]){ + EGL_PLATFORM_X11_SCREEN_EXT, + ps->scr, + EGL_NONE, + }); if (gd->display == EGL_NO_DISPLAY) { log_error("Failed to get EGL display."); goto end; @@ -129,6 +146,11 @@ static backend_t *egl_init(session_t *ps) { goto end; } + if (major < 1 || (major == 1 && minor < 5)) { + log_error("EGL version too old, need at least 1.5."); + goto end; + } + // Check if EGL supports OpenGL const char *apis = eglQueryString(gd->display, EGL_CLIENT_APIS); if (strstr(apis, "OpenGL") == NULL) { @@ -172,7 +194,7 @@ static backend_t *egl_init(session_t *ps) { EGLConfig target_cfg = cfgs[0]; free(cfgs); - gd->target_win = eglCreatePlatformWindowSurface( + gd->target_win = eglCreatePlatformWindowSurfaceProc( gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); @@ -260,8 +282,8 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b eglpixmap = cmalloc(struct egl_pixmap); eglpixmap->pixmap = pixmap; - eglpixmap->image = eglCreateImage(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, - (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->image = eglCreateImageProc(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); eglpixmap->owned = owned; if (eglpixmap->image == EGL_NO_IMAGE) { @@ -287,7 +309,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b return wd; err: if (eglpixmap && eglpixmap->image) { - eglDestroyImage(gd->display, eglpixmap->image); + eglDestroyImageProc(gd->display, eglpixmap->image); } free(eglpixmap); From 19a24ada9dcd4ff39d98fed900c32bb3533be697 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 27 Nov 2022 18:07:05 +0000 Subject: [PATCH 24/64] backend: egl: fix warning --- src/backend/gl/egl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 5c40b00..e6d4d90 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -108,6 +108,7 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { */ static backend_t *egl_init(session_t *ps) { bool success = false; + struct egl_data *gd = NULL; #define get_proc(name, type) \ name##Proc = (type)eglGetProcAddress(#name); \ @@ -128,7 +129,7 @@ static backend_t *egl_init(session_t *ps) { return NULL; } - auto gd = ccalloc(1, struct egl_data); + gd = ccalloc(1, struct egl_data); gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, (EGLAttrib[]){ EGL_PLATFORM_X11_SCREEN_EXT, @@ -249,7 +250,9 @@ static backend_t *egl_init(session_t *ps) { end: if (!success) { - egl_deinit(&gd->gl.base); + if (gd != NULL) { + egl_deinit(&gd->gl.base); + } return NULL; } From 0a2cd0f14eebc41cc4c7b7c9a3db964a2b1a5ab0 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 04:16:15 +0000 Subject: [PATCH 25/64] backend: gl: add dither Add bayer ordered dithering when presenting to screen. Reduce banding when using a strong blur. Also use 16-bit intermediary textures to preserve precision in the rendering pipeline. Related: #602 Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 2 +- src/backend/gl/gl_common.c | 6 ++++-- src/backend/gl/gl_common.h | 5 +++-- src/backend/gl/shaders.c | 30 ++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index b73aeee..aad1e5f 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -284,7 +284,7 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, } glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, tex_size->width, tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index a7d2aab..cfa98d1 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -627,7 +627,7 @@ void gl_resize(struct gl_data *gd, int width, int height) { assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); gl_check_err(); @@ -873,7 +873,9 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); + gd->present_prog = + gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL}, + (const char *[]){present_frag, dither_glsl, NULL}); if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 3a78865..bad75ed 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -288,5 +288,6 @@ static const GLuint vert_in_texcoord_loc = 1; #define QUOTE(...) #__VA_ARGS__ extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], - fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[], - win_shader_default[], present_vertex_shader[], shadow_colorization_frag[]; + present_frag[], fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], + win_shader_glsl[], win_shader_default[], present_vertex_shader[], dither_glsl[], + shadow_colorization_frag[]; diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 4a18e62..bd620fe 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -9,6 +9,15 @@ const char dummy_frag[] = GLSL(330, } ); +const char present_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + vec4 dither(vec4, vec2); + void main() { + gl_FragColor = dither(texelFetch(tex, ivec2(texcoord.xy), 0), texcoord); + } +); + const char copy_with_mask_frag[] = GLSL(330, uniform sampler2D tex; in vec2 texcoord; @@ -174,6 +183,27 @@ const char vertex_shader[] = GLSL(330, texcoord = in_texcoord + texorig; } ); +const char dither_glsl[] = GLSL(330, + // Stolen from: https://www.shadertoy.com/view/7sfXDn + float bayer2(vec2 a) { + a = floor(a); + return fract(a.x / 2. + a.y * a.y * .75); + } + // 16 * 16 is 2^8, so in total we have equivalent of 16-bit + // color depth, should be enough? + float bayer(vec2 a16) { + vec2 a8 = a16 * .5; + vec2 a4 = a8 * .5; + vec2 a2 = a4 * .5; + float bayer32 = ((bayer2(a2) * .25 + bayer2( a4)) + * .25 + bayer2( a8)) + * .25 + bayer2(a16); + return bayer32; + } + vec4 dither(vec4 c, vec2 coord) { + return vec4(c + bayer(coord) / 255.0); + } +); const char shadow_colorization_frag[] = GLSL(330, uniform vec4 color; uniform sampler2D tex; From 1271839bafbbbea3545cbcf1b936d1ac41135531 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 05:28:57 +0000 Subject: [PATCH 26/64] options: add dithered-present option See also 0a2cd0f14eebc41cc4c7b7c9a3db964a2b1a5ab0 Related: #602 Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 3 +++ picom.sample.conf | 5 +++++ src/backend/gl/blur.c | 16 ++++++++++------ src/backend/gl/gl_common.c | 22 ++++++++++++++++------ src/backend/gl/gl_common.h | 10 ++++++---- src/backend/xrender/xrender.c | 4 ++++ src/config.h | 2 ++ src/config_libconfig.c | 2 ++ src/options.c | 8 ++++++++ 9 files changed, 56 insertions(+), 16 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 61af5a2..5b2182f 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -268,6 +268,9 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--window-shader-fg-rule* 'SHADER':'CONDITION':: Specify GLSL fragment shader path for rendering window contents using patterns. Similar to *--opacity-rule*, arguments should be in the format of 'SHADER:CONDITION', e.g. "shader.frag:name = \'window\'". Leading and trailing whitespaces in 'SHADER' will be trimmed. If 'SHADER' is "default", then the default shader will be used for the matching windows. (This also unfortunately means you can't use a shader file named "default"). Does not work when *--legacy-backends* is enabled. +*--dithered-present* + Use higher precision during rendering, and apply dither when presenting the rendered screen. Reduces banding artifacts, but might cause performance degradation. Only works with OpenGL. + FORMAT OF CONDITIONS -------------------- Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators. diff --git a/picom.sample.conf b/picom.sample.conf index 7c74eb8..f0c7a79 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -215,6 +215,11 @@ blur-background-exclude = [ # backend = "glx" backend = "xrender"; +# Use higher precision during rendering, and apply dither when presenting the +# rendered screen. Reduces banding artifacts, but might cause performance +# degradation. Only works with OpenGL. +dithered-present = false; + # Enable/disable VSync. # vsync = false vsync = true; diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index aad1e5f..9297038 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -259,10 +259,10 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec return true; } -bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, - coord_t mask_dst, const region_t *reg_blur, - const region_t *reg_visible attr_unused, GLuint source_texture, - geometry_t source_size, GLuint target_fbo, GLuint default_mask) { +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused, + GLuint source_texture, geometry_t source_size, GLuint target_fbo, + GLuint default_mask, bool high_precision) { bool ret = false; if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) { @@ -284,7 +284,11 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, } glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, tex_size->width, + GLint format = GL_RGBA8; + if (high_precision) { + format = GL_RGBA16; + } + glTexImage2D(GL_TEXTURE_2D, 0, format, tex_size->width, tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { @@ -406,7 +410,7 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mas return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible, gd->back_texture, (geometry_t){.width = gd->width, .height = gd->height}, - gd->back_fbo, gd->default_mask_texture); + gd->back_fbo, gd->default_mask_texture, gd->dithered_present); } static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index cfa98d1..67b7e64 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -622,13 +622,16 @@ void gl_resize(struct gl_data *gd, int width, int height) { gd->height = height; gd->width = width; + GLint format = GL_RGB8; + if (gd->dithered_present) { + format = GL_RGB16; + } assert(viewport_dimensions[0] >= gd->width); assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, width, height, 0, GL_BGR, - GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); gl_check_err(); } @@ -873,9 +876,16 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - gd->present_prog = - gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL}, - (const char *[]){present_frag, dither_glsl, NULL}); + gd->dithered_present = ps->o.dithered_present; + if (gd->dithered_present) { + gd->present_prog = gl_create_program_from_strv( + (const char *[]){present_vertex_shader, NULL}, + (const char *[]){present_frag, dither_glsl, NULL}); + } else { + gd->present_prog = gl_create_program_from_strv( + (const char *[]){present_vertex_shader, NULL}, + (const char *[]){dummy_frag, NULL}); + } if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; @@ -1281,7 +1291,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, 1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, source_texture, (geometry_t){.width = new_inner->width, .height = new_inner->height}, - fbo, gd->default_mask_texture); + fbo, gd->default_mask_texture, gd->dithered_present); pixman_region32_fini(®_blur); } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index bad75ed..b7ef2b4 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -109,6 +109,8 @@ struct gl_data { GLuint back_texture, back_fbo; GLuint present_prog; + bool dithered_present; + GLuint default_mask_texture; /// Called when an gl_texture is decoupled from the texture it refers. Returns @@ -163,10 +165,10 @@ void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visi bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible); -bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, - coord_t mask_dst, const region_t *reg_blur, - const region_t *reg_visible attr_unused, GLuint source_texture, - geometry_t source_size, GLuint target_fbo, GLuint default_mask); +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused, + GLuint source_texture, geometry_t source_size, GLuint target_fbo, + GLuint default_mask, bool high_precision); void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); void gl_destroy_blur_context(backend_t *base, void *ctx); struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius); diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 2b7f8e1..e85a85f 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -858,6 +858,10 @@ static void get_blur_size(void *blur_context, int *width, int *height) { } static backend_t *backend_xrender_init(session_t *ps) { + if (ps->o.dithered_present) { + log_warn("\"dithered-present\" is not supported by the xrender backend."); + } + auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); diff --git a/src/config.h b/src/config.h index 7259dc1..3f11e1e 100644 --- a/src/config.h +++ b/src/config.h @@ -256,6 +256,8 @@ typedef struct options { /// A list of conditions of windows to which transparent clipping /// should not apply c2_lptr_t *transparent_clipping_blacklist; + + bool dithered_present; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 461fff3..ba987a8 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -452,6 +452,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen); // --transparent-clipping lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping); + // --dithered_present + lcfg_lookup_bool(&cfg, "dithered-present", &opt->dithered_present); // --transparent-clipping-exclude parse_cfg_condlst(&cfg, &opt->transparent_clipping_blacklist, "transparent-clipping-exclude"); diff --git a/src/options.c b/src/options.c index 0226714..d6138b9 100644 --- a/src/options.c +++ b/src/options.c @@ -168,6 +168,10 @@ static const struct picom_option picom_options[] = { "similar to --opacity-rule. SHADER_PATH can be \"default\", in which case " "the default shader will be used. Does not work when --legacy-backends is " "enabled. See man page for more details"}, + // 338 is transparent-clipping-exclude + {"dithered-present" , no_argument , 339, NULL , "Use higher precision during rendering, and apply dither when presenting the " + "rendered screen. Reduces banding artifacts, but might cause performance " + "degradation. Only works with OpenGL."}, {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, @@ -716,6 +720,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --clip-shadow-above condlst_add(&opt->shadow_clip_list, optarg); break; + case 339: + // --dithered-present + opt->dithered_present = true; + break; P_CASEBOOL(733, legacy_backends); P_CASEBOOL(800, monitor_repaint); case 801: From 1a82b8180a596594797f880a836b78e18b95c773 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 29 Nov 2022 22:20:22 -0800 Subject: [PATCH 27/64] Change dreaw -> draw --- src/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index 0226714..631f540 100644 --- a/src/options.c +++ b/src/options.c @@ -46,7 +46,7 @@ static const struct picom_option picom_options[] = { {"fade-delta" , required_argument, 'D', NULL , "The time between steps in a fade in milliseconds. (default 10)"}, {"menu-opacity" , required_argument, 'm', NULL , "The opacity for menus. (default 1.0)"}, {"shadow" , no_argument , 'c', NULL , "Enabled client-side shadows on windows."}, - {"clear-shadow" , no_argument , 'z', NULL , "Don't dreaw shadow behind the window."}, + {"clear-shadow" , no_argument , 'z', NULL , "Don't draw shadow behind the window."}, {"fading" , no_argument , 'f', NULL , "Fade windows in/out when opening/closing and when opacity changes, " "unless --no-fading-openclose is used."}, {"inactive-opacity" , required_argument, 'i', NULL , "Opacity of inactive windows. (0.1 - 1.0)"}, From d704e0f80e1df1198cc229b8abbc88eb3caec862 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 12:13:09 +0000 Subject: [PATCH 28/64] backend: gl: don't add dither where it's not needed If a pixel is perfectly representable as an 8-bit number, don't add dither. Reduce artifacts where dither is unnecessary. Signed-off-by: Yuxuan Shui --- src/backend/gl/shaders.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index bd620fe..90f636b 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -201,7 +201,9 @@ const char dither_glsl[] = GLSL(330, return bayer32; } vec4 dither(vec4 c, vec2 coord) { - return vec4(c + bayer(coord) / 255.0); + vec4 residual = mod(c, 1.0 / 255.0); + vec4 dithered = vec4(greaterThan(residual, vec4(1e-4))); + return vec4(c + dithered * bayer(coord) / 255.0); } ); const char shadow_colorization_frag[] = GLSL(330, From de3e1a80eb93f11cc0ad27cb46db35fcb6a4a5ac Mon Sep 17 00:00:00 2001 From: Evgeniy Baskov Date: Wed, 30 Nov 2022 20:00:46 +0300 Subject: [PATCH 29/64] win: consider border when creating the mask image With rounded corners, X11 native border and blur enabled, left and bottom 2*border_width pixels were not blurred, since mask did not include border_width, only content width and height. Create mask image with dimensions that include border width. Signed-off-by: Evgeniy Baskov --- src/win.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win.c b/src/win.c index fb61191..502bb82 100644 --- a/src/win.c +++ b/src/win.c @@ -350,7 +350,7 @@ bool win_bind_mask(struct backend_base *b, struct managed_win *w) { auto reg_bound_local = win_get_bounding_shape_global_by_val(w); pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); w->mask_image = b->ops->make_mask( - b, (geometry_t){.width = w->g.width, .height = w->g.height}, ®_bound_local); + b, (geometry_t){.width = w->widthb, .height = w->heightb}, ®_bound_local); pixman_region32_fini(®_bound_local); if (!w->mask_image) { From 8b4160123ed9b3a4780c682727322d7cc5de0ab4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 19:23:47 +0000 Subject: [PATCH 30/64] README: update pcre requirements Signed-off-by: Yuxuan Shui --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ccf0338..49ffedf 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,20 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) * libGL, libEGL (optional, disable with the `-Dopengl=false` meson configure flag) -* libpcre (optional, disable with the `-Dregex=false` meson configure flag) +* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag) * libev * uthash On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libpcre3-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are ``` -dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel +dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel ``` To build the documents, you need `asciidoc` From 756757ee76269962dbbcddf5fcb746929571697c Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 1 Dec 2022 20:55:57 +0300 Subject: [PATCH 31/64] README: fix meson's warnings about setup commands this fixes `WARNING: Running the setup command as `meson [options]` instead of `meson setup [options]` is ambiguous and deprecated.` warning while setting up the project --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 49ffedf..b1711eb 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ To build the documents, you need `asciidoc` ```bash $ git submodule update --init --recursive -$ meson --buildtype=release . build +$ meson setup --buildtype=release . build $ ninja -C build ``` @@ -70,13 +70,12 @@ If you have libraries and/or headers installed at non-default location (e.g. und You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: ```bash -$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build - +$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release . build ``` As an example, on FreeBSD, you might have to run meson with: ```bash -$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build +$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release . build $ ninja -C build ``` From d38b0ead7d516d7db0782261ea8c46e7477f1331 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 1 Dec 2022 18:54:22 +0000 Subject: [PATCH 32/64] backend: gl: try different back buffer formats Prefer RGB formats first, because they use less memory; but fallback to RGBA formats, as they are formats required by OpenGL. Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 30 +++++++++++++++++++----------- src/backend/gl/gl_common.h | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 67b7e64..44522b0 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -622,16 +622,13 @@ void gl_resize(struct gl_data *gd, int width, int height) { gd->height = height; gd->width = width; - GLint format = GL_RGB8; - if (gd->dithered_present) { - format = GL_RGB16; - } assert(viewport_dimensions[0] >= gd->width); assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, gd->back_format, width, height, 0, GL_BGR, + GL_UNSIGNED_BYTE, NULL); gl_check_err(); } @@ -919,14 +916,25 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - // Set up the size of the back texture - gl_resize(gd, ps->root_width, ps->root_height); - + // Set up the size and format of the back texture glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - gd->back_texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + const GLint *format = (const GLint[]){GL_RGB8, GL_RGBA8}; + if (gd->dithered_present) { + format = (const GLint[]){GL_RGB16, GL_RGBA16}; + } + for (int i = 0; i < 2; i++) { + gd->back_format = format[i]; + gl_resize(gd, ps->root_width, ps->root_height); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, gd->back_texture, 0); + if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + log_info("Using back buffer format %#x", gd->back_format); + break; + } + } + if (!gl_check_fb_complete(GL_DRAW_FRAMEBUFFER)) { return false; } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index b7ef2b4..4aad9c0 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -107,6 +107,7 @@ struct gl_data { gl_fill_shader_t fill_shader; gl_shadow_shader_t shadow_shader; GLuint back_texture, back_fbo; + GLint back_format; GLuint present_prog; bool dithered_present; From 8143c07de62371e3e1580addf8214b7d8cf61972 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 1 Dec 2022 21:21:19 +0000 Subject: [PATCH 33/64] Update README.md Signed-off-by: Yuxuan Shui --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b1711eb..f6bdfe1 100644 --- a/README.md +++ b/README.md @@ -89,15 +89,21 @@ Default install prefix is `/usr/local`, you can change it with `meson configure ## How to Contribute -### Code +All contributions are welcome! -You can look at the [Projects](https://github.com/yshui/picom/projects) page, and see if there is anything that interests you. Or you can take a look at the [Issues](https://github.com/yshui/picom/issues). +New features you think should be included in picom, a fix for a bug you found - please open a PR! -### Non-code +You can take a look at the [Issues](https://github.com/yshui/picom/issues). -Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find. +Contributions to the documents and wiki are also appreciated. -Contributions to the documents and wiki will also be appreciated. +Even if you don't want to add anything to picom, you are still helping by compiling and running this branch, and report any issue you can find. + +### Become a Collaborator + +Becoming a collaborator of picom requires significant time commitment. You are expected to reply to issue reports, reviewing PRs, and sometimes fix bugs or implement new feature. You won't be able to push to the main branch directly, and all you code still has to go through code review. + +If this sounds good to you, feel free to contact me. ## Contributors From 236c8228b8459e40067822006797d6b5c45dc9ba Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 2 Dec 2022 02:57:28 +0000 Subject: [PATCH 34/64] backend: gl: fix use-after-scope 'format' was pointing to an array with a shorter lifetime suggested by @tryone144 Co-authored-by: Bernd Busse --- src/backend/gl/gl_common.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 44522b0..9789eb5 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -919,10 +919,8 @@ bool gl_init(struct gl_data *gd, session_t *ps) { // Set up the size and format of the back texture glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); glDrawBuffer(GL_COLOR_ATTACHMENT0); - const GLint *format = (const GLint[]){GL_RGB8, GL_RGBA8}; - if (gd->dithered_present) { - format = (const GLint[]){GL_RGB16, GL_RGBA16}; - } + const GLint *format = gd->dithered_present ? (const GLint[]){GL_RGB16, GL_RGBA16} + : (const GLint[]){GL_RGB8, GL_RGBA8}; for (int i = 0; i < 2; i++) { gd->back_format = format[i]; gl_resize(gd, ps->root_width, ps->root_height); From 0d2b14d0c3435c72ce8b3f75c627a0fa937809fa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 2 Dec 2022 17:51:30 +0000 Subject: [PATCH 35/64] man: fix typo Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 61af5a2..e8c4cec 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -481,7 +481,7 @@ Available options of the 'blur' section are: :: An integer in the range 0-20. The strength of the 'dual_kawase' blur method. Corresponds to the *--blur-strength* command line option. If set to zero, the value requested by *--blur-size* is approximated (default: 5). *kernel*::: - A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kerns* option. Corresponds to the *--blur-kerns* command line option. + A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kern* option. Corresponds to the *--blur-kern* command line option. SIGNALS ------- From 91e023971d06b441c5b086ffc49e148d9b0c601f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 12 Dec 2022 08:51:42 +0000 Subject: [PATCH 36/64] utils: add rolling_max For tracking rolling max of a stream of integers. Signed-off-by: Yuxuan Shui --- src/utils.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.h | 8 ++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/utils.c b/src/utils.c index 8a27f39..8190226 100644 --- a/src/utils.c +++ b/src/utils.c @@ -4,6 +4,7 @@ #include "compiler.h" #include "string_utils.h" +#include "test.h" #include "utils.h" /// Report allocation failure without allocating memory @@ -36,8 +37,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int /// Calculates next closest power of two of 32bit integer n /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 /// -int next_power_of_two(int n) -{ +int next_power_of_two(int n) { n--; n |= n >> 1; n |= n >> 2; @@ -48,4 +48,120 @@ int next_power_of_two(int n) return n; } +/// Track the rolling maximum of a stream of integers. +struct rolling_max { + /// A priority queue holding the indices of the maximum element candidates. + /// The head of the queue is the index of the maximum element. + /// The indices in the queue are the "original" indices. + /// + /// There are only `capacity` elements in `elem`, all previous elements are + /// discarded. But the discarded elements' indices are not forgotten, that's why + /// it's called the "original" indices. + int *p; + int p_head, np; + + /// The elemets + int *elem; + int elem_head, nelem; + + int window_size; +}; + +void rolling_max_destroy(struct rolling_max *rm) { + free(rm->elem); + free(rm->p); + free(rm); +} + +struct rolling_max *rolling_max_new(int size) { + auto rm = ccalloc(1, struct rolling_max); + if (!rm) { + return NULL; + } + + rm->p = ccalloc(size, int); + rm->elem = ccalloc(size, int); + rm->window_size = size; + if (!rm->p || !rm->elem) { + goto err; + } + + return rm; + +err: + rolling_max_destroy(rm); + return NULL; +} + +void rolling_max_reset(struct rolling_max *rm) { + rm->p_head = 0; + rm->np = 0; + rm->nelem = 0; + rm->elem_head = 0; +} + +void rolling_max_push(struct rolling_max *rm, int val) { +#define IDX(n) ((n) % rm->window_size) + if (rm->nelem == rm->window_size) { + auto old_head = rm->elem_head; + // Discard the oldest element. + // rm->elem.pop_front(); + rm->nelem--; + rm->elem_head = IDX(rm->elem_head + 1); + + // Remove discarded element from the priority queue too. + assert(rm->np); + if (rm->p[rm->p_head] == old_head) { + // rm->p.pop_front() + rm->p_head = IDX(rm->p_head + 1); + rm->np--; + } + } + + // Add the new element to the queue. + // rm->elem.push_back(val) + rm->elem[IDX(rm->elem_head + rm->nelem)] = val; + rm->nelem++; + + // Update the prority queue. + // Remove all elements smaller than the new element from the queue. Because + // the new element will become the maximum element before them, and since they + // come b1efore the new element, they will have been popped before the new + // element, so they will never become the maximum element. + while (rm->np) { + int p_tail = IDX(rm->p_head + rm->np - 1); + if (rm->elem[rm->p[p_tail]] > val) { + break; + } + // rm->p.pop_back() + rm->np--; + } + // Add the new element to the end of the queue. + // rm->p.push_back(rm->start_index + rm->nelem - 1) + rm->p[IDX(rm->p_head + rm->np)] = IDX(rm->elem_head + rm->nelem - 1); + rm->np++; +#undef IDX +} + +int rolling_max_get_max(struct rolling_max *rm) { + if (rm->np == 0) { + return INT_MIN; + } + return rm->elem[rm->p[rm->p_head]]; +} + +TEST_CASE(rolling_max_test) { +#define NELEM 15 + auto rm = rolling_max_new(3); + const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; + const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2}; + int max[NELEM] = {0}; + for (int i = 0; i < NELEM; i++) { + rolling_max_push(rm, data[i]); + max[i] = rolling_max_get_max(rm); + } + TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0); +#undef NELEM +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index a35bfa8..a1a3d34 100644 --- a/src/utils.h +++ b/src/utils.h @@ -289,6 +289,14 @@ static inline void free_charpp(char **str) { /// int next_power_of_two(int n); +struct rolling_max; + +struct rolling_max *rolling_max_new(int window_size); +void rolling_max_free(struct rolling_max *rm); +void rolling_max_reset(struct rolling_max *rm); +void rolling_max_push(struct rolling_max *rm, int val); +int rolling_max_get_max(struct rolling_max *rm); + // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. #ifdef __ANDROID__ From 459416894657b9791da1205f0da57362a5d61b1f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 13 Dec 2022 08:39:11 +0000 Subject: [PATCH 37/64] utils: add rolling_avg Signed-off-by: Yuxuan Shui --- src/utils.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 7 +++++ 2 files changed, 86 insertions(+) diff --git a/src/utils.c b/src/utils.c index 8190226..c080e53 100644 --- a/src/utils.c +++ b/src/utils.c @@ -164,4 +164,83 @@ TEST_CASE(rolling_max_test) { #undef NELEM } +/// A rolling average of a stream of integers. +struct rolling_avg { + /// The sum of the elements in the window. + int64_t sum; + + /// The elements in the window. + int *elem; + int head, nelem; + + int window_size; +}; + +struct rolling_avg *rolling_avg_new(int size) { + auto rm = ccalloc(1, struct rolling_avg); + if (!rm) { + return NULL; + } + + rm->elem = ccalloc(size, int); + rm->window_size = size; + if (!rm->elem) { + free(rm); + return NULL; + } + + return rm; +} + +void rolling_avg_destroy(struct rolling_avg *rm) { + free(rm->elem); + free(rm); +} + +void rolling_avg_reset(struct rolling_avg *ra) { + ra->sum = 0; + ra->nelem = 0; + ra->head = 0; +} + +void rolling_avg_push(struct rolling_avg *ra, int val) { + if (ra->nelem == ra->window_size) { + // Discard the oldest element. + // rm->elem.pop_front(); + ra->sum -= ra->elem[ra->head % ra->window_size]; + ra->nelem--; + ra->head = (ra->head + 1) % ra->window_size; + } + + // Add the new element to the queue. + // rm->elem.push_back(val) + ra->elem[(ra->head + ra->nelem) % ra->window_size] = val; + ra->sum += val; + ra->nelem++; +} + +double rolling_avg_get_avg(struct rolling_avg *ra) { + if (ra->nelem == 0) { + return 0; + } + return (double)ra->sum / (double)ra->nelem; +} + +TEST_CASE(rolling_avg_test) { +#define NELEM 15 + auto rm = rolling_avg_new(3); + const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; + const double expected_avg[NELEM] = { + 1, 1.5, 2, 2, 8.0 / 3.0, 10.0 / 3.0, 11.0 / 3.0, 10.0 / 3.0, + 11.0 / 3.0, 14.0 / 3.0, 5, 4, 3, 5.0 / 3.0, 2.0 / 3.0}; + double avg[NELEM] = {0}; + for (int i = 0; i < NELEM; i++) { + rolling_avg_push(rm, data[i]); + avg[i] = rolling_avg_get_avg(rm); + } + for (int i = 0; i < NELEM; i++) { + TEST_EQUAL(avg[i], expected_avg[i]); + } +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index a1a3d34..5fa9219 100644 --- a/src/utils.h +++ b/src/utils.h @@ -297,6 +297,13 @@ void rolling_max_reset(struct rolling_max *rm); void rolling_max_push(struct rolling_max *rm, int val); int rolling_max_get_max(struct rolling_max *rm); +struct rolling_avg; +struct rolling_avg *rolling_avg_new(int window_size); +void rolling_avg_free(struct rolling_avg *ra); +void rolling_avg_reset(struct rolling_avg *ra); +void rolling_avg_push(struct rolling_avg *ra, int val); +double rolling_avg_get_avg(struct rolling_avg *ra); + // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. #ifdef __ANDROID__ From 88608027b82d3777a6eed111018013640e1f3636 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:36:07 +0000 Subject: [PATCH 38/64] backend: add a comment Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/backend.c b/src/backend/backend.c index 032c301..83af8fb 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -145,6 +145,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // region will be because of blur, we assume the worst case here. // That is, the damaged window is at the bottom of the stack, and // all other windows have semi-transparent background + // + // TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint? int resize_factor = 1; if (t) { resize_factor = t->stacking_rank; From e407c5c7652da7e340e7c7797723b78be7ab1ab7 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:37:27 +0000 Subject: [PATCH 39/64] x: add x_{create,destroy}_region Signed-off-by: Yuxuan Shui --- src/x.c | 32 ++++++++++++++++++++++++++++++++ src/x.h | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/src/x.c b/src/x.c index 795211d..7faa1c9 100644 --- a/src/x.c +++ b/src/x.c @@ -388,6 +388,38 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_ return ret; } +uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { + if (!reg) { + return XCB_NONE; + } + + int nrects; + auto rects = pixman_region32_rectangles(reg, &nrects); + auto xrects = ccalloc(nrects, xcb_rectangle_t); + for (int i = 0; i < nrects; i++) { + xrects[i] = + (xcb_rectangle_t){.x = to_i16_checked(rects[i].x1), + .y = to_i16_checked(rects[i].y1), + .width = to_u16_checked(rects[i].x2 - rects[i].x1), + .height = to_u16_checked(rects[i].y2 - rects[i].y1)}; + } + + xcb_xfixes_region_t ret = x_new_id(c); + bool success = + XCB_AWAIT_VOID(xcb_xfixes_create_region, c, ret, to_u32_checked(nrects), xrects); + free(xrects); + if (!success) { + return XCB_NONE; + } + return ret; +} + +void x_destroy_region(xcb_connection_t *c, xcb_xfixes_region_t r) { + if (r != XCB_NONE) { + xcb_xfixes_destroy_region(c, r); + } +} + void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, int16_t clip_x_origin, int16_t clip_y_origin, const region_t *reg) { diff --git a/src/x.h b/src/x.h index 3b8787c..a192886 100644 --- a/src/x.h +++ b/src/x.h @@ -216,6 +216,12 @@ x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, /// Fetch a X region and store it in a pixman region bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); +/// Create a X region from a pixman region +uint32_t x_create_region(xcb_connection_t *c, const region_t *reg); + +/// Destroy a X region +void x_destroy_region(xcb_connection_t *c, uint32_t region); + void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, int16_t clip_y_origin, const region_t *); From 23a29470e50ef1d68b9c99186b069659a793184c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:40:42 +0000 Subject: [PATCH 40/64] backend: xrender: set update region for PresentPixmap request Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index e85a85f..19c2ebf 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -606,13 +606,16 @@ static void present(backend_t *base, const region_t *region) { XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, 0, orig_x, orig_y, region_width, region_height); + auto xregion = x_create_region(base->c, region); + // Make sure we got reply from PresentPixmap before waiting for events, // to avoid deadlock auto e = xcb_request_check( base->c, xcb_present_pixmap_checked( xd->base.c, xd->target_win, - xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0, + xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL)); + x_destroy_region(base->c, xregion); if (e) { log_error("Failed to present pixmap"); free(e); From 8b189bc5ec1e1ed3aa601fc50eff1043307904a4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 12:35:37 +0000 Subject: [PATCH 41/64] core: print error with full_sequence ev->sequence was just the lower 16 bits of the sequence number. Signed-off-by: Yuxuan Shui --- src/picom.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/picom.c b/src/picom.c index ecd0907..0e40d45 100644 --- a/src/picom.c +++ b/src/picom.c @@ -297,7 +297,7 @@ void discard_ignore(session_t *ps, unsigned long sequence) { } } -static int should_ignore(session_t *ps, unsigned long sequence) { +static int should_ignore(session_t *ps, uint32_t sequence) { if (ps == NULL) { // Do not ignore errors until the session has been initialized return false; @@ -964,7 +964,7 @@ void root_damaged(session_t *ps) { * Xlib error handler function. */ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, ev->serial)) { + if (!should_ignore(ps_g, (uint32_t)ev->serial)) { x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); } return 0; @@ -974,8 +974,9 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { * XCB error handler function. */ void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->sequence)) { - x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); + if (!should_ignore(ps, err->full_sequence)) { + x_print_error(err->full_sequence, err->major_code, err->minor_code, + err->error_code); } } From 5a5ea76006125711061a72f4f972c58b48c2ab1d Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:35:11 +0000 Subject: [PATCH 42/64] event: restore event sequence number after passing it to Xlib handlers We set event sequence number to the last sequence xlib knows about to silence its complaint about missing sequence numbers, but we forgot to restore it back afterwards. This used to break error ignoring mechanism in `should_ignore`. In the last commit we updated it to use full_sequence which incidently fixed this problem. But let's restore the sequence number anyway for good measure. Signed-off-by: Yuxuan Shui --- src/event.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/event.c b/src/event.c index e6052f1..c4a62e8 100644 --- a/src/event.c +++ b/src/event.c @@ -121,11 +121,13 @@ static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) { CASESTRRET(ClientMessage); } - if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) + if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { return "Damage"; + } - if (ps->shape_exists && ev->response_type == ps->shape_event) + if (ps->shape_exists && ev->response_type == ps->shape_event) { return "ShapeNotify"; + } if (ps->xsync_exists) { int o = ev->response_type - ps->xsync_event; @@ -695,8 +697,11 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // missing sequence numbers. // // We only need the low 16 bits + uint16_t seq = ev->sequence; ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff); proc(ps->dpy, &dummy, (xEvent *)ev); + // Restore the sequence number + ev->sequence = seq; } // XXX redraw needs to be more fine grained From 3b342afa953cbb8b615d85ae1fb174597eb49224 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:47:38 +0000 Subject: [PATCH 43/64] general: fix compiler warning about unused results Signed-off-by: Yuxuan Shui --- src/dbus.c | 5 ++++- src/log.c | 2 +- src/picom.c | 6 +++++- src/utils.c | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/dbus.c b/src/dbus.c index 8b17b30..2b17341 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1435,7 +1435,10 @@ static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *ms continue; } char *tmp = NULL; - asprintf(&tmp, "\n", w->id); + if (asprintf(&tmp, "\n", w->id) < 0) { + log_fatal("Failed to allocate memory."); + abort(); + } mstrextend(&ret, tmp); free(tmp); } diff --git a/src/log.c b/src/log.c index 0b663e7..8a494b9 100644 --- a/src/log.c +++ b/src/log.c @@ -256,7 +256,7 @@ static void file_logger_write(struct log_target *tgt, const char *str, size_t le static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { auto f = (struct file_logger *)tgt; fflush(f->f); - writev(fileno(f->f), vec, vcnt); + ssize_t _ attr_unused = writev(fileno(f->f), vec, vcnt); } static void file_logger_destroy(struct log_target *tgt) { diff --git a/src/picom.c b/src/picom.c index 0e40d45..f922978 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2560,7 +2560,11 @@ int main(int argc, char **argv) { // Notify the parent that we are done. This might cause the parent // to quit, so only do this after setsid() int tmp = 1; - write(pfds[1], &tmp, sizeof tmp); + if (write(pfds[1], &tmp, sizeof tmp) != sizeof tmp) { + log_fatal("Failed to notify parent process"); + ret_code = 1; + break; + } close(pfds[1]); // We only do this once need_fork = false; diff --git a/src/utils.c b/src/utils.c index c080e53..a1114a0 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,7 +27,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, }; - writev(STDERR_FILENO, v, ARR_SIZE(v)); + ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v)); abort(); unreachable; From 4ecb8093cf68aa5b38d417744fa9047a40c62511 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:57:09 +0000 Subject: [PATCH 44/64] x: fix CI build failure Signed-off-by: Yuxuan Shui --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 7faa1c9..e71f1ee 100644 --- a/src/x.c +++ b/src/x.c @@ -394,7 +394,10 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { } int nrects; - auto rects = pixman_region32_rectangles(reg, &nrects); + // In older pixman versions, pixman_region32_rectangles doesn't take const + // region_t, instead of dealing with this version difference, just suppress the + // warning. + const pixman_box32_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); auto xrects = ccalloc(nrects, xcb_rectangle_t); for (int i = 0; i < nrects; i++) { xrects[i] = From aca3fdcef7bfcb1c3ce65cf87413fa6ab280d183 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 14:23:55 +0000 Subject: [PATCH 45/64] core: expand X error handling We used to have a list of X errors we should ignore in case they do occur. This commit expands that functionality to also allow us aborting on certain errors. Signed-off-by: Yuxuan Shui --- src/common.h | 44 ++++++++++++++++++------------ src/event.c | 2 +- src/picom.c | 76 ++++++++++++++++++++++++++++++++-------------------- src/picom.h | 2 +- src/x.c | 10 ++++++- src/x.h | 2 ++ 6 files changed, 87 insertions(+), 49 deletions(-) diff --git a/src/common.h b/src/common.h index c773187..863e14e 100644 --- a/src/common.h +++ b/src/common.h @@ -36,9 +36,9 @@ #include #include #include -#include #include #include +#include #include "uthash_extra.h" #ifdef CONFIG_OPENGL @@ -55,11 +55,11 @@ #include "backend/driver.h" #include "compiler.h" #include "config.h" +#include "list.h" #include "region.h" +#include "render.h" #include "types.h" #include "utils.h" -#include "list.h" -#include "render.h" #include "win_defs.h" #include "x.h" @@ -83,10 +83,17 @@ struct glx_session; struct atom; struct conv; -typedef struct _ignore { - struct _ignore *next; +enum pending_reply_action { + PENDING_REPLY_ACTION_IGNORE, + PENDING_REPLY_ACTION_ABORT, + PENDING_REPLY_ACTION_DEBUG_ABORT, +}; + +typedef struct pending_reply { + struct pending_reply *next; unsigned long sequence; -} ignore_t; + enum pending_reply_action action; +} pending_reply_t; #ifdef CONFIG_OPENGL #ifdef DEBUG_GLX_DEBUG_CONTEXT @@ -256,18 +263,18 @@ typedef struct session { /// Time of last fading. In milliseconds. long long fade_time; /// Head pointer of the error ignore linked list. - ignore_t *ignore_head; + pending_reply_t *pending_reply_head; /// Pointer to the next member of tail element of the error /// ignore linked list. - ignore_t **ignore_tail; + pending_reply_t **pending_reply_tail; // Cached blur convolution kernels. struct x_convolution_kernel **blur_kerns_cache; /// If we should quit - bool quit:1; + bool quit : 1; // TODO(yshui) use separate flags for dfferent kinds of updates so we don't // waste our time. /// Whether there are pending updates, like window creation, etc. - bool pending_updates:1; + bool pending_updates : 1; // === Expose event related === /// Pointer to an array of XRectangle-s of exposed region. @@ -468,18 +475,21 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -static void set_ignore(session_t *ps, unsigned long sequence) { - if (ps->o.show_all_xerrors) +static void set_ignore(session_t *ps, uint32_t sequence) { + if (ps->o.show_all_xerrors) { return; + } - auto i = cmalloc(ignore_t); - if (!i) - return; + auto i = cmalloc(pending_reply_t); + if (!i) { + abort(); + } i->sequence = sequence; i->next = 0; - *ps->ignore_tail = i; - ps->ignore_tail = &i->next; + i->action = PENDING_REPLY_ACTION_IGNORE; + *ps->pending_reply_tail = i; + ps->pending_reply_tail = &i->next; } /** diff --git a/src/event.c b/src/event.c index c4a62e8..89f9624 100644 --- a/src/event.c +++ b/src/event.c @@ -665,7 +665,7 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { void ev_handle(session_t *ps, xcb_generic_event_t *ev) { if ((ev->response_type & 0x7f) != KeymapNotify) { - discard_ignore(ps, ev->full_sequence); + discard_pending(ps, ev->full_sequence); } xcb_window_t wid = ev_window(ps, ev); diff --git a/src/picom.c b/src/picom.c index f922978..ba97f4c 100644 --- a/src/picom.c +++ b/src/picom.c @@ -282,14 +282,14 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) { // === Error handling === -void discard_ignore(session_t *ps, unsigned long sequence) { - while (ps->ignore_head) { - if (sequence > ps->ignore_head->sequence) { - ignore_t *next = ps->ignore_head->next; - free(ps->ignore_head); - ps->ignore_head = next; - if (!ps->ignore_head) { - ps->ignore_tail = &ps->ignore_head; +void discard_pending(session_t *ps, uint32_t sequence) { + while (ps->pending_reply_head) { + if (sequence > ps->pending_reply_head->sequence) { + auto next = ps->pending_reply_head->next; + free(ps->pending_reply_head); + ps->pending_reply_head = next; + if (!ps->pending_reply_head) { + ps->pending_reply_tail = &ps->pending_reply_head; } } else { break; @@ -297,13 +297,28 @@ void discard_ignore(session_t *ps, unsigned long sequence) { } } -static int should_ignore(session_t *ps, uint32_t sequence) { +static void handle_error(session_t *ps, xcb_generic_error_t *ev) { if (ps == NULL) { // Do not ignore errors until the session has been initialized - return false; + return; } - discard_ignore(ps, sequence); - return ps->ignore_head && ps->ignore_head->sequence == sequence; + discard_pending(ps, ev->full_sequence); + if (ps->pending_reply_head && ps->pending_reply_head->sequence == ev->full_sequence) { + if (ps->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) { + x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, + ev->minor_code, ev->error_code); + } + switch (ps->pending_reply_head->action) { + case PENDING_REPLY_ACTION_ABORT: + log_fatal("An unrecoverable X error occurred, aborting..."); + abort(); + case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break; + case PENDING_REPLY_ACTION_IGNORE: break; + } + return; + } + x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code, + ev->error_code); } // === Windows === @@ -964,9 +979,13 @@ void root_damaged(session_t *ps) { * Xlib error handler function. */ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, (uint32_t)ev->serial)) { - x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); - } + // Fake a xcb error, fill in just enough information + xcb_generic_error_t xcb_err; + xcb_err.full_sequence = (uint32_t)ev->serial; + xcb_err.major_code = ev->request_code; + xcb_err.minor_code = ev->minor_code; + xcb_err.error_code = ev->error_code; + handle_error(ps_g, &xcb_err); return 0; } @@ -974,10 +993,7 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { * XCB error handler function. */ void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->full_sequence)) { - x_print_error(err->full_sequence, err->major_code, err->minor_code, - err->error_code); - } + handle_error(ps, err); } /** @@ -1678,8 +1694,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, - .ignore_head = NULL, - .ignore_tail = NULL, + .pending_reply_head = NULL, + .pending_reply_tail = NULL, .quit = false, .expose_rects = NULL, @@ -1741,7 +1757,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->loop = EV_DEFAULT; pixman_region32_init(&ps->screen_reg); - ps->ignore_tail = &ps->ignore_head; + ps->pending_reply_tail = &ps->pending_reply_head; ps->o.show_all_xerrors = all_xerrors; @@ -2330,26 +2346,28 @@ static void session_destroy(session_t *ps) { // Free ignore linked list { - ignore_t *next = NULL; - for (ignore_t *ign = ps->ignore_head; ign; ign = next) { + pending_reply_t *next = NULL; + for (auto ign = ps->pending_reply_head; ign; ign = next) { next = ign->next; free(ign); } // Reset head and tail - ps->ignore_head = NULL; - ps->ignore_tail = &ps->ignore_head; + ps->pending_reply_head = NULL; + ps->pending_reply_tail = &ps->pending_reply_head; } // Free tgt_{buffer,picture} and root_picture - if (ps->tgt_buffer.pict == ps->tgt_picture) + if (ps->tgt_buffer.pict == ps->tgt_picture) { ps->tgt_buffer.pict = XCB_NONE; + } - if (ps->tgt_picture == ps->root_picture) + if (ps->tgt_picture == ps->root_picture) { ps->tgt_picture = XCB_NONE; - else + } else { free_picture(ps->c, &ps->tgt_picture); + } free_picture(ps->c, &ps->root_picture); free_paint(ps, &ps->tgt_buffer); diff --git a/src/picom.h b/src/picom.h index 7ee289b..b5a1e8a 100644 --- a/src/picom.h +++ b/src/picom.h @@ -46,7 +46,7 @@ void cxinerama_upd_scrs(session_t *ps); void queue_redraw(session_t *ps); -void discard_ignore(session_t *ps, unsigned long sequence); +void discard_pending(session_t *ps, uint32_t sequence); void set_root_flags(session_t *ps, uint64_t flags); diff --git a/src/x.c b/src/x.c index e71f1ee..e598345 100644 --- a/src/x.c +++ b/src/x.c @@ -562,8 +562,16 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c /** * Log a X11 error */ +void x_log_error(enum log_level level, unsigned long serial, uint8_t major, + uint16_t minor, uint8_t error_code) { + if (unlikely(level >= log_get_level_tls())) { + log_printf(tls_logger, level, __func__, "%s", + _x_strerror(serial, major, minor, error_code)); + } +} + void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { - log_debug("%s", _x_strerror(serial, major, minor, error_code)); + x_log_error(LOG_LEVEL_DEBUG, serial, major, minor, error_code); } /* diff --git a/src/x.h b/src/x.h index a192886..78efea3 100644 --- a/src/x.h +++ b/src/x.h @@ -231,6 +231,8 @@ void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); * Log a X11 error */ void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); +void x_log_error(enum log_level level, unsigned long serial, uint8_t major, + uint16_t minor, uint8_t error_code); /* * Convert a xcb_generic_error_t to a string that describes the error From 17b34ce3909d7e847670d6c121e1f3e5f499bf40 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 14:28:42 +0000 Subject: [PATCH 46/64] core: add set_cant_fail_cookie Enables picom to abort when certain requests fail. Signed-off-by: Yuxuan Shui --- src/common.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/common.h b/src/common.h index 863e14e..542dc94 100644 --- a/src/common.h +++ b/src/common.h @@ -475,11 +475,8 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -static void set_ignore(session_t *ps, uint32_t sequence) { - if (ps->o.show_all_xerrors) { - return; - } - +static void +set_reply_action(session_t *ps, uint32_t sequence, enum pending_reply_action action) { auto i = cmalloc(pending_reply_t); if (!i) { abort(); @@ -487,7 +484,7 @@ static void set_ignore(session_t *ps, uint32_t sequence) { i->sequence = sequence; i->next = 0; - i->action = PENDING_REPLY_ACTION_IGNORE; + i->action = action; *ps->pending_reply_tail = i; ps->pending_reply_tail = &i->next; } @@ -496,7 +493,15 @@ static void set_ignore(session_t *ps, uint32_t sequence) { * Ignore X errors caused by given X request. */ static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) { - set_ignore(ps, cookie.sequence); + if (ps->o.show_all_xerrors) { + return; + } + + set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_IGNORE); +} + +static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) { + set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_ABORT); } /** From 1ea3276bd135119a1f3da67a459f05151402958c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 18 Dec 2022 19:23:06 +0000 Subject: [PATCH 47/64] x: fix missing _checked Signed-off-by: Yuxuan Shui --- src/x.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index e598345..42bee3f 100644 --- a/src/x.c +++ b/src/x.c @@ -449,9 +449,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, } void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) { + assert(pict != XCB_NONE); xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE}; xcb_generic_error_t *e = xcb_request_check( - c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); + c, xcb_render_change_picture_checked(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); if (e) { log_error_x_error(e, "failed to clear clip region"); free(e); From c28462673e2b3332b355c33adbec1190572c5407 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 18 Dec 2022 19:23:49 +0000 Subject: [PATCH 48/64] backend: xrender: fix using of invalid picture when vsync is disabled Fixes #974 Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 19c2ebf..4381977 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -594,13 +594,13 @@ static void present(backend_t *base, const region_t *region) { uint16_t region_width = to_u16_checked(extent->x2 - extent->x1), region_height = to_u16_checked(extent->y2 - extent->y1); - // compose() sets clip region on the back buffer, so clear it first - x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); - // limit the region of update x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region); if (xd->vsync) { + // compose() sets clip region on the back buffer, so clear it first + x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); + // Update the back buffer first, then present xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, From 37ecb4b496815115049a1844a149d6bf63811a9c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 15 Dec 2022 10:53:31 +0000 Subject: [PATCH 49/64] core: detect screen off Use the DPMS extension to detect if screen is turned off, and unredirect if it is. This also helps working around the problem where OpenGL buffers lose data when screen is turned off, causing screen to flicker later when it turns back on if use-damage is enabled. Unfortunately the DPMS extension doesn't define an event, so we have to periodically poll the screen state. Signed-off-by: Yuxuan Shui --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 3 +- src/common.h | 6 ++++ src/meson.build | 3 +- src/picom.c | 51 +++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8b65b61..224c462 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index f6bdfe1..a23dfd0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * xproto * xcb * xcb-damage +* xcb-dpms * xcb-xfixes * xcb-shape * xcb-renderutil @@ -44,7 +45,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are diff --git a/src/common.h b/src/common.h index 542dc94..2e5495e 100644 --- a/src/common.h +++ b/src/common.h @@ -150,6 +150,8 @@ typedef struct session { // === Event handlers === /// ev_io for X connection ev_io xiow; + /// Timer for checking DPMS power level + ev_timer dpms_check_timer; /// Timeout for delayed unredirection. ev_timer unredir_timer; /// Timer for fading @@ -236,6 +238,8 @@ typedef struct session { xcb_sync_fence_t sync_fence; /// Whether we are rendering the first frame after screen is redirected bool first_frame; + /// Whether screen has been turned off + bool screen_is_off; // === Operation related === /// Flags related to the root window @@ -342,6 +346,8 @@ typedef struct session { int composite_error; /// Major opcode for X Composite extension. int composite_opcode; + /// Whether X DPMS extension exists + bool dpms_exists; /// Whether X Shape extension exists. bool shape_exists; /// Event base number for X Shape extension. diff --git a/src/meson.build b/src/meson.build index 09eb07b..b6b24b5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,7 +16,8 @@ cflags = [] required_xcb_packages = [ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb' + 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', + 'xcb-dpms', 'xcb' ] required_packages = [ diff --git a/src/picom.c b/src/picom.c index ba97f4c..b5ac3a3 100644 --- a/src/picom.c +++ b/src/picom.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,26 @@ void cxinerama_upd_scrs(session_t *ps) { free(xinerama_scrs); } +static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { + // state is a bool indicating whether dpms is enabled + return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); +} + +void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + auto ps = session_ptr(w, dpms_check_timer); + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS status."); + abort(); + } + auto now_screen_is_off = dpms_screen_is_off(r); + if (ps->screen_is_off != now_screen_is_off) { + ps->screen_is_off = now_screen_is_off; + queue_redraw(ps); + } + free(r); +} + /** * Find matched window. * @@ -920,6 +941,19 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { // If there's no window to paint, and the screen isn't redirected, // don't redirect it. unredir_possible = true; + } else if (ps->screen_is_off) { + // Screen is off, unredirect + // We do this unconditionally disregarding "unredir_if_possible" + // because it's important for correctness, because we need to + // workaround problems X server has around screen off. + // + // Known problems: + // 1. Sometimes OpenGL front buffer can lose content, and if we + // are doing partial updates (i.e. use-damage = true), the + // result will be wrong. + // 2. For frame pacing, X server sends bogus + // PresentCompleteNotify events when screen is off. + unredir_possible = true; } if (unredir_possible) { if (ps->redirected) { @@ -1800,6 +1834,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_prefetch_extension_data(ps->c, &xcb_present_id); xcb_prefetch_extension_data(ps->c, &xcb_sync_id); xcb_prefetch_extension_data(ps->c, &xcb_glx_id); + xcb_prefetch_extension_data(ps->c, &xcb_dpms_id); ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); if (!ext_info || !ext_info->present) { @@ -1868,6 +1903,21 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->glx_event = ext_info->first_event; } + ext_info = xcb_get_extension_data(ps->c, &xcb_dpms_id); + ps->dpms_exists = ext_info && ext_info->present; + if (ps->dpms_exists) { + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS info"); + goto err; + } + ps->screen_is_off = dpms_screen_is_off(r); + // Check screen status every half second + ev_timer_init(&ps->dpms_check_timer, check_dpms_status, 0, 0.5); + ev_timer_start(ps->loop, &ps->dpms_check_timer); + free(r); + } + // Parse configuration file win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}}; bool shadow_enabled = false, fading_enable = false, hasneg = false; @@ -2462,6 +2512,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->dpms_check_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); From df7b994d0033f891cba39a5214a2a1bf22a1ac2b Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 21 Dec 2022 23:19:51 +0300 Subject: [PATCH 50/64] x: fix leak in x_create_picture_with_pictfmt_and_pixmap --- src/x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/x.c b/src/x.c index 42bee3f..d00c9c2 100644 --- a/src/x.c +++ b/src/x.c @@ -299,6 +299,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, free(buf); if (e) { log_error_x_error(e, "failed to create picture"); + free(e); return XCB_NONE; } return tmp_picture; From 70032b92821725ac5b4e7f988aadd39ad03c8250 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 00:20:43 +0300 Subject: [PATCH 51/64] xrender: fix leak in bind_pixmap --- src/backend/xrender/xrender.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 4381977..d897819 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -508,6 +508,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool if (!r) { log_error("Invalid pixmap: %#010x", pixmap); x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code); + free(e); return NULL; } From dd9ffecd85b9616fd4a0c740fc38fc9dbe4af548 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 01:26:51 +0300 Subject: [PATCH 52/64] xrender: fix leak in deinit and check should we actually free something fixes at least #960 --- src/backend/xrender/xrender.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index d897819..f25a921 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -576,9 +576,13 @@ static void deinit(backend_t *backend_data) { xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]); } xcb_render_free_picture(xd->base.c, xd->target); - for (int i = 0; i < 2; i++) { - xcb_render_free_picture(xd->base.c, xd->back[i]); - xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); + for (int i = 0; i < 3; i++) { + if (xd->back[i] != XCB_NONE) { + xcb_render_free_picture(xd->base.c, xd->back[i]); + } + if (xd->back_pixmap[i] != XCB_NONE) { + xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); + } } if (xd->present_event) { xcb_unregister_for_special_event(xd->base.c, xd->present_event); From 32020ff659806576caf5966f98ff1b716f84f40e Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 20:40:02 +0300 Subject: [PATCH 53/64] xrender: fix leak in release_rounded_corner_cache calling wrong free function did nothing and produced ton of x errors fixes at least #892 --- src/backend/xrender/xrender.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index f25a921..113d55e 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -554,7 +554,7 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c assert(cache->refcount > 0); cache->refcount--; if (cache->refcount == 0) { - xcb_free_pixmap(base->c, cache->p); + xcb_render_free_picture(base->c, cache->p); free(cache); } } From cffc443bfd7960dab57802d35c649c511276fdb9 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 23 Dec 2022 15:21:52 +0300 Subject: [PATCH 54/64] xrender: make corner-radius respect inactive-dim for the new xrender backend it's enough to clip with a rounded rectangle after dimming, not before partially fixes #867 --- src/backend/xrender/xrender.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 113d55e..686e6de 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -265,13 +265,6 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { - // Clip tmp_pict with a rounded rectangle - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, - xrimg->rounded_rectangle->p, XCB_NONE, - tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - } - if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( @@ -294,6 +287,7 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, 0, 0, 0, 0, 0, 0, tmpw, tmph); } } + if (img->dim != 0) { // Dim the actually content of window xcb_rectangle_t rect = { @@ -307,6 +301,13 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, tmp_pict, dim_color, 1, &rect); } + if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { + // Clip tmp_pict with a rounded rectangle + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle->p, XCB_NONE, + tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, From 91828be79db2071061e9f290a8ef5915535cbf3b Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 30 Dec 2022 06:16:46 +0300 Subject: [PATCH 55/64] picom: free root image properly fixes #982 --- src/picom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/picom.c b/src/picom.c index b5ac3a3..8705de4 100644 --- a/src/picom.c +++ b/src/picom.c @@ -990,6 +990,7 @@ void root_damaged(session_t *ps) { if (ps->backend_data) { if (ps->root_image) { ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); + ps->root_image = NULL; } auto pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms); if (pixmap != XCB_NONE) { From 29342e7cead35775d6e4f68027c9338087acc7c1 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 9 Jan 2023 04:20:53 +0300 Subject: [PATCH 56/64] backend: egl: simplify getting a framebuffer configuration we're using the first available framebuffer configuration that meets our needs so there is no need to ask for more than one --- src/backend/gl/egl.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index e6d4d90..d619456 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -166,14 +166,9 @@ static backend_t *egl_init(session_t *ps) { goto end; } - int ncfgs = 0; - if (eglGetConfigs(gd->display, NULL, 0, &ncfgs) != EGL_TRUE) { - log_error("Failed to get EGL configs."); - goto end; - } - auto visual_info = x_get_visual_info(ps->c, ps->vis); - EGLConfig *cfgs = ccalloc(ncfgs, EGLConfig); + EGLConfig config = NULL; + int nconfigs = 1; // clang-format off if (eglChooseConfig(gd->display, (EGLint[]){ @@ -186,17 +181,14 @@ static backend_t *egl_init(session_t *ps) { EGL_STENCIL_SIZE, 1, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, - }, cfgs, ncfgs, &ncfgs) != EGL_TRUE) { + }, &config, nconfigs, &nconfigs) != EGL_TRUE) { log_error("Failed to choose EGL config for the root window."); goto end; } // clang-format on - EGLConfig target_cfg = cfgs[0]; - free(cfgs); - gd->target_win = eglCreatePlatformWindowSurfaceProc( - gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); + gd->display, config, (xcb_window_t[]){session_get_target_window(ps)}, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); goto end; @@ -207,7 +199,7 @@ static backend_t *egl_init(session_t *ps) { goto end; } - gd->ctx = eglCreateContext(gd->display, target_cfg, NULL, NULL); + gd->ctx = eglCreateContext(gd->display, config, NULL, NULL); if (gd->ctx == EGL_NO_CONTEXT) { log_error("Failed to get GLX context."); goto end; From ca64654256b1f62cbc564e8fa68fc80651835a60 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 10 Jan 2023 00:15:49 +0300 Subject: [PATCH 57/64] use _checked functions with xcb_request_check --- src/event.c | 3 ++- src/picom.c | 8 ++++---- src/win.c | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/event.c b/src/event.c index 89f9624..082686b 100644 --- a/src/event.c +++ b/src/event.c @@ -283,7 +283,8 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { // in redirected state. if (ps->overlay && ev->window == ps->overlay && !ps->redirected) { log_debug("Overlay is mapped while we are not redirected"); - auto e = xcb_request_check(ps->c, xcb_unmap_window(ps->c, ps->overlay)); + auto e = + xcb_request_check(ps->c, xcb_unmap_window_checked(ps->c, ps->overlay)); if (e) { log_error("Failed to unmap the overlay window"); free(e); diff --git a/src/picom.c b/src/picom.c index b5ac3a3..9d97a86 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1266,7 +1266,7 @@ static bool init_debug_window(session_t *ps) { goto err_out; } - err = xcb_request_check(ps->c, xcb_map_window(ps->c, ps->debug_window)); + err = xcb_request_check(ps->c, xcb_map_window_checked(ps->c, ps->debug_window)); if (err) { goto err_out; } @@ -2069,8 +2069,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->sync_fence = XCB_NONE; if (ps->xsync_exists) { ps->sync_fence = x_new_id(ps->c); - e = xcb_request_check( - ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); + e = xcb_request_check(ps->c, xcb_sync_create_fence_checked( + ps->c, ps->root, ps->sync_fence, 0)); if (e) { if (ps->o.xrender_sync_fence) { log_error_x_error(e, "Failed to create a XSync fence. " @@ -2280,7 +2280,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_query_tree_reply_t *query_tree_reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, ps->root), NULL); - e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c)); + e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "Failed to ungrab server"); free(e); diff --git a/src/win.c b/src/win.c index 502bb82..cef94fe 100644 --- a/src/win.c +++ b/src/win.c @@ -1299,7 +1299,7 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) } auto e = xcb_request_check( - ps->c, xcb_change_window_attributes( + ps->c, xcb_change_window_attributes_checked( ps->c, client, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)})); if (e) { From 81f2bf4c0721809cf452ded50cec65a1c5e37a46 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 10 Jan 2023 01:13:08 +0300 Subject: [PATCH 58/64] picom: fix xcb_request_check memory leaks --- src/picom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/picom.c b/src/picom.c index b5ac3a3..8fb0a73 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1463,6 +1463,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "failed to grab x server"); + free(e); return quit(ps); } @@ -1498,6 +1499,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "failed to ungrab x server"); + free(e); return quit(ps); } @@ -1822,6 +1824,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, XCB_EVENT_MASK_PROPERTY_CHANGE})); if (e) { log_error_x_error(e, "Failed to setup root window event mask"); + free(e); } xcb_prefetch_extension_data(ps->c, &xcb_render_id); From 0ecfd9fd32d92cf520c05b61378f296989b71c55 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 30 Dec 2022 07:38:28 +0300 Subject: [PATCH 59/64] backend: egl: fix creating eglpixmap according to the specification of the EGL_KHR_image_pixmap extension, if target is EGL_NATIVE_PIXMAP_KHR then ctx must be EGL_NO_CONTEXT, otherwise, the EGL_BAD_PARAMETER error is generated. source: https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_image_pixmap.txt fixes #981 --- src/backend/gl/egl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index e6d4d90..c83ff3c 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -285,8 +285,9 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b eglpixmap = cmalloc(struct egl_pixmap); eglpixmap->pixmap = pixmap; - eglpixmap->image = eglCreateImageProc(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, - (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->image = + eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); eglpixmap->owned = owned; if (eglpixmap->image == EGL_NO_IMAGE) { From 74ab307626d5d5d351df64495bc43a1ec13edfac Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 11 Jan 2023 05:23:02 +0300 Subject: [PATCH 60/64] opengl: fix segfault in ensure_glx_context --- src/opengl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengl.h b/src/opengl.h index dcd8697..d0d37dd 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -159,7 +159,7 @@ static inline bool ensure_glx_context(session_t *ps) { if (!glx_has_context(ps)) glx_init(ps, false); - return ps->psglx->context; + return glx_has_context(ps); } /** From 364463feaf89fa0b7ad82f15af6576400b6daec4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 17 Jan 2023 02:56:28 +0000 Subject: [PATCH 61/64] doc: list cases that trigger unredirect unredir-if-possible doesn't just happen for fullscreen windows, be more accurate. Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index b4ff69b..fdf0198 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -134,7 +134,13 @@ OPTIONS 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. *--unredir-if-possible*:: - 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. + Unredirect all windows in some cases. Known to cause flickering when redirecting/unredirecting windows. Currently, unredirecting is triggered by following conditions: + * If the top level window is taking up the entire screen. In multi-monitor setup, this means ALL monitors. + * If there is no window. + * If a window is fullscreen according to its WM hints. (can be disabled with *--no-ewmh-fullscreen*). + * If a window requests to bypass the compositor ('_NET_WM_BYPASS_COMPOSITOR'). + Windows are also unredirected unconditionally when monitors are powered off, regardless if *--unredir-if-possible* is set. + *--unredir-if-possible-delay* 'MILLISECONDS':: Delay before unredirecting the window, in milliseconds. Defaults to 0. From 223533acecc925e3b21970c8931134dc4d7f809c Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Mon, 23 Jan 2023 02:29:30 +0300 Subject: [PATCH 62/64] fixed open window type anim, closes #12 --- src/config.c | 24 ++--- src/config_libconfig.c | 2 +- src/win.c | 234 ++++++++++++++++++++--------------------- 3 files changed, 126 insertions(+), 134 deletions(-) diff --git a/src/config.c b/src/config.c index e58cfbd..495d4e8 100644 --- a/src/config.c +++ b/src/config.c @@ -736,19 +736,19 @@ enum open_window_animation parse_open_window_animation(const char *src) { 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) { + } 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; - } + } 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; } diff --git a/src/config_libconfig.c b/src/config_libconfig.c index fdecbf7..54efab6 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -233,7 +233,7 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ animation = OPEN_WINDOW_ANIMATION_NONE; o->animation = animation; - mask->animation = OPEN_WINDOW_ANIMATION_INVALID; + mask->animation = animation; } double fval; diff --git a/src/win.c b/src/win.c index ddf0835..e49faef 100644 --- a/src/win.c +++ b/src/win.c @@ -471,56 +471,63 @@ static void win_update_properties(session_t *ps, struct managed_win *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; + CLEAR_MASK(w->animation_is_tag) + 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; + anim_x = &w->animation_center_x, anim_y = &w->animation_center_y; + anim_w = &w->animation_w, anim_h = &w->animation_h; - 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; + 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; - } + 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; + 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 + 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 + 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); + angle = 2 * M_PI * ((double)rand() / RAND_MAX); const double radius = sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); @@ -530,106 +537,93 @@ revert: *anim_w = 0; *anim_h = 0; break; - } - case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location + 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 + 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 + 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 + 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; + 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; + 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; + 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_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; - } + 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; + 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; - } + 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; } } @@ -674,34 +668,33 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { add_damage_from_win(ps, w); } - - // Determine if a window should animate if (win_should_animate(ps, w)) { - win_update_bounding_shape(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; + win_update_bounding_shape(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); + } - 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_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 = @@ -709,10 +702,9 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { w->animation_dest_w = w->pending_g.width; w->animation_dest_h = w->pending_g.height; } - CLEAR_MASK(w->dwm_mask) + 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; @@ -744,7 +736,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { } } - w->animation_progress = 0.0; + w->animation_progress = 0.0; } else { w->g = w->pending_g; } From 8e8a62ae2993cb05a7cc1760505bac065eb6571b Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Mon, 23 Jan 2023 02:46:38 +0300 Subject: [PATCH 63/64] AUR pkg, closes #15, thanks fxzzi --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a23dfd0..d2abd67 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ $ ninja -C build ### To install +#### AUR (arch) +- picom-ftlabs-git +Thanks to @Fxzzi for maintaining the package. + + ``` bash $ ninja -C build install ``` From a88393a7dfaccec6883fc33cf445e6f183461ff7 Mon Sep 17 00:00:00 2001 From: Arda Atci Date: Mon, 23 Jan 2023 05:57:32 +0300 Subject: [PATCH 64/64] fix lerping on shadows, closes #4 --- src/backend/backend.c | 12 ++++---- src/backend/backend.h | 2 +- src/backend/dummy/dummy.c | 2 +- src/backend/gl/gl_common.c | 11 ++++--- src/backend/gl/gl_common.h | 2 +- src/backend/xrender/xrender.c | 2 +- src/picom.c | 55 +++++++++++++++++------------------ 7 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index f5b5479..d53577c 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -71,9 +71,9 @@ static void process_window_for_painting(session_t *ps, struct managed_win *w, 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 - 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); @@ -87,7 +87,6 @@ static void process_window_for_painting(session_t *ps, struct managed_win *w, // 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, @@ -102,9 +101,10 @@ static void process_window_for_painting(session_t *ps, struct managed_win *w, 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); + 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) { @@ -240,7 +240,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){.x = ps->root_width, .y = ps->root_height}, - ®_paint, ®_visible); + ®_paint, ®_visible, true); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, ®_paint); @@ -420,7 +420,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { } 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, @@ -501,7 +501,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { if (w->frame_opacity == 1 && !is_animating) { ps->backend_data->ops->compose(ps->backend_data, w->win_image, window_coord, NULL, dest_coord, - ®_paint_in_bound, ®_visible); + ®_paint_in_bound, ®_visible, true); } else { if (is_animating && w->old_win_image) { bool is_focused = win_is_focused_raw(ps, w); diff --git a/src/backend/backend.h b/src/backend/backend.h index 3d620f8..2758c68 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -181,7 +181,7 @@ 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, diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 7e06fac..987e54e 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -64,7 +64,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); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index ea08cd2..b61aae1 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -548,7 +548,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; @@ -574,9 +574,12 @@ 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]); + + 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); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 4aad9c0..4324604 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -147,7 +147,7 @@ bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, * @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); diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 686e6de..5d3c314 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -350,7 +350,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]); diff --git a/src/picom.c b/src/picom.c index 82bcd6d..7357c69 100644 --- a/src/picom.c +++ b/src/picom.c @@ -909,39 +909,38 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) { add_damage_from_win(ps, w); } - 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_has_frame(w)) { - w->frame_opacity = ps->o.frame_opacity; - } else { - w->frame_opacity = 1.0; - } + if (win_check_fade_finished(ps, w)) { + // the window has been destroyed because fading finished + continue; + } - // Update window mode - w->mode = win_calc_mode(w); + if (win_has_frame(w)) { + w->frame_opacity = ps->o.frame_opacity; + } else { + w->frame_opacity = 1.0; + } - // Destroy all reg_ignore above when frame opaque state changes on - // SOLID mode - if (was_painted && w->mode != mode_old) { - w->reg_ignore_valid = false; - } + // Update window mode + w->mode = win_calc_mode(w); + + // Destroy all reg_ignore above when frame opaque state changes on + // SOLID mode + if (was_painted && w->mode != mode_old) { + w->reg_ignore_valid = false; } }