aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-10-30 15:12:21 +1100
committerallusive-dev <[email protected]>2023-10-30 15:12:21 +1100
commitac33357e7ce7c474aeaffc92e381020289d767a2 (patch)
tree7f05fa79b3ccd7834f85cc65a07fbd4f8030eb94
parentCreate FUNDING.yml (diff)
downloadcompfy-1.0.0.tar.xz
compfy-1.0.0.zip
Version 1.01.0.0
-rwxr-xr-x[-rw-r--r--]bin/picom-trans0
-rw-r--r--dbus-examples/cdbus-driver.sh54
-rw-r--r--dbus-examples/inverter.sh69
-rw-r--r--man/picom-trans.1.asciidoc114
-rw-r--r--man/picom.1.asciidoc136
-rw-r--r--meson.build40
-rw-r--r--picom.desktop2
-rw-r--r--picom.sample.conf100
-rw-r--r--src/atom.c2
-rw-r--r--src/backend/backend.c118
-rw-r--r--src/backend/backend.h137
-rw-r--r--src/backend/backend_common.c62
-rw-r--r--src/backend/backend_common.h17
-rw-r--r--src/backend/dummy/dummy.c28
-rw-r--r--src/backend/gl/blur.c900
-rw-r--r--src/backend/gl/egl.c469
-rw-r--r--src/backend/gl/egl.h33
-rw-r--r--src/backend/gl/gl_common.c1479
-rw-r--r--src/backend/gl/gl_common.h100
-rw-r--r--src/backend/gl/glx.c51
-rw-r--r--src/backend/gl/glx.h1
-rw-r--r--src/backend/gl/shaders.c187
-rw-r--r--src/backend/meson.build2
-rw-r--r--src/backend/xrender/xrender.c382
-rw-r--r--src/c2.c48
-rw-r--r--src/c2.h20
-rw-r--r--src/cache.c2
-rw-r--r--src/common.h39
-rw-r--r--src/compiler.h8
-rw-r--r--src/config.c236
-rw-r--r--src/config.h45
-rw-r--r--src/config_libconfig.c268
-rw-r--r--src/dbus.c402
-rw-r--r--src/diagnostic.c8
-rw-r--r--src/event.c2
-rw-r--r--src/kernel.c19
-rw-r--r--src/kernel.h5
-rw-r--r--src/log.c2
-rw-r--r--src/meson.build2
-rw-r--r--src/options.c994
-rw-r--r--src/picom.c449
-rw-r--r--src/picom.h10
-rw-r--r--src/render.c8
-rw-r--r--src/string_utils.c28
-rw-r--r--src/string_utils.h1
-rw-r--r--src/utils.c3
-rw-r--r--src/utils.h68
-rw-r--r--src/win.c412
-rw-r--r--src/win.h20
-rw-r--r--src/win_defs.h3
-rw-r--r--src/x.c4
-rw-r--r--src/x.h4
-rw-r--r--subprojects/test.h/test.h38
53 files changed, 4904 insertions, 2727 deletions
diff --git a/bin/picom-trans b/bin/picom-trans
index b686128..b686128 100644..100755
--- a/bin/picom-trans
+++ b/bin/picom-trans
diff --git a/dbus-examples/cdbus-driver.sh b/dbus-examples/cdbus-driver.sh
deleted file mode 100644
index 31a9b2f..0000000
--- a/dbus-examples/cdbus-driver.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/sh
-
-if [ -z "$SED" ]; then
- SED="sed"
- command -v gsed > /dev/null && SED="gsed"
-fi
-
-# === Get connection parameters ===
-
-dpy=$(echo -n "$DISPLAY" | tr -c '[:alnum:]' _)
-
-if [ -z "$dpy" ]; then
- echo "Cannot find display."
- exit 1
-fi
-
-service="com.github.chjj.compton.${dpy}"
-interface='com.github.chjj.compton'
-object='/com/github/chjj/compton'
-type_win='uint32'
-type_enum='uint32'
-
-# === DBus methods ===
-
-# List all window ID compton manages (except destroyed ones)
-dbus-send --print-reply --dest="$service" "$object" "${interface}.list_win"
-
-# Get window ID of currently focused window
-focused=$(dbus-send --print-reply --dest="$service" "$object" "${interface}.find_win" string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p')
-
-if [ -n "$focused" ]; then
- # Get invert_color_force property of the window
- dbus-send --print-reply --dest="$service" "$object" "${interface}.win_get" "${type_win}:${focused}" string:invert_color_force
-
- # Set the window to have inverted color
- dbus-send --print-reply --dest="$service" "$object" "${interface}.win_set" "${type_win}:${focused}" string:invert_color_force "${type_enum}:1"
-else
- echo "Cannot find focused window."
-fi
-
-# Reset compton
-sleep 3
-dbus-send --print-reply --dest="$service" "$object" "${interface}.reset"
-
-# Undirect window
-sleep 3
-dbus-send --print-reply --dest="$service" "$object" "${interface}.opts_set" string:redirected_force "${type_enum}:0"
-
-# Revert back to auto
-sleep 3
-dbus-send --print-reply --dest="$service" "$object" "${interface}.opts_set" string:redirected_force "${type_enum}:2"
-
-# Force repaint
-dbus-send --print-reply --dest="$service" "$object" "${interface}.repaint"
diff --git a/dbus-examples/inverter.sh b/dbus-examples/inverter.sh
deleted file mode 100644
index e13cb2d..0000000
--- a/dbus-examples/inverter.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-# == Declare stderr function ===
-
-stderr() {
- printf "\033[1;31m%s\n\033[0m" "$@" >&2
-}
-
-# === Verify `picom --dbus` status ===
-
-if [ -z "$(dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep compton)" ]; then
- stderr "picom DBus interface unavailable"
- if [ -n "$(pgrep picom)" ]; then
- stderr "picom running without dbus interface"
- #killall picom & # Causes all windows to flicker away and come back ugly.
- #picom --dbus & # Causes all windows to flicker away and come back beautiful
- else
- stderr "picom not running"
- fi
- exit 1
-fi
-
-# === Setup sed ===
-
-SED="${SED:-$(command -v gsed || printf 'sed')}"
-
-# === Get connection parameters ===
-
-dpy=$(printf "$DISPLAY" | tr -c '[:alnum:]' _)
-
-if [ -z "$dpy" ]; then
- stderr "Cannot find display."
- exit 1
-fi
-
-service="com.github.chjj.compton.${dpy}"
-interface="com.github.chjj.compton"
-picom_dbus="dbus-send --print-reply --dest="${service}" / "${interface}"."
-type_win='uint32'
-type_enum='uint32'
-
-# === Color Inversion ===
-
-# Get window ID of window to invert
-if [ -z "$1" -o "$1" = "selected" ]; then
- window=$(xwininfo -frame | sed -n 's/^xwininfo: Window id: \(0x[[:xdigit:]][[:xdigit:]]*\).*/\1/p') # Select window by mouse
-elif [ "$1" = "focused" ]; then
- # Ensure we are tracking focus
- window=$(${picom_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query picom for the active window
-elif echo "$1" | grep -Eiq '^([[:digit:]][[:digit:]]*|0x[[:xdigit:]][[:xdigit:]]*)$'; then
- window="$1" # Accept user-specified window-id if the format is correct
-else
- echo "$0" "[ selected | focused | window-id ]"
-fi
-
-# Color invert the selected or focused window
-if [ -n "$window" ]; then
- invert_status="$(${picom_dbus}win_get "${type_win}:${window}" string:invert_color | $SED -n 's/^[[:space:]]*boolean[[:space:]]*\([[:alpha:]]*\).*/\1/p')"
- if [ "$invert_status" = true ]; then
- invert=0 # Set the window to have normal color
- else
- invert=1 # Set the window to have inverted color
- fi
- ${picom_dbus}win_set "${type_win}:${window}" string:invert_color_force "${type_enum}:${invert}" &
-else
- stderr "Cannot find $1 window."
- exit 1
-fi
-exit 0
diff --git a/man/picom-trans.1.asciidoc b/man/picom-trans.1.asciidoc
new file mode 100644
index 0000000..22b1dd2
--- /dev/null
+++ b/man/picom-trans.1.asciidoc
@@ -0,0 +1,114 @@
+picom-trans(1)
+================
+:doctype: manpage
+:man source: picom
+:man version: {picom-version}
+:man manual: User Commands
+
+NAME
+----
+picom-trans - an opacity setter tool
+
+SYNOPSIS
+--------
+
+*picom-trans* [-w 'WINDOW_ID'] [-n 'WINDOW_NAME'] [-c] [-s] 'OPACITY'
+
+DESCRIPTION
+-----------
+
+*picom-trans* is a bash script that sets '_NET_WM_WINDOW_OPACITY' attribute of a window using standard X11 command-line utilities, including *xprop*(1) and *xwininfo*(1). It is similar to *transset*(1) or *transset-df*(1).
+
+OPTIONS
+-------
+*-w*, *--window*='WINDOW_ID'::
+Specify the window id of the target window.
+
+*-n*, *--name*='WINDOW_NAME'::
+Specify and try to match a window name.
+
+*-c*, *--current*::
+Specify the currently active window as target. Only works if EWMH '_NET_ACTIVE_WINDOW' property exists on root window.
+
+*-s*, *--select*::
+Select target window with mouse cursor. This is the default if no window has been specified.
+
+*-o*, *--opacity*='OPACITY'::
+Specify the new opacity value for the window. This value can be anywhere from 1-100. If it is prefixed with a plus or minus (+/-), this will increment or decrement from the target window's current opacity instead.
+
+*-g*, *--get*::
+Print the target window's opacity instead of setting it.
+
+*-d*, *--delete*::
+Delete opacity of the target window instead of setting it.
+
+*-t*, *--toggle*::
+Toggle the target window's opacity: Set opacity if not already set, and delete if already set.
+
+*-r*, *--reset*::
+Reset opacity for all windows instead of setting it.
+
+EXAMPLES
+--------
+
+* Set the opacity of the window with specific window ID to 75%:
++
+------------
+picom-trans -w "$WINDOWID" 75
+------------
+
+* Set the opacity of the window with the name "urxvt" to 75%:
++
+------------
+picom-trans -n "urxvt" 75
+------------
+
+* Set current window to opacity of 75%:
++
+------------
+picom-trans -c 75
+------------
+
+* Select target window and set opacity to 75%:
++
+------------
+picom-trans -s 75
+------------
+
+* Increment opacity of current active window by 5%:
++
+------------
+picom-trans -c +5
+------------
+
+* Decrement opacity of current active window by 5%:
++
+------------
+picom-trans -c -- -5
+------------
+
+* Delete current window's opacity:
++
+------------
+picom-trans -c --delete
+------------
+
+* Toggle current window's opacity between 90 and unset
++
+------------
+picom-trans -c --toggle 90
+------------
+
+* Reset all windows:
++
+------------
+picom-trans --reset
+------------
+
+BUGS
+----
+Please submit bug reports to <https://github.com/yshui/picom>.
+
+SEE ALSO
+--------
+link:picom.1.html[*picom*(1)], *xprop*(1), *xwininfo*(1)
diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc
index 2a3f327..61af5a2 100644
--- a/man/picom.1.asciidoc
+++ b/man/picom.1.asciidoc
@@ -15,7 +15,7 @@ SYNOPSIS
DESCRIPTION
-----------
-picom is a compositor for X11 which gives features to WM and DE such as opacity, blurring, rounded edges, shadows and most importantly. Animations!
+picom is a compositor based on Dana Jansens' version of xcompmgr (which itself was written by Keith Packard). It includes some improvements over the original xcompmgr, like window frame opacity and inactive window transparency.
OPTIONS
-------
@@ -43,9 +43,6 @@ OPTIONS
*-D*, *--fade-delta*='MILLISECONDS'::
The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
-*-m*, *--menu-opacity*='OPACITY'::
- Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0)
-
*-c*, *--shadow*::
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.
@@ -55,28 +52,6 @@ OPTIONS
*-F*::
Equals to *-f*. Deprecated.
-*--animations*::
- Run animations for window geometry changes (movement and scaling).
- We use spring-based animations, which are parametrized by *--animation-stiffness*, *--animation-dampening*, *--animation-window-mass* and *--animation-clamp*. +
- A useful resource for selecting animation parameters is https://react-spring-visualizer.com/, which allows you to interactively try out various parameters and see the resulting animation curve. Or you can just edit the values in the config file, which is automatically reloaded.
-
-*--animation-for-open-window*::
- Which animation to run when opening a window. Must be one of `none`, `fly-in`, `zoom` (default: `none`).
-
-*--animation-stiffness*::
- Stiffness (a.k.a. tension) parameter for spring-based animation (default: 200.0).
-
-*--animation-dampening*::
- Dampening (a.k.a. friction) parameter for spring-based animation (default: 25.0).
-
-*--animation-window-mass*::
- Mass parameter for spring-based animation (default: 1.0).
-
-*--animation-clamping*::
- Whether to clamp animations (default: true).
-
- **NOTE:** With clamping disabled, on very low performance conditions (e.g., over 100ms to render an animation frame), windows may diverge. If this is your case, it is strongly advised to keep clamping on, as it protects against that.
-
*-i*, *--inactive-opacity*='OPACITY'::
Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
@@ -92,8 +67,8 @@ OPTIONS
*--log-file*::
Set the log file. If *--log-file* is never specified, logs will be written to stderr. Otherwise, logs will to written to the given file, though some of the early logs might still be written to the stderr. When setting this option from the config file, it is recommended to use an absolute path.
-*--experimental-backends*::
- Use the new, reimplemented version of the backends. The new backends are HIGHLY UNSTABLE at this point, you have been warned. This option is not available in the config file.
+*--legacy-backends*::
+ Use the old version of the backends. This option can not be set from the config file.
*--show-all-xerrors*::
Show all X errors (for debugging).
@@ -117,7 +92,7 @@ OPTIONS
Blue color value of shadow (0.0 - 1.0, defaults to 0).
*--inactive-opacity-override*::
- Let inactive opacity set by *-i* override the '_NET_WM_OPACITY' values of windows.
+ Let inactive opacity set by *-i* override the '_NET_WM_WINDOW_OPACITY' values of windows.
*--active-opacity* 'OPACITY'::
Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
@@ -150,10 +125,7 @@ OPTIONS
Try to detect windows with rounded corners and don't consider them shaped windows. The accuracy is not very high, unfortunately.
*--detect-client-opacity*::
- Detect '_NET_WM_OPACITY' on client windows, useful for window managers not passing '_NET_WM_OPACITY' of client windows to frame windows.
-
-*--refresh-rate* 'REFRESH_RATE'::
- Specify refresh rate of the screen. If not specified or 0, picom will try detecting this with X RandR extension.
+ Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows.
*--vsync*, *--no-vsync*::
Enable/disable VSync.
@@ -189,7 +161,7 @@ OPTIONS
Use 'WM_TRANSIENT_FOR' to group windows, and consider windows in the same group focused at the same time.
*--detect-client-leader*::
- Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if *--detect-transient* is enabled, too.
+ 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-method*, *--blur-size*, *--blur-deviation*, *--blur-strength*::
Parameters for background blurring, see the *BLUR* section for more information.
@@ -222,7 +194,7 @@ A 7x7 Gaussian blur kernel (sigma = 0.84089642) looks like:
--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'
----
+
-May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box`, `3x3gaussian`, `5x5gaussian`, `7x7gaussian`, `9x9gaussian`, `11x11gaussian`. All Gaussian kernels are generated with sigma = 0.84089642 . If you find yourself needing to generate custom blur kernels, you might want to try the new blur configuration supported by the experimental backends (See *BLUR* and *--experimental-backends*).
+May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box`, `3x3gaussian`, `5x5gaussian`, `7x7gaussian`, `9x9gaussian`, `11x11gaussian`. All Gaussian kernels are generated with sigma = 0.84089642 . If you find yourself needing to generate custom blur kernels, you might want to try the new blur configuration (See *BLUR*).
*--blur-background-exclude* 'CONDITION'::
Exclude conditions for background blur.
@@ -258,13 +230,13 @@ 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.
*--glx-fshader-win* 'SHADER'::
- 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.
+ 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. Only works with *--legacy-backends* enabled.
*--force-win-blend*::
Force all windows to be painted with blending. Useful if you have a *--glx-fshader-win* that could turn opaque pixels transparent.
@@ -287,6 +259,15 @@ 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.
+
+*--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.
+
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.
@@ -321,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.
@@ -357,8 +338,6 @@ Examples:
_NET_FRAME_EXTENTS@[2]:32c < 20 || !_NET_FRAME_EXTENTS@:32c
# The pattern here will be parsed as "dd4"
name = "\x64\x64\o64"
- # The pattern here will be parsed as "\x64\x64\x64"
- name = r"\x64\x64\o64"
LEGACY FORMAT OF CONDITIONS
@@ -376,6 +355,61 @@ This is the old condition format we once used. Support of this format might be r
'PATTERN' is the actual pattern string.
+SHADER INTERFACE
+----------------
+
+This secion describes the interface of a custom shader, how it is used by picom, and what parameters are passed by picom to the shader. This does not apply to the legacy backends.
+
+A custom shader is a GLSL fragment shader program, which can be used to override the default way of how a window is rendered. If a custom shader is used, the default picom effects (e.g. dimming, color inversion, etc.) will no longer be automatically applied. It would be the custom shader's responsibility to apply these effects.
+
+The interface between picom and a custom shader is dependent on which backend is being used. The xrender backend doesn't support shader at all. Here we descibe the interface provided by the glx backend.
+
+The shader must define a function, 'vec4 window_shader()', which would be the entry point of the shader. The returned 'vec4' will be used to set 'gl_FragColor'. A function, 'vec4 default_post_processing(vec4 c)', is provided for applying the default picom effects to input color 'c'.
+
+The following uniform/input variables are made available to the shader:
+
+[source,glsl]
+----
+in vec2 texcoord; // texture coordinate of the fragment
+
+uniform float opacity; // opacity of the window (0.0 - 1.0)
+uniform float dim; // dimming factor of the window (0.0 - 1.0, higher means more dim)
+uniform float corner_radius; // corner radius of the window (pixels)
+uniform float border_width; // estimated border width of the window (pixels)
+uniform bool invert_color; // whether to invert the color of the window
+uniform sampler2D tex; // texture of the window
+uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
+uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
+uniform float time; // time in milliseconds, counting from an unspecified starting point
+----
+
+The default behavior of picom window rendering can be replicated by the following shader:
+
+[source,glsl]
+----
+#version 330
+in vec2 texcoord; // texture coordinate of the fragment
+
+uniform sampler2D tex; // texture of the window
+
+// Default window post-processing:
+// 1) invert color
+// 2) opacity / transparency
+// 3) max-brightness clamping
+// 4) rounded corners
+vec4 default_post_processing(vec4 c);
+
+// Default window shader:
+// 1) fetch the specified pixel
+// 2) apply default post-processing
+vec4 window_shader() {
+ vec4 c = texelFetch(tex, ivec2(texcoord), 0);
+ return default_post_processing(c);
+}
+----
+
+The interface is expected to be mostly stable.
+
CONFIGURATION FILES
-------------------
picom could read from a configuration file if libconfig support is compiled in. If *--config* is not used, picom will seek for a configuration file in `$XDG_CONFIG_HOME/picom.conf` (`~/.config/picom.conf`, usually), then `$XDG_CONFIG_HOME/picom/picom.conf`, then `$XDG_CONFIG_DIRS/picom.conf` (often `/etc/xdg/picom.conf`), then `$XDG_CONFIG_DIRS/picom/picom.conf`.
@@ -405,13 +439,13 @@ Following per window-type options are available: ::
Controls whether the window of this type is to be always considered focused. (By default, all window types except "normal" and "dialog" has this on.)
blur-background:::
- Controls wether the window of this type will have its transparent background blurred.
+ Controls whether the window of this type will have its transparent background blurred.
full-shadow:::
Controls whether shadow is drawn under the parts of the window that you normally won't be able to see. Useful when the window has parts of it transparent, and you want shadows in those areas.
clip-shadow-above:::
- Controls wether shadows that would have been drawn above the window should be clipped. Useful for dock windows that should have no shadow painted on top.
+ 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:::
Controls whether this type of windows should cause screen to become redirected again after been unredirected. If you have *--unredir-if-possible* set, and doesn't want certain window to cause unnecessary screen redirection, you can set this to `true`.
@@ -434,7 +468,7 @@ Available options of the 'blur' section are: ::
*method*:::
A string. Controls the blur method. Corresponds to the *--blur-method* command line option. Available choices are:
'none' to disable blurring; 'gaussian' for gaussian blur; 'box' for box blur; 'kernel' for convolution blur with a custom kernel; 'dual_kawase' for dual-filter kawase blur.
- Note: 'gaussian', 'box' and 'dual_kawase' blur methods are only supported by the experimental backends.
+ Note: 'gaussian', 'box' and 'dual_kawase' blur methods are not supported by the legacy backends.
(default: none)
*size*:::
@@ -470,16 +504,16 @@ EXAMPLES
$ picom --config /dev/null
------------
-* Run picom with client-side shadow and fading, disable shadow on dock windows and drag-and-drop windows:
+* Run picom with client-side shadow and fading:
+
------------
-$ picom -cCGf
+$ picom -cf
------------
* Same thing as above, plus making inactive windows 80% transparent, making frame 80% transparent, don't fade on window open/close, and fork to background:
+
------------
-$ picom -bcCGf -i 0.8 -e 0.8 --no-fading-openclose
+$ picom -bcf -i 0.8 -e 0.8 --no-fading-openclose
------------
* Draw white shadows:
@@ -502,12 +536,14 @@ $ picom --backend glx --vsync
BUGS
----
-Please submit bug reports to <https://github.com/allusive-dev/picom-allusive>.
+Please submit bug reports to <https://github.com/yshui/picom>.
-Out dated information in this man page is NOT considered a bug.
+Out dated information in this man page is considered a bug.
RESOURCES
---------
-Website: <https://allusive.dev>
+Homepage: <https://github.com/yshui/picom>
-GithubPage: <https://github.com/allusive-dev/picom-allusive>
+SEE ALSO
+--------
+*xcompmgr*(1), link:picom-trans.html[*picom-trans*(1)]
diff --git a/meson.build b/meson.build
index 0349b7d..1001ecb 100644
--- a/meson.build
+++ b/meson.build
@@ -1,12 +1,21 @@
-project('picom', 'c', version: '8',
- default_options: ['c_std=c11'])
+project('picom', 'c', version: '1',
+ default_options: ['c_std=c11', 'warning_level=1'])
cc = meson.get_compiler('c')
# use project version by default
-version = 'v0.2.2-4'
+version = 'v'+meson.project_version()
-add_global_arguments('-DCOMPTON_VERSION="'+version+'"', language: 'c')
+# use git describe if that's available
+git = find_program('git', required: false)
+if git.found()
+ gitv = run_command('git', 'rev-parse', '--short=5', 'HEAD', check: false)
+ if gitv.returncode() == 0
+ version = 'vgit-'+gitv.stdout().strip()
+ endif
+endif
+
+add_global_arguments('-DPICOM_VERSION="'+version+'"', language: 'c')
if get_option('buildtype') == 'release'
add_global_arguments('-DNDEBUG', language: 'c')
@@ -44,16 +53,18 @@ if cc.has_header('stdc-predef.h')
add_global_arguments('-DHAS_STDC_PREDEF_H', language: 'c')
endif
-warns = [ 'all', 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type',
- 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init',
- 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value',
- 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough',
- 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ]
-foreach w : warns
- if cc.has_argument('-W'+w)
- add_global_arguments('-W'+w, language: 'c')
- endif
-endforeach
+if get_option('warning_level') != '0'
+ warns = [ 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type',
+ 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init',
+ 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value',
+ 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough=2',
+ 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ]
+ foreach w : warns
+ if cc.has_argument('-W'+w)
+ add_global_arguments('-W'+w, language: 'c')
+ endif
+ endforeach
+endif
test_h_dep = subproject('test.h').get_variable('test_h_dep')
@@ -61,3 +72,4 @@ subdir('src')
install_data('bin/picom-trans', install_dir: get_option('bindir'))
install_data('picom.desktop', install_dir: 'share/applications')
+install_data('picom.desktop', install_dir: get_option('sysconfdir') / 'xdg' / 'autostart')
diff --git a/picom.desktop b/picom.desktop
index 9d21f6d..051e893 100644
--- a/picom.desktop
+++ b/picom.desktop
@@ -9,5 +9,7 @@ Categories=Utility;
Keywords=compositor;composite manager;window effects;transparency;opacity;
TryExec=picom
Exec=picom
+StartupNotify=false
+Terminal=false
# Thanks to quequotion for providing this file!
Icon=picom
diff --git a/picom.sample.conf b/picom.sample.conf
index e516175..a8ba5c7 100644
--- a/picom.sample.conf
+++ b/picom.sample.conf
@@ -6,20 +6,24 @@
# 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 = false
+shadow = true;
# The blur radius for shadows, in pixels. (defaults to 12)
# shadow-radius = 12
-shadow-radius = 12;
+shadow-radius = 7;
# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
-# shadow-opacity = 0.75
+# shadow-opacity = .75
# The left offset for shadows, in pixels. (defaults to -15)
-shadow-offset-x = -15;
+# shadow-offset-x = -15
+shadow-offset-x = -7;
# The top offset for shadows, in pixels. (defaults to -15)
-shadow-offset-y = -15;
+# shadow-offset-y = -15
+shadow-offset-y = -7;
# Red color value of shadow (0.0 - 1.0, defaults to 0).
# shadow-red = 0
@@ -34,10 +38,33 @@ shadow-offset-y = -15;
# shadow-color = "#000000"
# Specify a list of conditions of windows that should have no shadow.
+#
+# examples:
+# shadow-exclude = "n:e:Notification";
+#
+# shadow-exclude = []
shadow-exclude = [
- "class_g = 'foo'",
+ "name = 'Notification'",
+ "class_g = 'Conky'",
+ "class_g ?= 'Notify-osd'",
+ "class_g = 'Cairo-clock'",
+ "_GTK_FRAME_EXTENTS@:c"
];
+# Specify a list of conditions of windows that should have no shadow painted over, such as a dock window.
+# clip-shadow-above = []
+
+# Specify a X geometry that describes the region in which shadow should not
+# be painted in, such as a dock window region. Use
+# shadow-exclude-reg = "x10+0+0"
+# for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
+#
+# shadow-exclude-reg = ""
+
+# Crop shadow of a window fully on a particular Xinerama screen to the screen.
+# xinerama-shadow-crop = false
+
+
#################################
# Fading #
#################################
@@ -62,10 +89,6 @@ fade-out-step = 0.03;
# Specify a list of conditions of windows that should not be faded.
# fade-exclude = []
-#
-# Advanced Options
-#
-
# Do not fade on window open/close.
# no-fading-openclose = false
@@ -80,13 +103,13 @@ fade-out-step = 0.03;
# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
# inactive-opacity = 1
-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 = 1.0;
+frame-opacity = 0.7;
-# Let inactive opacity set by -i override the '_NET_WM_OPACITY' values of windows.
+# Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows.
# inactive-opacity-override = true
inactive-opacity-override = false;
@@ -98,12 +121,18 @@ inactive-opacity-override = false;
# Specify a list of conditions of windows that should never be considered focused.
# focus-exclude = []
-focus-exclude = [];
+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 = []
@@ -174,6 +203,9 @@ blur-background-exclude = [
# General Settings #
#################################
+# Enable remote control via D-Bus. See the man page for more details.
+# dbus = true
+
# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
# daemon = false
@@ -206,18 +238,12 @@ mark-ovredir-focused = true;
# detect-rounded-corners = false
detect-rounded-corners = true;
-# Detect '_NET_WM_OPACITY' on client windows, useful for window managers
-# not passing '_NET_WM_OPACITY' of client windows to frame windows.
+# 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;
-# Specify refresh rate of the screen. If not specified or 0, picom will
-# try detecting this with X RandR extension.
-#
-# refresh-rate = 60
-refresh-rate = 0;
-
# 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.
@@ -243,11 +269,11 @@ refresh-rate = 0;
detect-transient = true;
# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
-# group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if
-# detect-transient is enabled, too.
+# 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.
#
# detect-client-leader = false
-detect-client-leader = true;
# Resize damaged region by a specific number of pixels.
# A positive value enlarges it while a negative one shrinks it.
@@ -282,7 +308,7 @@ detect-client-leader = 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
#
@@ -295,11 +321,17 @@ use-damage = true;
#
# xrender-sync-fence = false
-# 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.
+# GLX backend: Use specified GLSL fragment shader for rendering window
+# contents. Read the man page for a detailed explanation of the interface.
+#
+# 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.
#
-# glx-fshader-win = ""
+# window-shader-fg-rule = [
+# "my_shader.frag:window_type != 'dock'"
+# ]
# Force all windows to be painted with blending. Useful if you
# have a glx-fshader-win that could turn opaque pixels transparent.
@@ -323,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.
@@ -371,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/atom.c b/src/atom.c
index 0272dc8..c5107ea 100644
--- a/src/atom.c
+++ b/src/atom.c
@@ -3,8 +3,8 @@
#include "atom.h"
#include "common.h"
-#include "utils.h"
#include "log.h"
+#include "utils.h"
static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
xcb_connection_t *c = ud;
diff --git a/src/backend/backend.c b/src/backend/backend.c
index b0e562a..9d4d10c 100644
--- a/src/backend/backend.c
+++ b/src/backend/backend.c
@@ -16,6 +16,7 @@
extern struct backend_operations xrender_ops, dummy_ops;
#ifdef CONFIG_OPENGL
extern struct backend_operations glx_ops;
+extern struct backend_operations egl_ops;
#endif
struct backend_operations *backend_list[NUM_BKEND] = {
@@ -23,6 +24,7 @@ struct backend_operations *backend_list[NUM_BKEND] = {
[BKEND_DUMMY] = &dummy_ops,
#ifdef CONFIG_OPENGL
[BKEND_GLX] = &glx_ops,
+ [BKEND_EGL] = &egl_ops,
#endif
};
@@ -44,7 +46,7 @@ region_t get_damage(session_t *ps, bool all_damage) {
} else {
for (int i = 0; i < buffer_age; i++) {
auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
- log_trace("damage index: %d, damage ring offset: %ld", i, curr);
+ log_trace("damage index: %d, damage ring offset: %td", i, curr);
dump_region(&ps->damage_ring[curr]);
pixman_region32_union(&region, &region, &ps->damage_ring[curr]);
}
@@ -53,6 +55,31 @@ region_t get_damage(session_t *ps, bool all_damage) {
return region;
}
+void handle_device_reset(session_t *ps) {
+ log_error("Device reset detected");
+ // Wait for reset to complete
+ // Although ideally the backend should return DEVICE_STATUS_NORMAL after a reset
+ // is completed, it's not always possible.
+ //
+ // According to ARB_robustness (emphasis mine):
+ //
+ // "If a reset status other than NO_ERROR is returned and subsequent
+ // calls return NO_ERROR, the context reset was encountered and
+ // completed. If a reset status is repeatedly returned, the context **may**
+ // be in the process of resetting."
+ //
+ // Which means it may also not be in the process of resetting. For example on
+ // AMDGPU devices, Mesa OpenGL always return CONTEXT_RESET after a reset has
+ // started, completed or not.
+ //
+ // So here we blindly wait 5 seconds and hope ourselves best of the luck.
+ sleep(5);
+
+ // Reset picom
+ log_info("Resetting picom after device reset");
+ ev_break(ps->loop, EVBREAK_ALL);
+}
+
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,
@@ -66,6 +93,7 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo
// 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;
+ coord_t window_coord = {.x = w->g.x, .y = w->g.y};
{
// The bounding shape, in window local coordinates
region_t reg_bound_local;
@@ -75,16 +103,15 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
- reg_visible, reg_paint);
+ reg_visible, reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x,
- -w->g.y);
+ -w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
- pixman_region32_fini(&reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(
@@ -98,15 +125,18 @@ static void process_window_for_painting(session_t *ps, struct managed_win* w, vo
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
- w->g.x, w->g.y,
- w->g.x + w->widthb, w->g.y + w->heightb,
- reg_paint_in_bound, reg_visible);
+ window_coord, NULL, window_coord,
+ reg_paint_in_bound, reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
}
/// paint all windows
void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
+ if (ps->backend_data->ops->device_status &&
+ ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
+ return handle_device_reset(ps);
+ }
if (ps->o.xrender_sync_fence) {
if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
log_error("x_fence_sync failed, xrender-sync-fence will be "
@@ -206,7 +236,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,
- 0, 0, ps->root_width, ps->root_height,
+ (coord_t){0}, NULL, (coord_t){0},
&reg_paint, &reg_visible);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
@@ -227,6 +257,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// The bounding shape of the window, in global/target coordinates
// reminder: bounding shape contains the WM frame
auto reg_bound = win_get_bounding_shape_global_by_val(w);
+ auto reg_bound_no_corner =
+ win_get_bounding_shape_global_without_corners_by_val(w);
+
+ if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) {
+ win_bind_mask(ps->backend_data, w);
+ }
// The clip region for the current window, in global/target coordinates
// reg_paint_in_bound \in reg_paint
@@ -250,7 +286,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
* (e.g. with shaders), we should consult the backend whether the window
* is transparent or not. for now we will just rely on the force_win_blend
* option */
+
auto real_win_mode = w->mode;
+ coord_t window_coord = {.x = w->g.x, .y = w->g.y};
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
@@ -294,7 +332,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// We need to blur the bounding shape of the window
// (reg_paint_in_bound = reg_bound \cap reg_paint)
ps->backend_data->ops->blur(
- ps->backend_data, blur_opacity, ps->backend_blur_context,
+ ps->backend_data, blur_opacity,
+ ps->backend_blur_context, w->mask_image, window_coord,
&reg_paint_in_bound, &reg_visible);
} else {
// Window itself is solid, we only need to blur the frame
@@ -313,9 +352,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
pixman_region32_intersect(&reg_blur, &reg_blur,
&reg_visible);
}
- ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
- ps->backend_blur_context,
- &reg_blur, &reg_visible);
+ ps->backend_data->ops->blur(
+ ps->backend_data, blur_opacity, ps->backend_blur_context,
+ w->mask_image, window_coord, &reg_blur, &reg_visible);
pixman_region32_fini(&reg_blur);
}
}
@@ -327,9 +366,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// reg_shadow \in reg_paint
auto reg_shadow = win_extents_by_val(w);
pixman_region32_intersect(&reg_shadow, &reg_shadow, &reg_paint);
- if (!ps->o.wintype_option[w->window_type].full_shadow) {
- pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_bound);
- }
// Mask out the region we don't want shadow on
if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
@@ -365,12 +401,28 @@ 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_OPACITY, w->shadow_image,
&w->opacity);
+ coord_t shadow_coord = {.x = w->g.x + w->shadow_dx,
+ .y = w->g.y + w->shadow_dy};
+
+ auto inverted_mask = NULL;
+ if (!ps->o.wintype_option[w->window_type].full_shadow) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow,
+ &reg_bound_no_corner);
+ if (w->mask_image) {
+ inverted_mask = w->mask_image;
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_INVERTED,
+ inverted_mask, (bool[]){true});
+ }
+ }
ps->backend_data->ops->compose(
- ps->backend_data, w->shadow_image,
- w->g.x + w->shadow_dx, w->g.y + w->shadow_dy,
- w->g.x + w->shadow_dx + w->shadow_width,
- w->g.y + w->shadow_dy + w->shadow_height,
- &reg_shadow, &reg_visible);
+ ps->backend_data, w->shadow_image, shadow_coord,
+ inverted_mask, window_coord, &reg_shadow, &reg_visible);
+ if (inverted_mask) {
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_INVERTED,
+ inverted_mask, (bool[]){false});
+ }
pixman_region32_fini(&reg_shadow);
}
@@ -395,6 +447,25 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
&dim_opacity);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image,
+ (double[]){w->corner_radius});
+ if (w->corner_radius) {
+ int border_width = w->g.border_width;
+ if (border_width == 0) {
+ // Some WM has borders implemented as WM frames
+ border_width = min3(w->frame_extents.left,
+ w->frame_extents.right,
+ w->frame_extents.bottom);
+ }
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
+ w->win_image, &border_width);
+ }
+
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image,
+ w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
}
if (w->opacity * MAX_ALPHA < 1) {
@@ -411,13 +482,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
pixman_region32_subtract(&reg_shadow_clip, &reg_shadow_clip, &reg_bound);
}
- // Draw window on target
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
+ // Draw window on target
if (w->frame_opacity == 1 && !is_animating) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
- w->g.x, w->g.y,
- w->g.x + w->widthb, w->g.y + w->heightb,
- &reg_paint_in_bound, &reg_visible);
+ window_coord, NULL, window_coord,
+ &reg_paint_in_bound, &reg_visible);
} else {
if (is_animating && w->old_win_image) {
assert(w->old_win_image);
diff --git a/src/backend/backend.h b/src/backend/backend.h
index ae107d3..191e814 100644
--- a/src/backend/backend.h
+++ b/src/backend/backend.h
@@ -16,6 +16,8 @@
typedef struct session session_t;
struct managed_win;
+struct backend_shadow_context;
+
struct ev_loop;
struct backend_operations;
@@ -30,12 +32,31 @@ typedef struct backend_base {
// ...
} backend_t;
+typedef struct geometry {
+ int width;
+ int height;
+} geometry_t;
+
+typedef struct coord {
+ int x, y;
+} coord_t;
+
typedef void (*backend_ready_callback_t)(void *);
+// This mimics OpenGL's ARB_robustness extension, which enables detection of GPU context
+// resets.
+// See: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_robustness.txt, section
+// 2.6 "Graphics Reset Recovery".
+enum device_status {
+ DEVICE_STATUS_NORMAL,
+ DEVICE_STATUS_RESETTING,
+};
+
// When image properties are actually applied to the image, they are applied in a
// particular order:
//
-// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness
+// Corner radius -> Color inversion -> Dimming -> Opacity multiply -> Limit maximum
+// brightness
enum image_properties {
// Whether the color of the image is inverted
// 1 boolean, default: false
@@ -54,6 +75,15 @@ enum image_properties {
// brightness down to the max brightness value.
// 1 double, default: 1
IMAGE_PROPERTY_MAX_BRIGHTNESS,
+ // Gives the image a rounded corner.
+ // 1 double, default: 0
+ IMAGE_PROPERTY_CORNER_RADIUS,
+ // Border width
+ // 1 int, default: 0
+ IMAGE_PROPERTY_BORDER_WIDTH,
+ // Custom shader for this window.
+ // 1 pointer to shader struct, default: NULL
+ IMAGE_PROPERTY_CUSTOM_SHADER,
};
enum image_operations {
@@ -61,6 +91,12 @@ enum image_operations {
IMAGE_OP_APPLY_ALPHA,
};
+enum shader_attributes {
+ // Whether the shader needs to be render regardless of whether the window is
+ // updated.
+ SHADER_ATTRIBUTE_ANIMATED = 1,
+};
+
struct gaussian_blur_args {
int size;
double deviation;
@@ -133,26 +169,30 @@ struct backend_operations {
void (*prepare)(backend_t *backend_data, const region_t *reg_damage);
/**
- * Paint the content of an image onto the rendering buffer
+ * Paint the content of an image onto the rendering buffer.
*
- * @param backend_data the backend data
- * @param image_data the image to paint
- * @param dst_x1, dst_y1 the top left corner of the image in the target
- * @param dst_x2, dst_y2 the top right corner of the image in the target
- * @param reg_paint the clip region, in target coordinates
- * @param reg_visible the visible region, in target coordinates
+ * @param backend_data the backend data
+ * @param image_data the image to paint
+ * @param dst_x, dst_y the top left corner of the image in the target
+ * @param mask the mask image, the top left of the mask is aligned with
+ * the top left of the image
+ * @param reg_paint the clip region, in target coordinates
+ * @param reg_visible the visible region, in target coordinates
*/
- 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);
+ 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);
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
/// Blur a given region of the rendering buffer.
- bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx,
- const region_t *reg_blur, const region_t *reg_visible)
- attr_nonnull(1, 3, 4, 5);
+ ///
+ /// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the
+ /// mask is.
+ bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask,
+ coord_t mask_dst, const region_t *reg_blur,
+ const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7);
/// Update part of the back buffer with the rendering buffer, then present the
/// back buffer onto the target window (if not back buffered, update part of the
@@ -175,18 +215,67 @@ struct backend_operations {
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
- /// Create a shadow image based on the parameters
+ /// Create a shadow context for rendering shadows with radius `radius`.
+ /// Default implementation: default_backend_create_shadow_context
+ struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data,
+ double radius);
+ /// Destroy a shadow context
+ /// Default implementation: default_backend_destroy_shadow_context
+ void (*destroy_shadow_context)(backend_t *backend_data,
+ struct backend_shadow_context *ctx);
+
+ /// Create a shadow image based on the parameters. Resulting image should have a
+ /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the
+ /// shadow context is created.
/// Default implementation: default_backend_render_shadow
+ ///
+ /// Required.
void *(*render_shadow)(backend_t *backend_data, int width, int height,
- const conv *kernel, double r, double g, double b, double a);
+ struct backend_shadow_context *ctx, struct color color);
+
+ /// Create a shadow by blurring a mask. `size` is the size of the blur. The
+ /// backend can use whichever blur method is the fastest. The shadow produced
+ /// shoule be consistent with `render_shadow`.
+ ///
+ /// Optional.
+ void *(*shadow_from_mask)(backend_t *backend_data, void *mask,
+ struct backend_shadow_context *ctx, struct color color);
+
+ /// Create a mask image from region `reg`. This region can be used to create
+ /// shadow, or used as a mask for composing. When used as a mask, it should mask
+ /// out everything that is not inside the region used to create it.
+ ///
+ /// Image properties might be set on masks too, at least the INVERTED and
+ /// CORNER_RADIUS properties must be supported. Inversion should invert the inside
+ /// and outside of the mask. Corner radius should exclude the corners from the
+ /// mask. Corner radius should be applied before the inversion.
+ ///
+ /// Required.
+ void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg);
// ============ Resource management ===========
/// Free resources associated with an image data structure
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
+ /// Create a shader object from a shader source.
+ ///
+ /// Optional
+ void *(*create_shader)(backend_t *backend_data, const char *source)attr_nonnull(1, 2);
+
+ /// Free a shader object.
+ ///
+ /// Required if create_shader is present.
+ void (*destroy_shader)(backend_t *backend_data, void *shader) attr_nonnull(1, 2);
+
// =========== Query ===========
+ /// Get the attributes of a shader.
+ ///
+ /// Optional, Returns a bitmask of attributes, see `shader_attributes`.
+ uint64_t (*get_shader_attributes)(backend_t *backend_data, void *shader)
+ attr_nonnull(1, 2);
+
/// Return if image is not completely opaque.
///
/// This function is needed because some backend might change the content of the
@@ -243,20 +332,6 @@ struct backend_operations {
bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *args);
- /**
- * Read the color of the pixel at given position of the given image. Image
- * properties have no effect.
- *
- * @param backend_data backend_data
- * @param image_data an image data structure previously returned by the
- * backend. the image to read pixel from.
- * @param x, y coordinate of the pixel to read
- * @param[out] color the color of the pixel
- * @return whether the operation is successful
- */
- bool (*read_pixel)(backend_t *backend_data, void *image_data, int x, int y,
- struct color *output);
-
/// Create another instance of the `image_data`. All `image_op` and
/// `set_image_property` calls on the returned image should not affect the
/// original image
@@ -282,6 +357,8 @@ struct backend_operations {
enum driver (*detect_driver)(backend_t *backend_data);
void (*diagnostics)(backend_t *backend_data);
+
+ enum device_status (*device_status)(backend_t *backend_data);
};
extern struct backend_operations *backend_list[];
diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c
index c0377d3..d6fcce2 100644
--- a/src/backend/backend_common.c
+++ b/src/backend/backend_common.c
@@ -92,7 +92,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
}
unsigned char *data = ximage->data;
- long sstride = ximage->stride;
+ long long sstride = ximage->stride;
// If the window body is smaller than the kernel, we do convolution directly
if (width < r * 2 && height < r * 2) {
@@ -100,7 +100,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
for (int x = 0; x < swidth; x++) {
double sum = sum_kernel_normalized(
kernel, d - x - 1, d - y - 1, width, height);
- data[y * sstride + x] = (uint8_t)(sum * 255.0);
+ data[y * sstride + x] = (uint8_t)(sum * 255.0 * opacity);
}
}
return ximage;
@@ -118,14 +118,14 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
for (int x = 0; x < r * 2; x++) {
double sum = sum_kernel_normalized(kernel, d - x - 1,
d - y - 1, d, height) *
- 255.0;
+ 255.0 * opacity;
data[y * sstride + x] = (uint8_t)sum;
data[y * sstride + swidth - x - 1] = (uint8_t)sum;
}
}
for (int y = 0; y < sheight; y++) {
- double sum =
- sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0;
+ double sum = sum_kernel_normalized(kernel, 0, d - y - 1, d, height) *
+ 255.0 * opacity;
memset(&data[y * sstride + r * 2], (uint8_t)sum,
(size_t)(width - 2 * r));
}
@@ -137,14 +137,14 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
for (int x = 0; x < swidth; x++) {
double sum = sum_kernel_normalized(kernel, d - x - 1,
d - y - 1, width, d) *
- 255.0;
+ 255.0 * opacity;
data[y * sstride + x] = (uint8_t)sum;
data[(sheight - y - 1) * sstride + x] = (uint8_t)sum;
}
}
for (int x = 0; x < swidth; x++) {
- double sum =
- sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0;
+ double sum = sum_kernel_normalized(kernel, d - x - 1, 0, width, d) *
+ 255.0 * opacity;
for (int y = r * 2; y < height; y++) {
data[y * sstride + x] = (uint8_t)sum;
}
@@ -291,16 +291,16 @@ shadow_picture_err:
return false;
}
-void *
-default_backend_render_shadow(backend_t *backend_data, int width, int height,
- const conv *kernel, double r, double g, double b, double a) {
- xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root,
- true, 1, r, g, b),
+void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
+ struct backend_shadow_context *sctx, struct color color) {
+ const conv *kernel = (void *)sctx;
+ xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true,
+ 1, color.red, color.green, color.blue),
shadow = XCB_NONE;
xcb_render_picture_t pict = XCB_NONE;
- if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel,
- shadow_pixel, &shadow, &pict)) {
+ if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height,
+ kernel, shadow_pixel, &shadow, &pict)) {
return NULL;
}
@@ -311,6 +311,34 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height,
return ret;
}
+/// Implement render_shadow with shadow_from_mask
+void *
+backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
+ struct backend_shadow_context *sctx, struct color color) {
+ region_t reg;
+ pixman_region32_init_rect(&reg, 0, 0, (unsigned int)width, (unsigned int)height);
+ void *mask = backend_data->ops->make_mask(
+ backend_data, (geometry_t){.width = width, .height = height}, &reg);
+ pixman_region32_fini(&reg);
+
+ void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
+ backend_data->ops->release_image(backend_data, mask);
+ return shadow;
+}
+
+struct backend_shadow_context *
+default_create_shadow_context(backend_t *backend_data attr_unused, double radius) {
+ auto ret =
+ (struct backend_shadow_context *)gaussian_kernel_autodetect_deviation(radius);
+ sum_kernel_preprocess((conv *)ret);
+ return ret;
+}
+
+void default_destroy_shadow_context(backend_t *backend_data attr_unused,
+ struct backend_shadow_context *sctx) {
+ free_conv((conv *)sctx);
+}
+
static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) {
int r = args->size * 2 + 1;
assert(r > 0);
@@ -449,7 +477,10 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
tex->ewidth = iargs[0];
tex->eheight = iargs[1];
break;
+ case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break;
case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break;
+ case IMAGE_PROPERTY_BORDER_WIDTH: tex->border_width = *(int *)arg; break;
+ case IMAGE_PROPERTY_CUSTOM_SHADER: break;
}
return true;
@@ -468,6 +499,7 @@ struct backend_image *default_new_backend_image(int w, int h) {
ret->eheight = h;
ret->ewidth = w;
ret->color_inverted = false;
+ ret->corner_radius = 0;
return ret;
}
diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h
index 5c9c806..c72a168 100644
--- a/src/backend/backend_common.h
+++ b/src/backend/backend_common.h
@@ -37,9 +37,11 @@ struct backend_image {
double opacity;
double dim;
double max_brightness;
+ double corner_radius;
// Effective size of the image
int ewidth, eheight;
bool color_inverted;
+ int border_width;
};
bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width,
@@ -60,9 +62,18 @@ bool default_is_win_transparent(void *, win *, void *);
/// caveat as `default_is_win_transparent` applies.
bool default_is_frame_transparent(void *, win *, void *);
+void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
+ struct backend_shadow_context *sctx, struct color color);
+
+/// Implement `render_shadow` with `shadow_from_mask`.
void *
-default_backend_render_shadow(backend_t *backend_data, int width, int height,
- const conv *kernel, double r, double g, double b, double a);
+backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
+ struct backend_shadow_context *sctx, struct color color);
+struct backend_shadow_context *
+default_create_shadow_context(backend_t *backend_data, double radius);
+
+void default_destroy_shadow_context(backend_t *backend_data,
+ struct backend_shadow_context *sctx);
void init_backend_base(struct backend_base *base, session_t *ps);
@@ -70,8 +81,6 @@ struct conv **generate_blur_kernel(enum blur_method method, void *args, int *ker
struct dual_kawase_params *generate_dual_kawase_params(void *args);
void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
-void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width,
- uint16_t desired_height, const region_t *reg);
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
void *image_data, void *arg);
diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c
index a057b97..7e06fac 100644
--- a/src/backend/dummy/dummy.c
+++ b/src/backend/dummy/dummy.c
@@ -23,6 +23,8 @@ struct dummy_image {
struct dummy_data {
struct backend_base base;
struct dummy_image *images;
+
+ struct backend_image mask;
};
struct backend_base *dummy_init(struct session *ps attr_unused) {
@@ -47,6 +49,9 @@ void dummy_deinit(struct backend_base *data) {
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
auto dummy = (struct dummy_data *)base;
+ if (img == (struct dummy_image *)&dummy->mask) {
+ return;
+ }
struct dummy_image *tmp = NULL;
HASH_FIND_INT(dummy->images, &img->pixmap, tmp);
if (!tmp) {
@@ -56,10 +61,13 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag
assert(*tmp->refcount > 0);
}
-void dummy_compose(struct backend_base *base, void *image, int dst_x1 attr_unused,
- int dst_y1 attr_unused, int dst_x2 attr_unused, int dst_y2 attr_unused,
- const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) {
+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) {
+ auto dummy attr_unused = (struct dummy_data *)base;
dummy_check_image(base, image);
+ assert(mask == NULL || mask == &dummy->mask);
}
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
@@ -67,7 +75,8 @@ void dummy_fill(struct backend_base *backend_data attr_unused, struct color c at
}
bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
- void *blur_ctx attr_unused, const region_t *reg_blur attr_unused,
+ void *blur_ctx attr_unused, void *mask attr_unused,
+ coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused,
const region_t *reg_visible attr_unused) {
return true;
}
@@ -94,6 +103,9 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
void dummy_release_image(backend_t *base, void *image) {
auto dummy = (struct dummy_data *)base;
+ if (image == &dummy->mask) {
+ return;
+ }
auto img = (struct dummy_image *)image;
assert(*img->refcount > 0);
(*img->refcount)--;
@@ -121,6 +133,11 @@ bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unu
return true;
}
+void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
+ const region_t *reg attr_unused) {
+ return &(((struct dummy_data *)base)->mask);
+}
+
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
void *image, void *arg attr_unused) {
dummy_check_image(base, image);
@@ -158,7 +175,10 @@ struct backend_operations dummy_ops = {
.fill = dummy_fill,
.blur = dummy_blur,
.bind_pixmap = dummy_bind_pixmap,
+ .create_shadow_context = default_create_shadow_context,
+ .destroy_shadow_context = default_destroy_shadow_context,
.render_shadow = default_backend_render_shadow,
+ .make_mask = dummy_make_mask,
.release_image = dummy_release_image,
.is_image_transparent = dummy_is_image_transparent,
.buffer_age = dummy_buffer_age,
diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c
new file mode 100644
index 0000000..41022f5
--- /dev/null
+++ b/src/backend/gl/blur.c
@@ -0,0 +1,900 @@
+#include <locale.h>
+#include <stdbool.h>
+
+#include <backend/backend.h>
+#include <backend/backend_common.h>
+
+#include "gl_common.h"
+
+struct gl_blur_context {
+ enum blur_method method;
+ gl_blur_shader_t *blur_shader;
+
+ /// Temporary textures used for blurring
+ GLuint *blur_textures;
+ int blur_texture_count;
+ /// Temporary fbos used for blurring
+ GLuint *blur_fbos;
+ int blur_fbo_count;
+
+ /// Cached dimensions of each blur_texture. They are the same size as the target,
+ /// so they are always big enough without resizing.
+ /// Turns out calling glTexImage to resize is expensive, so we avoid that.
+ struct texture_size {
+ int width;
+ int height;
+ } *texture_sizes;
+
+ /// Cached dimensions of the offscreen framebuffer. It's the same size as the
+ /// target but is expanded in either direction by resize_width / resize_height.
+ int fb_width, fb_height;
+
+ /// How much do we need to resize the damaged region for blurring.
+ int resize_width, resize_height;
+
+ int npasses;
+};
+
+/**
+ * Blur contents in a particular region.
+ */
+bool gl_kernel_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent,
+ struct backend_image *mask, coord_t mask_dst, const GLuint vao[2],
+ const int vao_nelems[2], GLuint source_texture,
+ geometry_t source_size, GLuint target_fbo, GLuint default_mask) {
+ int dst_y_fb_coord = bctx->fb_height - extent->y2;
+
+ int curr = 0;
+ for (int i = 0; i < bctx->npasses; ++i) {
+ const gl_blur_shader_t *p = &bctx->blur_shader[i];
+ assert(p->prog);
+
+ assert(bctx->blur_textures[curr]);
+
+ // The origin to use when sampling from the source texture
+ GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord;
+ GLint tex_width, tex_height;
+ GLuint src_texture;
+
+ if (i == 0) {
+ src_texture = source_texture;
+ tex_width = source_size.width;
+ tex_height = source_size.height;
+ } else {
+ src_texture = bctx->blur_textures[curr];
+ auto src_size = bctx->texture_sizes[curr];
+ tex_width = src_size.width;
+ tex_height = src_size.height;
+ }
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ glUseProgram(p->prog);
+ glUniform2f(p->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
+ 1.0F / (GLfloat)tex_height);
+
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, default_mask);
+
+ glUniform1i(p->uniform_mask_tex, 1);
+ glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F);
+ glUniform1i(p->uniform_mask_inverted, 0);
+ glUniform1f(p->uniform_mask_corner_radius, 0.0F);
+
+ // The number of indices in the selected vertex array
+ GLsizei nelems;
+
+ if (i < bctx->npasses - 1) {
+ assert(bctx->blur_fbos[0]);
+ assert(bctx->blur_textures[!curr]);
+
+ // not last pass, draw into framebuffer, with resized regions
+ glBindVertexArray(vao[1]);
+ nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, bctx->blur_textures[!curr], 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
+ return false;
+ }
+
+ glUniform1f(p->uniform_opacity, 1.0F);
+ } else {
+ // last pass, draw directly into the back buffer, with origin
+ // regions. And apply mask if requested
+ if (mask) {
+ auto inner = (struct gl_texture *)mask->inner;
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+ glUniform1i(p->uniform_mask_inverted, mask->color_inverted);
+ glUniform1f(p->uniform_mask_corner_radius,
+ (float)mask->corner_radius);
+ glUniform2f(
+ p->uniform_mask_offset, (float)(mask_dst.x),
+ (float)(bctx->fb_height - mask_dst.y - inner->height));
+ }
+ glBindVertexArray(vao[0]);
+ nelems = vao_nelems[0];
+ glBindFramebuffer(GL_FRAMEBUFFER, target_fbo);
+
+ glUniform1f(p->uniform_opacity, (float)opacity);
+ }
+
+ glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y);
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+
+ // XXX use multiple draw calls is probably going to be slow than
+ // just simply blur the whole area.
+
+ curr = !curr;
+ }
+
+ return true;
+}
+
+bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent,
+ struct backend_image *mask, coord_t mask_dst, const GLuint vao[2],
+ const int vao_nelems[2], GLuint source_texture,
+ geometry_t source_size, GLuint target_fbo, GLuint default_mask) {
+ int dst_y_fb_coord = bctx->fb_height - extent->y2;
+
+ int iterations = bctx->blur_texture_count;
+ int scale_factor = 1;
+
+ // Kawase downsample pass
+ const gl_blur_shader_t *down_pass = &bctx->blur_shader[0];
+ assert(down_pass->prog);
+ glUseProgram(down_pass->prog);
+
+ glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
+
+ for (int i = 0; i < iterations; ++i) {
+ // Scale output width / height by half in each iteration
+ scale_factor <<= 1;
+
+ GLuint src_texture;
+ int tex_width, tex_height;
+
+ if (i == 0) {
+ // first pass: copy from back buffer
+ src_texture = source_texture;
+ tex_width = source_size.width;
+ tex_height = source_size.height;
+ } else {
+ // copy from previous pass
+ src_texture = bctx->blur_textures[i - 1];
+ auto src_size = bctx->texture_sizes[i - 1];
+ tex_width = src_size.width;
+ tex_height = src_size.height;
+ }
+
+ assert(src_texture);
+ assert(bctx->blur_fbos[i]);
+
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ glBindVertexArray(vao[1]);
+ auto nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+
+ glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor);
+
+ glUniform2f(down_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
+ 1.0F / (GLfloat)tex_height);
+
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+ }
+
+ // Kawase upsample pass
+ const gl_blur_shader_t *up_pass = &bctx->blur_shader[1];
+ assert(up_pass->prog);
+ glUseProgram(up_pass->prog);
+
+ glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
+
+ for (int i = iterations - 1; i >= 0; --i) {
+ // Scale output width / height back by two in each iteration
+ scale_factor >>= 1;
+
+ const GLuint src_texture = bctx->blur_textures[i];
+ assert(src_texture);
+
+ // Calculate normalized half-width/-height of a src pixel
+ auto src_size = bctx->texture_sizes[i];
+ int tex_width = src_size.width;
+ int tex_height = src_size.height;
+
+ // The number of indices in the selected vertex array
+ GLsizei nelems;
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, default_mask);
+
+ glUniform1i(up_pass->uniform_mask_tex, 1);
+ glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F);
+ glUniform1i(up_pass->uniform_mask_inverted, 0);
+ glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F);
+ if (i > 0) {
+ assert(bctx->blur_fbos[i - 1]);
+
+ // not last pass, draw into next framebuffer
+ glBindVertexArray(vao[1]);
+ nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+
+ glUniform1f(up_pass->uniform_opacity, (GLfloat)1);
+ } else {
+ // last pass, draw directly into the back buffer
+ if (mask) {
+ auto inner = (struct gl_texture *)mask->inner;
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+ glUniform1i(up_pass->uniform_mask_inverted,
+ mask->color_inverted);
+ glUniform1f(up_pass->uniform_mask_corner_radius,
+ (float)mask->corner_radius);
+ glUniform2f(
+ up_pass->uniform_mask_offset, (float)(mask_dst.x),
+ (float)(bctx->fb_height - mask_dst.y - inner->height));
+ }
+ glBindVertexArray(vao[0]);
+ nelems = vao_nelems[0];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target_fbo);
+
+ glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity);
+ }
+
+ glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor);
+ glUniform2f(up_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
+ 1.0F / (GLfloat)tex_height);
+
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+ }
+
+ 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 ret = false;
+
+ if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
+ // Resize the temporary textures used for blur in case the root
+ // size changed
+ bctx->fb_width = source_size.width;
+ bctx->fb_height = source_size.height;
+
+ for (int i = 0; i < bctx->blur_texture_count; ++i) {
+ auto tex_size = bctx->texture_sizes + i;
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ // Use smaller textures for each iteration (quarter of the
+ // previous texture)
+ tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1));
+ tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1));
+ } else {
+ tex_size->width = bctx->fb_width;
+ tex_size->height = bctx->fb_height;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width,
+ tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ // Attach texture to FBO target
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ bctx->blur_textures[i], 0);
+ if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return false;
+ }
+ }
+ }
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ }
+
+ // Remainder: regions are in Xorg coordinates
+ auto reg_blur_resized =
+ resize_region(reg_blur, bctx->resize_width, bctx->resize_height);
+ const rect_t *extent = pixman_region32_extents((region_t *)reg_blur),
+ *extent_resized = pixman_region32_extents(&reg_blur_resized);
+ int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1;
+ if (width == 0 || height == 0) {
+ return true;
+ }
+
+ int nrects, nrects_resized;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects),
+ *rects_resized =
+ pixman_region32_rectangles(&reg_blur_resized, &nrects_resized);
+ if (!nrects || !nrects_resized) {
+ return true;
+ }
+
+ auto coord = ccalloc(nrects * 16, GLint);
+ auto indices = ccalloc(nrects * 6, GLuint);
+ auto extent_height = extent_resized->y2 - extent_resized->y1;
+ x_rect_to_coords(
+ nrects, rects, (coord_t){.x = extent_resized->x1, .y = extent_resized->y1},
+ extent_height, bctx->fb_height, source_size.height, false, coord, indices);
+
+ auto coord_resized = ccalloc(nrects_resized * 16, GLint);
+ auto indices_resized = ccalloc(nrects_resized * 6, GLuint);
+ x_rect_to_coords(nrects_resized, rects_resized,
+ (coord_t){.x = extent_resized->x1, .y = extent_resized->y1},
+ extent_height, bctx->fb_height, bctx->fb_height, false,
+ coord_resized, indices_resized);
+ pixman_region32_fini(&reg_blur_resized);
+
+ GLuint vao[2];
+ glGenVertexArrays(2, vao);
+ GLuint bo[4];
+ glGenBuffers(4, bo);
+
+ glBindVertexArray(vao[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
+ indices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ glBindVertexArray(vao[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
+ coord_resized, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ int vao_nelems[2] = {nrects * 6, nrects_resized * 6};
+
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ ret = gl_dual_kawase_blur(opacity, bctx, extent_resized, mask, mask_dst,
+ vao, vao_nelems, source_texture, source_size,
+ target_fbo, default_mask);
+ } else {
+ ret = gl_kernel_blur(opacity, bctx, extent_resized, mask, mask_dst, vao,
+ vao_nelems, source_texture, source_size, target_fbo,
+ default_mask);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(4, bo);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(2, vao);
+ glUseProgram(0);
+
+ free(indices);
+ free(coord);
+ free(indices_resized);
+ free(coord_resized);
+
+ gl_check_err();
+ return ret;
+}
+
+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 attr_unused) {
+ auto gd = (struct gl_data *)base;
+ auto bctx = (struct gl_blur_context *)ctx;
+ 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);
+}
+
+static inline void gl_free_blur_shader(gl_blur_shader_t *shader) {
+ if (shader->prog) {
+ glDeleteProgram(shader->prog);
+ }
+
+ shader->prog = 0;
+}
+
+void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) {
+ auto bctx = (struct gl_blur_context *)ctx;
+ // Free GLSL shaders/programs
+ for (int i = 0; i < bctx->npasses; ++i) {
+ gl_free_blur_shader(&bctx->blur_shader[i]);
+ }
+ free(bctx->blur_shader);
+
+ if (bctx->blur_texture_count && bctx->blur_textures) {
+ glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures);
+ free(bctx->blur_textures);
+ }
+ if (bctx->blur_texture_count && bctx->texture_sizes) {
+ free(bctx->texture_sizes);
+ }
+ if (bctx->blur_fbo_count && bctx->blur_fbos) {
+ glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos);
+ free(bctx->blur_fbos);
+ }
+
+ bctx->blur_texture_count = 0;
+ bctx->blur_fbo_count = 0;
+
+ free(bctx);
+
+ gl_check_err();
+}
+
+/**
+ * Initialize GL blur filters.
+ */
+bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
+ enum blur_method method, void *args) {
+ bool success = false;
+ auto ctx = (struct gl_blur_context *)blur_context;
+
+ struct conv **kernels;
+
+ int nkernels;
+ ctx->method = BLUR_METHOD_KERNEL;
+ if (method == BLUR_METHOD_KERNEL) {
+ nkernels = ((struct kernel_blur_args *)args)->kernel_count;
+ kernels = ((struct kernel_blur_args *)args)->kernels;
+ } else {
+ kernels = generate_blur_kernel(method, args, &nkernels);
+ }
+
+ if (!nkernels) {
+ ctx->method = BLUR_METHOD_NONE;
+ return true;
+ }
+
+ // Specify required textures and FBOs
+ ctx->blur_texture_count = 2;
+ ctx->blur_fbo_count = 1;
+
+ ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t);
+
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ // clang-format off
+ static const char *FRAG_SHADER_BLUR = GLSL(330,
+ %s\n // other extension pragmas
+ uniform sampler2D tex_src;
+ uniform vec2 pixel_norm;
+ uniform float opacity;
+ in vec2 texcoord;
+ out vec4 out_color;
+ float mask_factor();
+ void main() {
+ vec2 uv = texcoord * pixel_norm;
+ vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
+ %s //body of the convolution
+ out_color = sum / float(%.7g) * opacity * mask_factor();
+ }
+ );
+ static const char *FRAG_SHADER_BLUR_ADD = QUOTE(
+ sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g));
+ );
+ // clang-format on
+
+ const char *shader_add = FRAG_SHADER_BLUR_ADD;
+ char *extension = strdup("");
+
+ for (int i = 0; i < nkernels; i++) {
+ auto kern = kernels[i];
+ // Build shader
+ int width = kern->w, height = kern->h;
+ int nele = width * height;
+ // '%.7g' is at most 14 characters, inserted 3 times
+ size_t body_len = (strlen(shader_add) + 42) * (uint)nele;
+ char *shader_body = ccalloc(body_len, char);
+ char *pc = shader_body;
+
+ // Make use of the linear interpolation hardware by sampling 2 pixels with
+ // one texture access by sampling between both pixels based on their
+ // relative weight. Easiest done in a single dimension as 2D bilinear
+ // filtering would raise additional constraints on the kernels. Therefore
+ // only use interpolation along the larger dimension.
+ double sum = 0.0;
+ if (width > height) {
+ // use interpolation in x dimension (width)
+ for (int j = 0; j < height; ++j) {
+ for (int k = 0; k < width; k += 2) {
+ double val1, val2;
+ val1 = kern->data[j * width + k];
+ val2 = (k + 1 < width)
+ ? kern->data[j * width + k + 1]
+ : 0;
+
+ double combined_weight = val1 + val2;
+ if (combined_weight == 0) {
+ continue;
+ }
+ sum += combined_weight;
+
+ double offset_x =
+ k + (val2 / combined_weight) - (width / 2);
+ double offset_y = j - (height / 2);
+ pc += snprintf(
+ pc, body_len - (ulong)(pc - shader_body),
+ shader_add, combined_weight, offset_x, offset_y);
+ assert(pc < shader_body + body_len);
+ }
+ }
+ } else {
+ // use interpolation in y dimension (height)
+ for (int j = 0; j < height; j += 2) {
+ for (int k = 0; k < width; ++k) {
+ double val1, val2;
+ val1 = kern->data[j * width + k];
+ val2 = (j + 1 < height)
+ ? kern->data[(j + 1) * width + k]
+ : 0;
+
+ double combined_weight = val1 + val2;
+ if (combined_weight == 0) {
+ continue;
+ }
+ sum += combined_weight;
+
+ double offset_x = k - (width / 2);
+ double offset_y =
+ j + (val2 / combined_weight) - (height / 2);
+ pc += snprintf(
+ pc, body_len - (ulong)(pc - shader_body),
+ shader_add, combined_weight, offset_x, offset_y);
+ assert(pc < shader_body + body_len);
+ }
+ }
+ }
+
+ auto pass = ctx->blur_shader + i;
+ size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) +
+ strlen(shader_body) + 10 /* sum */ +
+ 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR,
+ extension, shader_body, sum);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+ free(shader_body);
+
+ // Build program
+ pass->prog = gl_create_program_from_strv(
+ (const char *[]){vertex_shader, NULL},
+ (const char *[]){shader_str, masking_glsl, NULL});
+ free(shader_str);
+ if (!pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ bind_uniform(pass, pixel_norm);
+ bind_uniform(pass, opacity);
+
+ bind_uniform(pass, mask_tex);
+ bind_uniform(pass, mask_offset);
+ bind_uniform(pass, mask_inverted);
+ bind_uniform(pass, mask_corner_radius);
+ log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
+ pass->uniform_mask_offset, pass->uniform_mask_inverted,
+ pass->uniform_mask_corner_radius, pass->uniform_opacity);
+ pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
+
+ // Setup projection matrix
+ glUseProgram(pass->prog);
+ int pml = glGetUniformLocationChecked(pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+
+ ctx->resize_width += kern->w / 2;
+ ctx->resize_height += kern->h / 2;
+ }
+
+ if (nkernels == 1) {
+ // Generate an extra null pass so we don't need special code path for
+ // the single pass case
+ auto pass = &ctx->blur_shader[1];
+ pass->prog = gl_create_program_from_strv(
+ (const char *[]){vertex_shader, NULL},
+ (const char *[]){copy_with_mask_frag, masking_glsl, NULL});
+ pass->uniform_pixel_norm = -1;
+ pass->uniform_opacity = -1;
+ pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
+ bind_uniform(pass, mask_tex);
+ bind_uniform(pass, mask_offset);
+ bind_uniform(pass, mask_inverted);
+ bind_uniform(pass, mask_corner_radius);
+
+ // Setup projection matrix
+ glUseProgram(pass->prog);
+ int pml = glGetUniformLocationChecked(pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+
+ ctx->npasses = 2;
+ } else {
+ ctx->npasses = nkernels;
+ }
+
+ success = true;
+out:
+ if (method != BLUR_METHOD_KERNEL) {
+ // We generated the blur kernels, so we need to free them
+ for (int i = 0; i < nkernels; i++) {
+ free(kernels[i]);
+ }
+ free(kernels);
+ }
+
+ free(extension);
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ return success;
+}
+
+bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection,
+ enum blur_method method, void *args) {
+ bool success = false;
+ auto ctx = (struct gl_blur_context *)blur_context;
+
+ ctx->method = method;
+
+ auto blur_params = generate_dual_kawase_params(args);
+
+ // Specify required textures and FBOs
+ ctx->blur_texture_count = blur_params->iterations;
+ ctx->blur_fbo_count = blur_params->iterations;
+
+ ctx->resize_width += blur_params->expand;
+ ctx->resize_height += blur_params->expand;
+
+ ctx->npasses = 2;
+ ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t);
+
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ // Dual-kawase downsample shader / program
+ auto down_pass = ctx->blur_shader;
+ {
+ // clang-format off
+ static const char *FRAG_SHADER_DOWN = GLSL(330,
+ uniform sampler2D tex_src;
+ uniform float scale = 1.0;
+ uniform vec2 pixel_norm;
+ in vec2 texcoord;
+ out vec4 out_color;
+ void main() {
+ vec2 offset = %.7g * pixel_norm;
+ vec2 uv = texcoord * pixel_norm * (2.0 / scale);
+ vec4 sum = texture2D(tex_src, uv) * 4.0;
+ sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset);
+ sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset);
+ out_color = sum / 8.0;
+ }
+ );
+ // clang-format on
+
+ // Build shader
+ size_t shader_len =
+ strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len =
+ snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+
+ // Build program
+ down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
+ free(shader_str);
+ if (!down_pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(down_pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ bind_uniform(down_pass, pixel_norm);
+ down_pass->texorig_loc =
+ glGetUniformLocationChecked(down_pass->prog, "texorig");
+ down_pass->scale_loc =
+ glGetUniformLocationChecked(down_pass->prog, "scale");
+
+ // Setup projection matrix
+ glUseProgram(down_pass->prog);
+ int pml = glGetUniformLocationChecked(down_pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+ }
+
+ // Dual-kawase upsample shader / program
+ auto up_pass = ctx->blur_shader + 1;
+ {
+ // clang-format off
+ static const char *FRAG_SHADER_UP = GLSL(330,
+ uniform sampler2D tex_src;
+ uniform float scale = 1.0;
+ uniform vec2 pixel_norm;
+ uniform float opacity;
+ in vec2 texcoord;
+ out vec4 out_color;
+ float mask_factor();
+ void main() {
+ vec2 offset = %.7g * pixel_norm;
+ vec2 uv = texcoord * pixel_norm / (2 * scale);
+ vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0;
+ out_color = sum / 12.0 * opacity * mask_factor();
+ }
+ );
+ // clang-format on
+
+ // Build shader
+ size_t shader_len =
+ strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len =
+ snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+
+ // Build program
+ up_pass->prog = gl_create_program_from_strv(
+ (const char *[]){vertex_shader, NULL},
+ (const char *[]){shader_str, masking_glsl, NULL});
+ free(shader_str);
+ if (!up_pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(up_pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ bind_uniform(up_pass, pixel_norm);
+ bind_uniform(up_pass, opacity);
+
+ bind_uniform(up_pass, mask_tex);
+ bind_uniform(up_pass, mask_offset);
+ bind_uniform(up_pass, mask_inverted);
+ bind_uniform(up_pass, mask_corner_radius);
+
+ up_pass->texorig_loc =
+ glGetUniformLocationChecked(up_pass->prog, "texorig");
+ up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale");
+
+ // Setup projection matrix
+ glUseProgram(up_pass->prog);
+ int pml = glGetUniformLocationChecked(up_pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+ }
+
+ success = true;
+out:
+ free(blur_params);
+
+ if (!success) {
+ ctx = NULL;
+ }
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ return success;
+}
+
+void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) {
+ bool success;
+ auto gd = (struct gl_data *)base;
+
+ auto ctx = ccalloc(1, struct gl_blur_context);
+
+ if (!method || method >= BLUR_METHOD_INVALID) {
+ ctx->method = BLUR_METHOD_NONE;
+ return ctx;
+ }
+
+ // Set projection matrix to gl viewport dimensions so we can use screen
+ // coordinates for all vertices
+ // Note: OpenGL matrices are column major
+ GLint viewport_dimensions[2];
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
+ GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
+ {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
+ {0, 0, 0, 0},
+ {-1, -1, 0, 1}};
+
+ if (method == BLUR_METHOD_DUAL_KAWASE) {
+ success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0],
+ method, args);
+ } else {
+ success =
+ gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args);
+ }
+ if (!success || ctx->method == BLUR_METHOD_NONE) {
+ goto out;
+ }
+
+ // Texture size will be defined by gl_blur
+ ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint);
+ ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size);
+ glGenTextures(ctx->blur_texture_count, ctx->blur_textures);
+
+ for (int i = 0; i < ctx->blur_texture_count; ++i) {
+ glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+
+ // Generate FBO and textures when needed
+ ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint);
+ glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos);
+
+ for (int i = 0; i < ctx->blur_fbo_count; ++i) {
+ if (!ctx->blur_fbos[i]) {
+ log_error("Failed to generate framebuffer objects for blur");
+ success = false;
+ goto out;
+ }
+ }
+
+out:
+ if (!success) {
+ gl_destroy_blur_context(&gd->base, ctx);
+ ctx = NULL;
+ }
+
+ gl_check_err();
+ return ctx;
+}
+
+void gl_get_blur_size(void *blur_context, int *width, int *height) {
+ auto ctx = (struct gl_blur_context *)blur_context;
+ *width = ctx->resize_width;
+ *height = ctx->resize_height;
+}
diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c
new file mode 100644
index 0000000..e6d4d90
--- /dev/null
+++ b/src/backend/gl/egl.c
@@ -0,0 +1,469 @@
+// SPDX-License-Identifier: MPL-2.0
+/*
+ * Copyright (c) 2022 Yuxuan Shui <[email protected]>
+ */
+
+#include <X11/Xlib-xcb.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "backend/gl/egl.h"
+#include "backend/gl/gl_common.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h"
+#include "picom.h"
+#include "utils.h"
+#include "x.h"
+
+struct egl_pixmap {
+ EGLImage image;
+ xcb_pixmap_t pixmap;
+ bool owned;
+};
+
+struct egl_data {
+ struct gl_data gl;
+ EGLDisplay display;
+ EGLSurface target_win;
+ EGLContext ctx;
+};
+
+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.
+ */
+static void egl_release_image(backend_t *base, struct gl_texture *tex) {
+ struct egl_data *gd = (void *)base;
+ struct egl_pixmap *p = tex->user_data;
+ // Release binding
+ if (p->image != EGL_NO_IMAGE) {
+ eglDestroyImageProc(gd->display, p->image);
+ p->image = EGL_NO_IMAGE;
+ }
+
+ if (p->owned) {
+ xcb_free_pixmap(base->c, p->pixmap);
+ p->pixmap = XCB_NONE;
+ }
+
+ free(p);
+ tex->user_data = NULL;
+}
+
+/**
+ * Destroy GLX related resources.
+ */
+void egl_deinit(backend_t *base) {
+ struct egl_data *gd = (void *)base;
+
+ gl_deinit(&gd->gl);
+
+ // Destroy GLX context
+ if (gd->ctx != EGL_NO_CONTEXT) {
+ eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(gd->display, gd->ctx);
+ gd->ctx = EGL_NO_CONTEXT;
+ }
+
+ if (gd->target_win != EGL_NO_SURFACE) {
+ eglDestroySurface(gd->display, gd->target_win);
+ gd->target_win = EGL_NO_SURFACE;
+ }
+
+ if (gd->display != EGL_NO_DISPLAY) {
+ eglTerminate(gd->display);
+ gd->display = EGL_NO_DISPLAY;
+ }
+
+ free(gd);
+}
+
+static void *egl_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) {
+ auto ret = cmalloc(struct egl_pixmap);
+ ret->owned = false;
+ ret->image = EGL_NO_IMAGE;
+ ret->pixmap = 0;
+ return ret;
+}
+
+static bool egl_set_swap_interval(int interval, EGLDisplay dpy) {
+ return eglSwapInterval(dpy, interval);
+}
+
+/**
+ * Initialize OpenGL.
+ */
+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); \
+ 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) {
+ log_error("X11 platform not available.");
+ return NULL;
+ }
+
+ gd = ccalloc(1, struct egl_data);
+ 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;
+ }
+
+ EGLint major, minor;
+ if (!eglInitialize(gd->display, &major, &minor)) {
+ log_error("Failed to initialize EGL.");
+ 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) {
+ log_error("EGL does not support OpenGL.");
+ goto end;
+ }
+
+ eglext_init(gd->display);
+ init_backend_base(&gd->gl.base, ps);
+ if (!eglext.has_EGL_KHR_image_pixmap) {
+ log_error("EGL_KHR_image_pixmap not available.");
+ 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);
+ // clang-format off
+ if (eglChooseConfig(gd->display,
+ (EGLint[]){
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+ EGL_RED_SIZE, visual_info.red_size,
+ EGL_GREEN_SIZE, visual_info.green_size,
+ EGL_BLUE_SIZE, visual_info.blue_size,
+ EGL_ALPHA_SIZE, visual_info.alpha_size,
+ EGL_STENCIL_SIZE, 1,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_NONE,
+ }, cfgs, ncfgs, &ncfgs) != 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);
+ if (gd->target_win == EGL_NO_SURFACE) {
+ log_error("Failed to create EGL surface.");
+ goto end;
+ }
+
+ if (eglBindAPI(EGL_OPENGL_API) != EGL_TRUE) {
+ log_error("Failed to bind OpenGL API.");
+ goto end;
+ }
+
+ gd->ctx = eglCreateContext(gd->display, target_cfg, NULL, NULL);
+ if (gd->ctx == EGL_NO_CONTEXT) {
+ log_error("Failed to get GLX context.");
+ goto end;
+ }
+
+ if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) {
+ log_error("Failed to attach GLX context.");
+ goto end;
+ }
+
+ if (!gl_init(&gd->gl, ps)) {
+ log_error("Failed to setup OpenGL");
+ goto end;
+ }
+ if (!gd->gl.has_egl_image_storage) {
+ log_error("GL_EXT_EGL_image_storage extension not available.");
+ 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;
+
+ if (ps->o.vsync) {
+ if (!egl_set_swap_interval(1, gd->display)) {
+ log_error("Failed to enable vsync. %#x", eglGetError());
+ }
+ } else {
+ egl_set_swap_interval(0, gd->display);
+ }
+
+ success = true;
+
+end:
+ if (!success) {
+ if (gd != NULL) {
+ egl_deinit(&gd->gl.base);
+ }
+ return NULL;
+ }
+
+ return &gd->gl.base;
+}
+
+static void *
+egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
+ struct egl_data *gd = (void *)base;
+ struct egl_pixmap *eglpixmap = NULL;
+
+ auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL);
+ if (!r) {
+ log_error("Invalid pixmap %#010x", pixmap);
+ return NULL;
+ }
+
+ log_trace("Binding pixmap %#010x", pixmap);
+ auto wd = ccalloc(1, struct backend_image);
+ wd->max_brightness = 1;
+ auto inner = ccalloc(1, struct gl_texture);
+ inner->width = wd->ewidth = r->width;
+ inner->height = wd->eheight = r->height;
+ wd->inner = (struct backend_image_inner_base *)inner;
+ free(r);
+
+ log_debug("depth %d", fmt.visual_depth);
+
+ inner->y_inverted = true;
+
+ 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->owned = owned;
+
+ if (eglpixmap->image == EGL_NO_IMAGE) {
+ log_error("Failed to create eglpixmap for pixmap %#010x", pixmap);
+ goto err;
+ }
+
+ log_trace("EGLImage %p", eglpixmap->image);
+
+ // Create texture
+ inner->user_data = eglpixmap;
+ inner->texture = gl_new_texture(GL_TEXTURE_2D);
+ inner->has_alpha = fmt.alpha_size != 0;
+ wd->opacity = 1;
+ wd->color_inverted = false;
+ wd->dim = 0;
+ wd->inner->refcount = 1;
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+ glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ gl_check_err();
+ return wd;
+err:
+ if (eglpixmap && eglpixmap->image) {
+ eglDestroyImageProc(gd->display, eglpixmap->image);
+ }
+ free(eglpixmap);
+
+ if (owned) {
+ xcb_free_pixmap(base->c, pixmap);
+ }
+ free(wd);
+ return NULL;
+}
+
+static void egl_present(backend_t *base, const region_t *region attr_unused) {
+ struct egl_data *gd = (void *)base;
+ gl_present(base, region);
+ eglSwapBuffers(gd->display, gd->target_win);
+ if (!gd->gl.is_nvidia) {
+ glFinish();
+ }
+}
+
+static int egl_buffer_age(backend_t *base) {
+ if (!eglext.has_EGL_EXT_buffer_age) {
+ return -1;
+ }
+
+ struct egl_data *gd = (void *)base;
+ EGLint val;
+ eglQuerySurface(gd->display, (EGLSurface)gd->target_win, EGL_BUFFER_AGE_EXT, &val);
+ return (int)val ?: -1;
+}
+
+static void egl_diagnostics(backend_t *base) {
+ struct egl_data *gd = (void *)base;
+ bool warn_software_rendering = false;
+ const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"};
+ auto egl_vendor = eglQueryString(gd->display, EGL_VENDOR);
+ printf("* Driver vendors:\n");
+ printf(" * EGL: %s\n", egl_vendor);
+ if (eglext.has_EGL_MESA_query_driver) {
+ printf(" * EGL driver: %s\n", eglGetDisplayDriverName(gd->display));
+ }
+ printf(" * GL: %s\n", glGetString(GL_VENDOR));
+
+ auto gl_renderer = (const char *)glGetString(GL_RENDERER);
+ printf("* GL renderer: %s\n", gl_renderer);
+ if (strstr(egl_vendor, "Mesa")) {
+ for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) {
+ if (strstr(gl_renderer, software_renderer_names[i]) != NULL) {
+ warn_software_rendering = true;
+ break;
+ }
+ }
+ }
+
+ if (warn_software_rendering) {
+ printf("\n(You are using a software renderer. Unless you are doing this\n"
+ "intentionally, this means you don't have a graphics driver\n"
+ "properly installed. Performance will suffer. Please fix this\n"
+ "before reporting your issue.)\n");
+ }
+}
+
+struct backend_operations egl_ops = {
+ .init = egl_init,
+ .deinit = egl_deinit,
+ .bind_pixmap = egl_bind_pixmap,
+ .release_image = gl_release_image,
+ .compose = gl_compose,
+ .image_op = gl_image_op,
+ .set_image_property = gl_set_image_property,
+ .clone_image = default_clone_image,
+ .blur = gl_blur,
+ .is_image_transparent = default_is_image_transparent,
+ .present = egl_present,
+ .buffer_age = egl_buffer_age,
+ .create_shadow_context = gl_create_shadow_context,
+ .destroy_shadow_context = gl_destroy_shadow_context,
+ .render_shadow = backend_render_shadow_from_mask,
+ .shadow_from_mask = gl_shadow_from_mask,
+ .make_mask = gl_make_mask,
+ .fill = gl_fill,
+ .create_blur_context = gl_create_blur_context,
+ .destroy_blur_context = gl_destroy_blur_context,
+ .get_blur_size = gl_get_blur_size,
+ .diagnostics = egl_diagnostics,
+ .device_status = gl_device_status,
+ .create_shader = gl_create_window_shader,
+ .destroy_shader = gl_destroy_window_shader,
+ .get_shader_attributes = gl_get_shader_attributes,
+ .max_buffer_age = 5, // Why?
+};
+
+PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
+/**
+ * Check if a GLX extension exists.
+ */
+static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) {
+ const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS);
+ if (!egl_exts) {
+ log_error("Failed get EGL extension list.");
+ return false;
+ }
+
+ auto inlen = strlen(ext);
+ const char *curr = egl_exts;
+ bool match = false;
+ while (curr && !match) {
+ const char *end = strchr(curr, ' ');
+ if (!end) {
+ // Last extension string
+ match = strcmp(ext, curr) == 0;
+ } else if (curr + inlen == end) {
+ // Length match, do match string
+ match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
+ }
+ curr = end ? end + 1 : NULL;
+ }
+
+ if (!match) {
+ log_info("Missing EGL extension %s.", ext);
+ } else {
+ log_info("Found EGL extension %s.", ext);
+ }
+
+ return match;
+}
+
+struct eglext_info eglext = {0};
+
+void eglext_init(EGLDisplay dpy) {
+ if (eglext.initialized) {
+ return;
+ }
+ eglext.initialized = true;
+#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name)
+ check_ext(EGL_EXT_buffer_age);
+ check_ext(EGL_EXT_create_context_robustness);
+ check_ext(EGL_KHR_image_pixmap);
+#ifdef EGL_MESA_query_driver
+ check_ext(EGL_MESA_query_driver);
+#endif
+#undef check_ext
+
+ // Checking if the returned function pointer is NULL is not really necessary,
+ // or maybe not even useful, since eglGetProcAddress might always return
+ // something. We are doing it just for completeness' sake.
+
+#ifdef EGL_MESA_query_driver
+ eglGetDisplayDriverName =
+ (PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName");
+ if (!eglGetDisplayDriverName) {
+ eglext.has_EGL_MESA_query_driver = false;
+ }
+#endif
+}
diff --git a/src/backend/gl/egl.h b/src/backend/gl/egl.h
new file mode 100644
index 0000000..171b173
--- /dev/null
+++ b/src/backend/gl/egl.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+#include <stdbool.h>
+// Older version of glx.h defines function prototypes for these extensions...
+// Rename them to avoid conflicts
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <xcb/render.h>
+#include <xcb/xcb.h>
+
+#include "compiler.h"
+#include "log.h"
+#include "utils.h"
+#include "x.h"
+
+struct eglext_info {
+ bool initialized;
+ bool has_EGL_MESA_query_driver;
+ bool has_EGL_EXT_buffer_age;
+ bool has_EGL_EXT_create_context_robustness;
+ bool has_EGL_KHR_image_pixmap;
+};
+
+extern struct eglext_info eglext;
+
+#ifdef EGL_MESA_query_driver
+extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
+#endif
+
+void eglext_init(EGLDisplay);
diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c
index 8cc5a05..a7d2aab 100644
--- a/src/backend/gl/gl_common.c
+++ b/src/backend/gl/gl_common.c
@@ -2,10 +2,10 @@
// Copyright (c) Yuxuan Shui <[email protected]>
#include <GL/gl.h>
#include <GL/glext.h>
-#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <time.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include "backend/backend.h"
@@ -22,51 +22,6 @@
#include "backend/backend_common.h"
#include "backend/gl/gl_common.h"
-#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__
-#define QUOTE(...) #__VA_ARGS__
-
-static const GLuint vert_coord_loc = 0;
-static const GLuint vert_in_texcoord_loc = 1;
-
-struct gl_blur_context {
- enum blur_method method;
- gl_blur_shader_t *blur_shader;
-
- /// Temporary textures used for blurring
- GLuint *blur_textures;
- int blur_texture_count;
- /// Temporary fbos used for blurring
- GLuint *blur_fbos;
- int blur_fbo_count;
-
- /// Cached dimensions of each blur_texture. They are the same size as the target,
- /// so they are always big enough without resizing.
- /// Turns out calling glTexImage to resize is expensive, so we avoid that.
- struct texture_size {
- int width;
- int height;
- } * texture_sizes;
-
- /// Cached dimensions of the offscreen framebuffer. It's the same size as the
- /// target but is expanded in either direction by resize_width / resize_height.
- int fb_width, fb_height;
-
- /// How much do we need to resize the damaged region for blurring.
- int resize_width, resize_height;
-
- int npasses;
-};
-
-static GLint glGetUniformLocationChecked(GLuint p, const char *name) {
- auto ret = glGetUniformLocation(p, name);
- if (ret < 0) {
- log_info("Failed to get location of uniform '%s'. This is normal when "
- "using custom shaders.",
- name);
- }
- return ret;
-}
-
GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
log_trace("===\n%s\n===", shader_str);
@@ -83,7 +38,7 @@ GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
{
GLint status = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
- if (GL_FALSE == status) {
+ if (status == GL_FALSE) {
GLint log_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
@@ -103,6 +58,7 @@ end:
glDeleteShader(shader);
shader = 0;
}
+ gl_check_err();
return shader;
}
@@ -115,15 +71,16 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
goto end;
}
- for (int i = 0; i < nshaders; ++i)
+ for (int i = 0; i < nshaders; ++i) {
glAttachShader(program, shaders[i]);
+ }
glLinkProgram(program);
// Get program status
{
GLint status = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &status);
- if (GL_FALSE == status) {
+ if (status == GL_FALSE) {
GLint log_len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
@@ -138,59 +95,83 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
end:
if (program) {
- for (int i = 0; i < nshaders; ++i)
+ for (int i = 0; i < nshaders; ++i) {
glDetachShader(program, shaders[i]);
+ }
}
if (program && !success) {
glDeleteProgram(program);
program = 0;
}
+ gl_check_err();
return program;
}
/**
- * @brief Create a program from vertex and fragment shader strings.
+ * @brief Create a program from NULL-terminated arrays of vertex and fragment shader
+ * strings.
*/
-GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) {
- GLuint vert_shader = 0;
- GLuint frag_shader = 0;
- GLuint prog = 0;
-
- if (vert_shader_str)
- vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str);
- if (frag_shader_str)
- frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str);
+GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) {
+ int vert_count, frag_count;
+ for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) {
+ }
+ for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) {
+ }
- {
- GLuint shaders[2];
- int count = 0;
- if (vert_shader) {
- shaders[count++] = vert_shader;
- }
- if (frag_shader) {
- shaders[count++] = frag_shader;
+ GLuint prog = 0;
+ auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint);
+ for (int i = 0; i < vert_count; ++i) {
+ shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]);
+ if (shaders[i] == 0) {
+ goto out;
}
- if (count) {
- prog = gl_create_program(shaders, count);
+ }
+ for (int i = 0; i < frag_count; ++i) {
+ shaders[vert_count + i] =
+ gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]);
+ if (shaders[vert_count + i] == 0) {
+ goto out;
}
}
- if (vert_shader)
- glDeleteShader(vert_shader);
- if (frag_shader)
- glDeleteShader(frag_shader);
+ prog = gl_create_program(shaders, vert_count + frag_count);
+
+out:
+ for (int i = 0; i < vert_count + frag_count; ++i) {
+ if (shaders[i] != 0) {
+ glDeleteShader(shaders[i]);
+ }
+ }
+ free(shaders);
+ gl_check_err();
return prog;
}
-static void gl_free_prog_main(gl_win_shader_t *pprogram) {
- if (!pprogram)
+/**
+ * @brief Create a program from vertex and fragment shader strings.
+ */
+GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) {
+ const char *vert_shaders[2] = {vert_shader_str, NULL};
+ const char *frag_shaders[2] = {frag_shader_str, NULL};
+
+ return gl_create_program_from_strv(vert_shaders, frag_shaders);
+}
+
+void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) {
+ if (!shader) {
return;
+ }
+
+ auto pprogram = (gl_win_shader_t *)shader;
if (pprogram->prog) {
glDeleteProgram(pprogram->prog);
pprogram->prog = 0;
}
+ gl_check_err();
+
+ free(shader);
}
/*
@@ -368,9 +349,15 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im
* @param reg_visible ignored
*/
static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target,
- GLint *coord, GLuint *indices, int nrects) {
+ struct backend_image *mask, coord_t mask_offset, GLint *coord,
+ GLuint *indices, int nrects) {
+ // FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted
+ // value. but we don't ever hit this problem because all of our
+ // images and masks are y_inverted.
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
+ auto mask_texture =
+ mask ? ((struct gl_texture *)mask->inner)->texture : gd->default_mask_texture;
if (!img || !inner->texture) {
log_error("Missing texture.");
return;
@@ -381,27 +368,64 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
brightness = gl_average_texture_color(base, img);
}
- assert(gd->win_shader.prog);
- glUseProgram(gd->win_shader.prog);
- if (gd->win_shader.unifm_opacity >= 0) {
- glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity);
+ auto win_shader = inner->shader;
+ if (!win_shader) {
+ win_shader = gd->default_shader;
}
- if (gd->win_shader.unifm_invert_color >= 0) {
- glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted);
+
+ assert(win_shader);
+ assert(win_shader->prog);
+ glUseProgram(win_shader->prog);
+ if (win_shader->uniform_opacity >= 0) {
+ glUniform1f(win_shader->uniform_opacity, (float)img->opacity);
+ }
+ if (win_shader->uniform_invert_color >= 0) {
+ glUniform1i(win_shader->uniform_invert_color, img->color_inverted);
+ }
+ if (win_shader->uniform_tex >= 0) {
+ glUniform1i(win_shader->uniform_tex, 0);
}
- if (gd->win_shader.unifm_tex >= 0) {
- glUniform1i(gd->win_shader.unifm_tex, 0);
+ if (win_shader->uniform_dim >= 0) {
+ glUniform1f(win_shader->uniform_dim, (float)img->dim);
}
- if (gd->win_shader.unifm_dim >= 0) {
- glUniform1f(gd->win_shader.unifm_dim, (float)img->dim);
+ if (win_shader->uniform_brightness >= 0) {
+ glUniform1i(win_shader->uniform_brightness, 1);
}
- if (gd->win_shader.unifm_brightness >= 0) {
- glUniform1i(gd->win_shader.unifm_brightness, 1);
+ if (win_shader->uniform_max_brightness >= 0) {
+ glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness);
}
- if (gd->win_shader.unifm_max_brightness >= 0) {
- glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness);
+ if (win_shader->uniform_corner_radius >= 0) {
+ glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius);
+ }
+ if (win_shader->uniform_border_width >= 0) {
+ auto border_width = img->border_width;
+ if (border_width > img->corner_radius) {
+ border_width = 0;
+ }
+ glUniform1f(win_shader->uniform_border_width, (float)border_width);
+ }
+ if (win_shader->uniform_time >= 0) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ glUniform1f(win_shader->uniform_time,
+ (float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F);
+ }
+
+ glUniform1i(win_shader->uniform_mask_tex, 2);
+ 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);
+ glUniform1f(win_shader->uniform_mask_corner_radius,
+ (GLfloat)mask->corner_radius);
+ } else {
+ glUniform1i(win_shader->uniform_mask_inverted, 0);
+ glUniform1f(win_shader->uniform_mask_corner_radius, 0);
}
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, mask_texture);
+
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
// x, y, width, height, dx, dy, ptex->width, ptex->height, z);
@@ -437,6 +461,11 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
glDeleteVertexArrays(1, &vao);
// Cleanup
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
@@ -454,19 +483,18 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates
/// @param[in] nrects, rects rectangles
-/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture
+/// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture
/// coordinates
+/// @param[in] extend_height height of the drawing extent
/// @param[in] texture_height height of the OpenGL texture
/// @param[in] root_height height of the back buffer
/// @param[in] y_inverted whether the texture is y inverted
/// @param[out] coord, indices output
-static void
-x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height,
- int root_height, bool y_inverted, GLint *coord, GLuint *indices) {
- dst_y = root_height - dst_y;
- if (y_inverted) {
- dst_y -= texture_height;
- }
+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;
for (int i = 0; i < nrects; i++) {
// Y-flip. Note after this, crect.y1 > crect.y2
@@ -476,7 +504,8 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text
// Calculate texture coordinates
// (texture_x1, texture_y1), texture coord for the _bottom left_ corner
- GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y,
+ GLint texture_x1 = crect.x1 - image_dst.x,
+ texture_y1 = crect.y2 - image_dst.y,
texture_x2 = texture_x1 + (crect.x2 - crect.x1),
texture_y2 = texture_y1 + (crect.y1 - crect.y2);
@@ -516,9 +545,9 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text
}
// TODO(yshui) make use of reg_visible
-void gl_compose(backend_t *base, void *image_data,
- int dst_x1, int dst_y1, int dst_x2, int dst_y2,
- const region_t *reg_tgt, const region_t *reg_visible attr_unused) {
+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) {
auto gd = (struct gl_data *)base;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
@@ -541,373 +570,43 @@ void gl_compose(backend_t *base, void *image_data,
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
- x_rect_to_coords(nrects, rects, dst_x1, dst_y1, inner->height, gd->height,
- inner->y_inverted, coord, indices);
-
- // Interpolate the texture coordinates into the specified range
- for (unsigned int i = 2; i < 16; i+=4) {
- coord[i+0] = lerp_range(0, dst_x2 - dst_x1, 0, inner->width, coord[i+0]);
- coord[i+1] = lerp_range(0, dst_y2 - dst_y1, 0, inner->height, coord[i+1]);
- }
-
- _gl_compose(base, img, gd->back_fbo, coord, indices, nrects);
+ 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);
+ _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);
free(coord);
}
/**
- * Blur contents in a particular region.
- */
-bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
- const GLuint vao[2], const int vao_nelems[2]) {
- auto bctx = (struct gl_blur_context *)ctx;
- auto gd = (struct gl_data *)base;
-
- int dst_y_fb_coord = bctx->fb_height - extent->y2;
-
- int curr = 0;
- for (int i = 0; i < bctx->npasses; ++i) {
- const gl_blur_shader_t *p = &bctx->blur_shader[i];
- assert(p->prog);
-
- assert(bctx->blur_textures[curr]);
-
- // The origin to use when sampling from the source texture
- GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord;
- GLint tex_width, tex_height;
- GLuint src_texture;
-
- if (i == 0) {
- src_texture = gd->back_texture;
- tex_width = gd->width;
- tex_height = gd->height;
- } else {
- src_texture = bctx->blur_textures[curr];
- auto src_size = bctx->texture_sizes[curr];
- tex_width = src_size.width;
- tex_height = src_size.height;
- }
-
- glBindTexture(GL_TEXTURE_2D, src_texture);
- glUseProgram(p->prog);
- glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
- 1.0f / (GLfloat)tex_height);
-
- // The number of indices in the selected vertex array
- GLsizei nelems;
-
- if (i < bctx->npasses - 1) {
- assert(bctx->blur_fbos[0]);
- assert(bctx->blur_textures[!curr]);
-
- // not last pass, draw into framebuffer, with resized regions
- glBindVertexArray(vao[1]);
- nelems = vao_nelems[1];
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]);
-
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_2D, bctx->blur_textures[!curr], 0);
- glDrawBuffer(GL_COLOR_ATTACHMENT0);
- if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
- return false;
- }
-
- glUniform1f(p->unifm_opacity, 1.0);
- } else {
- // last pass, draw directly into the back buffer, with origin
- // regions
- glBindVertexArray(vao[0]);
- nelems = vao_nelems[0];
- glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo);
-
- glUniform1f(p->unifm_opacity, (float)opacity);
- }
-
- glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y);
- glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
-
- // XXX use multiple draw calls is probably going to be slow than
- // just simply blur the whole area.
-
- curr = !curr;
- }
-
- return true;
-}
-
-bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
- const GLuint vao[2], const int vao_nelems[2]) {
- auto bctx = (struct gl_blur_context *)ctx;
- auto gd = (struct gl_data *)base;
-
- int dst_y_fb_coord = bctx->fb_height - extent->y2;
-
- int iterations = bctx->blur_texture_count;
- int scale_factor = 1;
-
- // Kawase downsample pass
- const gl_blur_shader_t *down_pass = &bctx->blur_shader[0];
- assert(down_pass->prog);
- glUseProgram(down_pass->prog);
-
- glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
-
- for (int i = 0; i < iterations; ++i) {
- // Scale output width / height by half in each iteration
- scale_factor <<= 1;
-
- GLuint src_texture;
- int tex_width, tex_height;
-
- if (i == 0) {
- // first pass: copy from back buffer
- src_texture = gd->back_texture;
- tex_width = gd->width;
- tex_height = gd->height;
- } else {
- // copy from previous pass
- src_texture = bctx->blur_textures[i - 1];
- auto src_size = bctx->texture_sizes[i - 1];
- tex_width = src_size.width;
- tex_height = src_size.height;
- }
-
- assert(src_texture);
- assert(bctx->blur_fbos[i]);
-
- glBindTexture(GL_TEXTURE_2D, src_texture);
- glBindVertexArray(vao[1]);
- auto nelems = vao_nelems[1];
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
- glDrawBuffer(GL_COLOR_ATTACHMENT0);
-
- glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor);
-
- glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
- 1.0f / (GLfloat)tex_height);
-
- glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
- }
-
- // Kawase upsample pass
- const gl_blur_shader_t *up_pass = &bctx->blur_shader[1];
- assert(up_pass->prog);
- glUseProgram(up_pass->prog);
-
- glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
-
- for (int i = iterations - 1; i >= 0; --i) {
- // Scale output width / height back by two in each iteration
- scale_factor >>= 1;
-
- const GLuint src_texture = bctx->blur_textures[i];
- assert(src_texture);
-
- // Calculate normalized half-width/-height of a src pixel
- auto src_size = bctx->texture_sizes[i];
- int tex_width = src_size.width;
- int tex_height = src_size.height;
-
- // The number of indices in the selected vertex array
- GLsizei nelems;
-
- glBindTexture(GL_TEXTURE_2D, src_texture);
- if (i > 0) {
- assert(bctx->blur_fbos[i - 1]);
-
- // not last pass, draw into next framebuffer
- glBindVertexArray(vao[1]);
- nelems = vao_nelems[1];
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
- glDrawBuffer(GL_COLOR_ATTACHMENT0);
-
- glUniform1f(up_pass->unifm_opacity, (GLfloat)1);
- } else {
- // last pass, draw directly into the back buffer
- glBindVertexArray(vao[0]);
- nelems = vao_nelems[0];
- glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo);
-
- glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity);
- }
-
- glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor);
- glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
- 1.0f / (GLfloat)tex_height);
-
- glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
- }
-
- return true;
-}
-
-bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur,
- const region_t *reg_visible attr_unused) {
- auto bctx = (struct gl_blur_context *)ctx;
- auto gd = (struct gl_data *)base;
-
- bool ret = false;
-
- if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) {
- // Resize the temporary textures used for blur in case the root
- // size changed
- bctx->fb_width = gd->width;
- bctx->fb_height = gd->height;
-
- for (int i = 0; i < bctx->blur_texture_count; ++i) {
- auto tex_size = bctx->texture_sizes + i;
- if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
- // Use smaller textures for each iteration (quarter of the
- // previous texture)
- tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1));
- tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1));
- } else {
- tex_size->width = bctx->fb_width;
- tex_size->height = bctx->fb_height;
- }
-
- glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width,
- tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
-
- if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
- // Attach texture to FBO target
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
- GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- bctx->blur_textures[i], 0);
- if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- return false;
- }
- }
- }
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
- }
-
- // Remainder: regions are in Xorg coordinates
- auto reg_blur_resized =
- resize_region(reg_blur, bctx->resize_width, bctx->resize_height);
- const rect_t *extent = pixman_region32_extents((region_t *)reg_blur),
- *extent_resized = pixman_region32_extents(&reg_blur_resized);
- int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1;
- if (width == 0 || height == 0) {
- return true;
- }
-
- int nrects, nrects_resized;
- const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects),
- *rects_resized =
- pixman_region32_rectangles(&reg_blur_resized, &nrects_resized);
- if (!nrects || !nrects_resized) {
- return true;
- }
-
- auto coord = ccalloc(nrects * 16, GLint);
- auto indices = ccalloc(nrects * 6, GLuint);
- x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2,
- bctx->fb_height, gd->height, false, coord, indices);
-
- auto coord_resized = ccalloc(nrects_resized * 16, GLint);
- auto indices_resized = ccalloc(nrects_resized * 6, GLuint);
- x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1,
- extent_resized->y2, bctx->fb_height, bctx->fb_height, false,
- coord_resized, indices_resized);
- pixman_region32_fini(&reg_blur_resized);
-
- GLuint vao[2];
- glGenVertexArrays(2, vao);
- GLuint bo[4];
- glGenBuffers(4, bo);
-
- glBindVertexArray(vao[0]);
- glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
- glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
- indices, GL_STATIC_DRAW);
- glEnableVertexAttribArray(vert_coord_loc);
- glEnableVertexAttribArray(vert_in_texcoord_loc);
- glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
- glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
- sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
-
- glBindVertexArray(vao[1]);
- glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
- glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
- coord_resized, GL_STATIC_DRAW);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
- GL_STATIC_DRAW);
- glEnableVertexAttribArray(vert_coord_loc);
- glEnableVertexAttribArray(vert_in_texcoord_loc);
- glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
- glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
- sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
-
- int vao_nelems[2] = {nrects * 6, nrects_resized * 6};
-
- if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
- ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, vao, vao_nelems);
- } else {
- ret = gl_kernel_blur(base, opacity, ctx, extent_resized, vao, vao_nelems);
- }
-
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- glBindTexture(GL_TEXTURE_2D, 0);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- glDeleteBuffers(4, bo);
- glBindVertexArray(0);
- glDeleteVertexArrays(2, vao);
- glUseProgram(0);
-
- free(indices);
- free(coord);
- free(indices_resized);
- free(coord_resized);
-
- gl_check_err();
- return ret;
-}
-
-// clang-format off
-const char *vertex_shader = GLSL(330,
- uniform mat4 projection;
- uniform float scale = 1.0;
- uniform vec2 texorig;
- layout(location = 0) in vec2 coord;
- layout(location = 1) in vec2 in_texcoord;
- out vec2 texcoord;
- void main() {
- gl_Position = projection * vec4(coord, 0, scale);
- texcoord = in_texcoord + texorig;
- }
-);
-// clang-format on
-
-/**
* Load a GLSL main program from shader strings.
*/
-static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str,
- gl_win_shader_t *ret) {
+static bool gl_win_shader_from_stringv(const char **vshader_strv,
+ const char **fshader_strv, gl_win_shader_t *ret) {
// Build program
- ret->prog = gl_create_program_from_str(vshader_str, fshader_str);
+ ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv);
if (!ret->prog) {
log_error("Failed to create GLSL program.");
- return -1;
+ gl_check_err();
+ return false;
}
// Get uniform addresses
- ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity");
- ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color");
- ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex");
- ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim");
- ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness");
- ret->unifm_max_brightness =
- glGetUniformLocationChecked(ret->prog, "max_brightness");
+ bind_uniform(ret, opacity);
+ bind_uniform(ret, invert_color);
+ bind_uniform(ret, tex);
+ bind_uniform(ret, dim);
+ bind_uniform(ret, brightness);
+ bind_uniform(ret, max_brightness);
+ bind_uniform(ret, corner_radius);
+ bind_uniform(ret, border_width);
+ bind_uniform(ret, time);
+
+ bind_uniform(ret, mask_tex);
+ bind_uniform(ret, mask_offset);
+ bind_uniform(ret, mask_inverted);
+ bind_uniform(ret, mask_corner_radius);
gl_check_err();
@@ -934,51 +633,6 @@ void gl_resize(struct gl_data *gd, int width, int height) {
gl_check_err();
}
-// clang-format off
-static const char dummy_frag[] = GLSL(330,
- uniform sampler2D tex;
- in vec2 texcoord;
- void main() {
- gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0);
- }
-);
-
-static const char fill_frag[] = GLSL(330,
- uniform vec4 color;
- void main() {
- gl_FragColor = color;
- }
-);
-
-static const char fill_vert[] = GLSL(330,
- layout(location = 0) in vec2 in_coord;
- uniform mat4 projection;
- void main() {
- gl_Position = projection * vec4(in_coord, 0, 1);
- }
-);
-
-static const char interpolating_frag[] = GLSL(330,
- uniform sampler2D tex;
- in vec2 texcoord;
- void main() {
- gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1);
- }
-);
-
-static const char interpolating_vert[] = GLSL(330,
- uniform mat4 projection;
- uniform vec2 texsize;
- layout(location = 0) in vec2 in_coord;
- layout(location = 1) in vec2 in_texcoord;
- out vec2 texcoord;
- void main() {
- gl_Position = projection * vec4(in_coord, 0, 1);
- texcoord = in_texcoord / texsize;
- }
-);
-// clang-format on
-
/// Fill a given region in bound framebuffer.
/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted
static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target,
@@ -1048,9 +702,45 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) {
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
}
+void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
+ auto tex = ccalloc(1, struct gl_texture);
+ auto img = default_new_backend_image(size.width, size.height);
+ tex->width = size.width;
+ tex->height = size.height;
+ tex->texture = gl_new_texture(GL_TEXTURE_2D);
+ tex->has_alpha = false;
+ tex->y_inverted = true;
+ img->inner = (struct backend_image_inner_base *)tex;
+ img->inner->refcount = 1;
+
+ glBindTexture(GL_TEXTURE_2D, tex->texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size.width, size.height, 0, GL_RED,
+ GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ GLuint fbo;
+ glBlendFunc(GL_ONE, GL_ZERO);
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ tex->texture, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ _gl_fill(base, (struct color){1, 1, 1, 1}, reg, fbo, size.height, false);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glDeleteFramebuffers(1, &fbo);
+ return img;
+}
+
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
auto gd = (struct gl_data *)base;
- gd->release_user_data(base, inner);
+ if (inner->user_data) {
+ gd->release_user_data(base, inner);
+ }
assert(inner->user_data == NULL);
glDeleteTextures(1, &inner->texture);
@@ -1070,513 +760,44 @@ void gl_release_image(backend_t *base, void *image_data) {
free(wd);
}
-static inline void gl_free_blur_shader(gl_blur_shader_t *shader) {
- if (shader->prog) {
- glDeleteProgram(shader->prog);
- }
+void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) {
+ auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t);
- shader->prog = 0;
-}
+ const char *vert_shaders[2] = {vertex_shader, NULL};
+ const char *frag_shaders[4] = {win_shader_glsl, masking_glsl, source, NULL};
-void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) {
- auto bctx = (struct gl_blur_context *)ctx;
- // Free GLSL shaders/programs
- for (int i = 0; i < bctx->npasses; ++i) {
- gl_free_blur_shader(&bctx->blur_shader[i]);
+ if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) {
+ free(win_shader);
+ return NULL;
}
- free(bctx->blur_shader);
- if (bctx->blur_texture_count && bctx->blur_textures) {
- glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures);
- free(bctx->blur_textures);
- }
- if (bctx->blur_texture_count && bctx->texture_sizes) {
- free(bctx->texture_sizes);
- }
- if (bctx->blur_fbo_count && bctx->blur_fbos) {
- glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos);
- free(bctx->blur_fbos);
- }
-
- bctx->blur_texture_count = 0;
- bctx->blur_fbo_count = 0;
-
- free(bctx);
-
- gl_check_err();
-}
-
-/**
- * Initialize GL blur filters.
- */
-bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
- enum blur_method method, void *args) {
- bool success = false;
- auto ctx = (struct gl_blur_context *)blur_context;
-
- struct conv **kernels;
-
- int nkernels;
- ctx->method = BLUR_METHOD_KERNEL;
- if (method == BLUR_METHOD_KERNEL) {
- nkernels = ((struct kernel_blur_args *)args)->kernel_count;
- kernels = ((struct kernel_blur_args *)args)->kernels;
- } else {
- kernels = generate_blur_kernel(method, args, &nkernels);
- }
-
- if (!nkernels) {
- ctx->method = BLUR_METHOD_NONE;
- return true;
- }
-
- // Specify required textures and FBOs
- ctx->blur_texture_count = 2;
- ctx->blur_fbo_count = 1;
-
- ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t);
-
- char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
- // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
- // Thanks to hiciu for reporting.
- setlocale(LC_NUMERIC, "C");
-
- // clang-format off
- static const char *FRAG_SHADER_BLUR = GLSL(330,
- %s\n // other extension pragmas
- uniform sampler2D tex_src;
- uniform vec2 pixel_norm;
- uniform float opacity;
- in vec2 texcoord;
- out vec4 out_color;
- void main() {
- vec2 uv = texcoord * pixel_norm;
- vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
- %s //body of the convolution
- out_color = sum / float(%.7g) * opacity;
- }
- );
- static const char *FRAG_SHADER_BLUR_ADD = QUOTE(
- sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g));
- );
- // clang-format on
-
- const char *shader_add = FRAG_SHADER_BLUR_ADD;
- char *extension = strdup("");
-
- for (int i = 0; i < nkernels; i++) {
- auto kern = kernels[i];
- // Build shader
- int width = kern->w, height = kern->h;
- int nele = width * height;
- // '%.7g' is at most 14 characters, inserted 3 times
- size_t body_len = (strlen(shader_add) + 42) * (uint)nele;
- char *shader_body = ccalloc(body_len, char);
- char *pc = shader_body;
-
- // Make use of the linear interpolation hardware by sampling 2 pixels with
- // one texture access by sampling between both pixels based on their
- // relative weight. Easiest done in a single dimension as 2D bilinear
- // filtering would raise additional constraints on the kernels. Therefore
- // only use interpolation along the larger dimension.
- double sum = 0.0;
- if (width > height) {
- // use interpolation in x dimension (width)
- for (int j = 0; j < height; ++j) {
- for (int k = 0; k < width; k += 2) {
- double val1, val2;
- val1 = kern->data[j * width + k];
- val2 = (k + 1 < width)
- ? kern->data[j * width + k + 1]
- : 0;
-
- double combined_weight = val1 + val2;
- if (combined_weight == 0) {
- continue;
- }
- sum += combined_weight;
-
- double offset_x =
- k + (val2 / combined_weight) - (width / 2);
- double offset_y = j - (height / 2);
- pc += snprintf(
- pc, body_len - (ulong)(pc - shader_body),
- shader_add, combined_weight, offset_x, offset_y);
- assert(pc < shader_body + body_len);
- }
- }
- } else {
- // use interpolation in y dimension (height)
- for (int j = 0; j < height; j += 2) {
- for (int k = 0; k < width; ++k) {
- double val1, val2;
- val1 = kern->data[j * width + k];
- val2 = (j + 1 < height)
- ? kern->data[(j + 1) * width + k]
- : 0;
-
- double combined_weight = val1 + val2;
- if (combined_weight == 0) {
- continue;
- }
- sum += combined_weight;
-
- double offset_x = k - (width / 2);
- double offset_y =
- j + (val2 / combined_weight) - (height / 2);
- pc += snprintf(
- pc, body_len - (ulong)(pc - shader_body),
- shader_add, combined_weight, offset_x, offset_y);
- assert(pc < shader_body + body_len);
- }
- }
- }
-
- auto pass = ctx->blur_shader + i;
- size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) +
- strlen(shader_body) + 10 /* sum */ +
- 1 /* null terminator */;
- char *shader_str = ccalloc(shader_len, char);
- auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR,
- extension, shader_body, sum);
- CHECK(real_shader_len >= 0);
- CHECK((size_t)real_shader_len < shader_len);
- free(shader_body);
-
- // Build program
- pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
- free(shader_str);
- if (!pass->prog) {
- log_error("Failed to create GLSL program.");
- success = false;
- goto out;
- }
- glBindFragDataLocation(pass->prog, 0, "out_color");
-
- // Get uniform addresses
- pass->unifm_pixel_norm =
- glGetUniformLocationChecked(pass->prog, "pixel_norm");
- pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity");
- pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
-
- // Setup projection matrix
- glUseProgram(pass->prog);
- int pml = glGetUniformLocationChecked(pass->prog, "projection");
- glUniformMatrix4fv(pml, 1, false, projection);
- glUseProgram(0);
-
- ctx->resize_width += kern->w / 2;
- ctx->resize_height += kern->h / 2;
- }
-
- if (nkernels == 1) {
- // Generate an extra null pass so we don't need special code path for
- // the single pass case
- auto pass = &ctx->blur_shader[1];
- pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag);
- pass->unifm_pixel_norm = -1;
- pass->unifm_opacity = -1;
- pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
-
- // Setup projection matrix
- glUseProgram(pass->prog);
- int pml = glGetUniformLocationChecked(pass->prog, "projection");
- glUniformMatrix4fv(pml, 1, false, projection);
- glUseProgram(0);
-
- ctx->npasses = 2;
- } else {
- ctx->npasses = nkernels;
- }
-
- success = true;
-out:
- if (method != BLUR_METHOD_KERNEL) {
- // We generated the blur kernels, so we need to free them
- for (int i = 0; i < nkernels; i++) {
- free(kernels[i]);
- }
- free(kernels);
- }
-
- free(extension);
- // Restore LC_NUMERIC
- setlocale(LC_NUMERIC, lc_numeric_old);
- free(lc_numeric_old);
-
- return success;
-}
-
-bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection,
- enum blur_method method, void *args) {
- bool success = false;
- auto ctx = (struct gl_blur_context *)blur_context;
-
- ctx->method = method;
-
- auto blur_params = generate_dual_kawase_params(args);
-
- // Specify required textures and FBOs
- ctx->blur_texture_count = blur_params->iterations;
- ctx->blur_fbo_count = blur_params->iterations;
-
- ctx->resize_width += blur_params->expand;
- ctx->resize_height += blur_params->expand;
-
- ctx->npasses = 2;
- ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t);
-
- char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
- // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
- // Thanks to hiciu for reporting.
- setlocale(LC_NUMERIC, "C");
-
- // Dual-kawase downsample shader / program
- auto down_pass = ctx->blur_shader;
- {
- // clang-format off
- static const char *FRAG_SHADER_DOWN = GLSL(330,
- uniform sampler2D tex_src;
- uniform float scale = 1.0;
- uniform vec2 pixel_norm;
- in vec2 texcoord;
- out vec4 out_color;
- void main() {
- vec2 offset = %.7g * pixel_norm;
- vec2 uv = texcoord * pixel_norm * (2.0 / scale);
- vec4 sum = texture2D(tex_src, uv) * 4.0;
- sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset);
- sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset);
- sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset);
- sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset);
- out_color = sum / 8.0;
- }
- );
- // clang-format on
-
- // Build shader
- size_t shader_len =
- strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */;
- char *shader_str = ccalloc(shader_len, char);
- auto real_shader_len =
- snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset);
- CHECK(real_shader_len >= 0);
- CHECK((size_t)real_shader_len < shader_len);
-
- // Build program
- down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
- free(shader_str);
- if (!down_pass->prog) {
- log_error("Failed to create GLSL program.");
- success = false;
- goto out;
- }
- glBindFragDataLocation(down_pass->prog, 0, "out_color");
-
- // Get uniform addresses
- down_pass->unifm_pixel_norm =
- glGetUniformLocationChecked(down_pass->prog, "pixel_norm");
- down_pass->texorig_loc =
- glGetUniformLocationChecked(down_pass->prog, "texorig");
- down_pass->scale_loc =
- glGetUniformLocationChecked(down_pass->prog, "scale");
-
- // Setup projection matrix
- glUseProgram(down_pass->prog);
- int pml = glGetUniformLocationChecked(down_pass->prog, "projection");
- glUniformMatrix4fv(pml, 1, false, projection);
- glUseProgram(0);
- }
-
- // Dual-kawase upsample shader / program
- auto up_pass = ctx->blur_shader + 1;
- {
- // clang-format off
- static const char *FRAG_SHADER_UP = GLSL(330,
- uniform sampler2D tex_src;
- uniform float scale = 1.0;
- uniform vec2 pixel_norm;
- uniform float opacity;
- in vec2 texcoord;
- out vec4 out_color;
- void main() {
- vec2 offset = %.7g * pixel_norm;
- vec2 uv = texcoord * pixel_norm / (2 * scale);
- vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset);
- sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0;
- sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset);
- sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0;
- sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset);
- sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0;
- sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset);
- sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0;
- out_color = sum / 12.0 * opacity;
- }
- );
- // clang-format on
-
- // Build shader
- size_t shader_len =
- strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */;
- char *shader_str = ccalloc(shader_len, char);
- auto real_shader_len =
- snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset);
- CHECK(real_shader_len >= 0);
- CHECK((size_t)real_shader_len < shader_len);
-
- // Build program
- up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
- free(shader_str);
- if (!up_pass->prog) {
- log_error("Failed to create GLSL program.");
- success = false;
- goto out;
- }
- glBindFragDataLocation(up_pass->prog, 0, "out_color");
-
- // Get uniform addresses
- up_pass->unifm_pixel_norm =
- glGetUniformLocationChecked(up_pass->prog, "pixel_norm");
- up_pass->unifm_opacity =
- glGetUniformLocationChecked(up_pass->prog, "opacity");
- up_pass->texorig_loc =
- glGetUniformLocationChecked(up_pass->prog, "texorig");
- up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale");
-
- // Setup projection matrix
- glUseProgram(up_pass->prog);
- int pml = glGetUniformLocationChecked(up_pass->prog, "projection");
- glUniformMatrix4fv(pml, 1, false, projection);
- glUseProgram(0);
- }
-
- success = true;
-out:
- free(blur_params);
-
- if (!success) {
- ctx = NULL;
- }
-
- // Restore LC_NUMERIC
- setlocale(LC_NUMERIC, lc_numeric_old);
- free(lc_numeric_old);
-
- return success;
-}
-
-void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) {
- bool success;
- auto gd = (struct gl_data *)base;
-
- auto ctx = ccalloc(1, struct gl_blur_context);
-
- if (!method || method >= BLUR_METHOD_INVALID) {
- ctx->method = BLUR_METHOD_NONE;
- return ctx;
- }
+ GLint viewport_dimensions[2];
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
- GLint viewport_dimensions[2];
- glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
- GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
- {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
+ GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
+ {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
- if (method == BLUR_METHOD_DUAL_KAWASE) {
- success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0],
- method, args);
- } else {
- success =
- gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args);
- }
- if (!success || ctx->method == BLUR_METHOD_NONE) {
- goto out;
- }
-
- // Texture size will be defined by gl_blur
- ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint);
- ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size);
- glGenTextures(ctx->blur_texture_count, ctx->blur_textures);
-
- for (int i = 0; i < ctx->blur_texture_count; ++i) {
- glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- }
-
- // Generate FBO and textures when needed
- ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint);
- glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos);
-
- for (int i = 0; i < ctx->blur_fbo_count; ++i) {
- if (!ctx->blur_fbos[i]) {
- log_error("Failed to generate framebuffer objects for blur");
- success = false;
- goto out;
- }
- }
-
-out:
- if (!success) {
- gl_destroy_blur_context(&gd->base, ctx);
- ctx = NULL;
- }
-
- gl_check_err();
- return ctx;
-}
+ int pml = glGetUniformLocationChecked(win_shader->prog, "projection");
+ glUseProgram(win_shader->prog);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
-void gl_get_blur_size(void *blur_context, int *width, int *height) {
- auto ctx = (struct gl_blur_context *)blur_context;
- *width = ctx->resize_width;
- *height = ctx->resize_height;
+ return win_shader;
}
-// clang-format off
-const char *win_shader_glsl = GLSL(330,
- uniform float opacity;
- uniform float dim;
- uniform bool invert_color;
- in vec2 texcoord;
- uniform sampler2D tex;
- uniform sampler2D brightness;
- uniform float max_brightness;
-
- void main() {
- vec4 c = texelFetch(tex, ivec2(texcoord), 0);
- if (invert_color) {
- c = vec4(c.aaa - c.rgb, c.a);
- }
- c = vec4(c.rgb * (1.0 - dim), c.a) * opacity;
-
- vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb;
- // Ref: https://en.wikipedia.org/wiki/Relative_luminance
- float brightness = rgb_brightness.r * 0.21 +
- rgb_brightness.g * 0.72 +
- rgb_brightness.b * 0.07;
- if (brightness > max_brightness)
- c.rgb = c.rgb * (max_brightness / brightness);
-
- gl_FragColor = c;
+uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) {
+ auto win_shader = (gl_win_shader_t *)shader;
+ uint64_t ret = 0;
+ if (glGetUniformLocation(win_shader->prog, "time") >= 0) {
+ ret |= SHADER_ATTRIBUTE_ANIMATED;
}
-);
-
-const char *present_vertex_shader = GLSL(330,
- uniform mat4 projection;
- layout(location = 0) in vec2 coord;
- out vec2 texcoord;
- void main() {
- gl_Position = projection * vec4(coord, 0, 1);
- texcoord = coord;
- }
-);
-// clang-format on
+ return ret;
+}
bool gl_init(struct gl_data *gd, session_t *ps) {
// Initialize GLX data structure
@@ -1602,7 +823,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]);
// Clear screen
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glGenFramebuffers(1, &gd->back_fbo);
@@ -1619,24 +840,35 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
+ gd->default_mask_texture = gl_new_texture(GL_TEXTURE_2D);
+ if (!gd->default_mask_texture) {
+ log_error("Failed to generate a default mask texture");
+ return false;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE,
+ (GLbyte[]){'\xff'});
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ // Initialize shaders
+ gd->default_shader = gl_create_window_shader(NULL, win_shader_default);
+ if (!gd->default_shader) {
+ log_error("Failed to create window shaders");
+ return false;
+ }
+
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
- GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
- {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
+ GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
+ {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
- // Initialize shaders
- gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader);
- int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection");
- glUseProgram(gd->win_shader.prog);
- glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
- glUseProgram(0);
-
gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag);
gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color");
- pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
+ int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
glUseProgram(gd->fill_shader.prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
@@ -1652,6 +884,17 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
+ gd->shadow_shader.prog =
+ gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
+ gd->shadow_shader.uniform_color =
+ glGetUniformLocationChecked(gd->shadow_shader.prog, "color");
+ pml = glGetUniformLocationChecked(gd->shadow_shader.prog, "projection");
+ glUseProgram(gd->shadow_shader.prog);
+ glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
+ glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color");
+
gd->brightness_shader.prog =
gl_create_program_from_str(interpolating_vert, interpolating_frag);
if (!gd->brightness_shader.prog) {
@@ -1689,18 +932,24 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
} else {
gd->is_nvidia = false;
}
+ gd->has_robustness = gl_has_extension("GL_ARB_robustness");
+ gd->has_egl_image_storage = gl_has_extension("GL_EXT_EGL_image_storage");
+ gl_check_err();
return true;
}
void gl_deinit(struct gl_data *gd) {
- gl_free_prog_main(&gd->win_shader);
-
if (gd->logger) {
log_remove_target_tls(gd->logger);
gd->logger = NULL;
}
+ if (gd->default_shader) {
+ gl_destroy_window_shader(&gd->base, gd->default_shader);
+ gd->default_shader = NULL;
+ }
+
gl_check_err();
}
@@ -1832,7 +1081,8 @@ static void gl_image_apply_alpha(backend_t *base, struct backend_image *img,
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
inner->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
- _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false);
+ _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, inner->height,
+ !inner->y_inverted);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
@@ -1904,19 +1154,202 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
return true;
}
-bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y,
- struct color *output) {
- struct backend_image *tex = image_data;
- auto inner = (struct gl_texture *)tex->inner;
- GLfloat color[4];
- glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA,
- GL_FLOAT, color);
- output->alpha = color[3];
- output->red = color[0];
- output->green = color[1];
- output->blue = color[2];
-
- bool ret = glGetError() == GL_NO_ERROR;
- gl_clear_err();
- return ret;
+bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
+ void *image_data, void *args) {
+ if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
+ return default_set_image_property(backend_data, prop, image_data, args);
+ }
+
+ struct backend_image *img = image_data;
+ auto inner = (struct gl_texture *)img->inner;
+ inner->shader = args;
+ return true;
+}
+
+struct gl_shadow_context {
+ double radius;
+ void *blur_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;
+
+ 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);
+ if (!ctx->blur_context) {
+ log_error("Failed to create shadow context");
+ free(ctx);
+ return NULL;
+ }
+ }
+ 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;
+ if (ctx_->blur_context) {
+ gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context);
+ }
+ free(ctx_);
+}
+
+void *gl_shadow_from_mask(backend_t *base, void *mask,
+ struct backend_shadow_context *sctx, struct color color) {
+ log_debug("Create shadow from mask");
+ auto gd = (struct gl_data *)base;
+ auto img = (struct backend_image *)mask;
+ auto inner = (struct gl_texture *)img->inner;
+ auto gsctx = (struct gl_shadow_context *)sctx;
+ int radius = (int)gsctx->radius;
+
+ auto new_inner = ccalloc(1, struct gl_texture);
+ new_inner->width = inner->width + radius * 2;
+ new_inner->height = inner->height + radius * 2;
+ new_inner->texture = gl_new_texture(GL_TEXTURE_2D);
+ new_inner->has_alpha = inner->has_alpha;
+ new_inner->y_inverted = true;
+ auto new_img = default_new_backend_image(new_inner->width, new_inner->height);
+ new_img->inner = (struct backend_image_inner_base *)new_inner;
+ new_img->inner->refcount = 1;
+
+ // Render the mask to a texture, so inversion and corner radius can be
+ // applied.
+ 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);
+ GLuint fbo;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ source_texture, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ if (img->color_inverted) {
+ // If the mask is inverted, clear the source_texture to white, so the
+ // "outside" of the mask would be correct
+ glClearColor(1, 1, 1, 1);
+ } else {
+ glClearColor(0, 0, 0, 1);
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+ {
+ // clang-format off
+ // interleaved vertex coordinates and texture coordinates
+ GLint coords[] = {radius , radius , 0 , 0,
+ radius + inner->width, radius , inner->width, 0,
+ radius + inner->width, radius + inner->height, inner->width, inner->height,
+ radius , radius + inner->height, 0 , inner->height,};
+ // clang-format on
+ GLuint indices[] = {0, 1, 2, 2, 3, 0};
+ _gl_compose(base, mask, fbo, NULL, (coord_t){0}, coords, indices, 1);
+ }
+
+ gl_check_err();
+
+ 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);
+
+ region_t reg_blur;
+ pixman_region32_init_rect(&reg_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}, &reg_blur, NULL,
+ source_texture,
+ (geometry_t){.width = new_inner->width, .height = new_inner->height},
+ fbo, gd->default_mask_texture);
+ pixman_region32_fini(&reg_blur);
+ }
+
+ // Colorize the shadow with color.
+ log_debug("Colorize shadow");
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, new_inner->texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_inner->width, new_inner->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ new_inner->texture, 0);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindTexture(GL_TEXTURE_2D, tmp_texture);
+ glUseProgram(gd->shadow_shader.prog);
+ glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red,
+ (GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha);
+
+ // clang-format off
+ GLuint indices[] = {0, 1, 2, 2, 3, 0};
+ GLint coord[] = {0 , 0 ,
+ new_inner->width , 0 ,
+ new_inner->width , new_inner->height,
+ 0 , new_inner->height,};
+ // clang-format on
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
+ GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(vert_coord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
+
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
+
+ glDisableVertexAttribArray(vert_coord_loc);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(1, &vao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(2, bo);
+
+ 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();
+ return new_img;
+}
+
+enum device_status gl_device_status(backend_t *base) {
+ auto gd = (struct gl_data *)base;
+ if (!gd->has_robustness) {
+ return DEVICE_STATUS_NORMAL;
+ }
+ if (glGetGraphicsResetStatusARB() == GL_NO_ERROR) {
+ return DEVICE_STATUS_NORMAL;
+ }
+ return DEVICE_STATUS_RESETTING;
}
diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h
index b1d93b0..3a78865 100644
--- a/src/backend/gl/gl_common.h
+++ b/src/backend/gl/gl_common.h
@@ -12,16 +12,40 @@
#define CASESTRRET(s) \
case s: return #s
+struct gl_blur_context;
+
+static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
+ auto ret = glGetUniformLocation(p, name);
+ if (ret < 0) {
+ log_info("Failed to get location of uniform '%s'. This is normal when "
+ "using custom shaders.",
+ name);
+ }
+ return ret;
+}
+
+#define bind_uniform(shader, uniform) \
+ (shader)->uniform_##uniform = glGetUniformLocationChecked((shader)->prog, #uniform)
// Program and uniforms for window shader
typedef struct {
+ UT_hash_handle hh;
+ uint32_t id;
GLuint prog;
- GLint unifm_opacity;
- GLint unifm_invert_color;
- GLint unifm_tex;
- GLint unifm_dim;
- GLint unifm_brightness;
- GLint unifm_max_brightness;
+ GLint uniform_opacity;
+ GLint uniform_invert_color;
+ GLint uniform_tex;
+ GLint uniform_dim;
+ GLint uniform_brightness;
+ GLint uniform_max_brightness;
+ GLint uniform_corner_radius;
+ GLint uniform_border_width;
+ GLint uniform_time;
+
+ GLint uniform_mask_tex;
+ GLint uniform_mask_offset;
+ GLint uniform_mask_corner_radius;
+ GLint uniform_mask_inverted;
} gl_win_shader_t;
// Program and uniforms for brightness shader
@@ -29,13 +53,23 @@ typedef struct {
GLuint prog;
} gl_brightness_shader_t;
+typedef struct {
+ GLuint prog;
+ GLint uniform_color;
+} gl_shadow_shader_t;
+
// Program and uniforms for blur shader
typedef struct {
GLuint prog;
- GLint unifm_pixel_norm;
- GLint unifm_opacity;
+ GLint uniform_pixel_norm;
+ GLint uniform_opacity;
GLint texorig_loc;
GLint scale_loc;
+
+ GLint uniform_mask_tex;
+ GLint uniform_mask_offset;
+ GLint uniform_mask_corner_radius;
+ GLint uniform_mask_inverted;
} gl_blur_shader_t;
typedef struct {
@@ -53,6 +87,7 @@ struct gl_texture {
// Textures for auxiliary uses.
GLuint auxiliary_texture[2];
+ gl_win_shader_t *shader;
void *user_data;
};
@@ -60,14 +95,22 @@ struct gl_data {
backend_t base;
// If we are using proprietary NVIDIA driver
bool is_nvidia;
+ // If ARB_robustness extension is present
+ bool has_robustness;
+ // If EXT_EGL_image_storage extension is present
+ bool has_egl_image_storage;
// Height and width of the root window
int height, width;
- gl_win_shader_t win_shader;
+ // Hash-table of window shaders
+ gl_win_shader_t *default_shader;
gl_brightness_shader_t brightness_shader;
gl_fill_shader_t fill_shader;
+ gl_shadow_shader_t shadow_shader;
GLuint back_texture, back_fbo;
GLuint present_prog;
+ GLuint default_mask_texture;
+
/// Called when an gl_texture is decoupled from the texture it refers. Returns
/// the decoupled user_data
void *(*decouple_texture_user_data)(backend_t *base, void *user_data);
@@ -83,16 +126,25 @@ typedef struct session session_t;
#define GL_PROG_MAIN_INIT \
{ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
+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);
+
GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
GLuint gl_create_program(const GLuint *const shaders, int nshaders);
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
+GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders);
+void *gl_create_window_shader(backend_t *backend_data, const char *source);
+void gl_destroy_window_shader(backend_t *backend_data, void *shader);
+uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
+bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
+ void *image_data, void *args);
/**
* @brief Render a region with texture data.
*/
-void gl_compose(backend_t *, void *ptex,
- int dst_x1, int dst_y1, int dst_x2, int dst_y2,
- const region_t *reg_tgt, const region_t *reg_visible);
+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);
void gl_resize(struct gl_data *, int width, int height);
@@ -105,19 +157,29 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *arg);
void gl_release_image(backend_t *base, void *image_data);
+void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);
-bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur,
- const region_t *reg_visible);
+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);
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);
+void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx);
+void *gl_shadow_from_mask(backend_t *base, void *mask,
+ struct backend_shadow_context *sctx, struct color color);
void gl_get_blur_size(void *blur_context, int *width, int *height);
void gl_fill(backend_t *base, struct color, const region_t *clip);
void gl_present(backend_t *base, const region_t *);
bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output);
+enum device_status gl_device_status(backend_t *base);
static inline void gl_delete_texture(GLuint texture) {
glDeleteTextures(1, &texture);
@@ -218,3 +280,13 @@ static inline bool gl_has_extension(const char *ext) {
log_info("Missing GL extension %s.", ext);
return false;
}
+
+static const GLuint vert_coord_loc = 0;
+static const GLuint vert_in_texcoord_loc = 1;
+
+#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__
+#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[];
diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c
index 1397d19..109bec9 100644
--- a/src/backend/gl/glx.c
+++ b/src/backend/gl/glx.c
@@ -302,16 +302,21 @@ static backend_t *glx_init(session_t *ps) {
continue;
}
- gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true,
- (int[]){
- GLX_CONTEXT_MAJOR_VERSION_ARB,
- 3,
- GLX_CONTEXT_MINOR_VERSION_ARB,
- 3,
- GLX_CONTEXT_PROFILE_MASK_ARB,
- GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
- 0,
- });
+ int *attributes = (int[]){GLX_CONTEXT_MAJOR_VERSION_ARB,
+ 3,
+ GLX_CONTEXT_MINOR_VERSION_ARB,
+ 3,
+ GLX_CONTEXT_PROFILE_MASK_ARB,
+ GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
+ 0,
+ 0,
+ 0};
+ if (glxext.has_GLX_ARB_create_context_robustness) {
+ attributes[6] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB;
+ attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB;
+ }
+
+ gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, attributes);
free(cfg);
if (!gd->ctx) {
@@ -388,11 +393,10 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
}
log_trace("Binding pixmap %#010x", pixmap);
- auto wd = ccalloc(1, struct backend_image);
- wd->max_brightness = 1;
+ auto wd = default_new_backend_image(r->width, r->height);
auto inner = ccalloc(1, struct gl_texture);
- inner->width = wd->ewidth = r->width;
- inner->height = wd->eheight = r->height;
+ inner->width = r->width;
+ inner->height = r->height;
wd->inner = (struct backend_image_inner_base *)inner;
free(r);
@@ -440,9 +444,6 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
inner->user_data = glxpixmap;
inner->texture = gl_new_texture(GL_TEXTURE_2D);
inner->has_alpha = fmt.alpha_size != 0;
- wd->opacity = 1;
- wd->color_inverted = false;
- wd->dim = 0;
wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, inner->texture);
glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
@@ -494,7 +495,7 @@ static void glx_diagnostics(backend_t *base) {
auto gl_renderer = (const char *)glGetString(GL_RENDERER);
printf("* GL renderer: %s\n", gl_renderer);
- if (strcmp(glx_vendor, "Mesa Project and SGI")) {
+ if (strcmp(glx_vendor, "Mesa Project and SGI") == 0) {
for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) {
if (strstr(gl_renderer, software_renderer_names[i]) != NULL) {
warn_software_rendering = true;
@@ -529,19 +530,26 @@ struct backend_operations glx_ops = {
.release_image = gl_release_image,
.compose = gl_compose,
.image_op = gl_image_op,
- .set_image_property = default_set_image_property,
- .read_pixel = gl_read_pixel,
+ .set_image_property = gl_set_image_property,
.clone_image = default_clone_image,
.blur = gl_blur,
.is_image_transparent = default_is_image_transparent,
.present = glx_present,
.buffer_age = glx_buffer_age,
- .render_shadow = default_backend_render_shadow,
+ .create_shadow_context = gl_create_shadow_context,
+ .destroy_shadow_context = gl_destroy_shadow_context,
+ .render_shadow = backend_render_shadow_from_mask,
+ .shadow_from_mask = gl_shadow_from_mask,
+ .make_mask = gl_make_mask,
.fill = gl_fill,
.create_blur_context = gl_create_blur_context,
.destroy_blur_context = gl_destroy_blur_context,
.get_blur_size = gl_get_blur_size,
.diagnostics = glx_diagnostics,
+ .device_status = gl_device_status,
+ .create_shader = gl_create_window_shader,
+ .destroy_shader = gl_destroy_window_shader,
+ .get_shader_attributes = gl_get_shader_attributes,
.max_buffer_age = 5, // Why?
};
@@ -609,6 +617,7 @@ void glxext_init(Display *dpy, int screen) {
check_ext(GLX_EXT_texture_from_pixmap);
check_ext(GLX_ARB_create_context);
check_ext(GLX_EXT_buffer_age);
+ check_ext(GLX_ARB_create_context_robustness);
#ifdef GLX_MESA_query_renderer
check_ext(GLX_MESA_query_renderer);
#endif
diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h
index 1061f0b..44b4da0 100644
--- a/src/backend/gl/glx.h
+++ b/src/backend/gl/glx.h
@@ -55,6 +55,7 @@ struct glxext_info {
bool has_GLX_ARB_create_context;
bool has_GLX_EXT_buffer_age;
bool has_GLX_MESA_query_renderer;
+ bool has_GLX_ARB_create_context_robustness;
};
extern struct glxext_info glxext;
diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c
new file mode 100644
index 0000000..4a18e62
--- /dev/null
+++ b/src/backend/gl/shaders.c
@@ -0,0 +1,187 @@
+#include "gl_common.h"
+
+// clang-format off
+const char dummy_frag[] = GLSL(330,
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ void main() {
+ gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0);
+ }
+);
+
+const char copy_with_mask_frag[] = GLSL(330,
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ float mask_factor();
+ void main() {
+ gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor();
+ }
+);
+
+const char fill_frag[] = GLSL(330,
+ uniform vec4 color;
+ void main() {
+ gl_FragColor = color;
+ }
+);
+
+const char fill_vert[] = GLSL(330,
+ layout(location = 0) in vec2 in_coord;
+ uniform mat4 projection;
+ void main() {
+ gl_Position = projection * vec4(in_coord, 0, 1);
+ }
+);
+
+const char interpolating_frag[] = GLSL(330,
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ void main() {
+ gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1);
+ }
+);
+
+const char interpolating_vert[] = GLSL(330,
+ uniform mat4 projection;
+ uniform vec2 texsize;
+ layout(location = 0) in vec2 in_coord;
+ layout(location = 1) in vec2 in_texcoord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(in_coord, 0, 1);
+ texcoord = in_texcoord / texsize;
+ }
+);
+const char masking_glsl[] = GLSL(330,
+ uniform sampler2D mask_tex;
+ uniform vec2 mask_offset;
+ uniform float mask_corner_radius;
+ uniform bool mask_inverted;
+ in vec2 texcoord;
+ float mask_rectangle_sdf(vec2 point, vec2 half_size) {
+ vec2 d = abs(point) - half_size;
+ return length(max(d, 0.0));
+ }
+ float mask_factor() {
+ vec2 mask_size = textureSize(mask_tex, 0);
+ vec2 maskcoord = texcoord - mask_offset;
+ vec4 mask = texture2D(mask_tex, maskcoord / mask_size);
+ if (mask_corner_radius != 0) {
+ vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f;
+ float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f,
+ inner_size / 2.0f) - mask_corner_radius;
+ if (dist > 0.0f) {
+ mask.r *= (1.0f - clamp(dist, 0.0f, 1.0f));
+ }
+ }
+ if (mask_inverted) {
+ mask.rgb = 1.0 - mask.rgb;
+ }
+ return mask.r;
+ }
+);
+const char win_shader_glsl[] = GLSL(330,
+ uniform float opacity;
+ uniform float dim;
+ uniform float corner_radius;
+ uniform float border_width;
+ uniform bool invert_color;
+ in vec2 texcoord;
+ uniform sampler2D tex;
+ uniform sampler2D brightness;
+ uniform float max_brightness;
+ // Signed distance field for rectangle center at (0, 0), with size of
+ // half_size * 2
+ float rectangle_sdf(vec2 point, vec2 half_size) {
+ vec2 d = abs(point) - half_size;
+ return length(max(d, 0.0));
+ }
+
+ vec4 default_post_processing(vec4 c) {
+ vec4 border_color = texture(tex, vec2(0.0, 0.5));
+ if (invert_color) {
+ c = vec4(c.aaa - c.rgb, c.a);
+ border_color = vec4(border_color.aaa - border_color.rgb, border_color.a);
+ }
+ c = vec4(c.rgb * (1.0 - dim), c.a) * opacity;
+ border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity;
+
+ vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb;
+ // Ref: https://en.wikipedia.org/wiki/Relative_luminance
+ float brightness = rgb_brightness.r * 0.21 +
+ rgb_brightness.g * 0.72 +
+ rgb_brightness.b * 0.07;
+ if (brightness > max_brightness) {
+ c.rgb = c.rgb * (max_brightness / brightness);
+ border_color.rgb = border_color.rgb * (max_brightness / brightness);
+ }
+
+ // Rim color is the color of the outer rim of the window, if there is no
+ // border, it's the color of the window itself, otherwise it's the border.
+ // Using mix() to avoid a branch here.
+ vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f));
+
+ vec2 outer_size = vec2(textureSize(tex, 0));
+ vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f;
+ float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f,
+ inner_size / 2.0f) - corner_radius;
+ if (rect_distance > 0.0f) {
+ c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color;
+ } else {
+ float factor = clamp(rect_distance + border_width, 0.0f, 1.0f);
+ c = (1.0f - factor) * c + factor * border_color;
+ }
+
+ return c;
+ }
+
+ vec4 window_shader();
+ float mask_factor();
+
+ void main() {
+ gl_FragColor = window_shader() * mask_factor();
+ }
+);
+
+const char win_shader_default[] = GLSL(330,
+ in vec2 texcoord;
+ uniform sampler2D tex;
+ vec4 default_post_processing(vec4 c);
+ vec4 window_shader() {
+ vec4 c = texelFetch(tex, ivec2(texcoord), 0);
+ return default_post_processing(c);
+ }
+);
+
+const char present_vertex_shader[] = GLSL(330,
+ uniform mat4 projection;
+ layout(location = 0) in vec2 coord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(coord, 0, 1);
+ texcoord = coord;
+ }
+);
+const char vertex_shader[] = GLSL(330,
+ uniform mat4 projection;
+ uniform float scale = 1.0;
+ uniform vec2 texorig;
+ layout(location = 0) in vec2 coord;
+ layout(location = 1) in vec2 in_texcoord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(coord, 0, scale);
+ texcoord = in_texcoord + texorig;
+ }
+);
+const char shadow_colorization_frag[] = GLSL(330,
+ uniform vec4 color;
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ out vec4 out_color;
+ void main() {
+ vec4 c = texelFetch(tex, ivec2(texcoord), 0);
+ out_color = c.r * color;
+ }
+);
+// clang-format on
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b8f0ad9..1e8e72b 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -3,5 +3,5 @@ srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backe
# enable opengl
if get_option('opengl')
- srcs += [ files('gl/gl_common.c', 'gl/glx.c') ]
+ srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c', 'gl/egl.c') ]
endif
diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c
index ccf358b..2b7f8e1 100644
--- a/src/backend/xrender/xrender.c
+++ b/src/backend/xrender/xrender.c
@@ -88,19 +88,153 @@ struct _xrender_image_data_inner {
bool owned;
};
-static void compose_impl(struct _xrender_data *xd, const struct backend_image *img,
- int dst_x1, int dst_y1, int dst_x2, int dst_y2,
- const region_t *reg_paint, const region_t *reg_visible,
- xcb_render_picture_t result) {
- auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
+struct xrender_rounded_rectangle_cache {
+ int refcount;
+ // A cached picture of a rounded rectangle. Xorg rasterizes shapes on CPU so it's
+ // exceedingly slow.
+ xcb_render_picture_t p;
+};
+
+struct xrender_image {
+ struct backend_image base;
+
+ struct xrender_rounded_rectangle_cache *rounded_rectangle;
+};
+
+/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
+/// rendered in it.
+struct xrender_rounded_rectangle_cache *
+make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src,
+ xcb_drawable_t root, int width, int height, int corner_radius) {
+ auto picture = x_create_picture_with_standard(c, root, width, height,
+ XCB_PICT_STANDARD_ARGB_32, 0, NULL);
+ if (picture == XCB_NONE) {
+ return NULL;
+ }
+
+ int inner_height = height - 2 * corner_radius;
+ int cap_height = corner_radius;
+ if (inner_height < 0) {
+ cap_height = height / 2;
+ inner_height = 0;
+ }
+ auto points = ccalloc(cap_height * 4 + 4, xcb_render_pointfix_t);
+ int point_count = 0;
+
+#define ADD_POINT(px, py) \
+ assert(point_count < cap_height * 4 + 4); \
+ points[point_count].x = DOUBLE_TO_XFIXED(px); \
+ points[point_count].y = DOUBLE_TO_XFIXED(py); \
+ point_count += 1;
+
+ // The top cap
+ for (int i = 0; i <= cap_height; i++) {
+ double y = corner_radius - i;
+ double delta = sqrt(corner_radius * corner_radius - y * y);
+ double left = corner_radius - delta;
+ double right = width - corner_radius + delta;
+ if (left >= right) {
+ continue;
+ }
+ ADD_POINT(left, i);
+ ADD_POINT(right, i);
+ }
+
+ // The middle rectangle
+ if (inner_height > 0) {
+ ADD_POINT(0, cap_height + inner_height);
+ ADD_POINT(width, cap_height + inner_height);
+ }
+
+ // The bottom cap
+ for (int i = cap_height + inner_height + 1; i <= height; i++) {
+ double y = corner_radius - (height - i);
+ double delta = sqrt(corner_radius * corner_radius - y * y);
+ double left = corner_radius - delta;
+ double right = width - corner_radius + delta;
+ if (left >= right) {
+ break;
+ }
+ ADD_POINT(left, i);
+ ADD_POINT(right, i);
+ }
+#undef ADD_POINT
+
+ XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture,
+ x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0,
+ (uint32_t)point_count, points);
+ free(points);
+ auto ret = ccalloc(1, struct xrender_rounded_rectangle_cache);
+ ret->p = picture;
+ ret->refcount = 1;
+ return ret;
+}
+
+static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrender_image *mask,
+ xcb_render_picture_t alpha_pict, bool *allocated) {
+ auto inner = (struct _xrender_image_data_inner *)mask->base.inner;
+ if (!mask->base.color_inverted && mask->base.corner_radius == 0) {
+ *allocated = false;
+ return inner->pict;
+ }
+ const auto tmpw = to_u16_checked(inner->width);
+ const auto tmph = to_u16_checked(inner->height);
+ *allocated = true;
+ x_clear_picture_clip_region(xd->base.c, inner->pict);
+ auto ret = x_create_picture_with_visual(
+ xd->base.c, xd->base.root, inner->width, inner->height, inner->visual,
+ XCB_RENDER_CP_REPEAT,
+ (xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD});
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
+ ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ // Remember: the mask has a 1-pixel border
+ if (mask->base.corner_radius != 0) {
+ if (mask->rounded_rectangle == NULL) {
+ mask->rounded_rectangle = make_rounded_corner_cache(
+ xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2,
+ inner->height - 2, (int)mask->base.corner_radius);
+ }
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE,
+ mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0,
+ 0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2));
+ }
+
+ if (mask->base.color_inverted) {
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel,
+ XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ }
+
+ if (alpha_pict != XCB_NONE) {
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict,
+ ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
+ to_u16_checked(inner->height));
+ }
+
+ return ret;
+}
+
+static void
+compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
+ struct xrender_image *mask, coord_t mask_dst, const region_t *reg_paint,
+ const region_t *reg_visible, xcb_render_picture_t result) {
+ const struct backend_image *img = &xrimg->base;
+ bool mask_allocated = false;
+ auto mask_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
+ if (mask != NULL) {
+ mask_pict = process_mask(
+ xd, mask, img->opacity < 1.0 ? mask_pict : XCB_NONE, &mask_allocated);
+ }
auto inner = (struct _xrender_image_data_inner *)img->inner;
region_t reg;
bool has_alpha = inner->has_alpha || img->opacity != 1;
- const auto tmpw = to_u16_checked(dst_x2 - dst_x1);
- const auto tmph = to_u16_checked(dst_y2 - dst_y1);
- const auto tmpew = to_u16_checked(dst_x2 - dst_x1);
- const auto tmpeh = to_u16_checked(dst_y2 - dst_y1);
+ const auto tmpw = to_u16_checked(inner->width);
+ const auto tmph = to_u16_checked(inner->height);
+ const auto tmpew = to_u16_checked(img->ewidth);
+ const auto tmpeh = to_u16_checked(img->eheight);
+ // Remember: the mask has a 1-pixel border
+ const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1);
+ const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1);
const xcb_render_color_t dim_color = {
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
@@ -111,20 +245,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
-
-#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
- {
- const xcb_render_transform_t transform = {
- DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0),
- DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0),
- DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0),
- };
- xcb_render_set_picture_transform(xd->base.c, inner->pict, transform);
- xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL);
- }
-#undef DOUBLE_TO_XFIXED
-
- if ((img->color_inverted || img->dim != 0) && has_alpha) {
+ if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) {
+ xrimg->rounded_rectangle = make_rounded_corner_cache(
+ xd->base.c, xd->white_pixel, xd->base.root, inner->width,
+ inner->height, (int)img->corner_radius);
+ }
+ if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
// Apply image properties using a temporary image, because the source
// image is transparent. Otherwise the properties can be applied directly
// on the target image.
@@ -133,17 +259,25 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
inner->height, inner->visual, 0, NULL);
// Set clip region translated to source coordinate
- x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x1),
- to_i16_checked(-dst_y1), &reg);
+ x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
+ to_i16_checked(-dst.y), &reg);
// Copy source -> tmp
- xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict,
+ 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(
xd->base.c, xd->base.root, tmpw, tmph, inner->visual,
0, NULL);
- xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER,
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC,
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
0, 0, 0, 0, tmpw, tmph);
@@ -174,30 +308,31 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
}
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
- alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x1),
- to_i16_checked(dst_y1), tmpew, tmpeh);
+ mask_pict, result, 0, 0, mask_dst_x, mask_dst_y,
+ to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew,
+ tmpeh);
xcb_render_free_picture(xd->base.c, tmp_pict);
} else {
uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
- xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0,
- 0, 0, 0, to_i16_checked(dst_x1),
- to_i16_checked(dst_y1), tmpew, tmpeh);
+ xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0,
+ mask_dst_x, mask_dst_y, to_i16_checked(dst.x),
+ to_i16_checked(dst.y), tmpew, tmpeh);
if (img->dim != 0 || img->color_inverted) {
// Apply properties, if we reach here, then has_alpha == false
assert(!has_alpha);
if (img->color_inverted) {
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, result, 0,
- 0, 0, 0, to_i16_checked(dst_x1),
- to_i16_checked(dst_y1), tmpew, tmpeh);
+ 0, 0, 0, to_i16_checked(dst.x),
+ to_i16_checked(dst.y), tmpew, tmpeh);
}
if (img->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
- .x = to_i16_checked(dst_x1),
- .y = to_i16_checked(dst_y1),
+ .x = to_i16_checked(dst.x),
+ .y = to_i16_checked(dst.y),
.width = tmpew,
.height = tmpeh,
};
@@ -207,15 +342,17 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
}
}
}
+ if (mask_allocated) {
+ xcb_render_free_picture(xd->base.c, mask_pict);
+ }
pixman_region32_fini(&reg);
}
-static void compose(backend_t *base, void *img_data,
- int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+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) {
- // TODO(dccsillag): use dst_{x,y}2
struct _xrender_data *xd = (void *)base;
- return compose_impl(xd, img_data, dst_x1, dst_y1, dst_x2, dst_y2, reg_paint, reg_visible, xd->back[2]);
+ return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
+ xd->back[2]);
}
static void fill(backend_t *base, struct color c, const region_t *clip) {
@@ -236,8 +373,8 @@ static void fill(backend_t *base, struct color c, const region_t *clip) {
.height = to_u16_checked(extent->y2 - extent->y1)}});
}
-static bool blur(backend_t *backend_data, double opacity, void *ctx_,
- const region_t *reg_blur, const region_t *reg_visible) {
+static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask,
+ coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
struct _xrender_blur_context *bctx = ctx_;
if (bctx->method == BLUR_METHOD_NONE) {
return true;
@@ -288,7 +425,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_,
pixman_region32_fini(&clip);
xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0];
- auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)];
+ auto mask_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)];
+ bool mask_allocated = false;
+ if (mask != NULL) {
+ mask_pict = process_mask(xd, mask, opacity != 1.0 ? mask_pict : XCB_NONE,
+ &mask_allocated);
+ }
int current = 0;
x_set_picture_clip_region(c, src_pict, 0, 0, &reg_op_resized);
@@ -324,11 +466,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_,
} else {
x_set_picture_clip_region(c, xd->back[2], 0, 0, &reg_op);
// This is the last pass, and we are doing more than 1 pass
- xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict,
- alpha_pict, xd->back[2], 0, 0, 0, 0,
- to_i16_checked(extent_resized->x1),
- to_i16_checked(extent_resized->y1),
- width_resized, height_resized);
+ xcb_render_composite(
+ c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2],
+ 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
+ to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
+ to_i16_checked(extent_resized->x1),
+ to_i16_checked(extent_resized->y1), width_resized, height_resized);
}
// reset filter
@@ -344,8 +487,10 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_,
if (i == 1) {
x_set_picture_clip_region(c, xd->back[2], 0, 0, &reg_op);
xcb_render_composite(
- c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0,
- 0, 0, to_i16_checked(extent_resized->x1),
+ c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0,
+ to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
+ to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
+ to_i16_checked(extent_resized->x1),
to_i16_checked(extent_resized->y1), width_resized, height_resized);
}
@@ -366,11 +511,11 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
return NULL;
}
- auto img = ccalloc(1, struct backend_image);
+ auto img = ccalloc(1, struct xrender_image);
auto inner = ccalloc(1, struct _xrender_image_data_inner);
inner->depth = (uint8_t)fmt.visual_depth;
- inner->width = img->ewidth = r->width;
- inner->height = img->eheight = r->height;
+ inner->width = img->base.ewidth = r->width;
+ inner->height = img->base.eheight = r->height;
inner->pixmap = pixmap;
inner->has_alpha = fmt.alpha_size != 0;
inner->pict =
@@ -379,8 +524,9 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
inner->visual = fmt.visual;
inner->refcount = 1;
- img->inner = (struct backend_image_inner_base *)inner;
- img->opacity = 1;
+ img->base.inner = (struct backend_image_inner_base *)inner;
+ img->base.opacity = 1;
+ img->rounded_rectangle = NULL;
free(r);
if (inner->pict == XCB_NONE) {
@@ -397,11 +543,28 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne
}
free(inner);
}
+
+static void
+release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_cache *cache) {
+ if (!cache) {
+ return;
+ }
+
+ assert(cache->refcount > 0);
+ cache->refcount--;
+ if (cache->refcount == 0) {
+ xcb_free_pixmap(base->c, cache->p);
+ free(cache);
+ }
+}
+
static void release_image(backend_t *base, void *image) {
- struct backend_image *img = image;
- img->inner->refcount--;
- if (img->inner->refcount == 0) {
- release_image_inner(base, (void *)img->inner);
+ struct xrender_image *img = image;
+ release_rounded_corner_cache(base, img->rounded_rectangle);
+ img->rounded_rectangle = NULL;
+ img->base.inner->refcount -= 1;
+ if (img->base.inner->refcount == 0) {
+ release_image_inner(base, (void *)img->base.inner);
}
free(img);
}
@@ -523,6 +686,52 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
return new_inner;
}
+static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
+ struct _xrender_data *xd = (void *)base;
+ // Give the mask a 1 pixel wide border to emulate the clamp to border behavior of
+ // OpenGL textures.
+ auto w16 = to_u16_checked(size.width + 2);
+ auto h16 = to_u16_checked(size.height + 2);
+ auto inner =
+ new_inner(base, size.width + 2, size.height + 2,
+ x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32);
+ xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT,
+ (uint32_t[]){XCB_RENDER_REPEAT_PAD});
+ const rect_t *extent = pixman_region32_extents((region_t *)reg);
+ x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg);
+ xcb_render_fill_rectangles(
+ base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
+ (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1,
+ (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1),
+ .y = to_i16_checked(extent->y1 + 1),
+ .width = to_u16_checked(extent->x2 - extent->x1),
+ .height = to_u16_checked(extent->y2 - extent->y1)}});
+ x_clear_picture_clip_region(xd->base.c, inner->pict);
+
+ // Paint the border transparent
+ xcb_render_fill_rectangles(
+ base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
+ (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4,
+ (xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1},
+ {.x = 0, .y = 0, .width = 1, .height = h16},
+ {.x = 0, .y = (short)(h16 - 1), .width = w16, .height = 1},
+ {.x = (short)(w16 - 1), .y = 0, .width = 1, .height = h16}});
+ inner->refcount = 1;
+
+ auto img = ccalloc(1, struct xrender_image);
+ img->base.eheight = size.height + 2;
+ img->base.ewidth = size.width + 2;
+ img->base.border_width = 0;
+ img->base.color_inverted = false;
+ img->base.corner_radius = 0;
+ img->base.max_brightness = 1;
+ img->base.opacity = 1;
+ img->base.dim = 0;
+ img->base.inner = (struct backend_image_inner_base *)inner;
+ img->rounded_rectangle = NULL;
+ return img;
+}
+
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
if (img->inner->refcount == 1) {
return true;
@@ -648,30 +857,6 @@ static void get_blur_size(void *blur_context, int *width, int *height) {
*height = ctx->resize_height;
}
-static bool
-read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) {
- auto xd = (struct _xrender_data *)backend_data;
- auto img = (struct backend_image *)image_data;
- auto inner = (struct _xrender_image_data_inner *)img->inner;
-
- auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap,
- to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L);
-
- if (!r) {
- return false;
- }
-
- // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the
- // Xserver codebase.
- uint8_t *pixels = xcb_get_image_data(r);
- output->blue = pixels[0] / 255.0;
- output->green = pixels[1] / 255.0;
- output->red = pixels[2] / 255.0;
- output->alpha = pixels[3] / 255.0;
-
- return true;
-}
-
static backend_t *backend_xrender_init(session_t *ps) {
auto xd = ccalloc(1, struct _xrender_data);
init_backend_base(&xd->base, ps);
@@ -751,6 +936,29 @@ err:
return NULL;
}
+void *clone_image(backend_t *base attr_unused, const void *image_data,
+ const region_t *reg_visible attr_unused) {
+ auto new_img = ccalloc(1, struct xrender_image);
+ *new_img = *(struct xrender_image *)image_data;
+ new_img->base.inner->refcount++;
+ if (new_img->rounded_rectangle) {
+ new_img->rounded_rectangle->refcount++;
+ }
+ return new_img;
+}
+
+static bool
+set_image_property(backend_t *base, enum image_properties op, void *image, void *args) {
+ auto xrimg = (struct xrender_image *)image;
+ if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
+ ((double *)args)[0] != xrimg->base.corner_radius) {
+ // Free cached rounded rectangle if corner radius changed
+ release_rounded_corner_cache(base, xrimg->rounded_rectangle);
+ xrimg->rounded_rectangle = NULL;
+ }
+ return default_set_image_property(base, op, image, args);
+}
+
struct backend_operations xrender_ops = {
.init = backend_xrender_init,
.deinit = deinit,
@@ -760,7 +968,10 @@ struct backend_operations xrender_ops = {
.fill = fill,
.bind_pixmap = bind_pixmap,
.release_image = release_image,
+ .create_shadow_context = default_create_shadow_context,
+ .destroy_shadow_context = default_destroy_shadow_context,
.render_shadow = default_backend_render_shadow,
+ .make_mask = make_mask,
//.prepare_win = prepare_win,
//.release_win = release_win,
.is_image_transparent = default_is_image_transparent,
@@ -768,9 +979,8 @@ struct backend_operations xrender_ops = {
.max_buffer_age = 2,
.image_op = image_op,
- .read_pixel = read_pixel,
- .clone_image = default_clone_image,
- .set_image_property = default_set_image_property,
+ .clone_image = clone_image,
+ .set_image_property = set_image_property,
.create_blur_context = create_blur_context,
.destroy_blur_context = destroy_blur_context,
.get_blur_size = get_blur_size,
diff --git a/src/c2.c b/src/c2.c
index 3500f7b..80ecb24 100644
--- a/src/c2.c
+++ b/src/c2.c
@@ -579,8 +579,8 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) {
// Copy target name out
int tgtlen = 0;
- for (; pattern[offset] &&
- (isalnum((unsigned char)pattern[offset]) || '_' == pattern[offset]);
+ for (; pattern[offset] && (isalnum((unsigned char)pattern[offset]) ||
+ '_' == pattern[offset] || '.' == pattern[offset]);
++offset) {
++tgtlen;
}
@@ -852,6 +852,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult)
C2H_SKIP_SPACES();
}
+ if (raw == true) {
+ log_warn("Raw string patterns has been deprecated. pos %d", offset);
+ }
+
// Check for delimiters
if (pattern[offset] == '\"' || pattern[offset] == '\'') {
pleaf->ptntype = C2_L_PTSTRING;
@@ -886,11 +890,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult)
case 'v': *(ptptnstr++) = '\v'; break;
case 'o':
case 'x': {
- char *tstr = strndup(pattern + offset + 1, 2);
+ scoped_charp tstr = strndup(pattern + offset + 1, 2);
char *pstr = NULL;
long val = strtol(
tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
- free(tstr);
if (pstr != &tstr[2] || val <= 0)
c2_error("Invalid octal/hex escape "
"sequence.");
@@ -1148,11 +1151,16 @@ static void c2_free(c2_ptr_t p) {
/**
* Free a condition tree in c2_lptr_t.
*/
-c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) {
- if (!lp)
+c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
+ if (!lp) {
return NULL;
+ }
c2_lptr_t *pnext = lp->next;
+ if (f) {
+ f(lp->data);
+ }
+ lp->data = NULL;
c2_free(lp->ptr);
free(lp);
@@ -1324,13 +1332,13 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
switch (pleaf->ptntype) {
// Deal with integer patterns
case C2_L_PTINT: {
- long *targets = NULL;
- long *targets_free = NULL;
+ long long *targets = NULL;
+ long long *targets_free = NULL;
size_t ntargets = 0;
// Get the value
// A predefined target
- long predef_target = 0;
+ long long predef_target = 0;
if (pleaf->predef != C2_L_PUNDEFINED) {
*perr = false;
switch (pleaf->predef) {
@@ -1379,7 +1387,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
if (ntargets > 0) {
- targets = targets_free = ccalloc(ntargets, long);
+ targets = targets_free = ccalloc(ntargets, long long);
*perr = false;
for (size_t i = 0; i < ntargets; ++i) {
targets[i] = winprop_get_int(prop, i);
@@ -1395,7 +1403,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
// Do comparison
bool res = false;
for (size_t i = 0; i < ntargets; ++i) {
- long tgt = targets[i];
+ long long tgt = targets[i];
switch (pleaf->op) {
case C2_L_OEXISTS:
res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true);
@@ -1672,3 +1680,21 @@ bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condl
return false;
}
+
+/// Iterate over all conditions in a condition linked list. Call the callback for each of
+/// the conditions. If the callback returns true, the iteration stops early.
+///
+/// Returns whether the iteration was stopped early.
+bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) {
+ for (auto i = condlist; i; i = i->next) {
+ if (cb(i, data)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Return user data stored in a condition.
+void *c2_list_get_data(const c2_lptr_t *condlist) {
+ return condlist->data;
+}
diff --git a/src/c2.h b/src/c2.h
index d6b1d37..a7ddd39 100644
--- a/src/c2.h
+++ b/src/c2.h
@@ -12,15 +12,31 @@
#pragma once
#include <stdbool.h>
+#include <stddef.h>
typedef struct _c2_lptr c2_lptr_t;
typedef struct session session_t;
struct managed_win;
+typedef void (*c2_userdata_free)(void *);
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
-c2_lptr_t *c2_free_lptr(c2_lptr_t *lp);
+c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f);
-bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata);
+bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
+ void **pdata);
bool c2_list_postprocess(session_t *ps, c2_lptr_t *list);
+typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data);
+bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data);
+/// Return user data stored in a condition.
+void *c2_list_get_data(const c2_lptr_t *condlist);
+
+/**
+ * Destroy a condition list.
+ */
+static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) {
+ while ((*pcondlst = c2_free_lptr(*pcondlst, f))) {
+ }
+ *pcondlst = NULL;
+}
diff --git a/src/cache.c b/src/cache.c
index 1ffb31c..ce9c501 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1,8 +1,8 @@
#include <uthash.h>
+#include "cache.h"
#include "compiler.h"
#include "utils.h"
-#include "cache.h"
struct cache_entry {
char *key;
diff --git a/src/common.h b/src/common.h
index b7f2fe0..809d4e5 100644
--- a/src/common.h
+++ b/src/common.h
@@ -36,9 +36,9 @@
#include <X11/Xlib.h>
#include <ev.h>
#include <pixman.h>
-#include <xcb/xproto.h>
#include <xcb/render.h>
#include <xcb/sync.h>
+#include <xcb/xproto.h>
#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"
@@ -130,6 +130,14 @@ typedef struct _latom {
struct _latom *next;
} latom_t;
+struct shader_info {
+ char *key;
+ char *source;
+ void *backend_shader;
+ uint64_t attributes;
+ UT_hash_handle hh;
+};
+
/// Structure containing all necessary data for a session.
typedef struct session {
// === Event handlers ===
@@ -141,13 +149,10 @@ typedef struct session {
ev_timer fade_timer;
/// Timer for animations
ev_timer animation_timer;
- /// Timer for delayed drawing, right now only used by
- /// swopti
- ev_timer delayed_draw_timer;
/// 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;
@@ -155,6 +160,8 @@ typedef struct session {
ev_signal usr1_signal;
/// Signal handler for SIGINT
ev_signal int_signal;
+
+ // === Backend related ===
/// backend data
backend_t *backend_data;
/// backend blur context
@@ -165,6 +172,8 @@ typedef struct session {
void *file_watch_handle;
/// libev mainloop
struct ev_loop *loop;
+ /// Shaders
+ struct shader_info *shaders;
// === Display related ===
/// Whether the X server is grabbed by us
@@ -191,9 +200,7 @@ typedef struct session {
int root_desktop_num;
/// Desktop switch direction
int root_desktop_switch_direction;
- // Damage of root window.
- // Damage root_damage;
- /// X Composite overlay window. Used if <code>--paint-on-overlay</code>.
+ /// X Composite overlay window.
xcb_window_t overlay;
/// The target window for debug mode
xcb_window_t debug_window;
@@ -237,7 +244,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.
@@ -251,7 +258,7 @@ typedef struct session {
/// Pre-generated alpha pictures.
xcb_render_picture_t *alpha_picts;
/// Time of last fading. In milliseconds.
- long fade_time;
+ long long fade_time;
/// Time of last window animation step. In milliseconds.
long animation_time;
/// Head pointer of the error ignore linked list.
@@ -299,17 +306,13 @@ typedef struct session {
xcb_render_picture_t cshadow_picture;
/// 1x1 white Picture.
xcb_render_picture_t white_picture;
- /// Gaussian map of shadow.
- struct conv *gaussian_map;
+ /// Backend shadow context.
+ struct backend_shadow_context *shadow_context;
// for shadow precomputation
/// A region in which shadow is not painted on.
region_t shadow_exclude_reg;
// === Software-optimization-related ===
- /// Currently used refresh rate.
- int refresh_rate;
- /// Interval between refresh in nanoseconds.
- long refresh_intv;
/// Nanosecond offset of the first painting.
long paint_tm_offset;
diff --git a/src/compiler.h b/src/compiler.h
index f146bd2..00da4cb 100644
--- a/src/compiler.h
+++ b/src/compiler.h
@@ -85,6 +85,12 @@
# define fallthrough()
#endif
+#if __has_attribute(cleanup)
+# define cleanup(func) __attribute__((cleanup(func)))
+#else
+# error "Compiler is missing cleanup attribute"
+#endif
+
#if __STDC_VERSION__ >= 201112L
# define attr_noret _Noreturn
#else
@@ -102,7 +108,7 @@
#endif
#ifndef __has_include
-# define __has_include(x) 0
+#define __has_include(x) 0
#endif
#if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>)
diff --git a/src/config.c b/src/config.c
index 1d58f00..cb5e08a 100644
--- a/src/config.c
+++ b/src/config.c
@@ -3,13 +3,21 @@
// Copyright (c) 2013 Richard Grenville <[email protected]>
#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
+#include <test.h>
+
#include "c2.h"
#include "common.h"
#include "compiler.h"
@@ -23,6 +31,98 @@
#include "config.h"
+const char *xdg_config_home(void) {
+ char *xdgh = getenv("XDG_CONFIG_HOME");
+ char *home = getenv("HOME");
+ const char *default_dir = "/.config";
+
+ if (!xdgh) {
+ if (!home) {
+ return NULL;
+ }
+
+ xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
+
+ strcpy(xdgh, home);
+ strcat(xdgh, default_dir);
+ } else {
+ xdgh = strdup(xdgh);
+ }
+
+ return xdgh;
+}
+
+char **xdg_config_dirs(void) {
+ char *xdgd = getenv("XDG_CONFIG_DIRS");
+ size_t count = 0;
+
+ if (!xdgd) {
+ xdgd = "/etc/xdg";
+ }
+
+ for (int i = 0; xdgd[i]; i++) {
+ if (xdgd[i] == ':') {
+ count++;
+ }
+ }
+
+ // Store the string and the result pointers together so they can be
+ // freed together
+ char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
+ auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
+ auto path = dirs;
+
+ for (size_t i = 0; i < count; i++) {
+ dir_list[i] = path;
+ path = strchr(path, ':');
+ *path = '\0';
+ path++;
+ }
+ dir_list[count] = path;
+
+ size_t fill = 0;
+ for (size_t i = 0; i <= count; i++) {
+ if (dir_list[i][0] == '/') {
+ dir_list[fill] = dir_list[i];
+ fill++;
+ }
+ }
+
+ dir_list[fill] = NULL;
+
+ return dir_list;
+}
+
+TEST_CASE(xdg_config_dirs) {
+ auto old_var = getenv("XDG_CONFIG_DIRS");
+ if (old_var) {
+ old_var = strdup(old_var);
+ }
+ unsetenv("XDG_CONFIG_DIRS");
+
+ auto result = xdg_config_dirs();
+ TEST_STREQUAL(result[0], "/etc/xdg");
+ TEST_EQUAL(result[1], NULL);
+ free(result);
+
+ setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
+ result = xdg_config_dirs();
+ TEST_STREQUAL(result[0], "/etc/xdg");
+ TEST_STREQUAL(result[1], "/");
+ TEST_EQUAL(result[2], NULL);
+ free(result);
+
+ setenv("XDG_CONFIG_DIRS", ":", 1);
+ result = xdg_config_dirs();
+ TEST_EQUAL(result[0], NULL);
+ free(result);
+
+ if (old_var) {
+ setenv("XDG_CONFIG_DIRS", old_var, 1);
+ free(old_var);
+ }
+}
+
/**
* Parse a long number.
*/
@@ -468,6 +568,114 @@ bool parse_rule_corners(c2_lptr_t **res, const char *src) {
return c2_parse(res, endptr, (void *)val);
}
+/// Search for auxiliary file under a base directory
+static char *locate_auxiliary_file_at(const char *base, const char *scope, const char *file) {
+ scoped_charp path = mstrjoin(base, scope);
+ mstrextend(&path, "/");
+ mstrextend(&path, file);
+ if (access(path, O_RDONLY) == 0) {
+ // Canonicalize path to avoid duplicates
+ char *abspath = realpath(path, NULL);
+ return abspath;
+ }
+ return NULL;
+}
+
+/**
+ * Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
+ * file.
+ *
+ * Follows the XDG specification to search for the shader file in configuration locations.
+ *
+ * The search order is:
+ * 1) If an absolute path is given, use it directly.
+ * 2) Search for the file directly under `include_dir`.
+ * 3) Search for the file in the XDG configuration directories, under path
+ * /picom/<scope>/
+ */
+char *locate_auxiliary_file(const char *scope, const char *path, const char *include_dir) {
+ if (!path || strlen(path) == 0) {
+ return NULL;
+ }
+
+ // Filename is absolute path, so try to load from there
+ if (path[0] == '/') {
+ if (access(path, O_RDONLY) == 0) {
+ return realpath(path, NULL);
+ }
+ }
+
+ // First try to load file from the include directory (i.e. relative to the
+ // config file)
+ if (include_dir && strlen(include_dir)) {
+ char *ret = locate_auxiliary_file_at(include_dir, "", path);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ // Fall back to searching in user config directory
+ scoped_charp picom_scope = mstrjoin("/picom/", scope);
+ scoped_charp config_home = (char *)xdg_config_home();
+ char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
+ if (ret) {
+ return ret;
+ }
+
+ // Fall back to searching in system config directory
+ auto config_dirs = xdg_config_dirs();
+ for (int i = 0; config_dirs[i]; i++) {
+ ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
+ if (ret) {
+ free(config_dirs);
+ return ret;
+ }
+ }
+ free(config_dirs);
+
+ return ret;
+}
+
+/**
+ * Parse a list of window shader rules.
+ */
+bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *include_dir) {
+ if (!src) {
+ return false;
+ }
+
+ // Find custom shader terminator
+ const char *endptr = strchr(src, ':');
+ if (!endptr) {
+ log_error("Custom shader terminator not found: %s", src);
+ return false;
+ }
+
+ // Parse and create custom shader
+ scoped_charp untrimed_shader_source = strdup(src);
+ if (!untrimed_shader_source) {
+ return false;
+ }
+ auto source_end = strchr(untrimed_shader_source, ':');
+ *source_end = '\0';
+
+ size_t length;
+ char *tmp = (char *)trim_both(untrimed_shader_source, &length);
+ tmp[length] = '\0';
+ char *shader_source = NULL;
+
+ if (strcasecmp(tmp, "default") != 0) {
+ shader_source = locate_auxiliary_file("shaders", tmp, include_dir);
+ if (!shader_source) {
+ log_error("Custom shader file \"%s\" not found for rule: %s", tmp, src);
+ free(shader_source);
+ return false;
+ }
+ }
+
+ return c2_parse(res, ++endptr, (void *)shader_source);
+}
+
/**
* Add a pattern to a condition linked list.
*/
@@ -578,7 +786,8 @@ 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
*opt = (struct options){
- .backend = BKEND_GLX,
+ .backend = BKEND_XRENDER,
+ .legacy_backends = false,
.glx_no_stencil = false,
.mark_wmwin_focused = false,
.mark_ovredir_focused = false,
@@ -594,14 +803,12 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.benchmark_wid = XCB_NONE,
.logpath = NULL,
- .refresh_rate = 0,
- .sw_opti = false,
.use_damage = true,
.shadow_red = 0.0,
.shadow_green = 0.0,
.shadow_blue = 0.0,
- .shadow_radius = 12,
+ .shadow_radius = 18,
.shadow_offset_x = -15,
.shadow_offset_y = -15,
.shadow_opacity = .75,
@@ -610,18 +817,20 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.xinerama_shadow_crop = false,
.shadow_clip_list = NULL,
- .corner_radius = 12,
+ .corner_radius = 0,
- .fade_in_step = 0.03,
+ .fade_in_step = 0.028,
.fade_out_step = 0.03,
.fade_delta = 10,
.no_fading_openclose = false,
.no_fading_destroyed_argb = false,
.fade_blacklist = NULL,
+ // Picom Allusive
+
.animations = true,
.animation_for_open_window = OPEN_WINDOW_ANIMATION_ZOOM,
- .animation_for_transient_window = OPEN_WINDOW_ANIMATION_ZOOM,
+ .animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_unmap_window = OPEN_WINDOW_ANIMATION_ZOOM,
.animation_for_workspace_switch_in = OPEN_WINDOW_ANIMATION_ZOOM,
.animation_for_workspace_switch_out = OPEN_WINDOW_ANIMATION_ZOOM,
@@ -631,6 +840,11 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.animation_delta = 10,
.animation_force_steps = false,
.animation_clamping = true,
+ .animation_open_blacklist = NULL,
+ .animation_unmap_blacklist = NULL,
+
+ .corner_rules = NULL,
+ .blur_rules = NULL,
.inactive_opacity = 1.0,
.inactive_opacity_override = false,
@@ -647,6 +861,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.blur_background_blacklist = NULL,
.blur_kerns = NULL,
.blur_kernel_count = 0,
+ .window_shader_fg = NULL,
+ .window_shader_fg_rules = NULL,
.inactive_dim = 0.0,
.inactive_dim_fixed = false,
.invert_color_list = NULL,
@@ -661,11 +877,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.track_leader = false,
- .rounded_corners_blacklist = NULL,
-
- .animation_open_blacklist = NULL,
- .animation_unmap_blacklist = NULL,
- .corner_rules = NULL
+ .rounded_corners_blacklist = NULL
};
// clang-format on
diff --git a/src/config.h b/src/config.h
index 0ed0357..84418d1 100644
--- a/src/config.h
+++ b/src/config.h
@@ -15,6 +15,8 @@
#include <xcb/xcb.h>
#include <xcb/xfixes.h>
+#include "uthash_extra.h"
+
#ifdef CONFIG_LIBCONFIG
#include <libconfig.h>
#endif
@@ -34,6 +36,7 @@ enum backend {
BKEND_GLX,
BKEND_XR_GLX_HYBRID,
BKEND_DUMMY,
+ BKEND_EGL,
NUM_BKEND,
};
@@ -100,8 +103,8 @@ typedef struct options {
/// Render to a separate window instead of taking over the screen
bool debug_mode;
// === General ===
- /// Use the experimental new backends?
- bool experimental_backends;
+ /// Use the legacy backends?
+ bool legacy_backends;
/// Path to write PID to.
char *write_pid_path;
/// The backend in use.
@@ -152,10 +155,6 @@ typedef struct options {
win_option_t wintype_option[NUM_WINTYPES];
// === VSync & software optimization ===
- /// User-specified refresh rate.
- int refresh_rate;
- /// Whether to enable refresh-rate-based software optimization.
- bool sw_opti;
/// VSync method to use;
bool vsync;
/// Whether to use glFinish() instead of glFlush() for (possibly) better
@@ -221,12 +220,10 @@ typedef struct options {
bool animation_force_steps;
/// Whether to clamp animations
bool animation_clamping;
- /// TODO: window animation blacklist
- /// TODO: open/close animations
// === Opacity ===
/// Default opacity for inactive windows.
- /// 32-bit integer with the format of _NET_WM_OPACITY.
+ /// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY.
double inactive_opacity;
/// Default opacity for inactive windows.
double active_opacity;
@@ -236,8 +233,8 @@ typedef struct options {
/// Frame opacity. Relative to window opacity, also affects shadow
/// opacity.
double frame_opacity;
- /// Whether to detect _NET_WM_OPACITY on client windows. Used on window
- /// managers that don't pass _NET_WM_OPACITY to frame windows.
+ /// Whether to detect _NET_WM_WINDOW_OPACITY on client windows. Used on window
+ /// managers that don't pass _NET_WM_WINDOW_OPACITY to frame windows.
bool detect_client_opacity;
// === Other window processing ===
@@ -261,6 +258,10 @@ typedef struct options {
struct conv **blur_kerns;
/// Number of convolution kernels
int blur_kernel_count;
+ /// Custom fragment shader for painting windows
+ char *window_shader_fg;
+ /// Rules to change custom fragment shader for painting windows.
+ c2_lptr_t *window_shader_fg_rules;
/// How much to dim an inactive window. 0.0 - 1.0, 0 to disable.
double inactive_dim;
/// Whether to use fixed inactive dim opacity, instead of deciding
@@ -270,8 +271,14 @@ typedef struct options {
c2_lptr_t *invert_color_list;
/// Rules to change window opacity.
c2_lptr_t *opacity_rules;
-
+ // Rules for setting corner radius
c2_lptr_t *corner_rules;
+ // Rules to make windows use blur
+ c2_lptr_t *blur_rules;
+ // Rules to exclude windows from having a open animation
+ c2_lptr_t *animation_open_blacklist;
+ // Rules to exclude windows from having a unmap animation
+ c2_lptr_t *animation_unmap_blacklist;
/// Limit window brightness
double max_brightness;
// Radius of rounded window corners
@@ -279,10 +286,6 @@ typedef struct options {
/// Rounded corners blacklist. A linked list of conditions.
c2_lptr_t *rounded_corners_blacklist;
- c2_lptr_t *animation_open_blacklist;
-
- c2_lptr_t *animation_unmap_blacklist;
-
// === Focus related ===
/// Whether to try to detect WM windows and mark them as focused.
bool mark_wmwin_focused;
@@ -307,6 +310,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];
@@ -316,7 +322,9 @@ bool must_use parse_int(const char *, int *);
struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count);
bool must_use parse_geometry(session_t *, const char *, region_t *);
bool must_use parse_rule_opacity(c2_lptr_t **, const char *);
-bool must_use parse_rule_corners(c2_lptr_t **, const char *);
+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);
@@ -326,6 +334,9 @@ enum open_window_animation must_use parse_open_window_animation(const char *src)
bool condlst_add(c2_lptr_t **, const char *);
#ifdef CONFIG_LIBCONFIG
+const char *xdg_config_home(void);
+char **xdg_config_dirs(void);
+
/// Parse a configuration file
/// Returns the actually config_file name used, allocated on heap
/// Outputs:
diff --git a/src/config_libconfig.c b/src/config_libconfig.c
index 2494e0e..0df975b 100644
--- a/src/config_libconfig.c
+++ b/src/config_libconfig.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2012-2014 Richard Grenville <[email protected]>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -30,102 +31,11 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo
int ival;
int ret = config_lookup_bool(config, path, &ival);
- if (ret)
+ if (ret) {
*value = ival;
-
- return ret;
-}
-
-const char *xdg_config_home(void) {
- char *xdgh = getenv("XDG_CONFIG_HOME");
- char *home = getenv("HOME");
- const char *default_dir = "/.config";
-
- if (!xdgh) {
- if (!home) {
- return NULL;
- }
-
- xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
-
- strcpy(xdgh, home);
- strcat(xdgh, default_dir);
- } else {
- xdgh = strdup(xdgh);
- }
-
- return xdgh;
-}
-
-char **xdg_config_dirs(void) {
- char *xdgd = getenv("XDG_CONFIG_DIRS");
- size_t count = 0;
-
- if (!xdgd) {
- xdgd = "/etc/xdg";
- }
-
- for (int i = 0; xdgd[i]; i++) {
- if (xdgd[i] == ':') {
- count++;
- }
- }
-
- // Store the string and the result pointers together so they can be
- // freed together
- char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
- auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
- auto path = dirs;
-
- for (size_t i = 0; i < count; i++) {
- dir_list[i] = path;
- path = strchr(path, ':');
- *path = '\0';
- path++;
- }
- dir_list[count] = path;
-
- size_t fill = 0;
- for (size_t i = 0; i <= count; i++) {
- if (dir_list[i][0] == '/') {
- dir_list[fill] = dir_list[i];
- fill++;
- }
}
- dir_list[fill] = NULL;
-
- return dir_list;
-}
-
-TEST_CASE(xdg_config_dirs) {
- auto old_var = getenv("XDG_CONFIG_DIRS");
- if (old_var) {
- old_var = strdup(old_var);
- }
- unsetenv("XDG_CONFIG_DIRS");
-
- auto result = xdg_config_dirs();
- TEST_STREQUAL(result[0], "/etc/xdg");
- TEST_EQUAL(result[1], NULL);
- free(result);
-
- setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
- result = xdg_config_dirs();
- TEST_STREQUAL(result[0], "/etc/xdg");
- TEST_STREQUAL(result[1], "/");
- TEST_EQUAL(result[2], NULL);
- free(result);
-
- setenv("XDG_CONFIG_DIRS", ":", 1);
- result = xdg_config_dirs();
- TEST_EQUAL(result[0], NULL);
- free(result);
-
- if (old_var) {
- setenv("XDG_CONFIG_DIRS", old_var, 1);
- free(old_var);
- }
+ return ret;
}
/// Search for config file under a base directory
@@ -275,6 +185,36 @@ parse_cfg_condlst_corner(options_t *opt, const config_t *pcfg, const char *name)
}
}
+/**
+ * Parse a window shader rule list in configuration file.
+ */
+static inline void parse_cfg_condlst_shader(options_t *opt, const config_t *pcfg,
+ const char *name, const char *include_dir) {
+ config_setting_t *setting = config_lookup(pcfg, name);
+ if (setting) {
+ // Parse an array of options
+ if (config_setting_is_array(setting)) {
+ int i = config_setting_length(setting);
+ while (i--) {
+ if (!parse_rule_window_shader(
+ &opt->window_shader_fg_rules,
+ config_setting_get_string_elem(setting, i),
+ include_dir)) {
+ exit(1);
+ }
+ }
+ }
+ // Treat it as a single pattern if it's a string
+ else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
+ if (!parse_rule_window_shader(&opt->window_shader_fg_rules,
+ config_setting_get_string(setting),
+ include_dir)) {
+ exit(1);
+ }
+ }
+ }
+}
+
static inline void parse_wintype_config(const config_t *cfg, const char *member_name,
win_option_t *o, win_option_mask_t *mask) {
char *str = mstrjoin("wintypes.", member_name);
@@ -283,7 +223,7 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
int ival = 0;
const char *sval = NULL;
-
+
if (setting) {
if (config_setting_lookup_bool(setting, "shadow", &ival)) {
o->shadow = ival;
@@ -362,6 +302,11 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *conv_kern_hasneg,
win_option_mask_t *winopt_mask) {
+
+ const char *deprecation_message =
+ "option has been deprecated. Please remove it from your configuration file. "
+ "If you encounter any problems without this feature, please feel free to "
+ "open a bug report";
char *path = NULL;
FILE *f;
config_t cfg;
@@ -387,15 +332,14 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
config_set_options(&cfg, CONFIG_OPTION_ALLOW_OVERRIDES);
#endif
{
- // dirname() could modify the original string, thus we must pass a
- // copy
- char *path2 = strdup(path);
- char *parent = dirname(path2);
+ char *abspath = realpath(path, NULL);
+ char *parent = dirname(abspath); // path2 may be modified
- if (parent)
+ if (parent) {
config_set_include_dir(&cfg, parent);
+ }
- free(path2);
+ free(abspath);
}
{
@@ -414,15 +358,21 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
// Get options from the configuration file. We don't do range checking
// right now. It will be done later
+ // --dbus
+ lcfg_lookup_bool(&cfg, "dbus", &opt->dbus);
+
// -D (fade_delta)
- if (config_lookup_int(&cfg, "fade-delta", &ival))
+ if (config_lookup_int(&cfg, "fade-delta", &ival)) {
opt->fade_delta = ival;
+ }
// -I (fade_in_step)
- if (config_lookup_float(&cfg, "fade-in-step", &dval))
+ if (config_lookup_float(&cfg, "fade-in-step", &dval)) {
opt->fade_in_step = normalize_d(dval);
+ }
// -O (fade_out_step)
- if (config_lookup_float(&cfg, "fade-out-step", &dval))
+ if (config_lookup_float(&cfg, "fade-out-step", &dval)) {
opt->fade_out_step = normalize_d(dval);
+ }
// -r (shadow_radius)
config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius);
// -o (shadow_opacity)
@@ -432,11 +382,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
// -t (shadow_offset_y)
config_lookup_int(&cfg, "shadow-offset-y", &opt->shadow_offset_y);
// -i (inactive_opacity)
- if (config_lookup_float(&cfg, "inactive-opacity", &dval))
+ if (config_lookup_float(&cfg, "inactive-opacity", &dval)) {
opt->inactive_opacity = normalize_d(dval);
+ }
// --active_opacity
- if (config_lookup_float(&cfg, "active-opacity", &dval))
+ if (config_lookup_float(&cfg, "active-opacity", &dval)) {
opt->active_opacity = normalize_d(dval);
+ }
// --corner-radius
config_lookup_int(&cfg, "corner-radius", &opt->corner_radius);
// --rounded-corners-exclude
@@ -444,26 +396,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
// -e (frame_opacity)
config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity);
// -c (shadow_enable)
- if (config_lookup_bool(&cfg, "shadow", &ival))
- *shadow_enable = ival;
- // -C (no_dock_shadow)
- if (config_lookup_bool(&cfg, "no-dock-shadow", &ival)) {
- log_error("Option `no-dock-shadow` has been removed. Please use the "
- "wintype option `shadow` of `dock` instead.");
- goto err;
- }
- // -G (no_dnd_shadow)
- if (config_lookup_bool(&cfg, "no-dnd-shadow", &ival)) {
- log_error("Option `no-dnd-shadow` has been removed. Please use the "
- "wintype option `shadow` of `dnd` instead.");
- goto err;
- };
+ lcfg_lookup_bool(&cfg, "shadow", shadow_enable);
// -m (menu_opacity)
if (config_lookup_float(&cfg, "menu-opacity", &dval)) {
- log_warn("Option `menu-opacity` is deprecated, and will be "
- "removed.Please use the "
- "wintype option `opacity` of `popup_menu` and `dropdown_menu` "
- "instead.");
+ log_warn("Option `menu-opacity` is deprecated, and will be removed."
+ "Please use the wintype option `opacity` of `popup_menu`"
+ "and `dropdown_menu` instead.");
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = dval;
opt->wintype_option[WINTYPE_POPUP_MENU].opacity = dval;
winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true;
@@ -510,19 +448,16 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
// --detect-client-opacity
lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity);
// --refresh-rate
- if (config_lookup_int(&cfg, "refresh-rate", &opt->refresh_rate)) {
- if (opt->refresh_rate < 0) {
- log_warn("Invalid refresh rate %d, fallback to 0", opt->refresh_rate);
- opt->refresh_rate = 0;
- }
+ if (config_lookup_int(&cfg, "refresh-rate", &ival)) {
+ log_warn("The refresh-rate %s", deprecation_message);
}
// --vsync
if (config_lookup_string(&cfg, "vsync", &sval)) {
- opt->vsync = parse_vsync(sval);
- log_warn("vsync option will take a boolean from now on. \"%s\" is "
- "interpreted as \"%s\" for compatibility, but this will stop "
- "working soon",
- sval, opt->vsync ? "true" : "false");
+ bool parsed_vsync = parse_vsync(sval);
+ log_error("vsync option will take a boolean from now on. \"%s\" in "
+ "your configuration should be changed to \"%s\"",
+ sval, parsed_vsync ? "true" : "false");
+ goto err;
}
lcfg_lookup_bool(&cfg, "vsync", &opt->vsync);
// --backend
@@ -551,7 +486,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
opt->logpath = strdup(sval);
}
// --sw-opti
- lcfg_lookup_bool(&cfg, "sw-opti", &opt->sw_opti);
+ if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) {
+ log_warn("The sw-opti %s", deprecation_message);
+ }
// --use-ewmh-active-win
lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);
// --unredir-if-possible
@@ -574,6 +511,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
@@ -641,16 +581,18 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
// --focus-exclude
parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude");
+ // --invert-color-include
+ parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include");
+ // --blur-background-exclude
+ parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, "blur-background-exclude");
// animation exclude
parse_cfg_condlst(&cfg, &opt->animation_open_blacklist, "animation-open-exclude");
// animation exclude
parse_cfg_condlst(&cfg, &opt->animation_unmap_blacklist, "animation-unmap-exclude");
- // --corners-rule
+ // corners-rule
parse_cfg_condlst_corner(opt, &cfg, "corners-rule");
- // --invert-color-include
- parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include");
- // --blur-background-exclude
- parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, "blur-background-exclude");
+ // blur-rule
+ parse_cfg_condlst(&cfg, &opt->blur_rules, "blur-rule");
// --opacity-rule
parse_cfg_condlst_opct(opt, &cfg, "opacity-rule");
// --unredir-if-possible-exclude
@@ -701,6 +643,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
if (config_lookup_string(&cfg, "glx-swap-method", &sval)) {
char *endptr;
long val = strtol(sval, &endptr, 10);
+ bool should_remove = true;
if (*endptr || !(*sval)) {
// sval is not a number, or an empty string
val = -1;
@@ -708,12 +651,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
if (strcmp(sval, "undefined") != 0 && val != 0) {
// If not undefined, we will use damage and buffer-age to limit
// the rendering area.
- opt->use_damage = true;
+ should_remove = false;
}
- log_warn("glx-swap-method has been deprecated since v6, your setting "
- "\"%s\" should be %s.",
- sval,
- opt->use_damage ? "replaced by `use-damage = true`" : "removed");
+ log_error("glx-swap-method has been removed, your setting "
+ "\"%s\" should be %s.",
+ sval,
+ !should_remove ? "replaced by `use-damage = true`" : "removed");
+ goto err;
}
// --use-damage
lcfg_lookup_bool(&cfg, "use-damage", &opt->use_damage);
@@ -726,15 +670,20 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
opt->max_brightness = 1.0;
}
+ // --window-shader-fg
+ if (config_lookup_string(&cfg, "window-shader-fg", &sval)) {
+ opt->window_shader_fg =
+ locate_auxiliary_file("shaders", sval, config_get_include_dir(&cfg));
+ }
+
+ // --window-shader-fg-rule
+ parse_cfg_condlst_shader(opt, &cfg, "window-shader-fg-rule",
+ config_get_include_dir(&cfg));
+
// --glx-use-gpushader4
- if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival) && ival) {
- log_warn("glx-use-gpushader4 is deprecated since v6, please remove it "
- "from"
- "your config file");
- }
- // --xrender-sync
- if (config_lookup_bool(&cfg, "xrender-sync", &ival) && ival) {
- log_error("Please use xrender-sync-fence instead of xrender-sync.");
+ if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) {
+ log_error("glx-use-gpushader4 has been removed, please remove it "
+ "from your config file");
goto err;
}
// --xrender-sync-fence
@@ -743,21 +692,6 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval))
log_warn("\"clear-shadow\" is removed as an option, and is always"
" enabled now. Consider removing it from your config file");
- if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) {
- log_error("\"paint-on-overlay\" has been removed as an option, and "
- "the feature is enabled whenever possible");
- goto err;
- }
-
- if (config_lookup_float(&cfg, "alpha-step", &dval)) {
- log_error("\"alpha-step\" has been removed, compton now tries to make use"
- " of all alpha values");
- goto err;
- }
-
- const char *deprecation_message attr_unused =
- "has been removed. If you encounter problems "
- "without this feature, please feel free to open a bug report";
config_setting_t *blur_cfg = config_lookup(&cfg, "blur");
if (blur_cfg) {
diff --git a/src/dbus.c b/src/dbus.c
index 8d6094e..8b17b30 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -72,7 +72,11 @@ typedef uint32_t cdbus_enum_t;
cdbus_reply_errm((ps), dbus_message_new_error_printf( \
(srcmsg), (err_name), (err_format), ##__VA_ARGS__))
+#define PICOM_WINDOW_INTERFACE "picom.Window"
+#define PICOM_COMPOSITOR_INTERFACE "picom.Compositor"
+
static DBusHandlerResult cdbus_process(DBusConnection *conn, DBusMessage *m, void *);
+static DBusHandlerResult cdbus_process_windows(DBusConnection *c, DBusMessage *msg, void *ud);
static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data);
@@ -179,7 +183,12 @@ bool cdbus_init(session_t *ps, const char *uniq) {
dbus_error_free(&err);
goto fail;
}
- dbus_connection_add_filter(cd->dbus_conn, cdbus_process, ps, NULL);
+ dbus_connection_register_object_path(
+ cd->dbus_conn, CDBUS_OBJECT_NAME,
+ (DBusObjectPathVTable[]){{NULL, cdbus_process}}, ps);
+ dbus_connection_register_fallback(
+ cd->dbus_conn, CDBUS_OBJECT_NAME "/windows",
+ (DBusObjectPathVTable[]){{NULL, cdbus_process_windows}}, ps);
return true;
fail:
ps->dbus_data = NULL;
@@ -436,6 +445,56 @@ static bool cdbus_apdarg_wid(session_t *ps attr_unused, DBusMessage *msg, const
}
/**
+ * Callback to append a Window argument to a message as a variant.
+ */
+static bool
+cdbus_append_wid_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ assert(data);
+ cdbus_window_t val = *(const xcb_window_t *)data;
+
+ DBusMessageIter it, it2;
+ dbus_message_iter_init_append(msg, &it);
+ if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT,
+ CDBUS_TYPE_WINDOW_STR, &it2)) {
+ return false;
+ }
+ if (!dbus_message_iter_append_basic(&it2, CDBUS_TYPE_WINDOW, &val)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+ if (!dbus_message_iter_close_container(&it, &it2)) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append a bool argument to a message as a variant.
+ */
+static bool
+cdbus_append_bool_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ assert(data);
+
+ dbus_bool_t val = *(const bool *)data;
+ DBusMessageIter it, it2;
+ dbus_message_iter_init_append(msg, &it);
+ if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_BOOLEAN_AS_STRING, &it2)) {
+ return false;
+ }
+ if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_BOOLEAN, &val)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+ if (!dbus_message_iter_close_container(&it, &it2)) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
* Callback to append an cdbus_enum_t argument to a message.
*/
static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
@@ -466,6 +525,46 @@ cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *dat
}
/**
+ * Callback to append a string argument to a message as a variant.
+ */
+static bool
+cdbus_append_string_variant(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ const char *str = *(const char **)data;
+ if (!str) {
+ str = "";
+ }
+
+ DBusMessageIter it, it2;
+ dbus_message_iter_init_append(msg, &it);
+ if (!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_STRING_AS_STRING, &it2)) {
+ return false;
+ }
+ if (!dbus_message_iter_append_basic(&it2, DBUS_TYPE_STRING, &str)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+ if (!dbus_message_iter_close_container(&it, &it2)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool cdbus_append_empty_dict(session_t *ps attr_unused, DBusMessage *msg,
+ const void *data attr_unused) {
+ DBusMessageIter it, it2;
+ dbus_message_iter_init_append(msg, &it);
+ if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &it2)) {
+ return false;
+ }
+ if (!dbus_message_iter_close_container(&it, &it2)) {
+ return false;
+ }
+ return true;
+}
+
+/**
* Callback to append all window IDs to a message.
*/
static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data attr_unused) {
@@ -515,14 +614,14 @@ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data
* add an argument
* @param data data pointer to pass to the function
*/
-static bool cdbus_signal(session_t *ps, const char *name,
+static bool cdbus_signal(session_t *ps, const char *interface, const char *name,
bool (*func)(session_t *ps, DBusMessage *msg, const void *data),
const void *data) {
struct cdbus_data *cd = ps->dbus_data;
DBusMessage *msg = NULL;
// Create a signal
- msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, name);
+ msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, interface, name);
if (!msg) {
log_error("Failed to create D-Bus signal.");
return false;
@@ -551,8 +650,9 @@ static bool cdbus_signal(session_t *ps, const char *name,
/**
* Send a signal with a Window ID as argument.
*/
-static inline bool cdbus_signal_wid(session_t *ps, const char *name, xcb_window_t wid) {
- return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid);
+static inline bool
+cdbus_signal_wid(session_t *ps, const char *interface, const char *name, xcb_window_t wid) {
+ return cdbus_signal(ps, interface, name, cdbus_apdarg_wid, &wid);
}
/**
@@ -735,6 +835,84 @@ static bool cdbus_process_list_win(session_t *ps, DBusMessage *msg) {
/**
* Process a win_get D-Bus request.
*/
+static bool
+cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_t wid) {
+ const char *target = NULL;
+ const char *interface = NULL;
+ DBusError err = {};
+
+ if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &target, DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse argument of \"Get\" (%s).", err.message);
+ dbus_error_free(&err);
+ return false;
+ }
+
+ if (strcmp(interface, PICOM_WINDOW_INTERFACE)) {
+ return false;
+ }
+
+ auto w = find_managed_win(ps, wid);
+
+ if (!w) {
+ log_error("Window %#010x not found.", wid);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return true;
+ }
+
+#define cdbus_m_win_get_do(tgt, member, apdarg_func) \
+ if (!strcmp(#tgt, target)) { \
+ cdbus_reply(ps, msg, apdarg_func, &w->member); \
+ return true; \
+ }
+
+ if (!strcmp("Mapped", target)) {
+ cdbus_reply(ps, msg, cdbus_append_bool_variant,
+ (bool[]){win_is_mapped_in_x(w)});
+ return true;
+ }
+
+ if (!strcmp(target, "Id")) {
+ cdbus_reply(ps, msg, cdbus_append_wid_variant, &w->base.id);
+ return true;
+ }
+
+ // next
+ if (!strcmp("Next", target)) {
+ cdbus_window_t next_id = 0;
+ if (!list_node_is_last(&ps->window_stack, &w->base.stack_neighbour)) {
+ next_id = list_entry(w->base.stack_neighbour.next, struct win,
+ stack_neighbour)
+ ->id;
+ }
+ cdbus_reply(ps, msg, cdbus_append_wid_variant, &next_id);
+ return true;
+ }
+
+ cdbus_m_win_get_do(ClientWin, client_win, cdbus_append_wid_variant);
+ cdbus_m_win_get_do(Leader, leader, cdbus_append_wid_variant);
+ cdbus_m_win_get_do(Name, name, cdbus_append_string_variant);
+ if (!strcmp("Type", target)) {
+ cdbus_reply(ps, msg, cdbus_append_string_variant, &WINTYPES[w->window_type]);
+ return true;
+ }
+ if (!strcmp("RawFocused", target)) {
+ cdbus_reply(ps, msg, cdbus_append_bool_variant,
+ (bool[]){win_is_focused_raw(ps, w)});
+ return true;
+ }
+
+#undef cdbus_m_win_get_do
+
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+}
+
+/**
+ * Process a win_get D-Bus request.
+ */
static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
cdbus_window_t wid = XCB_NONE;
const char *target = NULL;
@@ -761,7 +939,10 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
return true; \
}
- cdbus_m_win_get_do(base.id, cdbus_reply_wid);
+ if (!strcmp(target, "id")) {
+ cdbus_reply_wid(ps, msg, w->base.id);
+ return true;
+ }
// next
if (!strcmp("next", target)) {
@@ -972,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;
}
@@ -1005,8 +1186,8 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum);
cdbus_m_opts_get_do(logpath, cdbus_reply_string);
- cdbus_m_opts_get_do(refresh_rate, cdbus_reply_int32);
- cdbus_m_opts_get_do(sw_opti, cdbus_reply_bool);
+ cdbus_m_opts_get_stub(refresh_rate, cdbus_reply_int32, 0);
+ cdbus_m_opts_get_stub(sw_opti, cdbus_reply_bool, false);
cdbus_m_opts_get_do(vsync, cdbus_reply_bool);
if (!strcmp("backend", target)) {
assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0]));
@@ -1207,6 +1388,21 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) {
" <method name='reset' />\n"
" <method name='repaint' />\n"
" </interface>\n"
+ " <interface name='" PICOM_COMPOSITOR_INTERFACE "'>\n"
+ " <signal name='WinAdded'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='WinDestroyed'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='WinMapped'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='WinUnmapped'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " </interface>\n"
+ " <node name='windows' />\n"
"</node>\n";
cdbus_reply_string(ps, msg, str_introspect);
@@ -1216,6 +1412,90 @@ static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) {
///@}
/**
+ * Process an D-Bus Introspect request, for /windows.
+ */
+static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *msg) {
+ static const char *str_introspect =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node>\n"
+ " <interface name='org.freedesktop.DBus.Introspectable'>\n"
+ " <method name='Introspect'>\n"
+ " <arg name='data' direction='out' type='s' />\n"
+ " </method>\n"
+ " </interface>\n";
+
+ char *ret = NULL;
+ mstrextend(&ret, str_introspect);
+
+ HASH_ITER2(ps->windows, w) {
+ assert(!w->destroyed);
+ if (!w->managed) {
+ continue;
+ }
+ char *tmp = NULL;
+ asprintf(&tmp, "<node name='%#010x'/>\n", w->id);
+ mstrextend(&ret, tmp);
+ free(tmp);
+ }
+ mstrextend(&ret, "</node>");
+
+ bool success = cdbus_reply_string(ps, msg, ret);
+ free(ret);
+ return success;
+}
+
+static bool cdbus_process_window_introspect(session_t *ps, DBusMessage *msg) {
+ // clang-format off
+ static const char *str_introspect =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node>\n"
+ " <interface name='org.freedesktop.DBus.Introspectable'>\n"
+ " <method name='Introspect'>\n"
+ " <arg name='data' direction='out' type='s' />\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name='org.freedesktop.DBus.Properties'>\n"
+ " <method name='Get'>\n"
+ " <arg type='s' name='interface_name' direction='in'/>\n"
+ " <arg type='s' name='property_name' direction='in'/>\n"
+ " <arg type='v' name='value' direction='out'/>\n"
+ " </method>\n"
+ " <method name='GetAll'>\n"
+ " <arg type='s' name='interface_name' direction='in'/>\n"
+ " <arg type='a{sv}' name='properties' direction='out'/>\n"
+ " </method>\n"
+ " <method name='Set'>\n"
+ " <arg type='s' name='interface_name' direction='in'/>\n"
+ " <arg type='s' name='property_name' direction='in'/>\n"
+ " <arg type='v' name='value' direction='in'/>\n"
+ " </method>\n"
+ " <signal name='PropertiesChanged'>\n"
+ " <arg type='s' name='interface_name'/>\n"
+ " <arg type='a{sv}' name='changed_properties'/>\n"
+ " <arg type='as' name='invalidated_properties'/>\n"
+ " </signal>\n"
+ " </interface>\n"
+ " <interface name='" PICOM_WINDOW_INTERFACE "'>\n"
+ " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Leader' access='read'/>\n"
+ " <property type='" CDBUS_TYPE_WINDOW_STR "' name='ClientWin' access='read'/>\n"
+ " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Id' access='read'/>\n"
+ " <property type='" CDBUS_TYPE_WINDOW_STR "' name='Next' access='read'/>\n"
+ " <property type='b' name='RawFocused' access='read'/>\n"
+ " <property type='b' name='Mapped' access='read'/>\n"
+ " <property type='s' name='Name' access='read'/>\n"
+ " <property type='s' name='Type' access='read'/>\n"
+ " </interface>\n"
+ "</node>\n";
+ // clang-format on
+
+ return cdbus_reply_string(ps, msg, str_introspect);
+}
+
+/**
* Process a message from D-Bus.
*/
static DBusHandlerResult
@@ -1299,42 +1579,122 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) {
return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
+/**
+ * Process a message from D-Bus, for /windows path.
+ */
+static DBusHandlerResult
+cdbus_process_windows(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) {
+ session_t *ps = ud;
+ bool handled = false;
+ const char *path = dbus_message_get_path(msg);
+ const char *last_segment = strrchr(path, '/');
+ if (last_segment == NULL) {
+ if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
+ !dbus_message_get_no_reply(msg))
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ bool is_root = strncmp(last_segment, "/windows", 8) == 0;
+ if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable",
+ "Introspect")) {
+ if (is_root) {
+ handled = cdbus_process_windows_root_introspect(ps, msg);
+ } else {
+ handled = cdbus_process_window_introspect(ps, msg);
+ }
+ goto finished;
+ }
+
+ if (!is_root) {
+ auto wid = (cdbus_window_t)strtol(last_segment + 1, NULL, 0);
+ if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties",
+ "GetAll")) {
+ handled = cdbus_reply(ps, msg, cdbus_append_empty_dict, NULL);
+ goto finished;
+ }
+
+ if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) {
+ handled = cdbus_process_window_property_get(ps, msg, wid);
+ goto finished;
+ }
+ }
+
+ if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") ||
+ dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) {
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) {
+ log_error("Error message of path \"%s\" "
+ "interface \"%s\", member \"%s\", error \"%s\"",
+ dbus_message_get_path(msg), dbus_message_get_interface(msg),
+ dbus_message_get_member(msg), dbus_message_get_error_name(msg));
+ } else {
+ log_error("Illegal message of type \"%s\", path \"%s\" "
+ "interface \"%s\", member \"%s\"",
+ cdbus_repr_msgtype(msg), dbus_message_get_path(msg),
+ dbus_message_get_interface(msg), dbus_message_get_member(msg));
+ }
+ if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
+ !dbus_message_get_no_reply(msg)) {
+ handled = cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
+ }
+
+finished:
+ if (!handled && dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL &&
+ !dbus_message_get_no_reply(msg)) {
+ handled =
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S);
+ }
+ return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
/** @name Core callbacks
*/
///@{
void cdbus_ev_win_added(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_added", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_added", w->id);
+ cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinAdded", w->id);
+ }
}
void cdbus_ev_win_destroyed(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_destroyed", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_destroyed", w->id);
+ cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", w->id);
+ }
}
void cdbus_ev_win_mapped(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_mapped", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_mapped", w->id);
+ cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinMapped", w->id);
+ }
}
void cdbus_ev_win_unmapped(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_unmapped", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_unmapped", w->id);
+ cdbus_signal_wid(ps, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", w->id);
+ }
}
void cdbus_ev_win_focusout(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_focusout", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusout", w->id);
+ }
}
void cdbus_ev_win_focusin(session_t *ps, struct win *w) {
struct cdbus_data *cd = ps->dbus_data;
- if (cd->dbus_conn)
- cdbus_signal_wid(ps, "win_focusin", w->id);
+ if (cd->dbus_conn) {
+ cdbus_signal_wid(ps, CDBUS_INTERFACE_NAME, "win_focusin", w->id);
+ }
}
//!@}
diff --git a/src/diagnostic.c b/src/diagnostic.c
index d275b1a..2cb3c8f 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -2,17 +2,17 @@
// Copyright (c) 2018 Yuxuan Shui <[email protected]>
#include <stdio.h>
-#include <xcb/xcb.h>
#include <xcb/composite.h>
+#include <xcb/xcb.h>
#include "backend/driver.h"
-#include "diagnostic.h"
+#include "common.h"
#include "config.h"
+#include "diagnostic.h"
#include "picom.h"
-#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/event.c b/src/event.c
index 5e4017f..beec195 100644
--- a/src/event.c
+++ b/src/event.c
@@ -508,7 +508,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
queue_redraw(ps);
}
- // If _NET_WM_OPACITY changes
+ // If _NET_WM_WINDOW_OPACITY changes
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY) {
auto w = find_managed_win(ps, ev->window) ?: find_toplevel(ps, ev->window);
if (w) {
diff --git a/src/kernel.c b/src/kernel.c
index 5151045..cbb5cd1 100644
--- a/src/kernel.c
+++ b/src/kernel.c
@@ -90,15 +90,20 @@ conv *gaussian_kernel(double r, int size) {
/// Estimate the element of the sum of the first row in a gaussian kernel with standard
/// deviation `r` and size `size`,
static inline double estimate_first_row_sum(double size, double r) {
+ // `factor` is integral of gaussian from -size to size
double factor = erf(size / r / sqrt(2));
+ // `a` is gaussian at (size, 0)
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
+ // The sum of the whole kernel is normalized to 1, i.e. each element is divided by
+ // factor sqaured. So the sum of the first row is a * factor / factor^2 = a /
+ // factor
return a / factor;
}
-/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius
-/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in
-/// the kernel are less than `row_limit` (up to certain precision).
-static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
+/// Pick a suitable gaussian kernel standard deviation for a given kernel size. The
+/// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of
+/// the rows in the kernel are less than `row_limit` (up to certain precision).
+double gaussian_kernel_std_for_size(double size, double row_limit) {
assert(size > 0);
if (row_limit >= 1.0 / 2.0 / size) {
return size * 2;
@@ -121,14 +126,14 @@ static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
/// transparent, so the transition from shadow to the background is smooth.
///
/// @param[in] shadow_radius the radius of the shadow
-conv *gaussian_kernel_autodetect_deviation(int shadow_radius) {
+conv *gaussian_kernel_autodetect_deviation(double shadow_radius) {
assert(shadow_radius >= 0);
- int size = shadow_radius * 2 + 1;
+ int size = (int)(shadow_radius * 2 + 1);
if (shadow_radius == 0) {
return gaussian_kernel(0, size);
}
- double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0);
+ double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0);
return gaussian_kernel(std, size);
}
diff --git a/src/kernel.h b/src/kernel.h
index 251d127..d1dd2ee 100644
--- a/src/kernel.h
+++ b/src/kernel.h
@@ -22,12 +22,15 @@ double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width,
/// `size`.
conv *gaussian_kernel(double r, int size);
+/// Estimate the best standard deviation for a give kernel size.
+double gaussian_kernel_std_for_size(double size, double row_limit);
+
/// Create a gaussian kernel with auto detected standard deviation. The choosen standard
/// deviation tries to make sure the outer most pixels of the shadow are completely
/// transparent.
///
/// @param[in] shadow_radius the radius of the shadow
-conv *gaussian_kernel_autodetect_deviation(int shadow_radius);
+conv *gaussian_kernel_autodetect_deviation(double shadow_radius);
/// preprocess kernels to make shadow generation faster
/// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive
diff --git a/src/log.c b/src/log.c
index 0b663e7..c8b158b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -340,7 +340,7 @@ static void
gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) {
auto g = (struct gl_string_marker_logger *)tgt;
// strip newlines at the end of the string
- while (len > 0 && str[len-1] == '\n') {
+ while (len > 0 && str[len - 1] == '\n') {
len--;
}
g->gl_string_marker((GLsizei)len, str);
diff --git a/src/meson.build b/src/meson.build
index 0a882f9..60d83a8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -59,7 +59,7 @@ endif
if get_option('opengl')
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
- deps += [dependency('gl', required: true)]
+ deps += [dependency('gl', required: true), dependency('egl', required: true)]
srcs += [ 'opengl.c' ]
endif
diff --git a/src/options.c b/src/options.c
index 785adb4..c102042 100644
--- a/src/options.c
+++ b/src/options.c
@@ -7,6 +7,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
#include <unistd.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
@@ -20,524 +22,301 @@
#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 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 "
+ "--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."},
+ {"animations", no_argument, 804, NULL, "Toggles Animations"},
+ {"animation-stiffness", required_argument, 805, NULL, "stiffness"},
+ {"animation-dampening", required_argument, 806, NULL, "dampening"},
+ {"animation-window-mass", required_argument, 807, NULL, "window mass"},
+ {"animation-clamping", no_argument, 808, NULL, "clamping"},
+ {"animation-for-open-window", required_argument, 809, NULL, "open window animation"},
+ {"animation-for-transient-window", required_argument, 810, NULL, "transient window animation"},
+ {"animation-for-unmap-window", required_argument, 811, NULL, "unmap window animation"},
+ {"corners-rule", required_argument, NULL, 812, "rounded corner rules"},
+ {"blur-rule", required_argument, NULL, 813, "blur rules"},
+ {"animation-open-exclude", required_argument, NULL, 814, "animation open exclude list"},
+ {"animation-unmap-exclude", required_argument, NULL, 815, "animation unmap exclude list"},
+};
+// 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-allusive (" COMPTON_VERSION ")\n"
- "Please report bugs to https://github.com/allusive-dev/picom-allusive\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"
- "--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-for-unmap-window\n"
- " Which animation to run when hiding (e.g. minimize) a window.\n"
- " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
- " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
- " `slide-in`, `slide-out`\n"
- " (default: auto).\n"
- "\n"
- "--animation-for-workspace-switch-in\n"
- " Which animation to run on switching workspace for windows\n"
- " comming into view.\n"
- " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n"
- " before doing the hide/show of windows\n"
- " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
- " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
- " `slide-in`, `slide-out`\n"
- " (default: auto).\n"
- "\n"
- "--animation-for-workspace-switch-out\n"
- " Which animation to run on switching workspace for windows\n"
- " going out of view.\n"
- " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n"
- " before doing the hide/show of windows\n"
- " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
- " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
- " `slide-in`, `slide-out`\n"
- " (default: auto).\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-delta\n"
- " The time between steps in animation, in milliseconds. (> 0, defaults to 10).\n"
- "\n"
- "--animation-force-steps\n"
- " Force animations to go step by step even if cpu usage is high \n"
- " (default: false)\n"
- "\n"
- "--animation-clamping\n"
- " Whether to clamp animations (default: true)\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_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_OPACITY on client windows, useful for window\n"
- " managers not passing _NET_WM_OPACITY of client windows to frame\n"
- " windows.\n"
- "\n"
- "--refresh-rate val\n"
- " Specify refresh rate of the screen. If not specified or 0, we\n"
- " will try detecting this with X RandR extension.\n"
- "\n"
- "--vsync\n"
- " Enable VSync\n"
- "\n"
- "--paint-on-overlay\n"
- " Painting on X Composite overlay window.\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. WM_TRANSIENT_FOR has\n"
- " higher priority if --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' require --experimental-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";
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:d:r:o:m:l:t:i:e:hscnfFCaSzGb";
-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'},
- {"no-dock-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'},
- {"no-dnd-shadow", no_argument, NULL, 'G'},
- {"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},
- {"alpha-step", required_argument, NULL, 271},
- {"dbe", no_argument, NULL, 272},
- {"paint-on-overlay", no_argument, NULL, 273},
- {"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", no_argument, NULL, 312},
- {"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},
- {"no-name-pixmap", no_argument, NULL, 320},
- {"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},
- {"experimental-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},
- {"animations", no_argument, NULL, 804},
- {"animation-stiffness", required_argument, NULL, 805},
- {"animation-dampening", required_argument, NULL, 806},
- {"animation-window-mass", required_argument, NULL, 807},
- {"animation-clamping", no_argument, NULL, 808},
- {"animation-for-open-window", required_argument, NULL, 809},
- {"animation-for-transient-window", required_argument, NULL, 810},
- {"animation-open-exclude", required_argument, NULL, 830},
- {"animation-unmap-exclude", required_argument, NULL, 831},
- {"corners-rule", required_argument, NULL, 840},
- // Must terminate with a NULL entry
- {NULL, 0, NULL, 0},
-};
+static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb";
/// 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
@@ -556,21 +335,11 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all
} else if (o == 'b') {
*fork = true;
- } else if (o == 'd') {
- log_error("-d is removed, please use the DISPLAY "
- "environment variable");
- goto err;
} 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 == 'S') {
- log_error("-S is no longer available");
- goto err;
- } else if (o == 320) {
- log_error("--no-name-pixmap is no longer available");
- goto err;
} else if (o == '?' || o == ':') {
usage(argv[0], 1);
goto err;
@@ -639,9 +408,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
// so assert(false) here
assert(false);
break;
- case 'd':
case 'b':
- case 'S':
case 314:
case 320:
// These options are handled by get_early_config()
@@ -650,15 +417,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
case 'I': opt->fade_in_step = normalize_d(atof(optarg)); break;
case 'O': opt->fade_out_step = normalize_d(atof(optarg)); break;
case 'c': shadow_enable = true; break;
- case 'C':
- log_error("Option `--no-dock-shadow`/`-C` has been removed. Please"
- " use the wintype option `shadow` of `dock` instead.");
- failed = true; break;
- case 'G':
- log_error("Option `--no-dnd-shadow`/`-G` has been removed. Please "
- "use the wintype option `shadow` of `dnd` instead.");
- failed = true; break;
case 'm':;
+ log_warn("--menu-opacity is deprecated, and will be removed."
+ "Please use the wintype option `opacity` of `popup_menu`"
+ "and `dropdown_menu` instead.");
double tmp;
tmp = normalize_d(atof(optarg));
winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true;
@@ -730,35 +492,30 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
P_CASEBOOL(266, shadow_ignore_shaped);
P_CASEBOOL(267, detect_rounded_corners);
P_CASEBOOL(268, detect_client_opacity);
- P_CASEINT(269, refresh_rate);
+ case 269:
+ log_warn("--refresh-rate has been deprecated, please remove it from"
+ "your command line options");
+ break;
case 270:
if (optarg) {
- opt->vsync = parse_vsync(optarg);
- log_warn("--vsync doesn't take argument anymore. \"%s\" "
- "is interpreted as \"%s\" for compatibility, but "
- "this will stop working soon",
- optarg, opt->vsync ? "true" : "false");
+ bool parsed_vsync = parse_vsync(optarg);
+ log_error("--vsync doesn't take argument anymore. \"%s\" "
+ "should be changed to \"%s\"",
+ optarg, parsed_vsync ? "true" : "false");
+ failed = true;
} else {
opt->vsync = true;
}
break;
- case 271:
- // --alpha-step
- log_error("--alpha-step has been removed, we now tries to "
- "make use of all alpha values");
- failed = true; break;
- case 272:
- log_error("--dbe has been removed");
- failed = true; break;
- case 273:
- log_error("--paint-on-overlay has been removed, the feature is enabled "
- "whenever possible");
- failed = true; break;
- P_CASEBOOL(274, sw_opti);
+ case 274:
+ log_warn("--sw-opti has been deprecated, please remove it from the "
+ "command line options");
+ break;
case 275:
// --vsync-aggressive
- log_warn("--vsync-aggressive has been deprecated, please remove it"
+ log_error("--vsync-aggressive has been removed, please remove it"
" from the command line options");
+ failed = true;
break;
P_CASEBOOL(276, use_ewmh_active_win);
case 277:
@@ -831,14 +588,14 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
if (strcmp(optarg, "undefined") != 0 && tmpval != 0) {
// If not undefined, we will use damage and buffer-age to
// limit the rendering area.
- opt->use_damage = true;
should_remove = false;
}
- log_warn("--glx-swap-method has been deprecated, your setting "
+ log_error("--glx-swap-method has been removed, your setting "
"\"%s\" should be %s.",
optarg,
!should_remove ? "replaced by `--use-damage`" :
"removed");
+ failed = true;
break;
}
case 300:
@@ -856,8 +613,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
P_CASEINT(302, resize_damage);
case 303:
// --glx-use-gpushader4
- log_warn("--glx-use-gpushader4 is deprecated since v6."
+ log_error("--glx-use-gpushader4 has been removed."
" Please remove it from command line options.");
+ failed = true;
break;
case 304:
// --opacity-rule
@@ -890,16 +648,35 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
}
break;
P_CASEBOOL(311, vsync_use_glfinish);
- case 312:
- // --xrender-sync
- log_error("Please use --xrender-sync-fence instead of --xrender-sync");
- failed = true; break;
P_CASEBOOL(313, xrender_sync_fence);
P_CASEBOOL(315, no_fading_destroyed_argb);
P_CASEBOOL(316, force_win_blend);
case 317:
opt->glx_fshader_win_str = strdup(optarg);
break;
+ case 336: {
+ // --window-shader-fg
+ scoped_charp cwd = getcwd(NULL, 0);
+ opt->window_shader_fg =
+ locate_auxiliary_file("shaders", optarg, cwd);
+ if (!opt->window_shader_fg) {
+ exit(1);
+ }
+ break;
+ }
+ case 337: {
+ // --window-shader-fg-rule
+ scoped_charp cwd = getcwd(NULL, 0);
+ if (!parse_rule_window_shader(&opt->window_shader_fg_rules, optarg, cwd)) {
+ exit(1);
+ }
+ 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) {
@@ -911,13 +688,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
}
P_CASEBOOL(319, no_x_selection);
P_CASEBOOL(323, use_damage);
- case 324:
- opt->use_damage = false;
- break;
- case 325:
- opt->vsync = false;
- break;
-
+ case 324: opt->use_damage = false; break;
+ case 325: opt->vsync = false; break;
case 326:
opt->max_brightness = atof(optarg);
break;
@@ -956,9 +728,11 @@ 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;
- P_CASEBOOL(733, experimental_backends);
+ P_CASEBOOL(733, legacy_backends);
P_CASEBOOL(800, monitor_repaint);
- case 801: opt->print_diagnostics = true; break;
+ case 801:
+ opt->print_diagnostics = true;
+ break;
P_CASEBOOL(802, debug_mode);
P_CASEBOOL(803, no_ewmh_fullscreen);
P_CASEBOOL(804, animations);
@@ -988,17 +762,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
}
break;
}
- case 830:
- condlst_add(&opt->animation_open_blacklist, optarg);
- break;
- case 831:
- condlst_add(&opt->animation_unmap_blacklist, optarg);
- break;
- case 840:
- // --opacity-rule
- if (!parse_rule_corners(&opt->corner_rules, optarg))
- exit(1);
- break;
case 810: {
// --animation-for-transient-window
enum open_window_animation animation = parse_open_window_animation(optarg);
@@ -1009,6 +772,30 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
}
break;
}
+ case 811: {
+ // --animation-for-unmap-window
+ enum open_window_animation animation = parse_open_window_animation(optarg);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_warn("Invalid unmap-window animation %s, ignoring.", optarg);
+ } else {
+ opt->animation_for_unmap_window = animation;
+ }
+ break;
+ }
+ case 812:
+ // corner rules
+ if (!parse_rule_corners(&opt->corner_rules, optarg))
+ exit(1);
+ break;
+ case 813:
+ condlst_add(&opt->blur_rules, optarg);
+ break;
+ case 814:
+ condlst_add(&opt->animation_open_blacklist, optarg);
+ break;
+ case 815:
+ condlst_add(&opt->animation_unmap_blacklist, optarg);
+ break;
default: usage(argv[0], 1); break;
#undef P_CASEBOOL
}
@@ -1028,29 +815,56 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
return false;
}
- if (opt->monitor_repaint && opt->backend != BKEND_XRENDER &&
- !opt->experimental_backends) {
+ if (opt->monitor_repaint && opt->backend != BKEND_XRENDER && opt->legacy_backends) {
log_warn("--monitor-repaint has no effect when backend is not xrender");
}
- if (opt->experimental_backends && !backend_list[opt->backend]) {
- log_error("Backend \"%s\" is not available as part of the experimental "
+ if (opt->backend == BKEND_EGL) {
+ if (opt->legacy_backends) {
+ log_error("The egl backend is not supported with "
+ "--legacy-backends");
+ return false;
+ }
+ log_warn("The egl backend is still experimental, use with care.");
+ }
+
+ if (!opt->legacy_backends && !backend_list[opt->backend]) {
+ log_error("Backend \"%s\" is only available as part of the legacy "
"backends.",
BACKEND_STRS[opt->backend]);
return false;
}
- if (opt->debug_mode && !opt->experimental_backends) {
- log_error("Debug mode only works with the experimental backends.");
+ if (opt->debug_mode && opt->legacy_backends) {
+ log_error("Debug mode does not work with the legacy backends.");
return false;
}
- if (opt->transparent_clipping && !opt->experimental_backends) {
- log_error("Transparent clipping only works with the experimental "
+ if (opt->transparent_clipping && opt->legacy_backends) {
+ log_error("Transparent clipping does not work with the legacy "
"backends");
return false;
}
+ if (opt->glx_fshader_win_str && !opt->legacy_backends) {
+ log_warn("--glx-fshader-win has been replaced by "
+ "\"--window-shader-fg\" for the new backends.");
+ }
+
+ if (opt->window_shader_fg || opt->window_shader_fg_rules) {
+ if (opt->legacy_backends || opt->backend != BKEND_GLX) {
+ log_warn("The new window shader interface does not work with the "
+ "legacy glx backend.%s",
+ (opt->backend == BKEND_GLX) ? " You may want to use "
+ "\"--glx-fshader-win\" "
+ "instead on the legacy "
+ "glx backend."
+ : "");
+ opt->window_shader_fg = NULL;
+ c2_list_free(&opt->window_shader_fg_rules, free);
+ }
+ }
+
// Range checking and option assignments
opt->fade_delta = max2(opt->fade_delta, 1);
opt->shadow_radius = max2(opt->shadow_radius, 0);
@@ -1060,7 +874,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->inactive_dim = normalize_d(opt->inactive_dim);
opt->frame_opacity = normalize_d(opt->frame_opacity);
opt->shadow_opacity = normalize_d(opt->shadow_opacity);
- opt->refresh_rate = normalize_i_range(opt->refresh_rate, 0, 300);
opt->max_brightness = normalize_d(opt->max_brightness);
if (opt->max_brightness < 1.0) {
@@ -1070,8 +883,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->max_brightness = 1.0;
}
- if (!opt->experimental_backends || opt->backend != BKEND_GLX) {
- log_warn("--max-brightness requires the experimental glx "
+ if (opt->legacy_backends || opt->backend != BKEND_GLX) {
+ log_warn("--max-brightness requires the new glx "
"backend. Falling back to 1.0");
opt->max_brightness = 1.0;
}
@@ -1114,10 +927,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
"capping to 20.");
opt->blur_strength = 20;
}
- if (!opt->experimental_backends) {
+ if (opt->legacy_backends) {
log_warn("Dual-kawase blur is not implemented by the legacy "
- "backends, you must use the `experimental-backends` "
- "option.");
+ "backends.");
}
}
@@ -1130,12 +942,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
"properly under X Render backend.");
}
- if (opt->corner_radius > 0 && opt->experimental_backends) {
- log_warn("Rounded corner is only supported on legacy backends, it "
- "will be disabled");
- opt->corner_radius = 0;
- }
-
return true;
}
diff --git a/src/picom.c b/src/picom.c
index 24d5ece..980655d 100644
--- a/src/picom.c
+++ b/src/picom.c
@@ -69,8 +69,6 @@
(session_t *)((char *)__mptr - offsetof(session_t, member)); \
})
-static const long SWOPTI_TOLERANCE = 3000;
-
static bool must_use redirect_start(session_t *ps);
static void unredirect(session_t *ps);
@@ -90,6 +88,7 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender",
[BKEND_GLX] = "glx",
[BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid",
[BKEND_DUMMY] = "dummy",
+ [BKEND_EGL] = "egl",
NULL};
// clang-format on
@@ -433,6 +432,14 @@ static void destroy_backend(session_t *ps) {
free_paint(ps, &w->paint);
}
+ HASH_ITER2(ps->shaders, shader) {
+ if (shader->backend_shader != NULL) {
+ ps->backend_data->ops->destroy_shader(ps->backend_data,
+ shader->backend_shader);
+ shader->backend_shader = NULL;
+ }
+ }
+
if (ps->backend_data && ps->root_image) {
ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
ps->root_image = NULL;
@@ -445,6 +452,11 @@ static void destroy_backend(session_t *ps) {
ps->backend_data, ps->backend_blur_context);
ps->backend_blur_context = NULL;
}
+ if (ps->shadow_context) {
+ ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
+ ps->shadow_context);
+ ps->shadow_context = NULL;
+ }
ps->backend_data->ops->deinit(ps->backend_data);
ps->backend_data = NULL;
}
@@ -487,7 +499,7 @@ static bool initialize_blur(session_t *ps) {
/// Init the backend and bind all the window pixmap to backend images
static bool initialize_backend(session_t *ps) {
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
assert(!ps->backend_data);
// Reinitialize win_data
assert(backend_list[ps->o.backend]);
@@ -498,13 +510,38 @@ static bool initialize_backend(session_t *ps) {
return false;
}
ps->backend_data->ops = backend_list[ps->o.backend];
+ ps->shadow_context = ps->backend_data->ops->create_shadow_context(
+ ps->backend_data, ps->o.shadow_radius);
+ if (!ps->shadow_context) {
+ log_fatal("Failed to initialize shadow context, aborting...");
+ goto err;
+ }
if (!initialize_blur(ps)) {
log_fatal("Failed to prepare for background blur, aborting...");
- ps->backend_data->ops->deinit(ps->backend_data);
- ps->backend_data = NULL;
- quit(ps);
- return false;
+ goto err;
+ }
+
+ // Create shaders
+ HASH_ITER2(ps->shaders, shader) {
+ assert(shader->backend_shader == NULL);
+ shader->backend_shader = ps->backend_data->ops->create_shader(
+ ps->backend_data, shader->source);
+ if (shader->backend_shader == NULL) {
+ log_warn("Failed to create shader for shader file %s, "
+ "this shader will not be used",
+ shader->key);
+ } else {
+ if (ps->backend_data->ops->get_shader_attributes) {
+ shader->attributes =
+ ps->backend_data->ops->get_shader_attributes(
+ ps->backend_data, shader->backend_shader);
+ } else {
+ shader->attributes = 0;
+ }
+ log_debug("Shader %s has attributes %" PRIu64,
+ shader->key, shader->attributes);
+ }
}
// window_stack shouldn't include window that's
@@ -527,6 +564,16 @@ static bool initialize_backend(session_t *ps) {
// The old backends binds pixmap lazily, nothing to do here
return true;
+err:
+ if (ps->shadow_context) {
+ ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
+ ps->shadow_context);
+ ps->shadow_context = NULL;
+ }
+ ps->backend_data->ops->deinit(ps->backend_data);
+ ps->backend_data = NULL;
+ quit(ps);
+ return false;
}
/// Handle configure event of the root window
@@ -541,7 +588,7 @@ static void configure_root(session_t *ps) {
bool has_root_change = false;
if (ps->redirected) {
// On root window changes
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
assert(ps->backend_data);
has_root_change = ps->backend_data->ops->root_change != NULL;
} else {
@@ -585,7 +632,7 @@ static void configure_root(session_t *ps) {
ps->damage = ps->damage_ring + ps->ndamage - 1;
#ifdef CONFIG_OPENGL
// GLX root change callback
- if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) {
+ if (BKEND_GLX == ps->o.backend && ps->o.legacy_backends) {
glx_on_root_change(ps);
}
#endif
@@ -619,14 +666,6 @@ static void handle_root_flags(session_t *ps) {
if (ps->o.xinerama_shadow_crop) {
cxinerama_upd_scrs(ps);
}
-
- if (ps->o.sw_opti && !ps->o.refresh_rate) {
- update_refresh_rate(ps);
- if (!ps->refresh_rate) {
- log_warn("Refresh rate detection failed. swopti will be "
- "temporarily disabled");
- }
- }
ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE;
}
@@ -639,8 +678,7 @@ static void handle_root_flags(session_t *ps) {
static struct managed_win *
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.
- // Same goes for `animation_running`.
+ // means if fade is still ongoing after the current frame is rendered
struct managed_win *bottom = NULL;
*fade_running = false;
*animation_running = false;
@@ -670,7 +708,9 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
animation_delta = min2(animation_delta, ps->o.animation_delta/1000);
}
- // First, let's process fading
+ // 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.
win_stack_foreach_managed_safe(w, &ps->window_stack) {
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
@@ -850,39 +890,42 @@ 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;
- }
+ if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
+ add_damage_from_win(ps, w);
+ *animation_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);
- }
+ // Run fading
+ if (run_fade(ps, &w, steps)) {
+ *fade_running = true;
+ }
- if (win_check_fade_finished(ps, w)) {
- // the window has been destroyed because fading finished
- continue;
- }
+ // 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_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;
}
}
@@ -983,7 +1026,7 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// 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) {
@@ -1098,9 +1141,13 @@ void root_damaged(session_t *ps) {
if (pixmap != XCB_NONE) {
ps->root_image = ps->backend_data->ops->bind_pixmap(
ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false);
- ps->backend_data->ops->set_image_property(
- ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
- ps->root_image, (int[]){ps->root_width, ps->root_height});
+ if (ps->root_image) {
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
+ ps->root_image, (int[]){ps->root_width, ps->root_height});
+ } else {
+ log_error("Failed to bind root back pixmap");
+ }
}
}
@@ -1240,7 +1287,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);
@@ -1295,77 +1342,6 @@ static inline bool write_pid(session_t *ps) {
}
/**
- * Update refresh rate info with X Randr extension.
- */
-void update_refresh_rate(session_t *ps) {
- xcb_randr_get_screen_info_reply_t *randr_info = xcb_randr_get_screen_info_reply(
- ps->c, xcb_randr_get_screen_info(ps->c, ps->root), NULL);
-
- if (!randr_info)
- return;
- ps->refresh_rate = randr_info->rate;
- free(randr_info);
-
- if (ps->refresh_rate)
- ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
- else
- ps->refresh_intv = 0;
-}
-
-/**
- * Initialize refresh-rated based software optimization.
- *
- * @return true for success, false otherwise
- */
-static bool swopti_init(session_t *ps) {
- log_warn("--sw-opti is going to be deprecated. If you get real benefits from "
- "using "
- "this option, please open an issue to let us know.");
- // Prepare refresh rate
- // Check if user provides one
- ps->refresh_rate = ps->o.refresh_rate;
- if (ps->refresh_rate)
- ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
-
- // Auto-detect refresh rate otherwise
- if (!ps->refresh_rate && ps->randr_exists) {
- update_refresh_rate(ps);
- }
-
- // Turn off vsync_sw if we can't get the refresh rate
- if (!ps->refresh_rate)
- return false;
-
- return true;
-}
-
-/**
- * Modify a struct timeval timeout value to render at a fixed pace.
- *
- * @param ps current session
- * @param[in,out] ptv pointer to the timeout
- */
-static double swopti_handle_timeout(session_t *ps) {
- if (!ps->refresh_intv)
- return 0;
-
- // Get the microsecond offset of the time when the we reach the timeout
- // I don't think a 32-bit long could overflow here.
- long offset = (get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv;
- // XXX this formula dones't work if refresh rate is not a whole number
- if (offset < 0)
- offset += ps->refresh_intv;
-
- // If the target time is sufficiently close to a refresh time, don't add
- // an offset, to avoid certain blocking conditions.
- if (offset < SWOPTI_TOLERANCE || offset > ps->refresh_intv - SWOPTI_TOLERANCE)
- return 0;
-
- // Add an offset so we wait until the next refresh after timeout
- return (double)(ps->refresh_intv - offset) / 1e6;
-}
-
-/**
* Initialize X composite overlay window.
*/
static bool init_overlay(session_t *ps) {
@@ -1456,10 +1432,10 @@ uint8_t session_redirection_mode(session_t *ps) {
if (ps->o.debug_mode) {
// If the backend is not rendering to the screen, we don't need to
// take over the screen.
- assert(ps->o.experimental_backends);
+ assert(!ps->o.legacy_backends);
return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
}
- if (ps->o.experimental_backends && !backend_list[ps->o.backend]->present) {
+ if (!ps->o.legacy_backends && !backend_list[ps->o.backend]->present) {
// if the backend doesn't render anything, we don't need to take over the
// screen.
return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
@@ -1496,7 +1472,7 @@ static bool redirect_start(session_t *ps) {
return false;
}
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
assert(ps->backend_data);
ps->ndamage = ps->backend_data->ops->max_buffer_age;
} else {
@@ -1738,6 +1714,7 @@ 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);
@@ -1751,7 +1728,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
static int paint = 0;
log_trace("Render start, frame %d", paint);
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
paint_all_new(ps, bottom, false);
} else {
paint_all(ps, bottom, false);
@@ -1780,57 +1757,17 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
}
static void draw_callback(EV_P_ ev_idle *w, int revents) {
- // This function is not used if we are using --swopti
session_t *ps = session_ptr(w, draw_idle);
draw_callback_impl(EV_A_ ps, revents);
- // Don't do painting non-stop unless we are in benchmark mode
- if (!ps->o.benchmark) {
+ // 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) {
ev_idle_stop(EV_A_ & ps->draw_idle);
}
}
-static void delayed_draw_timer_callback(EV_P_ ev_timer *w, int revents) {
- session_t *ps = session_ptr(w, delayed_draw_timer);
- draw_callback_impl(EV_A_ ps, revents);
-
- // We might have stopped the ev_idle in delayed_draw_callback,
- // so we restart it if we are in benchmark mode
- if (ps->o.benchmark)
- ev_idle_start(EV_A_ & ps->draw_idle);
-}
-
-static void delayed_draw_callback(EV_P_ ev_idle *w, int revents) {
- // This function is only used if we are using --swopti
- session_t *ps = session_ptr(w, draw_idle);
- assert(ps->redraw_needed);
- assert(!ev_is_active(&ps->delayed_draw_timer));
-
- double delay = swopti_handle_timeout(ps);
- if (delay < 1e-6) {
- if (!ps->o.benchmark) {
- ev_idle_stop(EV_A_ & ps->draw_idle);
- }
- return draw_callback_impl(EV_A_ ps, revents);
- }
-
- // This is a little bit hacky. When we get to this point in code, we need
- // to update the screen , but we will only be updating after a delay, So
- // we want to stop the ev_idle, so this callback doesn't get call repeatedly
- // during the delay, we also want queue_redraw to not restart the ev_idle.
- // So we stop ev_idle and leave ps->redraw_needed to be true. (effectively,
- // ps->redraw_needed means if redraw is needed or if draw is in progress).
- //
- // We do this anyway even if we are in benchmark mode. That means we will
- // have to restart draw_idle after the draw actually happened when we are in
- // benchmark mode.
- ev_idle_stop(EV_A_ & ps->draw_idle);
-
- ev_timer_set(&ps->delayed_draw_timer, delay, 0);
- ev_timer_start(EV_A_ & ps->delayed_draw_timer);
-}
-
static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) {
session_t *ps = (session_t *)w;
xcb_generic_event_t *ev = xcb_poll_for_event(ps->c);
@@ -1861,6 +1798,62 @@ static void config_file_change_cb(void *_ps) {
reset_enable(ps->loop, NULL, 0);
}
+static bool load_shader_source(session_t *ps, const char *path) {
+ if (!path) {
+ // Using the default shader.
+ return false;
+ }
+
+ log_info("Loading shader source from %s", path);
+
+ struct shader_info *shader = NULL;
+ HASH_FIND_STR(ps->shaders, path, shader);
+ if (shader) {
+ log_debug("Shader already loaded, reusing");
+ return false;
+ }
+
+ shader = ccalloc(1, struct shader_info);
+ shader->key = strdup(path);
+ HASH_ADD_KEYPTR(hh, ps->shaders, shader->key, strlen(shader->key), shader);
+
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ log_error("Failed to open custom shader file: %s", path);
+ goto err;
+ }
+ struct stat statbuf;
+ if (fstat(fileno(f), &statbuf) < 0) {
+ log_error("Failed to access custom shader file: %s", path);
+ goto err;
+ }
+
+ auto num_bytes = (size_t)statbuf.st_size;
+ shader->source = ccalloc(num_bytes + 1, char);
+ auto read_bytes = fread(shader->source, sizeof(char), num_bytes, f);
+ if (read_bytes < num_bytes || ferror(f)) {
+ // This is a difficult to hit error case, review thoroughly.
+ log_error("Failed to read custom shader at %s. (read %lu bytes, expected "
+ "%lu bytes)",
+ path, read_bytes, num_bytes);
+ goto err;
+ }
+ return false;
+err:
+ HASH_DEL(ps->shaders, shader);
+ if (f) {
+ fclose(f);
+ }
+ free(shader->source);
+ free(shader->key);
+ free(shader);
+ return true;
+}
+
+static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) {
+ return load_shader_source(data, c2_list_get_data(cond));
+}
+
/**
* Initialize a session.
*
@@ -1912,11 +1905,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
.black_picture = XCB_NONE,
.cshadow_picture = XCB_NONE,
.white_picture = XCB_NONE,
- .gaussian_map = NULL,
-
- .refresh_rate = 0,
- .refresh_intv = 0UL,
- .paint_tm_offset = 0L,
+ .shadow_context = NULL,
#ifdef CONFIG_VSYNC_DRM
.drm_fd = -1,
@@ -2093,6 +2082,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
return NULL;
}
+ if (ps->o.window_shader_fg) {
+ log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg);
+ }
+
if (ps->o.logpath) {
auto l = file_logger_new(ps->o.logpath);
if (l) {
@@ -2142,9 +2135,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
c2_list_postprocess(ps, ps->o.fade_blacklist) &&
c2_list_postprocess(ps, ps->o.blur_background_blacklist) &&
c2_list_postprocess(ps, ps->o.invert_color_list) &&
+ c2_list_postprocess(ps, ps->o.window_shader_fg_rules) &&
c2_list_postprocess(ps, ps->o.opacity_rules) &&
- c2_list_postprocess(ps, ps->o.corner_rules) &&
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
+ c2_list_postprocess(ps, ps->o.corner_rules) &&
+ c2_list_postprocess(ps, ps->o.blur_rules) &&
c2_list_postprocess(ps, ps->o.animation_open_blacklist) &&
c2_list_postprocess(ps, ps->o.animation_unmap_blacklist) &&
c2_list_postprocess(ps, ps->o.focus_blacklist))) {
@@ -2152,8 +2147,27 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
"might not work");
}
- ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
- sum_kernel_preprocess(ps->gaussian_map);
+ // Load shader source file specified in the shader rules
+ if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) {
+ log_error("Failed to load shader source file for some of the window "
+ "shader rules");
+ }
+ if (load_shader_source(ps, ps->o.window_shader_fg)) {
+ log_error("Failed to load window shader source file");
+ }
+
+ if (log_get_level_tls() <= LOG_LEVEL_DEBUG) {
+ HASH_ITER2(ps->shaders, shader) {
+ log_debug("Shader %s:", shader->key);
+ log_debug("%s", shader->source);
+ }
+ }
+
+ if (ps->o.legacy_backends) {
+ ps->shadow_context =
+ (void *)gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
+ sum_kernel_preprocess((conv *)ps->shadow_context);
+ }
rebuild_shadow_exclude_reg(ps);
@@ -2224,11 +2238,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
}
// Query X RandR
- if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) {
+ if (ps->o.xinerama_shadow_crop) {
if (!ps->randr_exists) {
- log_fatal("No XRandR extension. sw-opti, refresh-rate or "
- "xinerama-shadow-crop "
- "cannot be enabled.");
+ log_fatal("No XRandR extension. xinerama-shadow-crop cannot be "
+ "enabled.");
goto err;
}
}
@@ -2280,7 +2293,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
// The old backends doesn't have a automatic redirection mode
log_info("The compositor is started in automatic redirection mode.");
- assert(ps->o.experimental_backends);
+ assert(!ps->o.legacy_backends);
if (backend_list[ps->o.backend]->present) {
// If the backend has `present`, we couldn't be in automatic
@@ -2296,7 +2309,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
apply_driver_workarounds(ps, ps->drivers);
// Initialize filters, must be preceded by OpenGL context creation
- if (!ps->o.experimental_backends && !init_render(ps)) {
+ if (ps->o.legacy_backends && !init_render(ps)) {
log_fatal("Failed to initialize the backend");
exit(1);
}
@@ -2314,7 +2327,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
free(config_file_to_free);
- if (bkend_use_glx(ps) && !ps->o.experimental_backends) {
+ if (bkend_use_glx(ps) && ps->o.legacy_backends) {
auto gl_logger = gl_string_marker_logger_new();
if (gl_logger) {
log_info("Enabling gl string marker");
@@ -2322,7 +2335,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
}
}
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
if (ps->o.monitor_repaint && !backend_list[ps->o.backend]->fill) {
log_warn("--monitor-repaint is not supported by the backend, "
"disabling");
@@ -2330,15 +2343,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
}
}
- // Initialize software optimization
- if (ps->o.sw_opti)
- ps->o.sw_opti = swopti_init(ps);
-
// Monitor screen changes if vsync_sw is enabled and we are using
// an auto-detected refresh rate, or when Xinerama features are enabled
- if (ps->randr_exists &&
- ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop))
+ if (ps->randr_exists && ps->o.xinerama_shadow_crop) {
xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
+ }
cxinerama_upd_scrs(ps);
@@ -2359,14 +2368,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ);
ev_io_start(ps->loop, &ps->xiow);
ev_init(&ps->unredir_timer, tmout_unredir_callback);
- if (ps->o.sw_opti)
- ev_idle_init(&ps->draw_idle, delayed_draw_callback);
- else
- ev_idle_init(&ps->draw_idle, draw_callback);
+ ev_idle_init(&ps->draw_idle, draw_callback);
ev_init(&ps->fade_timer, fade_timer_callback);
ev_init(&ps->animation_timer, animation_timer_callback);
- ev_init(&ps->delayed_draw_timer, delayed_draw_timer_callback);
// Set up SIGUSR1 signal handler to reset program
ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1);
@@ -2517,19 +2522,21 @@ static void session_destroy(session_t *ps) {
list_init_head(&ps->window_stack);
// Free blacklists
- free_wincondlst(&ps->o.shadow_blacklist);
- free_wincondlst(&ps->o.shadow_clip_list);
- free_wincondlst(&ps->o.fade_blacklist);
- free_wincondlst(&ps->o.focus_blacklist);
- free_wincondlst(&ps->o.invert_color_list);
- free_wincondlst(&ps->o.blur_background_blacklist);
- free_wincondlst(&ps->o.opacity_rules);
- free_wincondlst(&ps->o.corner_rules);
- free_wincondlst(&ps->o.paint_blacklist);
- free_wincondlst(&ps->o.unredir_if_possible_blacklist);
- free_wincondlst(&ps->o.rounded_corners_blacklist);
- free_wincondlst(&ps->o.animation_open_blacklist);
- free_wincondlst(&ps->o.animation_unmap_blacklist);
+ c2_list_free(&ps->o.shadow_blacklist, NULL);
+ c2_list_free(&ps->o.shadow_clip_list, NULL);
+ c2_list_free(&ps->o.fade_blacklist, NULL);
+ c2_list_free(&ps->o.focus_blacklist, NULL);
+ c2_list_free(&ps->o.invert_color_list, NULL);
+ c2_list_free(&ps->o.blur_background_blacklist, NULL);
+ c2_list_free(&ps->o.opacity_rules, NULL);
+ c2_list_free(&ps->o.paint_blacklist, NULL);
+ c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL);
+ c2_list_free(&ps->o.rounded_corners_blacklist, NULL);
+ c2_list_free(&ps->o.corner_rules, NULL);
+ c2_list_free(&ps->o.blur_rules, NULL);
+ c2_list_free(&ps->o.animation_open_blacklist, NULL);
+ c2_list_free(&ps->o.animation_unmap_blacklist, NULL);
+ c2_list_free(&ps->o.window_shader_fg_rules, free);
// Free tracked atom list
{
@@ -2580,6 +2587,17 @@ static void session_destroy(session_t *ps) {
free(ps->o.glx_fshader_win_str);
free_xinerama_info(ps);
+ // Release custom window shaders
+ free(ps->o.window_shader_fg);
+ struct shader_info *shader, *tmp;
+ HASH_ITER(hh, ps->shaders, shader, tmp) {
+ HASH_DEL(ps->shaders, shader);
+ assert(shader->backend_shader == NULL);
+ free(shader->source);
+ free(shader->key);
+ free(shader);
+ }
+
#ifdef CONFIG_VSYNC_DRM
// Close file opened for DRM VSync
if (ps->drm_fd >= 0) {
@@ -2615,7 +2633,7 @@ static void session_destroy(session_t *ps) {
ps->damaged_region = XCB_NONE;
}
- if (ps->o.experimental_backends) {
+ if (!ps->o.legacy_backends) {
// backend is deinitialized in unredirect()
assert(ps->backend_data == NULL);
} else {
@@ -2632,7 +2650,9 @@ static void session_destroy(session_t *ps) {
// Flush all events
x_sync(ps->c);
ev_io_stop(ps->loop, &ps->xiow);
- free_conv(ps->gaussian_map);
+ if (ps->o.legacy_backends) {
+ free_conv((conv *)ps->shadow_context);
+ }
destroy_atoms(ps->atoms);
#ifdef DEBUG_XRC
@@ -2658,9 +2678,6 @@ static void session_destroy(session_t *ps) {
* @param ps current session
*/
static void session_run(session_t *ps) {
- if (ps->o.sw_opti)
- ps->paint_tm_offset = get_time_timeval().tv_usec;
-
// In benchmark mode, we want draw_idle handler to always be active
if (ps->o.benchmark) {
ev_idle_start(ps->loop, &ps->draw_idle);
diff --git a/src/picom.h b/src/picom.h
index 25f7580..7ee289b 100644
--- a/src/picom.h
+++ b/src/picom.h
@@ -40,8 +40,6 @@ uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode);
void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce);
-void update_refresh_rate(session_t *ps);
-
void root_damaged(session_t *ps);
void cxinerama_upd_scrs(session_t *ps);
@@ -86,14 +84,6 @@ static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_wind
return false;
}
-/**
- * Destroy a condition list.
- */
-static inline void free_wincondlst(c2_lptr_t **pcondlst) {
- while ((*pcondlst = c2_free_lptr(*pcondlst)))
- continue;
-}
-
#ifndef CONFIG_OPENGL
static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) {
}
diff --git a/src/render.c b/src/render.c
index ac9b40e..db627de 100644
--- a/src/render.c
+++ b/src/render.c
@@ -649,8 +649,9 @@ static bool get_root_tile(session_t *ps) {
static void paint_root(session_t *ps, const region_t *reg_paint) {
// If there is no root tile pixmap, try getting one.
// If that fails, give up.
- if (!ps->root_tile_paint.pixmap && !get_root_tile(ps))
+ if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) {
return;
+ }
paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
ps->root_tile_paint.pict);
@@ -669,7 +670,7 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit
xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
xcb_gcontext_t gc = XCB_NONE;
- shadow_image = make_shadow(ps->c, ps->gaussian_map, opacity, width, height);
+ shadow_image = make_shadow(ps->c, (conv *)ps->shadow_context, opacity, width, height);
if (!shadow_image) {
log_error("failed to make shadow");
return XCB_NONE;
@@ -689,8 +690,9 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit
ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
- if (!shadow_picture || !shadow_picture_argb)
+ if (!shadow_picture || !shadow_picture_argb) {
goto shadow_picture_err;
+ }
gc = x_new_id(ps->c);
xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL);
diff --git a/src/string_utils.c b/src/string_utils.c
index 65af0f2..ee5bb2d 100644
--- a/src/string_utils.c
+++ b/src/string_utils.c
@@ -127,3 +127,31 @@ TEST_CASE(strtod_simple) {
TEST_EQUAL(result, 0.5);
TEST_EQUAL(*end, '\0');
}
+
+const char *trim_both(const char *src, size_t *length) {
+ size_t i = 0;
+ while (isspace(src[i])) {
+ i++;
+ }
+ size_t j = strlen(src) - 1;
+ while (j > i && isspace(src[j])) {
+ j--;
+ }
+ *length = j - i + 1;
+ return src + i;
+}
+
+TEST_CASE(trim_both) {
+ size_t length;
+ const char *str = trim_both(" \t\n\r\f", &length);
+ TEST_EQUAL(length, 0);
+ TEST_EQUAL(*str, '\0');
+
+ str = trim_both(" asdfas ", &length);
+ TEST_EQUAL(length, 6);
+ TEST_STRNEQUAL(str, "asdfas", length);
+
+ str = trim_both(" asdf asdf ", &length);
+ TEST_EQUAL(length, 9);
+ TEST_STRNEQUAL(str, "asdf asdf", length);
+}
diff --git a/src/string_utils.h b/src/string_utils.h
index 38febde..461173a 100644
--- a/src/string_utils.h
+++ b/src/string_utils.h
@@ -11,6 +11,7 @@
char *mstrjoin(const char *src1, const char *src2);
char *mstrjoin3(const char *src1, const char *src2, const char *src3);
void mstrextend(char **psrc1, const char *src2);
+const char *trim_both(const char *src, size_t *length);
/// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
double strtod_simple(const char *, const char **);
diff --git a/src/utils.c b/src/utils.c
index 8a27f39..c236a4a 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -36,8 +36,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;
diff --git a/src/utils.h b/src/utils.h
index 6bb8643..a35bfa8 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -14,6 +14,8 @@
#include <test.h>
+#include <time.h>
+
#include "compiler.h"
#include "types.h"
@@ -80,39 +82,39 @@ safe_isnan(double a) {
#define to_int_checked(val) \
({ \
- int64_t tmp = (val); \
- ASSERT_IN_RANGE(tmp, INT_MIN, INT_MAX); \
- (int)tmp; \
+ int64_t __to_tmp = (val); \
+ ASSERT_IN_RANGE(__to_tmp, INT_MIN, INT_MAX); \
+ (int)__to_tmp; \
})
#define to_char_checked(val) \
({ \
- int64_t tmp = (val); \
- ASSERT_IN_RANGE(tmp, CHAR_MIN, CHAR_MAX); \
- (char)tmp; \
+ int64_t __to_tmp = (val); \
+ ASSERT_IN_RANGE(__to_tmp, CHAR_MIN, CHAR_MAX); \
+ (char)__to_tmp; \
})
#define to_u16_checked(val) \
({ \
- auto tmp = (val); \
- ASSERT_IN_RANGE(tmp, 0, UINT16_MAX); \
- (uint16_t) tmp; \
+ auto __to_tmp = (val); \
+ ASSERT_IN_RANGE(__to_tmp, 0, UINT16_MAX); \
+ (uint16_t) __to_tmp; \
})
#define to_i16_checked(val) \
({ \
- int64_t tmp = (val); \
- ASSERT_IN_RANGE(tmp, INT16_MIN, INT16_MAX); \
- (int16_t) tmp; \
+ int64_t __to_tmp = (val); \
+ ASSERT_IN_RANGE(__to_tmp, INT16_MIN, INT16_MAX); \
+ (int16_t) __to_tmp; \
})
#define to_u32_checked(val) \
({ \
- auto tmp = (val); \
+ auto __to_tmp = (val); \
int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
comparison warning*/ \
- ASSERT_IN_RANGE(tmp, 0, max); \
- (uint32_t) tmp; \
+ ASSERT_IN_RANGE(__to_tmp, 0, max); \
+ (uint32_t) __to_tmp; \
})
/**
* Normalize an int value to a specific range.
@@ -130,21 +132,9 @@ 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))
/// clamp `val` into interval [min, max]
#define clamp(val, min, max) max2(min2(val, max), min)
@@ -283,6 +273,15 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
void name##_ref(type *a); \
void name##_unref(type **a);
+static inline void free_charpp(char **str) {
+ if (str) {
+ free(*str);
+ *str = NULL;
+ }
+}
+
+/// An allocated char* that is automatically freed when it goes out of scope.
+#define scoped_charp char *cleanup(free_charpp)
///
/// Calculates next closest power of two of 32bit integer n
@@ -290,5 +289,18 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
///
int next_power_of_two(int n);
+// Some versions of the Android libc do not have timespec_get(), use
+// clock_gettime() instead.
+#ifdef __ANDROID__
+
+#ifndef TIME_UTC
+#define TIME_UTC 1
+#endif
+
+static inline int timespec_get(struct timespec *ts, int base) {
+ assert(base == TIME_UTC);
+ return clock_gettime(CLOCK_REALTIME, ts);
+}
+#endif
// vim: set noet sw=8 ts=8 :
diff --git a/src/win.c b/src/win.c
index 767efc1..116b2a2 100644
--- a/src/win.c
+++ b/src/win.c
@@ -322,6 +322,13 @@ static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
}
}
+static inline void win_release_mask(backend_t *base, struct managed_win *w) {
+ if (w->mask_image) {
+ base->ops->release_image(base, w->mask_image);
+ w->mask_image = NULL;
+ }
+}
+
static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) {
assert(!w->win_image);
auto pixmap = x_new_id(b->c);
@@ -346,14 +353,36 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w
return true;
}
+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(&reg_bound_local, -w->g.x, -w->g.y);
+ w->mask_image = b->ops->make_mask(
+ b, (geometry_t){.width = w->widthb, .height = w->heightb}, &reg_bound_local);
+ pixman_region32_fini(&reg_bound_local);
+
+ if (!w->mask_image) {
+ return false;
+ }
+ b->ops->set_image_property(b, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image,
+ (double[]){w->corner_radius});
+ return true;
+}
+
bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c,
- struct conv *kernel) {
+ struct backend_shadow_context *sctx) {
assert(!w->shadow_image);
assert(w->shadow);
- w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red,
- c.green, c.blue, c.alpha);
+ if ((w->corner_radius == 0 && w->bounding_shaped == false) ||
+ b->ops->shadow_from_mask == NULL) {
+ w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c);
+ } else {
+ win_bind_mask(b, w);
+ w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c);
+ }
if (!w->shadow_image) {
- log_error("Failed to bind shadow image, shadow will be disabled for "
+ log_error("Failed to bind shadow image, shadow will be disabled "
+ "for "
"%#010x (%s)",
w->base.id, w->name);
win_set_flags(w, WIN_FLAGS_SHADOW_NONE);
@@ -367,10 +396,9 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color
}
void win_release_images(struct backend_base *backend, struct managed_win *w) {
- // We don't want to decide what we should do if the image we want to release is
- // stale (do we clear the stale flags or not?)
- // But if we are not releasing any images anyway, we don't care about the stale
- // flags.
+ // We don't want to decide what we should do if the image we want to
+ // release is stale (do we clear the stale flags or not?) But if we are
+ // not releasing any images anyway, we don't care about the stale flags.
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
@@ -382,15 +410,19 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE));
win_release_shadow(backend, w);
}
+
+ win_release_mask(backend, w);
}
-/// Returns true if the `prop` property is stale, as well as clears the stale flag.
+/// Returns true if the `prop` property is stale, as well as clears the stale
+/// flag.
static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop);
-/// Returns true if any of the properties are stale, as well as clear all the stale flags.
+/// Returns true if any of the properties are stale, as well as clear all the
+/// stale flags.
static void win_clear_all_properties_stale(struct managed_win *w);
-/// Fetch new window properties from the X server, and run appropriate updates. Might set
-/// WIN_FLAGS_FACTOR_CHANGED
+/// Fetch new window properties from the X server, and run appropriate updates.
+/// Might set WIN_FLAGS_FACTOR_CHANGED
static void win_update_properties(session_t *ps, struct managed_win *w) {
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) {
win_update_wintype(ps, w);
@@ -576,10 +608,6 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) {
animation = ps->o.wintype_option[w->window_type].animation_unmap;
}
- if (c2_match(ps, w, ps->o.animation_unmap_blacklist, NULL)) {
- animation = OPEN_WINDOW_ANIMATION_NONE;
- }
-
if (ps->root_desktop_switch_direction != 0) {
if (ps->o.animation_for_workspace_switch_out == OPEN_WINDOW_ANIMATION_AUTO)
animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT;
@@ -587,6 +615,10 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) {
animation = ps->o.animation_for_workspace_switch_out;
}
+ if (c2_match(ps, w, ps->o.animation_unmap_blacklist, NULL)) {
+ animation = OPEN_WINDOW_ANIMATION_NONE;
+ }
+
switch (animation) {
case OPEN_WINDOW_ANIMATION_AUTO:
case OPEN_WINDOW_ANIMATION_NONE: { // No animation
@@ -672,8 +704,8 @@ static void init_animation_unmap(session_t *ps, struct managed_win *w) {
/// 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. is the
- // window just mapped.
+ // Whether the window was visible before we process the mapped flag. i.e.
+ // is the window just mapped.
bool was_visible = win_is_real_visible(w);
log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id,
w->name, was_visible);
@@ -688,8 +720,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
return;
}
- // Check client first, because later property updates need accurate client window
- // information
+ // Check client first, because later property updates need accurate client
+ // window information
if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) {
win_recheck_client(ps, w);
win_clear_flags(w, WIN_FLAGS_CLIENT_STALE);
@@ -698,15 +730,15 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
bool damaged = false;
if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
if (was_visible) {
- // Mark the old extents of this window as damaged. The new extents
- // will be marked damaged below, after the window extents are
- // updated.
+ // Mark the old extents of this window as damaged. The new
+ // extents will be marked damaged below, after the window
+ // extents are updated.
//
- // If the window is just mapped, we don't need to mark the old
- // extent as damaged. (It's possible that the window was in fading
- // and is interrupted by being mapped. In that case, the fading
- // window will be added to damage by map_win_start, so we don't
- // need to do it here)
+ // If the window is just mapped, we don't need to mark the
+ // old extent as damaged. (It's possible that the window
+ // was in fading and is interrupted by being mapped. In
+ // that case, the fading window will be added to damage by
+ // map_win_start, so we don't need to do it here)
add_damage_from_win(ps, w);
}
@@ -800,7 +832,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE);
}
- // Factor change flags could be set by previous stages, so must be handled last
+ // Factor change flags could be set by previous stages, so must be handled
+ // last
if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) {
win_on_factor_change(ps, w);
win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED);
@@ -832,9 +865,10 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
}
if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) {
- // Check to make sure the window is still mapped, otherwise we
- // won't be able to rebind pixmap after releasing it, yet we might
- // still need the pixmap for rendering.
+ // Check to make sure the window is still mapped,
+ // otherwise we won't be able to rebind pixmap after
+ // releasing it, yet we might still need the pixmap for
+ // rendering.
assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING);
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
// Must release images first, otherwise breaks
@@ -854,7 +888,7 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
.green = ps->o.shadow_green,
.blue = ps->o.shadow_blue,
.alpha = ps->o.shadow_opacity},
- ps->gaussian_map);
+ ps->shadow_context);
}
}
@@ -914,7 +948,8 @@ int win_update_name(session_t *ps, struct managed_win *w) {
}
if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
- log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.",
+ log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
+ "WM_NAME.",
w->client_win);
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) {
@@ -1036,8 +1071,8 @@ winmode_t win_calc_mode(const struct managed_win *w) {
if (win_has_alpha(w)) {
if (w->client_win == XCB_NONE) {
- // This is a window not managed by the WM, and it has alpha,
- // so it's transparent. No need to check WM frame.
+ // This is a window not managed by the WM, and it has
+ // alpha, so it's transparent. No need to check WM frame.
return WMODE_TRANS;
}
// The WM window has alpha
@@ -1047,12 +1082,12 @@ winmode_t win_calc_mode(const struct managed_win *w) {
return WMODE_TRANS;
}
if (win_has_frame(w)) {
- // The client window doesn't have alpha, but we have a WM frame
- // window, which has alpha.
+ // The client window doesn't have alpha, but we have a WM
+ // frame window, which has alpha.
return WMODE_FRAME_TRANS;
}
- // Although the WM window has alpha, the frame window has 0 size, so
- // consider the window solid
+ // Although the WM window has alpha, the frame window has 0 size,
+ // so consider the window solid
}
if (w->frame_opacity != 1.0 && win_has_frame(w)) {
@@ -1068,8 +1103,9 @@ winmode_t win_calc_mode(const struct managed_win *w) {
*
* The priority of opacity settings are:
*
- * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) >
- * opacity-rules (if matched) > window type default opacity > active/inactive opacity
+ * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if
+ * set) > opacity-rules (if matched) > window type default opacity >
+ * active/inactive opacity
*
* @param ps current session
* @param w struct _win object representing the window
@@ -1097,7 +1133,8 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
} else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) {
opacity = ps->o.wintype_option[w->window_type].opacity;
} else {
- // Respect active_opacity only when the window is physically focused
+ // Respect active_opacity only when the window is physically
+ // focused
if (win_is_focused_raw(ps, w))
opacity = ps->o.active_opacity;
else if (!w->focused)
@@ -1133,7 +1170,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) {
* Determine if a window should fade on opacity change.
*/
bool win_should_fade(session_t *ps, const struct managed_win *w) {
- // To prevent it from being overwritten by last-paint value if the window is
+ // To prevent it from being overwritten by last-paint value if the window
+ // is
if (w->fade_force != UNSET) {
return w->fade_force;
}
@@ -1177,7 +1215,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id,
w->name, shadow_new);
- // We don't handle property updates of non-visible windows until they are mapped.
+ // We don't handle property updates of non-visible windows until they are
+ // mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
@@ -1199,33 +1238,35 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
// Note: because the release and creation of the shadow images are
// delayed. When multiple shadow changes happen in a row, without
- // rendering phase between them, there could be a stale shadow image
- // attached to the window even if w->shadow was previously false. And vice
- // versa. So we check the STALE flag before asserting the existence of the
- // shadow image.
+ // rendering phase between them, there could be a stale shadow
+ // image attached to the window even if w->shadow was previously
+ // false. And vice versa. So we check the STALE flag before
+ // asserting the existence of the shadow image.
if (w->shadow) {
// Mark the new extents as damaged if the shadow is added
assert(!w->shadow_image ||
win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) ||
- !ps->o.experimental_backends);
+ ps->o.legacy_backends);
pixman_region32_clear(&extents);
win_extents(w, &extents);
add_damage_from_win(ps, w);
} else {
- // Mark the old extents as damaged if the shadow is removed
+ // Mark the old extents as damaged if the shadow is
+ // removed
assert(w->shadow_image ||
win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) ||
- !ps->o.experimental_backends);
+ ps->o.legacy_backends);
add_damage(ps, &extents);
}
// Delayed update of shadow image
// By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to
- // re-create or release the shaodw in based on whether w->shadow is set.
+ // re-create or release the shaodw in based on whether w->shadow
+ // is set.
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
- // Only set pending_updates if we are redirected. Otherwise change of a
- // shadow won't have influence on whether we should redirect.
+ // Only set pending_updates if we are redirected. Otherwise change
+ // of a shadow won't have influence on whether we should redirect.
ps->pending_updates = true;
}
@@ -1268,7 +1309,7 @@ static void win_determine_shadow(session_t *ps, struct managed_win *w) {
* things.
*/
void win_update_prop_shadow(session_t *ps, struct managed_win *w) {
- long attr_shadow_old = w->prop_shadow;
+ long long attr_shadow_old = w->prop_shadow;
win_update_prop_shadow_raw(ps, w);
@@ -1357,8 +1398,22 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou
w->blur_background = blur_background_new;
- // This damage might not be absolutely necessary (e.g. when the window is opaque),
- // but blur_background changes should be rare, so this should be fine.
+ // This damage might not be absolutely necessary (e.g. when the window is
+ // opaque), but blur_background changes should be rare, so this should be
+ // fine.
+ add_damage_from_win(ps, w);
+}
+
+static void
+win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shader_new) {
+ if (w->fg_shader == shader_new) {
+ return;
+ }
+
+ w->fg_shader = shader_new;
+
+ // A different shader might change how the window is drawn, these changes
+ // should be rare however, so this should be fine.
add_damage_from_win(ps, w);
}
@@ -1377,7 +1432,12 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
log_debug("Blur background disabled by wintypes");
blur_background_new = false;
} else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) {
- log_debug("Blur background disabled by blur-background-exclude");
+ log_debug("Blur background disabled by "
+ "blur-background-exclude");
+ blur_background_new = false;
+ } else if (c2_match(ps, w, ps->o.blur_rules, NULL)) {
+ blur_background_new = true;
+ } else {
blur_background_new = false;
}
}
@@ -1393,7 +1453,7 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
w->corner_radius = 0;
return;
}
-
+
void *val = NULL;
// Don't round full screen windows & excluded windows
@@ -1413,6 +1473,28 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
}
/**
+ * Determine custom window shader to use for a window.
+ */
+static void win_determine_fg_shader(session_t *ps, struct managed_win *w) {
+ if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
+ return;
+ }
+
+ auto shader_new = ps->o.window_shader_fg;
+ void *val = NULL;
+ if (c2_match(ps, w, ps->o.window_shader_fg_rules, &val)) {
+ shader_new = val;
+ }
+
+ struct shader_info *shader = NULL;
+ if (shader_new) {
+ HASH_FIND_STR(ps->shaders, shader_new, shader);
+ }
+
+ win_set_fg_shader(ps, w, shader);
+}
+
+/**
* Update window opacity according to opacity rules.
*/
void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
@@ -1439,8 +1521,8 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
*/
void win_on_factor_change(session_t *ps, struct managed_win *w) {
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
- // Focus needs to be updated first, as other rules might depend on the focused
- // state of the window
+ // Focus needs to be updated first, as other rules might depend on the
+ // focused state of the window
win_update_focused(ps, w);
win_determine_shadow(ps, w);
@@ -1448,6 +1530,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
win_determine_invert_color(ps, w);
win_determine_blur_background(ps, w);
win_determine_rounded_corners(ps, w);
+ win_determine_fg_shader(ps, w);
w->mode = win_calc_mode(w);
log_debug("Window mode changed to %d", w->mode);
win_update_opacity_rule(ps, w);
@@ -1461,6 +1544,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;
@@ -1477,14 +1563,14 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) {
w->shadow_width = w->widthb + ps->o.shadow_radius * 2;
w->shadow_height = w->heightb + ps->o.shadow_radius * 2;
- // We don't handle property updates of non-visible windows until they are mapped.
+ // We don't handle property updates of non-visible windows until they are
+ // mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
// Invalidate the shadow we built
- if (w->state != WSTATE_DESTROYING)
- win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
-
+ win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
+ win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->shadow_paint);
}
@@ -1707,21 +1793,21 @@ struct win *add_win_top(session_t *ps, xcb_window_t id) {
return add_win(ps, id, &ps->window_stack);
}
-/// Insert a new window above window with id `below`, if there is no window, add to top
-/// New window will be in unmapped state
+/// Insert a new window above window with id `below`, if there is no window, add
+/// to top New window will be in unmapped state
struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) {
struct win *w = NULL;
HASH_FIND_INT(ps->windows, &below, w);
if (!w) {
if (!list_is_empty(&ps->window_stack)) {
- // `below` window is not found even if the window stack is not
- // empty
+ // `below` window is not found even if the window stack is
+ // not empty
return NULL;
}
return add_win_top(ps, id);
} else {
- // we found something from the hash table, so if the stack is empty,
- // we are in an inconsistent state.
+ // we found something from the hash table, so if the stack is
+ // empty, we are in an inconsistent state.
assert(!list_is_empty(&ps->window_stack));
return add_win(ps, id, w->stack_neighbour.prev);
}
@@ -1743,19 +1829,20 @@ 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
+ .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
+ .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,
@@ -1783,9 +1870,11 @@ struct win *fill_win(session_t *ps, struct win *w) {
.win_image = NULL,
.old_win_image = NULL,
.shadow_image = NULL,
+ .mask_image = NULL,
.prev_trans = NULL,
.shadow = false,
.clip_shadow_above = false,
+ .fg_shader = NULL,
.xinerama_scr = -1,
.mode = WMODE_TRANS,
.ever_damaged = false,
@@ -1807,6 +1896,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
@@ -1834,8 +1924,9 @@ struct win *fill_win(session_t *ps, struct win *w) {
auto duplicated_win = find_managed_win(ps, w->id);
if (duplicated_win) {
- log_debug("Window %#010x (recorded name: %s) added multiple times", w->id,
- duplicated_win->name);
+ log_debug("Window %#010x (recorded name: %s) added multiple "
+ "times",
+ w->id, duplicated_win->name);
return &duplicated_win->base;
}
@@ -1847,14 +1938,15 @@ struct win *fill_win(session_t *ps, struct win *w) {
// Failed to get window attributes or geometry probably means
// the window is gone already. Unviewable means the window is
// already reparented elsewhere.
- // BTW, we don't care about Input Only windows, except for stacking
- // proposes, so we need to keep track of them still.
+ // BTW, we don't care about Input Only windows, except for
+ // stacking proposes, so we need to keep track of them still.
free(a);
return w;
}
if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
- // No need to manage this window, but we still keep it on the window stack
+ // No need to manage this window, but we still keep it on the
+ // window stack
w->managed = false;
free(a);
return w;
@@ -1924,8 +2016,8 @@ struct win *fill_win(session_t *ps, struct win *w) {
assert(replaced == w);
free(w);
- // Set all the stale flags on this new window, so it's properties will get updated
- // when it's mapped
+ // Set all the stale flags on this new window, so it's properties will get
+ // updated when it's mapped
win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE |
WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE |
WIN_FLAGS_FACTOR_CHANGED);
@@ -1961,8 +2053,8 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
// gets mapped before parent, or when the window is a waypoint
clear_cache_win_leaders(ps);
- // Update the old and new window group and active_leader if the window
- // could affect their state.
+ // Update the old and new window group and active_leader if the
+ // window could affect their state.
xcb_window_t cache_leader = win_get_leader(ps, w);
if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) {
ps->active_leader = cache_leader;
@@ -2009,7 +2101,8 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
if (!(w->cache_leader = w->leader))
w->cache_leader = w->client_win;
- // If the leader of this window isn't itself, look for its ancestors
+ // If the leader of this window isn't itself, look for its
+ // ancestors
if (w->cache_leader && w->cache_leader != w->client_win) {
auto wp = find_toplevel(ps, w->cache_leader);
if (wp) {
@@ -2157,7 +2250,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
w->bounding_shaped = win_bounding_shaped(ps, w->base.id);
}
- // We don't handle property updates of non-visible windows until they are mapped.
+ // We don't handle property updates of non-visible windows until they are
+ // mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
@@ -2198,8 +2292,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// We think the top left of the border is the origin
pixman_region32_translate(&br, w->g.border_width, w->g.border_width);
- // Intersect the bounding region we got with the window rectangle, to
- // make sure the bounding region is not bigger than the window
+ // Intersect the bounding region we got with the window rectangle,
+ // to make sure the bounding region is not bigger than the window
// rectangle
pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br);
pixman_region32_fini(&br);
@@ -2213,6 +2307,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// Window shape changed, we should free old wpaint and shadow pict
// log_trace("free out dated pict");
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
+ win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->paint);
@@ -2255,7 +2350,8 @@ void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t
for (int i = 0; i < 4; i++) {
if (prop.c32[i] > (uint32_t)INT_MAX) {
log_warn("Your window manager sets a absurd "
- "_NET_FRAME_EXTENTS value (%u), ignoring it.",
+ "_NET_FRAME_EXTENTS value (%u), "
+ "ignoring it.",
prop.c32[i]);
memset(extents, 0, sizeof(extents));
break;
@@ -2369,11 +2465,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) {
auto mw = (struct managed_win *)w;
if (mw->state != WSTATE_UNMAPPED) {
- // Only UNMAPPED state has window resources freed, otherwise
- // we need to call unmap_win_finish to free them.
- // XXX actually we unmap_win_finish only frees the rendering
- // resources, we still need to call free_win_res. will fix
- // later.
+ // Only UNMAPPED state has window resources freed,
+ // otherwise we need to call unmap_win_finish to free
+ // them.
+ // XXX actually we unmap_win_finish only frees the
+ // rendering resources, we still need to call free_win_res.
+ // will fix later.
unmap_win_finish(ps, mw);
}
@@ -2382,6 +2479,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??
@@ -2396,11 +2494,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) {
}
if (mw == ps->active_win) {
- // Usually, the window cannot be the focused at destruction.
- // FocusOut should be generated before the window is destroyed. We
- // do this check just to be completely sure we don't have dangling
- // references.
- log_debug("window %#010x (%s) is destroyed while being focused",
+ // Usually, the window cannot be the focused at
+ // destruction. FocusOut should be generated before the
+ // window is destroyed. We do this check just to be
+ // completely sure we don't have dangling references.
+ log_debug("window %#010x (%s) is destroyed while being "
+ "focused",
w->id, mw->name);
ps->active_win = NULL;
}
@@ -2433,11 +2532,13 @@ static inline void restack_win(session_t *ps, struct win *w, struct list_node *n
}
if (mw) {
- // This invalidates all reg_ignore below the new stack position of `w`
+ // This invalidates all reg_ignore below the new stack position of
+ // `w`
mw->reg_ignore_valid = false;
rc_region_unref(&mw->reg_ignore);
- // This invalidates all reg_ignore below the old stack position of `w`
+ // This invalidates all reg_ignore below the old stack position of
+ // `w`
auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour);
if (next_w) {
next_w->reg_ignore_valid = false;
@@ -2519,39 +2620,42 @@ bool destroy_win_start(session_t *ps, struct win *w) {
log_debug("Destroying %#010x \"%s\", managed = %d", w->id,
(w->managed ? mw->name : NULL), w->managed);
- // Delete destroyed window from the hash table, even though the window might still
- // be rendered for a while. We need to make sure future window with the same
- // window id won't confuse us. Keep the window in the window stack if it's managed
- // and mapped, since we might still need to render it (e.g. fading out). Window
- // will be removed from the stack when it finishes destroying.
+ // Delete destroyed window from the hash table, even though the window
+ // might still be rendered for a while. We need to make sure future window
+ // with the same window id won't confuse us. Keep the window in the window
+ // stack if it's managed and mapped, since we might still need to render
+ // it (e.g. fading out). Window will be removed from the stack when it
+ // finishes destroying.
HASH_DEL(ps->windows, w);
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
- // Window is already unmapped, or is an unmanged window, just destroy it
+ // Window is already unmapped, or is an unmanged window, just
+ // destroy it
destroy_win_finish(ps, w);
return true;
}
if (w->managed) {
// Clear IMAGES_STALE flags since the window is destroyed: Clear
- // PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't
- // make sense.
- // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window
- // doesn't work leading to an inconsistent state where the shadow is
- // refreshed but the flags are stuck in STALE.
- // Do this before changing the window state to destroying
+ // PIXMAP_STALE as there is no pixmap available anymore, so STALE
+ // doesn't make sense.
+ // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed
+ // window doesn't work leading to an inconsistent state where the
+ // shadow is refreshed but the flags are stuck in STALE. Do this
+ // before changing the window state to destroying
win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE);
- // If size/shape/position information is stale, win_process_update_flags
- // will update them and add the new window extents to damage. Since the
- // window has been destroyed, we cannot get the complete information at
- // this point, so we just add what we currently have to the damage.
+ // If size/shape/position information is stale,
+ // win_process_update_flags will update them and add the new
+ // window extents to damage. Since the window has been destroyed,
+ // we cannot get the complete information at this point, so we
+ // just add what we currently have to the damage.
if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
add_damage_from_win(ps, mw);
}
- // Clear some flags about stale window information. Because now the window
- // is destroyed, we can't update them anyway.
+ // Clear some flags about stale window information. Because now
+ // the window is destroyed, we can't update them anyway.
win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
WIN_FLAGS_PROPERTY_STALE |
WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE);
@@ -2598,7 +2702,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
// Clear the pending map as this window is now unmapped
win_clear_flags(w, WIN_FLAGS_MAPPED);
} else {
- log_warn("Trying to unmapping an already unmapped window %#010x "
+ log_warn("Trying to unmapping an already unmapped window "
+ "%#010x "
"\"%s\"",
w->base.id, w->name);
assert(false);
@@ -2648,10 +2753,10 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
#endif
if (!ps->redirected || !was_damaged) {
- // If we are not redirected, we skip fading because we aren't rendering
- // anything anyway.
- // If the window wasn't ever damaged, it shouldn't be painted either. But
- // a fading out window is always painted, so we have to skip fading here.
+ // If we are not redirected, we skip fading because we aren't
+ // rendering anything anyway. If the window wasn't ever damaged,
+ // it shouldn't be painted either. But a fading out window is
+ // always painted, so we have to skip fading here.
CHECK(!win_skip_fading(ps, w));
}
}
@@ -2667,7 +2772,6 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
assert(w->opacity_target == w->opacity);
return false;
}
-
if (w->opacity == w->opacity_target) {
switch (w->state) {
case WSTATE_UNMAPPING: unmap_win_finish(ps, w); return false;
@@ -2720,14 +2824,16 @@ void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) {
if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb &&
e->y2 >= w->g.y + w->heightb) {
w->xinerama_scr = i;
- log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d "
+ log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen "
+ "%d "
"(%dx%d+%dx%d)",
w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb,
i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1);
return;
}
}
- log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen",
+ log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any "
+ "screen",
w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height);
}
@@ -2752,23 +2858,24 @@ void map_win_start(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_UNMAPPING) {
CHECK(!win_skip_fading(ps, w));
- // We skipped the unmapping process, the window was rendered, now it is
- // not anymore. So we need to mark the then unmapping window as damaged.
+ // We skipped the unmapping process, the window was rendered, now
+ // it is not anymore. So we need to mark the then unmapping window
+ // as damaged.
//
- // Solves problem when, for example, a window is unmapped then mapped in a
- // different location
+ // Solves problem when, for example, a window is unmapped then
+ // mapped in a different location
add_damage_from_win(ps, w);
assert(w);
}
assert(w->state == WSTATE_UNMAPPED);
- // Rant: window size could change after we queried its geometry here and before
- // we get its pixmap. Later, when we get back to the event processing loop, we
- // will get the notification about size change from Xserver and try to refresh the
- // pixmap, while the pixmap is actually already up-to-date (i.e. the notification
- // is stale). There is basically no real way to prevent this, aside from grabbing
- // the server.
+ // Rant: window size could change after we queried its geometry here and
+ // before we get its pixmap. Later, when we get back to the event
+ // processing loop, we will get the notification about size change from
+ // Xserver and try to refresh the pixmap, while the pixmap is actually
+ // already up-to-date (i.e. the notification is stale). There is basically
+ // no real way to prevent this, aside from grabbing the server.
// XXX Can we assume map_state is always viewable?
w->a.map_state = XCB_MAP_STATE_VIEWABLE;
@@ -2788,9 +2895,9 @@ void map_win_start(session_t *ps, struct managed_win *w) {
w->opacity, w->opacity_target);
// Cannot set w->ever_damaged = false here, since window mapping could be
- // delayed, so a damage event might have already arrived before this function
- // is called. But this should be unnecessary in the first place, since
- // ever_damaged is set to false in unmap_win_finish anyway.
+ // delayed, so a damage event might have already arrived before this
+ // function is called. But this should be unnecessary in the first place,
+ // since ever_damaged is set to false in unmap_win_finish anyway.
// Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section
// the window's image will be bound
@@ -2925,17 +3032,17 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) {
* @return struct _win object of the found window, NULL if not found
*/
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) {
- // TODO(yshui) this should probably be an "update tree", then find_toplevel.
- // current approach is a bit more "racy", as the server state might be ahead of
- // our state
+ // TODO(yshui) this should probably be an "update tree", then
+ // find_toplevel. current approach is a bit more "racy", as the server
+ // state might be ahead of our state
struct win *w = NULL;
// We traverse through its ancestors to find out the frame
- // Using find_win here because if we found a unmanaged window we know about, we
- // can stop early.
+ // Using find_win here because if we found a unmanaged window we know
+ // about, we can stop early.
while (wid && wid != ps->root && !(w = find_win(ps, wid))) {
- // xcb_query_tree probably fails if you run picom when X is somehow
- // initializing (like add it in .xinitrc). In this case
+ // xcb_query_tree probably fails if you run picom when X is
+ // somehow initializing (like add it in .xinitrc). In this case
// just leave it alone.
auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL);
if (reply == NULL) {
@@ -3101,7 +3208,8 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
}
/**
- * Check if a window is focused, without using any focus rules or forced focus settings
+ * Check if a window is focused, without using any focus rules or forced focus
+ * settings
*/
bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) {
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w;
diff --git a/src/win.h b/src/win.h
index d3a74f9..366fd9e 100644
--- a/src/win.h
+++ b/src/win.h
@@ -7,6 +7,8 @@
#include <xcb/render.h>
#include <xcb/xcb.h>
+#include <backend/backend.h>
+
#include "uthash_extra.h"
// FIXME shouldn't need this
@@ -105,6 +107,7 @@ struct managed_win {
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.
struct managed_win *prev_trans;
/// Number of windows above this window
@@ -140,7 +143,7 @@ struct managed_win {
/// bitmap for properties which needs to be updated
uint64_t *stale_props;
/// number of uint64_ts that has been allocated for stale_props
- uint64_t stale_props_capacity;
+ size_t stale_props_capacity;
/// Bounding shape of the window. In local coordinates.
/// See above about coordinate systems.
@@ -171,6 +174,7 @@ struct managed_win {
bool unredir_if_possible_excluded;
/// Whether this window is in open/close state.
bool in_openclose;
+
/// Whether this window was transient when animated on open
bool animation_transient;
/// Current position and destination, for animation
@@ -225,7 +229,8 @@ struct managed_win {
/// Previous window opacity.
double opacity_target_old;
/// true if window (or client window, for broken window managers
- /// not transferring client window's _NET_WM_OPACITY value) has opacity prop
+ /// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity
+ /// prop
bool has_opacity_prop;
/// Cached value of opacity window attribute.
opacity_t opacity_prop;
@@ -244,6 +249,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;
@@ -269,7 +277,7 @@ struct managed_win {
paint_t shadow_paint;
/// The value of _COMPTON_SHADOW attribute of the window. Below 0 for
/// none.
- long prop_shadow;
+ long long prop_shadow;
/// Do not paint shadow over this window.
bool clip_shadow_above;
@@ -286,6 +294,9 @@ struct managed_win {
/// Whether to blur window background.
bool blur_background;
+ /// The custom window shader to use when rendering.
+ struct shader_info *fg_shader;
+
#ifdef CONFIG_OPENGL
/// Textures and FBO background blur use.
glx_blur_cache_t glx_blur_cache;
@@ -298,9 +309,10 @@ struct managed_win {
/// section
void win_process_update_flags(session_t *ps, struct managed_win *w);
void win_process_image_flags(session_t *ps, struct managed_win *w);
+bool win_bind_mask(struct backend_base *b, struct managed_win *w);
/// Bind a shadow to the window, with color `c` and shadow kernel `kernel`
bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c,
- struct conv *kernel);
+ struct backend_shadow_context *kernel);
/// Start the unmap of a window. We cannot unmap immediately since we might need to fade
/// the window out.
diff --git a/src/win_defs.h b/src/win_defs.h
index e032bc7..bf2b7d8 100644
--- a/src/win_defs.h
+++ b/src/win_defs.h
@@ -96,7 +96,6 @@ enum win_flags {
WIN_FLAGS_FACTOR_CHANGED = 1024,
};
-static const uint64_t WIN_FLAGS_IMAGES_STALE =
- WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE;
+static const uint64_t WIN_FLAGS_IMAGES_STALE = WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE;
#define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE)
diff --git a/src/x.c b/src/x.c
index c146f48..795211d 100644
--- a/src/x.c
+++ b/src/x.c
@@ -652,10 +652,6 @@ err:
return false;
}
-// xcb-render specific macros
-#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
-#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
-
/**
* Convert a struct conv to a X picture convolution filter, normalizing the kernel
* in the process. Allow the caller to specify the element at the center of the kernel,
diff --git a/src/x.h b/src/x.h
index e01aa0a..3b8787c 100644
--- a/src/x.h
+++ b/src/x.h
@@ -87,6 +87,10 @@ struct xvisual_info {
#define log_fatal_x_error(e, fmt, ...) \
LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
+// xcb-render specific macros
+#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
+#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
+
/// Wraps x_new_id. abort the program if x_new_id returns error
static inline uint32_t x_new_id(xcb_connection_t *c) {
auto ret = xcb_generate_id(c);
diff --git a/subprojects/test.h/test.h b/subprojects/test.h/test.h
index fd8dc22..71522e1 100644
--- a/subprojects/test.h/test.h
+++ b/subprojects/test.h/test.h
@@ -25,6 +25,7 @@ struct test_failure {
const char *message;
const char *file;
int line;
+ bool owned;
};
struct test_case_metadata {
@@ -43,15 +44,16 @@ struct test_file_metadata {
struct test_file_metadata __attribute__((weak)) * test_file_head;
-#define SET_FAILURE(_message) \
- metadata->failure = (struct test_failure) { \
- .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
+#define SET_FAILURE(_message, _owned) \
+ metadata->failure = (struct test_failure) { \
+ .message = _message, .file = __FILE__, .line = __LINE__, \
+ .present = true, .owned = _owned, \
}
#define TEST_EQUAL(a, b) \
do { \
if ((a) != (b)) { \
- SET_FAILURE(#a " != " #b); \
+ SET_FAILURE(#a " != " #b, false); \
return; \
} \
} while (0)
@@ -59,7 +61,7 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#define TEST_TRUE(a) \
do { \
if (!(a)) { \
- SET_FAILURE(#a " is not true"); \
+ SET_FAILURE(#a " is not true", false); \
return; \
} \
} while (0)
@@ -67,7 +69,23 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#define TEST_STREQUAL(a, b) \
do { \
if (strcmp(a, b) != 0) { \
- SET_FAILURE(#a " != " #b); \
+ const char *part2 = " != " #b; \
+ size_t len = strlen(a) + strlen(part2) + 3; \
+ char *buf = malloc(len); \
+ snprintf(buf, len, "\"%s\"%s", a, part2); \
+ SET_FAILURE(buf, true); \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_STRNEQUAL(a, b, len) \
+ do { \
+ if (strncmp(a, b, len) != 0) { \
+ const char *part2 = " != " #b; \
+ size_t len2 = len + strlen(part2) + 3; \
+ char *buf = malloc(len2); \
+ snprintf(buf, len2, "\"%.*s\"%s", (int)len, a, part2); \
+ SET_FAILURE(buf, true); \
return; \
} \
} while (0)
@@ -152,6 +170,10 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
if (j->failure.present) {
fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
j->failure.file, j->failure.line);
+ if (j->failure.owned) {
+ free((char *)j->failure.message);
+ j->failure.message = NULL;
+ }
failed++;
} else {
fprintf(stderr, "passed\n");
@@ -181,5 +203,9 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
#define TEST_STREQUAL(a, b) \
(void)(a); \
(void)(b)
+#define TEST_STRNEQUAL(a, b, len) \
+ (void)(a); \
+ (void)(b); \
+ (void)(len)
#endif