124 Commits

Author SHA1 Message Date
Yuxuan Shui
580aef939f atom: add get_atom_cached
Add a version of get_atom that does not query the X server.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 20:12:20 +00:00
Yuxuan Shui
071b77c49f atom: remove an unnecessary use of get_atom
Replace get_atom("COMPTON_VERSION") with a fixed atom.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 20:11:42 +00:00
Yuxuan Shui
0dcca2228e cache: slight refactor
Added a pure version of `cache_get` which does not change the cache, and
renamed the old `cache_get` to `cache_get_or_fetch`.

Remove unused `cache_set`, and remove prefix underscore from function
names.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 20:07:34 +00:00
Yuxuan Shui
bb097730c7 test.h: import upstream updates
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 13:58:29 +00:00
Yuxuan Shui
8d0284da1b compiler: abort in debug build if unreachable() is reached
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 13:58:27 +00:00
Yuxuan Shui
9b6b6855d7 Merge pull request #1182 from yshui/image-handle 2024-02-15 14:55:39 +01:00
Yuxuan Shui
2d98518b7d backend: give images a type
It's quite confusing what should be passed into what because too many
things are `void *`. So give images a type to make things a bit clearer.
Because of C's limited type system, we lose the ability to annotate them
as nonnull or const, well you win some you lose some.

Also while doing this I noticed error handling around this is a bit
lacking.

Co-authored-by: Maxim Solovyov <msolovyov@protonmail.com>
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 13:48:47 +00:00
Yuxuan Shui
7f3f8b37b5 Merge pull request #1199 from Lenivaya/fix/nix-overlay 2024-02-15 14:20:28 +01:00
Lenivaya
42782689e8 chore: deduplication and reformat of flake.nix 2024-02-15 14:18:11 +01:00
Lenivaya
216bfefd9f fix: add libepoxy to default nix overlay 2024-02-15 14:18:11 +01:00
Yuxuan Shui
bef1240b41 Merge pull request #1198 from yshui/cache-focused 2024-02-15 14:17:38 +01:00
Yuxuan Shui
7e7c2b0cef win: store focused state in struct managed_win
So we don't need the whole session_t just to check if a window is
focused.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-15 11:22:07 +00:00
Yuxuan Shui
f509008cb9 win: don't call win_on_factor_change in win_update_bounding_shape
Later in win_process_update_flags, we check for WIN_FLAGS_FACTOR_CHANGED
and will call win_on_factor_change if needed. So in
win_update_bounding_shape we just need to set that flag. Otherwise we
call win_on_factor_change multiple times unnecessarily.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 23:46:12 +00:00
Yuxuan Shui
8c96fbebc4 Merge pull request #1196 from yshui/cache-fullscreen 2024-02-15 00:45:06 +01:00
Yuxuan Shui
05b1fbff9e win: remember calculated fullscreen state for window
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 23:41:28 +00:00
Yuxuan Shui
613d179f2d win: cache the EWMH fullscreen property
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 23:41:27 +00:00
Yuxuan Shui
84b9ff3148 x: support getting winprop_t items as atoms
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 23:41:24 +00:00
Yuxuan Shui
7da8b7afeb Merge pull request #1197 from absolutelynothelix/bsd-werror 2024-02-14 23:18:43 +01:00
Maxim Solovyov
5b81ea2c58 ci: build with --werror on freebsd and openbsd 2024-02-15 00:09:11 +03:00
Maxim Solovyov
2238cf1e54 Merge pull request #1190 from yshui/cache-fbconfig
cache GLX fbconfigs
2024-02-14 23:23:29 +03:00
Yuxuan Shui
85bb56e8a6 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 19:44:06 +00:00
Yuxuan Shui
241d7f1d03 backend: glx: cache GLX FBConfigs
This should marginally speed up pixmap binding for the glx backend (we
don't need FBConfigs for egl).

Fix a long running complaint in #381 (unrelated issue, but there is
complaint in there about glXChooseFBConfig being called whenever we bind
a new pixmap).

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 19:44:02 +00:00
Yuxuan Shui
e8d42885fa backend: gl: don't force fbconfig info on to heap
It's fairly small, so it's reasonable to put it on the stack.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 19:42:40 +00:00
Yuxuan Shui
e948b74363 backend: gl: remove an unused type
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 19:42:16 +00:00
Yuxuan Shui
64f6c4885a Merge pull request #1195 from absolutelynothelix/xcb-util 2024-02-14 20:16:58 +01:00
Maxim Solovyov
23b0c5a1d5 x: don't require an entire struct x_connection in x_get_visual_for_depth
inspired by the xcb-util's xcb_aux_get_depth_of_visual function
implementation
2024-02-14 21:31:32 +03:00
Maxim Solovyov
c7591982b6 x: remove x_get_visual_depth and use xcb_aux_get_depth_of_visual instead 2024-02-14 21:13:00 +03:00
Maxim Solovyov
1aa90f6466 x: remove x_sync and use xcb_aux_sync instead 2024-02-14 21:12:39 +03:00
Maxim Solovyov
b0dfcf4a32 x: remove x_screen_of_display and use xcb_aux_get_screen instead 2024-02-14 21:12:09 +03:00
Maxim Solovyov
28cb220b5b Merge pull request #1194 from yshui/update-clang
ci: update git-clang-format
2024-02-14 20:37:34 +03:00
Yuxuan Shui
c6db632d9d ci: update git-clang-format
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-14 17:29:11 +00:00
Yuxuan Shui
f28905b62d Merge pull request #1192 from absolutelynothelix/ci-fixes 2024-02-14 02:40:21 +01:00
Maxim Solovyov
53dd8a4e66 fix most of build warnings on openbsd
sorry openbsd people, we're not going to use the snprintf function.
2024-02-14 02:19:16 +03:00
Maxim Solovyov
f179119d84 ci: use meson setup instead of meson 2024-02-14 00:21:03 +03:00
Maxim Solovyov
466fb4c9e0 ci: update github actions to use node.js 20 2024-02-14 00:20:45 +03:00
Yuxuan Shui
4f792243c1 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-13 10:44:38 +00:00
Maxim Solovyov
9392829d84 backend: xrender: cache the present region
to avoid creating and destroying it every frame

(cherry picked from commit 5a1990b236c85f1222098ef147398855cbb3af69)
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-13 10:27:51 +00:00
Maxim Solovyov
a4ec70982c x: add the x_set_region function
it sets an x region to a pixman region

(cherry picked from commit efb7a1430f2c530c7b9cc0cb6d6d6cff95d8a4d9)
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-13 10:27:41 +00:00
Yuxuan Shui
0ab3e0740e Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 23:18:47 +00:00
Yuxuan Shui
7ada6db4a3 Merge pull request #1181 from yshui/defroster
See #1145 and #1166
2024-02-12 00:12:50 +01:00
Yuxuan Shui
75d0b7ba1e core: don't flush X connection before go to sleep
See the added comments for details.

Fixes #1145
Fixes #1166
Fixes #1040?

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 23:11:10 +00:00
Yuxuan Shui
a5826b6fb0 event: fix dumb bug in repair_win
Basically we won't call xcb_damage_subtract if show_all_xerrors is set,
which is very bad.

Fixes that, and also make sure the damage subtract request is flushed in
all branches.

Fixes: 1307d9ec70
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 23:05:53 +00:00
Yuxuan Shui
bdc0943399 event: make sure ev_property_notify flushes its requests
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 21:20:19 +00:00
Yuxuan Shui
baeafb3a3b event: tweak ev_reparent_notify
Instead of change window attributes back and forth, calculate the evmask
and set it just once. And also make sure the request is flushed.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 21:20:17 +00:00
Maxim Solovyov
037be5cca2 update contributors list 2024-02-11 21:33:14 +03:00
Yuxuan Shui
0e1628e031 Merge pull request #1189 from yshui/libepoxy
Use libepoxy + fix OpenBSD build
2024-02-11 17:27:32 +01:00
Yuxuan Shui
642a43acbb Update README.md and CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 02:07:07 +00:00
Yuxuan Shui
755996a42c backend: gl: use libepoxy's has_*_extension
So we don't need maintain our own version.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-11 02:07:05 +00:00
Yuxuan Shui
eb723eee29 backend: gl: use libepoxy
There is actually no specification what symbols are exported from a
libGL implementation. The (extremely outdated) OpenGL ABI specification
says only GL 1.2 functions are guaranteed. Don't know how relevant that
is now, but different libGL implementations do export different set of
symbols. On Linux we are most likely to be linked with libglvnd, which
has everything we need. But on other platforms this is not necessarily
the case, for example on OpenBSD we are missing glGetQueryObjectui64v.

Use libepoxy so we can outsource this problem and never worry about it
ever again. Plus it also saves us from calling GetProcAddress ourselves.

Changes other than trivial build fixes I have to make:

1. Can't use eglCreatePlatformWindowSurface/eglGetPlatformDisplay.
   libepoxy checks for EGL 1.5 when resolving these functions. But
   without a current context, libepoxy assumes we only have EGL 1.4.
   This creates a chicken and egg problem - we need a display to call
   eglGetPlatformDisplay. We have to use the *EXT version instead.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-10 20:36:02 +00:00
Yuxuan Shui
fcd51e7373 build: add libepoxy
Add libepoxy dependency to CI manifest and Nix.

For Nix, we need to set shellHook to workaround a NixOS limitation, see:

https://github.com/NixOS/nixpkgs/issues/287763

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-10 20:35:24 +00:00
Yuxuan Shui
dff77aae27 core: use pthread_setschedparam across the board
I think I was trying to avoid introducing pthread as a dependency, but
now we are using pthread for SGI_video_sync thread anyway. Let's remove
the ifdefs.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-10 20:32:45 +00:00
Yuxuan Shui
709f0168d9 ci: build on OpenBSD
Building on OpenBSD fails currently.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-10 10:22:16 +00:00
Yuxuan Shui
726b8d0e28 Merge pull request #1188 from yukiteruamano/next
Partially fix building on OpenBSD
2024-02-10 11:21:30 +01:00
Jose Maldonado aka Yukiteru
023103c620 core: use pthread_setschedparam on OpenBSD
OpenBSD don't have support for sched_getparam(), sched_setparam(), or
sched_setscheduler() functions (yet). In this case, we need use
pthead-equivalents for real-time sched for picom. Theses changes add
this support.

Authored-by: Jose Maldonado aka Yukiteru <josemald89@gmail.com>
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-10 10:18:28 +00:00
Yuxuan Shui
0f22b70705 Merge pull request #1183 from yshui/gl-leak 2024-02-09 00:43:16 +01:00
Yuxuan Shui
f265e049a8 backend: gl: don't leak resources
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-07 23:42:38 +00:00
Yuxuan Shui
a39cd94e1f Merge pull request #1179 from absolutelynothelix/inherit-shader-on-decouple 2024-02-07 13:47:53 +01:00
Yuxuan Shui
7fad0d51d7 Merge pull request #1178 from absolutelynothelix/postprocess-corner-radius-rules
Fixes #1177
2024-02-07 00:00:36 +01:00
Yuxuan Shui
9204426caf backend: gl: don't use present shader for decoupling
Present shader optionally does dithering, but that's not needed for
decoupling.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-06 22:58:39 +00:00
Yuxuan Shui
0d3d18cd88 backend: gl: remove unused field from gl_win_shader_t
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-06 13:19:49 +00:00
Yuxuan Shui
c738400058 backend: clarify what clone_image does
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-02-06 12:56:55 +00:00
Maxim Solovyov
eb39426b08 backend: gl: inherit image's inner properties in the gl_image_decouple function
Image decouple should keep all the image properies from the source
image, so shader must be copied. And there are also some internal
properties what should be inherited but wasn't.

In particular this prevents images from losing their shaders when
alpha is applied.

Fixes #1174
2024-02-06 12:54:59 +00:00
Maxim Solovyov
0ed8d0cadf picom: post-process and free the corner radius rules list
to make conditions based on non-standard atoms in this list work.
2024-02-02 17:06:18 +03:00
Yuxuan Shui
bbc657e4bc Merge pull request #984 from absolutelynothelix/fix-binding-root-back-pixmap
fix binding the root background pixmap in case of depth mismatch
2024-02-02 10:44:10 +00:00
Maxim Solovyov
839a101527 Merge pull request #1176 from Reith77/next
Update README.md (Fedora Build Dependencies)
2024-02-02 10:32:56 +03:00
Reith
5c640ac452 Update README.md
Updated README.md to include xcb-util-devel package for fedora
2024-02-02 12:17:42 +05:30
Maxim Solovyov
4a79e7b777 render: fix binding the root background pixmap in case of depth mismatch
fix the same issue in the legacy backends, see the previous commit for
details. this commit also removes the x_validate_pixmap function because
it was used only in the get_root_tile function and the fix pretty much
implies and embeds it.
2024-02-02 02:04:45 +03:00
Maxim Solovyov
a655730e49 picom: fix binding the root background pixmap in case of depth mismatch
if the root background pixmap's depth doesn't match the root window's
depth, find a suitable visual for the root background pixmap's depth and
use it instead of the root window's visual
2024-02-02 02:04:35 +03:00
Maxim Solovyov
4401666cfb x: add the x_get_visual_for_depth function
it returns the first found visual for the given depth
2024-02-01 02:31:29 +03:00
Maxim Solovyov
90f5f4ca29 fix a bunch of typos in comments 2024-01-31 06:09:28 +03:00
Yuxuan Shui
c1b4ede06e Merge pull request #1172 from yshui/nvidia-suspend
workaround for a NVIDIA driver bug, fixes #1168
2024-01-30 22:44:01 +00:00
Yuxuan Shui
238c3cc833 vblank: reset SGI_video_sync scheduler if it's getting nonsense
If resetting fails, set a flag so all future schedule calls will fail.

Fixes #1168

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-30 22:41:03 +00:00
Yuxuan Shui
0638de5c56 Update CHANGELOG.md
On a second thought, the `corner-radius-rules` is a user noticeable
behavioral change, it should be more prominent.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-30 22:40:19 +00:00
Yuxuan Shui
fdbcd15975 backend: fix clang warning
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 23:57:11 +00:00
Yuxuan Shui
bacdb919c7 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 23:56:30 +00:00
Yuxuan Shui
733113c691 Merge pull request #1171 from absolutelynothelix/corner-radius-rules-only 2024-01-29 23:53:24 +00:00
Yuxuan Shui
14a345a817 vblank: make init fallible
Improve error handling in sgi_video_sync_scheduler_init a bit.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 22:11:01 +00:00
Yuxuan Shui
5fba210ad6 vblank: make schedule fallible
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 22:07:04 +00:00
Yuxuan Shui
1bfb129985 backend: don't choose SGI_video_sync vblank if opengl is not enabled
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 21:02:36 +00:00
Maxim Solovyov
8ca66f8a00 win: allow corner-radius-rules without corner-radius
check if there is an override in corner-radius-rules even when
corner-radius is 0.
2024-01-29 23:16:51 +03:00
Yuxuan Shui
f3bdd01dc8 vblank: don't use symbols from backend/gl/glx.h
sgi video sync runs in a separate thread, we don't want to have
potential races between threads.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 17:52:44 +00:00
Yuxuan Shui
71e29c4128 Add .direnv to .gitignore
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-29 17:44:18 +00:00
Yuxuan Shui
606844c4bb Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-28 21:44:19 +00:00
Yuxuan Shui
702e30df4a Update nix flake
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-22 16:51:41 +00:00
Yuxuan Shui
d87dd41f4c win: make sure window's client_win can't be XCB_NONE
This is a follow up to 81d137a3cc and
bug #704. Basically a window will have a `XCB_NONE` as `client_win` if its
previous client_win detached and then the window itself is immediately
destroyed. Because the window is destroyed we couldn't call
`win_recheck_client` so its `client_win` will remain `XCB_NONE`.

However, it turns out we have a convention of setting `client_win` to
the window itself if windows that don't have a client window. So make
sure this convention is followed even for destroyed windows.

Doesn't really fix anything, just to make sure an invariant holds.

Related: #704

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 19:58:29 +00:00
Yuxuan Shui
ce205d1591 win: add more debug logs for window updates
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 19:26:20 +00:00
Yuxuan Shui
4c4df9b918 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 19:26:00 +00:00
Yuxuan Shui
56da153f1c Update copyright
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 17:35:18 +00:00
Yuxuan Shui
81d137a3cc core: fix debug window check in paint_preprocess
Fixes #704

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 17:27:05 +00:00
Yuxuan Shui
6e3c86226b core: improve debug log formatting around paint_preprocess
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-21 17:26:46 +00:00
Yuxuan Shui
197b4bd396 Update CHANGELOG.md for v11 release
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-20 17:12:34 +00:00
Yuxuan Shui
4c34944d76 Update CHANGELOG.md
I forgot to mention the priviledge needed for real-time scheduling.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-18 14:18:58 +00:00
Yuxuan Shui
a73ca2dc8d Merge pull request #1162 from yshui/deprecations-11 2024-01-15 13:41:30 +00:00
Yuxuan Shui
1b8d321c45 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 22:51:09 +00:00
Yuxuan Shui
30e37dbf09 Update CHANGELOG.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 17:10:20 +00:00
Yuxuan Shui
dc2d7b2876 options: use of respect-prop-shadow is now an error
Deprecated in v8.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 17:06:34 +00:00
Yuxuan Shui
5e119123a7 options: use of sw-opti is now an error
Deprecated in v6, we forgot to remove it.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 17:04:58 +00:00
Yuxuan Shui
6d4eaec811 options: remove -F
Deprecated in compton, in distant history.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 16:58:56 +00:00
Yuxuan Shui
eb3a58a6b0 Add a CHANGELOG.md
The intention is we update this file as PRs are merged, so the workload
when making a new release is reduced.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 16:50:47 +00:00
Yuxuan Shui
3390494bfe Bump version number
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 15:53:31 +00:00
Yuxuan Shui
148e61a0b2 Merge pull request #1156 from yshui/pacing-fixes 2024-01-14 15:46:24 +00:00
Yuxuan Shui
d8f303761b core: reset msc counter if it went backwards
Otherwise we might be repeatedly hitting this condition and spam the
warning.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2024-01-14 15:41:41 +00:00
Yuxuan Shui
496452cfce Merge pull request #1157 from absolutelynothelix/fix-segfaults-when-homeless 2023-12-22 10:59:55 +00:00
Maxim Solovyov
cacb45fbcd core: fix segfaults when the HOME environment variable is not set 2023-12-22 13:26:32 +03:00
Yuxuan Shui
b368072e12 Merge pull request #1002 from tryone144/fix/use-effective-texture-size-for-corner-radius 2023-12-20 10:48:14 +00:00
Yuxuan Shui
359d004b99 core: disable frame pacing when vsync is disabled
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:19 +00:00
Yuxuan Shui
6e0bad0034 core: make missing dpms extension non-fatal
We are not using it for anything at the moment, and it is breaking CI.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:16 +00:00
Yuxuan Shui
a28e221b83 driver: choose sgi_video_sync scheduler for NVIDIA
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:15 +00:00
Yuxuan Shui
b582d2989e core: add debug options to override the vblank scheduler
Useful for debugging.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:14 +00:00
Yuxuan Shui
db9b808bfb vblank: add GLX_SGI_video_sync based scheduler
Present extension based scheduler doesn't work well on NVIDIA drivers.
GLX_SGI_video_sync is less accurate, but is rumoured to work. See
[kwin's usage](https://invent.kde.org/plasma/kwin/-/blob/master/src/
backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp)

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:12 +00:00
Yuxuan Shui
db6ed75b60 core: don't always delay schedule_render to vblank
It's kind of dumb anyway. If we get damage event right after a vblank
event, we would waste the whole vblank.

Instead improve the frame scheduling logic to target the right vblank
interval. This only affects smart_frame_pacing anyway.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:11 +00:00
Yuxuan Shui
dd83f550e1 core: always check if render is finished at next vblank
Right now we rely on `reschedule_render_at_vblank` being scheduled for
the render finish check. Which means we will only know if a frame has
finished rendering the next time we queue a render. And we only check at
vblank boundary, which means when a render is queued we have to wait for
the next vblank, when had we checked earlier, we will be able to start
rendering immediately

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:10 +00:00
Yuxuan Shui
c42e6cef0a vblank: winding down vblank events instead of stopping immediately
I noticed sometimes full frame rate video is rendered at half frame rate
sometimes. That is because the damage notify is sent very close to
vblank, and since we request vblank events when we get the damage, we
miss the vblank event immediately after, despite the damage happening
before the vblank.

       request  next  ......  next next
damage  vblank vblank          vblank
   |    |       |     ......      |
   v    v       v                 v
---------------------->>>>>>---------

`request vblank` is triggered by `damage`, but because it's too close to
`next vblank`, that vblank is missed despite we requested before it
happening, and we only get `next next vblank`. The result is we will
drop every other frame.

The solution in this commit is that we will keep requesting vblank
events, right after we received a vblank event, even when nobody is
asking for them. We would do that for a set number of vblanks before
stopping (currently 4).

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:08 +00:00
Yuxuan Shui
25d16b0352 x: fix x_request_vblank_event
present_notify_msc with divisor == 0 has undocumented special meaning,
it means async present notify, which means we could receive MSC
notifications from the past.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:07 +00:00
Yuxuan Shui
3838b45e2c core: simplify pacing logic a bit more
Also, vblank event callback should call schedule_render to queue renders
instead of starting the draw timer directly, so that the CPU time
calculation will be correct.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:06 +00:00
Yuxuan Shui
4b57e3aa4c config: add debug options to enable timing based pacing
Disable timing estimation based pacing by default, as it might not work
well across drivers, and might have subtle bugs.

You can try setting `PICOM_DEBUG=smart_frame_pacing` if you want to try
it out.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:27:04 +00:00
Yuxuan Shui
1430d28116 core: refactor frame pacing
Factored out vblank event generation, add abstraction over how vblank
events are generated. The goal is so we can request vblank events in
different ways based on the driver we are running on.

Tried to simplify the frame scheduling logic, we will see if I succeeded
or not.

Also, the logic to exclude vblank events for vblank interval estimation
when the screen off is dropped. It's too hard to get right, we need to
find something robust.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 23:26:59 +00:00
Yuxuan Shui
147561a313 config: add a debug environment variable
This is where we keep temporary, short living, private debug options.

Adding and removing command line and config file options are
troublesome, and we don't want people adding these to their config
files.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 22:36:35 +00:00
Yuxuan Shui
23eb3d5f52 core: don't unredir when display is turned off
We unredirect because we receive bad vblank events, and also vblank
events at a different interval compared to when the screen is on. But it
is enough to just not record the vblank interval statistics when the
screen is off.

Although, unredirecting when display is off can also fix the problem
where use-damage causes the screen to flicker when the display is turned
off then back on. So we need something else for that.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 22:36:32 +00:00
Yuxuan Shui
2e1c4e51b3 core: don't request vblank events when we are not rendering
Previously everytime we receive a vblank event, we always request a new
one. This made the logic somewhat simpler. But this generated many
useless vblank events, and wasted power. We only need vblank events for
two things:

1. after we rendered a frame, we need to know when it has been displayed
   on the screen.
2. estimating the refresh rate.

This commit makes sure we only request vblank events when it's actually
needed.

Fixes #1079

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 22:36:31 +00:00
Yuxuan Shui
827380e1e3 core: simplify the pacing logic a little bit
Make it simpler to stop requesting PresentCompleteNotify when there is
nothing to render.

Related: #1079

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 22:36:28 +00:00
Yuxuan Shui
8d98b7d639 core: don't call schedule_render too early
I mistakenly assumed that PresentCompleteNotify event signifies the end
of a vblank (or the start of scanout). But actually this event can in
theory in sent at any point during a vblank, with its timestamp pointing
to when the end of vblank is. (that's why we often find the timestamp to
be in the future).

Add a delay so schedule_render is actually called at the end of vblank,
so it doesn't mistakenly think the render is too slow to complete.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 22:36:17 +00:00
Yuxuan Shui
e76cf43f02 backend: gl: adjust some log levels
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 11:30:42 +00:00
Bernd Busse
332501aef7 backend: gl: use effective texture size for rounded corner calculation
The rounded corner calculation only took the actual texture dimensions
into account, effectively turning all pixels outside completely
transparent. Furthermore, `texelFetch()` in the default window shader
does not work with any `GL_TEXTURE_WRAP_x` parameter.

As a result, a to-be-tiled root pixmap wasn't properly tiled and only
visible in the original top-left corner.

- Changed the default window shader to use `textureSize()` to scale the
  texture coordinates into the proper range for use with `texture2D()`.
- Added a new uniform to the default post-processing shader containing
  the effective texture size for rounded corners calculation instead of
  relying on `textureSize()`.
2023-01-20 00:08:03 +01:00
64 changed files with 1375 additions and 2057 deletions

View File

@@ -12,6 +12,7 @@ packages:
- uthash
- libconfig
- libglvnd
- libepoxy
- dbus
- pcre
sources:
@@ -19,7 +20,7 @@ sources:
tasks:
- setup: |
cd picom
CPPFLAGS="-I/usr/local/include" meson -Dunittest=true build
CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build
- build: |
cd picom
ninja -C build

23
.builds/openbsd.yml Normal file
View File

@@ -0,0 +1,23 @@
image: openbsd/latest
packages:
- libev
- xcb
- meson
- pkgconf
- cmake
- uthash
- libconfig
- dbus
- pcre2
sources:
- https://github.com/yshui/picom
tasks:
- setup: |
cd picom
CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" meson setup -Dunittest=true --werror build
- build: |
cd picom
ninja -C build
- unittest: |
cd picom
ninja -C build test

View File

@@ -29,7 +29,7 @@ commands:
- ".git"
- run:
name: config
command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build
command: CC=<< parameters.cc >> meson setup << parameters.build-config >> -Dunittest=true --werror . build
- run:
name: build
command: ninja -vC build

View File

@@ -3,3 +3,6 @@ root = true
indent_style = tab
indent_size = 8
max_line_length = 90
[*.nix]
indent_style = space
indent_size = 2

View File

@@ -18,11 +18,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
@@ -32,7 +32,7 @@ jobs:
# Autobuild
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -6,8 +6,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
- uses: yshui/git-clang-format-lint@v1.14
- uses: yshui/git-clang-format-lint@v1.15
with:
base: ${{ github.event.pull_request.base.sha }}

View File

@@ -6,9 +6,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: yshui/git-clang-format-lint@v1.14
- uses: yshui/git-clang-format-lint@v1.15
with:
base: ${{ github.event.ref }}~1

2
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Build files
.deps
.direnv
aclocal.m4
autom4te.cache
config.log
@@ -15,7 +16,6 @@ compton
build/
compile_commands.json
build.ninja
make.sh
# language servers
.ccls-cache

74
CHANGELOG.md Normal file
View File

@@ -0,0 +1,74 @@
# Unreleased
## New features
* Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170)
## Notable changes
* Marginally improve performance when resizing/opening/closing windows. (#1190)
# v11.2 (2024-Feb-13)
## Build changes
* `picom` now depends on `libepoxy` for OpenGL symbol management.
## Bug fixes
* Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168)
* Fix building on OpenBSD (#1189, #1188)
* Fix occasional freezes (#1040, #1145, #1166)
* Fix `corner-radius-rules` not applying sometimes (#1177)
* Fix window shader not having an effect when frame opacity is enabled (#1174)
* Fix binding root pixmap in case of depth mismatch (#984)
# v11.1 (2024-Jan-28)
## Bug fixes
* Fix missing fading on window close for some window managers. (#704)
# v11 (2024-Jan-20)
## Build changes
* Due to some caveats discovered related to setting the `CAP_SYS_NICE` capability, it is now recommended to **NOT** set this capability for picom.
## Deprecations
* Uses of `--sw-opti`, and `--respect-prop-shadow` are now hard errors.
* `-F` has been removed completely. It was deprecated before the picom fork.
# v11-rc1 (2024-Jan-14)
## Notable features
* picom now uses dithering to prevent banding. Banding is most notable when using a strong background blur. (#952)
* Frame pacing. picom uses present feedback information to schedule new frames when it makes sense to do so. This improves latency, and replaces the `glFlush` and `GL_MaxFramesAllowed=1` hacks we used to do for NVIDIA. (#968 #1156)
* Some missing features have been implemented for the EGL backend (#1004 #1007)
## Bug fixes
* Many memory/resource leak fixes thanks to @absolutelynothelix . (#977 #978 #979 #980 #982 #985 #992 #1009 #1022)
* Fix tiling of wallpaper. (#1002)
* Fix some blur artifacts (#1095)
* Fix shadow color for transparent shadows (#1124)
* Don't spam logs when another compositor is running (#1104)
* Fix rounded corners showing as black with the xrender backend (#1003)
* Fix blur with rounded windows (#950)
## Build changes
* Dependency `pcre` has been replaced by `pcre2`.
* New dependency `xcb-util`.
* `xinerama` is no longer used.
* `picom` now tries to give itself a real-time scheduling priority. ~~Please consider giving `picom` the `CAP_SYS_NICE` capability when packaging it.~~
## Deprecations
* The `kawase` blur method is removed. Note this is just an alias to the `dual_kawase` method, which is still available. (#1102)
# Earlier versions
Please see the GitHub releases page.

View File

@@ -34,6 +34,7 @@ hasufell <julian.ospald at googlemail.com>
i-c-u-p
Ignacio Taranto <ignacio.taranto at eclypsium.com>
Istvan Petres
Ivan Malison <ivanmalison at gmail.com>
Jake <jakeroggenbuck2 at gmail.com>
James Cloos <cloos at jhcloos.com>
Jamey Sharp <jamey at minilop.net>
@@ -43,6 +44,7 @@ Javeed Shaikh <syscrash2k at gmail.com>
Jerónimo Navarro <jnavarro at ancasrl.com.ar>
jialeens <jialeadmin at 163.com>
Johnny Pribyl <pribylsnbits at gmail.com>
Jose Maldonado aka Yukiteru <josemald89 at gmail.com>
Keith Packard <keithp at keithp.com>
Kevin Kelley <kelleyk at kelleyk.net>
ktprograms <ktprograms at gmail.com>
@@ -68,6 +70,7 @@ Peter Mattern <matternp at arcor.de>
Phil Blundell <pb at reciva.com>
Que Quotion <quequotion at bugmenot.com>
Rafael Kitover <rkitover at gmail.com>
Reith
Richard Grenville <pyxlcy at gmail.com>
Rytis Karpuska <rytis.karpuska at gmail.com>
Samuel Hand <samuel.d.hand at gmail.com>

View File

@@ -41,7 +41,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
* pixman
* libdbus (optional, disable with the `-Ddbus=false` meson configure flag)
* libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag)
* libGL, libEGL (optional, disable with the `-Dopengl=false` meson configure flag)
* libGL, libEGL, libepoxy (optional, disable with the `-Dopengl=false` meson configure flag)
* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag)
* libev
* uthash
@@ -49,13 +49,13 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
On Debian based distributions (e.g. Ubuntu), the needed packages are
```
libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libepoxy-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
```
On Fedora, the needed packages are
```
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel libepoxy-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel xcb-util-devel
```
To build the documents, you need `asciidoc`
@@ -85,11 +85,6 @@ $ ninja -C build
### To install
#### AUR (arch)
- picom-ftlabs-git
Thanks to @Fxzzi for maintaining the package.
``` bash
$ ninja -C build install
```

23
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@@ -25,11 +25,11 @@
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"type": "github"
},
"original": {
@@ -41,11 +41,12 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1691186842,
"narHash": "sha256-wxBVCvZUwq+XS4N4t9NqsHV4E64cPVqQ2fdDISpjcw0=",
"path": "/nix/store/d42v5grfq77vr10r336kks0qjp0wij8d-source",
"rev": "18036c0be90f4e308ae3ebcab0e14aae0336fe42",
"type": "path"
"lastModified": 1705856552,
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
"type": "github"
},
"original": {
"id": "nixpkgs",

View File

@@ -7,28 +7,55 @@
};
};
outputs = {
self, flake-utils, nixpkgs, git-ignore-nix, ...
}: flake-utils.lib.eachDefaultSystem (system: let
overlay = self: super: {
picom = super.picom.overrideAttrs (oldAttrs: rec {
pname = "picom";
buildInputs = [
self.pcre2 self.xorg.xcbutil
] ++ self.lib.remove self.xorg.libXinerama (
self.lib.remove self.pcre oldAttrs.buildInputs
);
src = git-ignore-nix.lib.gitignoreSource ./.;
});
};
pkgs = import nixpkgs { inherit system overlays; config.allowBroken = true; };
overlays = [ overlay ];
in rec {
inherit overlay overlays;
defaultPackage = pkgs.picom;
devShell = defaultPackage.overrideAttrs {
buildInputs = defaultPackage.buildInputs ++ [
pkgs.clang-tools
];
};
});
self,
flake-utils,
nixpkgs,
git-ignore-nix,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
overlay = self: super: {
picom = super.picom.overrideAttrs (oldAttrs: rec {
version = "11";
pname = "picom";
buildInputs =
[
self.pcre2
self.xorg.xcbutil
self.libepoxy
]
++ self.lib.remove self.xorg.libXinerama (
self.lib.remove self.pcre oldAttrs.buildInputs
);
src = git-ignore-nix.lib.gitignoreSource ./.;
});
};
pkgs = import nixpkgs {
inherit system overlays;
config.allowBroken = true;
};
overlays = [overlay];
in rec {
inherit
overlay
overlays
;
defaultPackage = pkgs.picom;
devShell = defaultPackage.overrideAttrs {
buildInputs =
defaultPackage.buildInputs
++ (with pkgs; [
clang-tools_17
llvmPackages_17.clang-unwrapped.python
]);
hardeningDisable = ["fortify"];
shellHook = ''
# Workaround a NixOS limitation on sanitizers:
# See: https://github.com/NixOS/nixpkgs/issues/287763
export LD_LIBRARY_PATH+=":/run/opengl-driver/lib"
'';
};
});
}

View File

@@ -49,9 +49,6 @@ OPTIONS
*-f*, *--fading*::
Fade windows in/out when opening/closing and when opacity changes, unless *--no-fading-openclose* is used.
*-F*::
Equals to *-f*. Deprecated.
*-i*, *--inactive-opacity*='OPACITY'::
Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
@@ -393,6 +390,7 @@ 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 vec2 effective_size; // effective dimensions of the texture (repeats pixels if larger than tex)
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
@@ -418,7 +416,8 @@ vec4 default_post_processing(vec4 c);
// 1) fetch the specified pixel
// 2) apply default post-processing
vec4 window_shader() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
vec2 texsize = textureSize(tex, 0);
vec4 c = texture2D(tex, texcoord / texsize, 0);
return default_post_processing(c);
}
----

View File

@@ -1,4 +1,4 @@
project('picom', 'c', version: '10',
project('picom', 'c', version: '11',
default_options: ['c_std=c11', 'warning_level=1'])
cc = meson.get_compiler('c')

View File

@@ -1,76 +1,29 @@
#################################
# Animations #
# !These animations WILL NOT work correctly for any other wm other than phyOS-dwm fork!
# fly-in: Windows fly in from random directions to the screen
# maximize: Windows pop from center of the screen to their respective positions
# minimize: Windows minimize from their position to the center of the screen
# slide-in-center: Windows move from upper-center of the screen to their respective positions
# slide-out-center: Windows move to the upper-center of the screen
# slide-left: Windows are created from the right-most window position and slide leftwards
# slide right: Windows are created from the left-most window position and slide rightwards
# slide-down: Windows are moved from the top of the screen and slide downward
# slide-up: Windows are moved from their position to top of the screen
# squeeze: Windows are either closed or created to/from their center y-position (the animation is similar to a blinking eye)
# squeeze-bottom: Similar to squeeze, but the animation starts from bottom-most y-position
# zoom: Windows are either created or destroyed from/to their center (not the screen center)
#################################
#enable or disable animations
animations = true;
#change animation speed of windows in current tag e.g open window in current tag
animation-stiffness-in-tag = 125;
#change animation speed of windows when tag changes
animation-stiffness-tag-change = 90.0;
animation-window-mass = 0.4;
animation-dampening = 15;
animation-clamping = true;
#open windows
animation-for-open-window = "zoom";
#minimize or close windows
animation-for-unmap-window = "squeeze";
#popup windows
animation-for-transient-window = "slide-up"; #available options: slide-up, slide-down, slide-left, slide-right, squeeze, squeeze-bottom, zoom
#set animation for windows being transitioned out while changings tags
animation-for-prev-tag = "minimize";
#enables fading for windows being transitioned out while changings tags
enable-fading-prev-tag = true;
#set animation for windows being transitioned in while changings tags
animation-for-next-tag = "slide-in-center";
#enables fading for windows being transitioned in while changings tags
enable-fading-next-tag = true;
#################################
# Shadows #
#################################
# Enabled client-side shadows on windows. Note desktop windows
# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
# unless explicitly requested using the wintypes option.
#
# shadow = false
shadow = false;
shadow = true;
# The blur radius for shadows, in pixels. (defaults to 12)
# shadow-radius = 12
shadow-radius = 60;
shadow-radius = 7;
# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
# shadow-opacity = .75
# The left offset for shadows, in pixels. (defaults to -15)
# shadow-offset-x = -15
shadow-offset-x = -20;
shadow-offset-x = -7;
# The top offset for shadows, in pixels. (defaults to -15)
# shadow-offset-y = -15
shadow-offset-y = -20;
shadow-offset-y = -7;
# Red color value of shadow (0.0 - 1.0, defaults to 0).
# shadow-red = 0
@@ -95,9 +48,6 @@ shadow-exclude = [
"class_g = 'Conky'",
"class_g ?= 'Notify-osd'",
"class_g = 'Cairo-clock'",
"class_g = 'dwm'",
"class_g = 'chromium'",
"class_g *?= 'slop'",
"_GTK_FRAME_EXTENTS@:c"
];
@@ -123,18 +73,19 @@ shadow-exclude = [
# Fade windows in/out when opening/closing and when opacity changes,
# unless no-fading-openclose is used.
# fading = false
fading = true;
# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
# fade-in-step = 0.028
fade-in-step = 0.023;
fade-in-step = 0.03;
# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
# fade-out-step = 0.03
fade-out-step = 0.035;
fade-out-step = 0.03;
# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
fade-delta = 10
# fade-delta = 10
# Specify a list of conditions of windows that should not be faded.
# fade-exclude = []
@@ -153,13 +104,15 @@ fade-delta = 10
# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
# inactive-opacity = 1
inactive-opacity = 0.8;
# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
# frame-opacity = 1.0
frame-opacity = 0.7;
# Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows.
# inactive-opacity-override = true
inactive-opacity-override = true;
inactive-opacity-override = false;
# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
# active-opacity = 1.0
@@ -169,13 +122,21 @@ inactive-opacity-override = true;
# Specify a list of conditions of windows that should never be considered focused.
# focus-exclude = []
focus-exclude = [
"class_g = 'Cairo-clock'" ,
];
focus-exclude = [ "class_g = 'Cairo-clock'" ];
# Use fixed inactive dim value, instead of adjusting according to window opacity.
# inactive-dim-fixed = 1.0
# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
# like `50:name *= "Firefox"`. picom-trans is recommended over this.
# Note we don't make any guarantee about possible conflicts with other
# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
# example:
# opacity-rule = [ "80:class_g = 'URxvt'" ];
#
# opacity-rule = []
#################################
# Corners #
#################################
@@ -183,21 +144,52 @@ focus-exclude = [
# Sets the radius of rounded window corners. When > 0, the compositor will
# round the corners of windows. Does not interact well with
# `transparent-clipping`.
corner-radius = 11;
corner-radius = 0
# Exclude conditions for rounded corners.
#rounded-corners-exclude = [
# "window_type = 'dock'",
# "window_type = 'desktop'"
#];
rounded-corners-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'"
];
blur: {
method = "dual_kawase";
strength = 9;
background = true;
background-frame = false;
background-fixed = false;
}
#################################
# Background-Blurring #
#################################
# Parameters for background blurring, see the *BLUR* section for more information.
# blur-method =
# blur-size = 12
#
# blur-deviation = false
#
# blur-strength = 5
# Blur background of semi-transparent / ARGB windows.
# Bad in performance, with driver-dependent behavior.
# The name of the switch may change without prior notifications.
#
# blur-background = false
# Blur background of windows when the window frame is not opaque.
# Implies:
# blur-background
# Bad in performance, with driver-dependent behavior. The name may change.
#
# blur-background-frame = false
# Use fixed blur strength rather than adjusting according to window opacity.
# blur-background-fixed = false
# Specify the blur convolution kernel, with the following format:
# example:
# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
#
# blur-kern = ""
blur-kern = "3x3box";
# Exclude conditions for background blur.
@@ -205,25 +197,24 @@ blur: {
blur-background-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'",
"_GTK_FRAME_EXTENTS@:c",
"class_g = 'Chromium'",
"class_g = 'Discord'",
"class_g = 'Dunst'",
"class_g = 'Peek'",
"class_g *?= 'slop'",
"_GTK_FRAME_EXTENTS@:c"
];
#################################
# General Settings #
#################################
# Enable remote control via D-Bus. See the man page for more details.
# dbus = true
# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
# daemon = false
# Specify the backend to use: `xrender`, `glx`, `egl` or `xr_glx_hybrid`.
# `xrender` is the default one.
#
backend = "glx"
# backend = "glx"
backend = "xrender";
# Use higher precision during rendering, and apply dither when presenting the
# rendered screen. Reduces banding artifacts, but might cause performance
@@ -231,7 +222,8 @@ backend = "glx"
dithered-present = false;
# Enable/disable VSync.
# vsync = true
# vsync = false
vsync = true;
# Try to detect WM windows (a non-override-redirect window with no
# child that has 'WM_STATE') and mark them as active.
@@ -247,25 +239,25 @@ mark-ovredir-focused = true;
# shaped windows. The accuracy is not very high, unfortunately.
#
# detect-rounded-corners = false
detect-rounded-corners = false;
detect-rounded-corners = true;
# 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 = false;
detect-client-opacity = true;
# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
# provided that the WM supports it.
#
use-ewmh-active-win = true;
# use-ewmh-active-win = false
# Unredirect all windows if a full-screen opaque window is detected,
# to maximize performance for full-screen windows. Known to cause flickering
# when redirecting/unredirecting windows.
#
unredir-if-possible = false;
# unredir-if-possible = false
# Delay before unredirecting the window, in milliseconds. Defaults to 0.
# unredir-if-possible-delay = 0
@@ -309,7 +301,7 @@ detect-transient = true;
# practically happened) and may not work with blur-background.
# My tests show a 15% performance boost. Recommended.
#
glx-no-stencil = true;
# glx-no-stencil = false
# GLX backend: Avoid rebinding pixmap on window damage.
# Probably could improve performance on rapid window content changes,
@@ -330,18 +322,24 @@ use-damage = true;
# calls are finished before picom starts drawing. Needed on nvidia-drivers
# with GLX backend for some users.
#
xrender-sync-fence = 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";
# window-shader-fg = "default"
# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
#
# window-shader-fg-rule = [
# "my_shader.frag:window_type != 'dock'"
# ]
# Force all windows to be painted with blending. Useful if you
# have a glx-fshader-win that could turn opaque pixels transparent.
#
# force-win-blend = true;
# force-win-blend = false
# Do not use EWMH to detect fullscreen windows.
# Reverts to checking if a window is fullscreen based only on its size and coordinates.
@@ -358,7 +356,7 @@ window-shader-fg = "default";
# Make transparent windows clip other windows like non-transparent windows do,
# instead of blending on top of them.
#
transparent-clipping = false;
# transparent-clipping = false
# Specify a list of conditions of windows that should never have transparent
# clipping applied. Useful for screenshot tools, where you need to be able to
@@ -431,9 +429,3 @@ wintypes:
popup_menu = { opacity = 0.8; }
dropdown_menu = { opacity = 0.8; }
};
opacity-rule = [
"100:class_g = 'St' && focused",
"50:class_g = 'St' && !focused",
"100:fullscreen",
];

View File

@@ -29,7 +29,8 @@ static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
struct atom *init_atoms(xcb_connection_t *c) {
auto atoms = ccalloc(1, struct atom);
atoms->c = new_cache((void *)c, atom_getter, NULL);
#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
#define ATOM_GET(x) \
atoms->a##x = (xcb_atom_t)(intptr_t)cache_get_or_fetch(atoms->c, #x, NULL)
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
#undef ATOM_GET

View File

@@ -23,6 +23,7 @@
WM_CLIENT_MACHINE, \
_NET_ACTIVE_WINDOW, \
_COMPTON_SHADOW, \
COMPTON_VERSION, \
_NET_WM_WINDOW_TYPE, \
_XROOTPMAP_ID, \
ESETROOT_PMAP_ID, \
@@ -61,7 +62,11 @@ struct atom {
struct atom *init_atoms(xcb_connection_t *);
static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
return (xcb_atom_t)(intptr_t)cache_get_or_fetch(a->c, key, NULL);
}
static inline xcb_atom_t get_atom_cached(struct atom *a, const char *key) {
return (xcb_atom_t)(intptr_t)cache_get(a->c, key);
}
static inline void destroy_atoms(struct atom *a) {

View File

@@ -56,58 +56,6 @@ region_t get_damage(session_t *ps, bool all_damage) {
return region;
}
static void process_window_for_painting(session_t *ps, struct managed_win *w,
void *win_image, double additional_alpha,
region_t *reg_bound, region_t *reg_visible,
region_t *reg_paint, region_t *reg_paint_in_bound) {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local, reg_visible, reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x, -w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(&reg_visible_local, &reg_visible_local,
&reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(ps->backend_data, win_image,
&reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
double alpha = additional_alpha * w->opacity;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
ps->backend_data->ops->image_op(ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img,
&reg_frame, &reg_visible_local,
(double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, dest_coord,
reg_paint_in_bound, reg_visible, true);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_bound_local);
}
void handle_device_reset(session_t *ps) {
log_error("Device reset detected");
// Wait for reset to complete
@@ -246,7 +194,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
auto after_damage_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
log_trace("Getting damage took %" PRIu64 " us", after_damage_us - after_sync_fence_us);
if (ps->next_render > 0) {
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld",
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %" PRIu64,
labs((long)after_damage_us - (long)ps->next_render),
after_damage_us < ps->next_render ? "early" : "late",
after_damage_us, ps->next_render);
@@ -262,8 +210,8 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
(coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height},
&reg_paint, &reg_visible, true);
(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},
&reg_paint);
@@ -314,7 +262,6 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
* option */
auto real_win_mode = w->mode;
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
@@ -443,7 +390,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
}
ps->backend_data->ops->compose(
ps->backend_data, w->shadow_image, shadow_coord,
inverted_mask, window_coord, &reg_shadow, &reg_visible, false);
inverted_mask, window_coord, &reg_shadow, &reg_visible);
if (inverted_mask) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
@@ -487,17 +434,6 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->win_image, &border_width);
if (w->old_win_image) {
// TODO(dccsillag): explain why the following is
// "necessary"
double zero = 0.0;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->old_win_image, &zero);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS,
w->old_win_image, &zero);
}
}
ps->backend_data->ops->set_image_property(
@@ -520,43 +456,53 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
}
// Draw window on target
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
if (w->frame_opacity == 1 && !is_animating) {
if (w->frame_opacity == 1) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
window_coord, NULL, dest_coord,
&reg_paint_in_bound, &reg_visible, true);
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
} else {
if (is_animating && w->old_win_image) {
bool is_focused = win_is_focused_raw(ps, w);
if (!is_focused && w->focused && w->opacity_is_set)
is_focused = true;
assert(w->old_win_image);
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
// Only animate opacity here if we are resizing
// a transparent window
process_window_for_painting(ps, w, w->win_image,
is_focused ? 1.0 : w->opacity >= 1 ? 1.0 : w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
// Only do this if size changes as otherwise moving
// transparent windows will flicker and if you just
// move so slightly they will keep flickering
if (resizing && (!is_focused || !w->opacity_is_set)) {
process_window_for_painting(ps, w, w->old_win_image,
1.0 - w->animation_progress,
&reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
}
} else {
process_window_for_painting(
ps, w, w->win_image, 1.0, &reg_bound, &reg_visible,
&reg_paint, &reg_paint_in_bound);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
&reg_visible, &reg_paint);
pixman_region32_translate(&reg_visible_local, -w->g.x,
-w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &reg_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, &reg_frame,
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_bound_local);
}
skip:
pixman_region32_fini(&reg_bound);

View File

@@ -115,6 +115,10 @@ struct dual_kawase_blur_args {
int strength;
};
typedef struct {
// Intentionally left blank
} *image_handle;
struct backend_operations {
// =========== Initialization ===========
@@ -167,31 +171,27 @@ struct backend_operations {
* Paint the content of an image onto the rendering buffer.
*
* @param backend_data the backend data
* @param image_data the image to paint
* @param image the image to paint, cannot be NULL
* @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
* the top left of the image. Optional, can be
* NULL.
* @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, coord_t image_dst,
void *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible, bool lerp);
void (*_compose)(backend_t *backend_data, void *image_data,
int dst_x1, int dst_y1, int dst_x2, int dst_y2,
const region_t *reg_paint, const region_t *reg_visible);
void (*compose)(backend_t *backend_data, image_handle image, coord_t image_dst,
image_handle mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible) attr_nonnull(1, 2, 6, 7);
/// 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.
///
/// 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,
/// The blur can be limited by `mask`. `mask_dst` specifies the top left corner of
/// the mask. `mask` can be NULL.
bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx,
image_handle mask, coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible) attr_nonnull(1, 3, 6, 7);
/// Update part of the back buffer with the rendering buffer, then present the
@@ -207,13 +207,15 @@ struct backend_operations {
* Bind a X pixmap to the backend's internal image data structure.
*
* @param backend_data backend data
* @param pixmap X pixmap to bind
* @param fmt information of the pixmap's visual
* @param owned whether the ownership of the pixmap is transfered to the backend
* @return backend internal data structure bound with this pixmap
* @param pixmap X pixmap to bind
* @param fmt information of the pixmap's visual
* @param owned whether the ownership of the pixmap is transferred to the
* backend.
* @return backend specific image handle for the pixmap. May be
* NULL.
*/
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
image_handle (*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
/// Create a shadow context for rendering shadows with radius `radius`.
/// Default implementation: default_create_shadow_context
@@ -225,21 +227,27 @@ struct backend_operations {
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
/// size of `width + radius * 2` x `height + radius * 2`. Radius is set when the
/// shadow context is created.
/// Default implementation: default_render_shadow
///
/// @return the shadow image, may be NULL.
///
/// Required.
void *(*render_shadow)(backend_t *backend_data, int width, int height,
struct backend_shadow_context *ctx, struct color color);
image_handle (*render_shadow)(backend_t *backend_data, int width, int height,
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`.
///
/// @param mask the input mask, must not be NULL.
/// @return the shadow image, may be NULL.
///
/// Optional.
void *(*shadow_from_mask)(backend_t *backend_data, void *mask,
struct backend_shadow_context *ctx, struct color color);
image_handle (*shadow_from_mask)(backend_t *backend_data, image_handle 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
@@ -250,13 +258,18 @@ struct backend_operations {
/// and outside of the mask. Corner radius should exclude the corners from the
/// mask. Corner radius should be applied before the inversion.
///
/// @return the mask image, may be NULL.
///
/// Required.
void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg);
image_handle (*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);
///
/// @param image the image to be released, cannot be NULL.
void (*release_image)(backend_t *backend_data, image_handle image) attr_nonnull(1, 2);
/// Create a shader object from a shader source.
///
@@ -281,12 +294,14 @@ struct backend_operations {
/// This function is needed because some backend might change the content of the
/// window (e.g. when using a custom shader with the glx backend), so only the
/// backend knows if an image is transparent.
bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
///
/// @param image the image to be checked, must not be NULL.
bool (*is_image_transparent)(backend_t *backend_data, image_handle image)
attr_nonnull(1, 2);
/// Get the age of the buffer content we are currently rendering ontop
/// Get the age of the buffer content we are currently rendering on top
/// of. The buffer that has just been `present`ed has a buffer age of 1.
/// Everytime `present` is called, buffers get older. Return -1 if the
/// Every time `present` is called, buffers get older. Return -1 if the
/// buffer is empty.
///
/// Optional
@@ -295,7 +310,7 @@ struct backend_operations {
/// Get the render time of the last frame. If the render is still in progress,
/// returns false. The time is returned in `ts`. Frames are delimited by the
/// present() calls. i.e. after a present() call, last_render_time() should start
/// reporting the time of the just presen1ted frame.
/// reporting the time of the just presented frame.
///
/// Optional, if not available, the most conservative estimation will be used.
bool (*last_render_time)(backend_t *backend_data, struct timespec *ts);
@@ -316,35 +331,39 @@ struct backend_operations {
*
* @param backend_data backend data
* @param prop the property to change
* @param image_data an image data structure returned by the backend
* @param image an image handle, cannot be NULL.
* @param args property value
* @return whether the operation is successful
* @return whether the operation is successful
*/
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args);
image_handle image, void *args) attr_nonnull(1, 3);
/**
* Manipulate an image. Image properties are untouched.
*
* @param backend_data backend data
* @param op the operation to perform
* @param image_data an image data structure returned by the backend
* @param image an image handle, cannot be NULL.
* @param reg_op the clip region, define the part of the image to be
* operated on.
* @param reg_visible define the part of the image that will eventually
* be visible on target. this is a hint to the backend
* for optimization purposes.
* @param args extra arguments, operation specific
* @return whether the operation is successful
* @return whether the operation is successful
*/
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);
bool (*image_op)(backend_t *backend_data, enum image_operations op,
image_handle image, const region_t *reg_op,
const region_t *reg_visible, void *args) attr_nonnull(1, 3, 4, 5);
/// 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
void *(*clone_image)(backend_t *base, const void *image_data,
const region_t *reg_visible);
/// Create another instance of the `image`. The newly created image
/// inherits its content and all image properties from the image being
/// cloned. All `image_op` and `set_image_property` calls on the
/// returned image should not affect the original image.
///
/// @param image the image to be cloned, must not be NULL.
image_handle (*clone_image)(backend_t *base, image_handle image,
const region_t *reg_visible) attr_nonnull_all;
/// Create a blur context that can be used to call `blur`
void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);

View File

@@ -293,8 +293,8 @@ shadow_picture_err:
return false;
}
void *default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
const conv *kernel = (void *)sctx;
xcb_render_picture_t shadow_pixel =
solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue);
@@ -308,7 +308,7 @@ void *default_render_shadow(backend_t *backend_data, int width, int height,
}
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
void *ret = backend_data->ops->bind_pixmap(
auto ret = backend_data->ops->bind_pixmap(
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
x_free_picture(backend_data->c, pict);
x_free_picture(backend_data->c, shadow_pixel);
@@ -316,16 +316,16 @@ void *default_render_shadow(backend_t *backend_data, int width, int height,
}
/// Implement render_shadow with shadow_from_mask
void *
image_handle
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(
auto 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);
auto shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
backend_data->ops->release_image(backend_data, mask);
return shadow;
}
@@ -458,17 +458,17 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) {
return params;
}
void *default_clone_image(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
image_handle default_clone_image(backend_t *base attr_unused, image_handle image,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct backend_image);
*new_img = *(struct backend_image *)image_data;
*new_img = *(struct backend_image *)image;
new_img->inner->refcount++;
return new_img;
return (image_handle)new_img;
}
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
void *image_data, void *arg) {
struct backend_image *tex = image_data;
image_handle image, void *arg) {
auto tex = (struct backend_image *)image;
int *iargs = arg;
bool *bargs = arg;
double *dargs = arg;
@@ -490,8 +490,8 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
return true;
}
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) {
struct backend_image *img = image_data;
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image) {
auto img = (struct backend_image *)image;
return img->opacity < 1 || img->inner->has_alpha;
}

View File

@@ -54,11 +54,11 @@ solid_picture(struct x_connection *, bool argb, double a, double r, double g, do
xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity,
int width, int height);
void *default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
image_handle default_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 *
image_handle
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 *
@@ -72,8 +72,8 @@ void init_backend_base(struct backend_base *base, session_t *ps);
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
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);
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
image_handle default_clone_image(backend_t *base, image_handle image, const region_t *reg);
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image);
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
void *image_data, void *arg);
image_handle image, void *arg);
struct backend_image *default_new_backend_image(int w, int h);

View File

@@ -19,11 +19,14 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) {
}
}
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) {
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) {
enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT;
#ifdef CONFIG_OPENGL
if (driver & DRIVER_NVIDIA) {
return VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
}
return VBLANK_SCHEDULER_PRESENT;
#endif
return type;
}
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {

View File

@@ -50,8 +50,9 @@ void dummy_deinit(struct backend_base *data) {
free(dummy);
}
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
static void dummy_check_image(struct backend_base *base, image_handle image) {
auto dummy = (struct dummy_data *)base;
auto img = (struct dummy_image *)image;
if (img == (struct dummy_image *)&dummy->mask) {
return;
}
@@ -64,13 +65,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, coord_t dst attr_unused,
void *mask attr_unused, coord_t mask_dst attr_unused,
void dummy_compose(struct backend_base *base, image_handle image, coord_t dst attr_unused,
image_handle mask attr_unused, coord_t mask_dst attr_unused,
const region_t *reg_paint attr_unused,
const region_t *reg_visible attr_unused, bool lerp 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);
assert(mask == NULL || (struct backend_image *)mask == &dummy->mask);
}
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
@@ -78,20 +79,20 @@ 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, void *mask attr_unused,
void *blur_ctx attr_unused, image_handle 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;
}
void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned) {
image_handle dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned) {
auto dummy = (struct dummy_data *)base;
struct dummy_image *img = NULL;
HASH_FIND_INT(dummy->images, &pixmap, img);
if (img) {
(*img->refcount)++;
return img;
return (image_handle)img;
}
img = ccalloc(1, struct dummy_image);
@@ -102,12 +103,12 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
img->owned = owned;
HASH_ADD_INT(dummy->images, pixmap, img);
return (void *)img;
return (image_handle)img;
}
void dummy_release_image(backend_t *base, void *image) {
void dummy_release_image(backend_t *base, image_handle image) {
auto dummy = (struct dummy_data *)base;
if (image == &dummy->mask) {
if ((struct backend_image *)image == &dummy->mask) {
return;
}
auto img = (struct dummy_image *)image;
@@ -123,10 +124,9 @@ void dummy_release_image(backend_t *base, void *image) {
}
}
bool dummy_is_image_transparent(struct backend_base *base, void *image) {
auto img = (struct dummy_image *)image;
dummy_check_image(base, img);
return img->transparent;
bool dummy_is_image_transparent(struct backend_base *base, image_handle image) {
dummy_check_image(base, image);
return ((struct dummy_image *)image)->transparent;
}
int dummy_buffer_age(struct backend_base *base attr_unused) {
@@ -134,29 +134,31 @@ int dummy_buffer_age(struct backend_base *base attr_unused) {
}
bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
void *image, const region_t *reg_op attr_unused,
image_handle image, const region_t *reg_op attr_unused,
const region_t *reg_visible attr_unused, void *args attr_unused) {
dummy_check_image(base, image);
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);
image_handle dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
const region_t *reg attr_unused) {
auto dummy = (struct dummy_data *)base;
auto mask = &dummy->mask;
return (image_handle)mask;
}
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
void *image, void *arg attr_unused) {
image_handle image, void *arg attr_unused) {
dummy_check_image(base, image);
return true;
}
void *dummy_clone_image(struct backend_base *base, const void *image,
const region_t *reg_visible attr_unused) {
auto img = (const struct dummy_image *)image;
dummy_check_image(base, img);
(*img->refcount)++;
return (void *)img;
image_handle dummy_clone_image(struct backend_base *base, image_handle image,
const region_t *reg_visible attr_unused) {
dummy_check_image(base, image);
auto image_impl = (struct dummy_image *)image;
(*image_impl->refcount)++;
return image;
}
void *dummy_create_blur_context(struct backend_base *base attr_unused,

View File

@@ -256,10 +256,11 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
return true;
}
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible attr_unused,
GLuint source_texture, geometry_t source_size, GLuint target_fbo,
GLuint default_mask, bool high_precision) {
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
bool high_precision) {
bool ret = false;
if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
@@ -400,12 +401,12 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor
return ret;
}
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle 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,
return gl_blur_impl(opacity, bctx, (struct backend_image *)mask, mask_dst,
reg_blur, reg_visible, gd->back_texture,
(geometry_t){.width = gd->width, .height = gd->height},
gd->back_fbo, gd->default_mask_texture, gd->dithered_present);
}
@@ -604,9 +605,9 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
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);
log_debug("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

View File

@@ -36,12 +36,6 @@ struct egl_data {
EGLContext ctx;
};
static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL;
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL;
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL;
static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL;
static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL;
const char *eglGetErrorString(EGLint error) {
#define CASE_STR(value) \
case value: return #value;
@@ -74,7 +68,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) {
struct egl_pixmap *p = tex->user_data;
// Release binding
if (p->image != EGL_NO_IMAGE) {
eglDestroyImageProc(gd->display, p->image);
eglDestroyImage(gd->display, p->image);
p->image = EGL_NO_IMAGE;
}
@@ -134,18 +128,6 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
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) {
@@ -154,12 +136,12 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
}
gd = ccalloc(1, struct egl_data);
gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->c.dpy,
(EGLAttrib[]){
EGL_PLATFORM_X11_SCREEN_EXT,
ps->c.screen,
EGL_NONE,
});
gd->display = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, ps->c.dpy,
(EGLint[]){
EGL_PLATFORM_X11_SCREEN_EXT,
ps->c.screen,
EGL_NONE,
});
if (gd->display == EGL_NO_DISPLAY) {
log_error("Failed to get EGL display.");
goto end;
@@ -212,7 +194,7 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
// clang-format on
gd->target_win =
eglCreatePlatformWindowSurfaceProc(gd->display, config, &target, NULL);
eglCreatePlatformWindowSurfaceEXT(gd->display, config, &target, NULL);
if (gd->target_win == EGL_NO_SURFACE) {
log_error("Failed to create EGL surface.");
goto end;
@@ -243,14 +225,6 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
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;
@@ -275,7 +249,7 @@ end:
return &gd->gl.base;
}
static void *
static image_handle
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;
@@ -302,9 +276,8 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
eglpixmap = cmalloc(struct egl_pixmap);
eglpixmap->pixmap = pixmap;
eglpixmap->image =
eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
eglpixmap->image = eglCreateImage(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
eglpixmap->owned = owned;
if (eglpixmap->image == EGL_NO_IMAGE) {
@@ -324,14 +297,14 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
wd->dim = 0;
wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, inner->texture);
glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL);
glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return wd;
return (image_handle)wd;
err:
if (eglpixmap && eglpixmap->image) {
eglDestroyImageProc(gd->display, eglpixmap->image);
eglDestroyImage(gd->display, eglpixmap->image);
}
free(eglpixmap);
@@ -422,41 +395,6 @@ struct backend_operations egl_ops = {
.max_buffer_age = 5, // Why?
};
PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
/**
* Check if a EGL 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) {
@@ -464,7 +402,10 @@ void eglext_init(EGLDisplay dpy) {
return;
}
eglext.initialized = true;
#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name)
#define check_ext(name) \
eglext.has_##name = epoxy_has_egl_extension(dpy, #name); \
log_info("Extension " #name " - %s", eglext.has_##name ? "present" : "absent")
check_ext(EGL_EXT_buffer_age);
check_ext(EGL_EXT_create_context_robustness);
check_ext(EGL_KHR_image_pixmap);
@@ -472,16 +413,4 @@ void eglext_init(EGLDisplay dpy) {
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
}

View File

@@ -1,10 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <epoxy/egl.h>
#include <epoxy/gl.h>
#include <stdbool.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
@@ -24,8 +22,4 @@ struct eglext_info {
extern struct eglext_info eglext;
#ifdef EGL_MESA_query_driver
extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
#endif
void eglext_init(EGLDisplay);

View File

@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <GL/gl.h>
#include <GL/glext.h>
#include <epoxy/gl.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
@@ -185,7 +184,7 @@ void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader)
* @note In order to reduce number of textures which needs to be
* allocated and deleted during this recursive render
* we reuse the same two textures for render source and
* destination simply by alterating between them.
* destination simply by alternating between them.
* Unfortunately on first iteration source_texture might
* be read-only. In this case we will select auxiliary_texture as
* destination_texture in order not to touch that read-only source
@@ -253,7 +252,7 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina
/*
* @brief Builds a 1x1 texture which has color corresponding to the average of all
* pixels of img by recursively rendering into texture of quorter the size (half
* pixels of img by recursively rendering into texture of quarter the size (half
* width and half height).
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
* deleted when the gl_image is released.
@@ -390,6 +389,10 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
if (win_shader->uniform_tex >= 0) {
glUniform1i(win_shader->uniform_tex, 0);
}
if (win_shader->uniform_effective_size >= 0) {
glUniform2f(win_shader->uniform_effective_size, (float)img->ewidth,
(float)img->eheight);
}
if (win_shader->uniform_dim >= 0) {
glUniform1f(win_shader->uniform_dim, (float)img->dim);
}
@@ -417,7 +420,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
}
glUniform1i(win_shader->uniform_mask_tex, 2);
glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x ,
glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x,
(float)mask_offset.y);
if (mask != NULL) {
glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted);
@@ -499,7 +502,6 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
image_dst.y = root_height - image_dst.y;
image_dst.y -= extent_height;
for (int i = 0; i < nrects; i++) {
// Y-flip. Note after this, crect.y1 > crect.y2
rect_t crect = rects[i];
@@ -549,11 +551,12 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
}
// TODO(yshui) make use of reg_visible
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused, bool lerp) {
void gl_compose(backend_t *base, image_handle image, coord_t image_dst,
image_handle mask_, coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
struct backend_image *img = image_data;
auto img = (struct backend_image *)image;
auto mask = (struct backend_image *)mask_;
auto inner = (struct gl_texture *)img->inner;
// Painting
@@ -577,14 +580,6 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask
coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y};
x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height,
gd->height, inner->y_inverted, coord, indices);
if (lerp) {
for (unsigned int i = 2; i < 16; i+=4) {
coord[i+0] = lerp_range(0, mask_offset.x, 0, inner->width, coord[i+0]);
coord[i+1] = lerp_range(0, mask_offset.y, 0, inner->height, coord[i+1]);
}
}
_gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);
@@ -608,6 +603,7 @@ static bool gl_win_shader_from_stringv(const char **vshader_strv,
bind_uniform(ret, opacity);
bind_uniform(ret, invert_color);
bind_uniform(ret, tex);
bind_uniform(ret, effective_size);
bind_uniform(ret, dim);
bind_uniform(ret, brightness);
bind_uniform(ret, max_brightness);
@@ -714,7 +710,7 @@ 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) {
image_handle 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;
@@ -745,7 +741,7 @@ void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
return img;
return (image_handle)img;
}
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
@@ -761,8 +757,8 @@ static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
gl_check_err();
}
void gl_release_image(backend_t *base, void *image_data) {
struct backend_image *wd = image_data;
void gl_release_image(backend_t *base, image_handle image) {
auto wd = (struct backend_image *)image;
auto inner = (struct gl_texture *)wd->inner;
inner->refcount--;
assert(inner->refcount >= 0);
@@ -889,24 +885,35 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glUseProgram(0);
gd->dithered_present = ps->o.dithered_present;
gd->dummy_prog =
gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL},
(const char *[]){dummy_frag, NULL});
if (!gd->dummy_prog) {
log_error("Failed to create the dummy shader");
return false;
}
if (gd->dithered_present) {
gd->present_prog = gl_create_program_from_strv(
(const char *[]){present_vertex_shader, NULL},
(const char *[]){present_frag, dither_glsl, NULL});
} else {
gd->present_prog = gl_create_program_from_strv(
(const char *[]){present_vertex_shader, NULL},
(const char *[]){dummy_frag, NULL});
gd->present_prog = gd->dummy_prog;
}
if (!gd->present_prog) {
log_error("Failed to create the present shader");
return false;
}
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
glUseProgram(gd->present_prog);
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
pml = glGetUniformLocationChecked(gd->dummy_prog, "projection");
glUseProgram(gd->dummy_prog);
glUniform1i(glGetUniformLocationChecked(gd->dummy_prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
if (gd->present_prog != gd->dummy_prog) {
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
glUseProgram(gd->present_prog);
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
}
gd->shadow_shader.prog =
gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
@@ -916,7 +923,6 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
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 =
@@ -929,6 +935,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glUseProgram(gd->brightness_shader.prog);
glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
// Set up the size and format of the back texture
@@ -965,8 +972,8 @@ 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");
gd->has_robustness = epoxy_has_gl_extension("GL_ARB_robustness");
gd->has_egl_image_storage = epoxy_has_gl_extension("GL_EXT_EGL_image_storage");
gl_check_err();
return true;
@@ -982,10 +989,25 @@ void gl_deinit(struct gl_data *gd) {
gl_destroy_window_shader(&gd->base, gd->default_shader);
gd->default_shader = NULL;
}
glDeleteProgram(gd->dummy_prog);
if (gd->present_prog != gd->dummy_prog) {
glDeleteProgram(gd->present_prog);
}
gd->dummy_prog = 0;
gd->present_prog = 0;
glDeleteProgram(gd->fill_shader.prog);
glDeleteProgram(gd->brightness_shader.prog);
glDeleteProgram(gd->shadow_shader.prog);
gd->fill_shader.prog = 0;
gd->brightness_shader.prog = 0;
gd->shadow_shader.prog = 0;
glDeleteTextures(1, &gd->default_mask_texture);
glDeleteTextures(1, &gd->back_texture);
glDeleteQueries(2, gd->frame_timing);
gl_check_err();
}
@@ -1017,9 +1039,11 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
auto new_tex = ccalloc(1, struct gl_texture);
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
new_tex->y_inverted = true;
new_tex->y_inverted = inner->y_inverted;
new_tex->has_alpha = inner->has_alpha;
new_tex->height = inner->height;
new_tex->width = inner->width;
new_tex->shader = inner->shader;
new_tex->refcount = 1;
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
@@ -1028,8 +1052,7 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
assert(gd->present_prog);
glUseProgram(gd->present_prog);
glUseProgram(gd->dummy_prog);
glBindTexture(GL_TEXTURE_2D, inner->texture);
GLuint fbo;
@@ -1199,9 +1222,9 @@ bool gl_last_render_time(backend_t *base, struct timespec *ts) {
return true;
}
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
struct backend_image *tex = image_data;
auto tex = (struct backend_image *)image;
switch (op) {
case IMAGE_OP_APPLY_ALPHA:
gl_image_decouple(base, tex);
@@ -1214,12 +1237,12 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
}
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args) {
image_handle image, void *args) {
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
return default_set_image_property(backend_data, prop, image_data, args);
return default_set_image_property(backend_data, prop, image, args);
}
struct backend_image *img = image_data;
auto img = (struct backend_image *)image;
auto inner = (struct gl_texture *)img->inner;
inner->shader = args;
return true;
@@ -1258,12 +1281,12 @@ void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shado
free(ctx_);
}
void *gl_shadow_from_mask(backend_t *base, void *mask,
struct backend_shadow_context *sctx, struct color color) {
image_handle gl_shadow_from_mask(backend_t *base, image_handle 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 mask = (struct backend_image *)mask_;
auto inner = (struct gl_texture *)mask->inner;
auto gsctx = (struct gl_shadow_context *)sctx;
int radius = (int)gsctx->radius;
@@ -1293,7 +1316,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
source_texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
if (img->color_inverted) {
if (mask->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);
@@ -1402,7 +1425,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
gl_check_err();
return new_img;
return (image_handle)new_img;
}
enum device_status gl_device_status(backend_t *base) {

View File

@@ -1,12 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <GL/gl.h>
#include <GL/glext.h>
#include <epoxy/gl.h>
#include <stdbool.h>
#include <string.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "log.h"
#include "region.h"
@@ -29,12 +29,11 @@ static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
// Program and uniforms for window shader
typedef struct {
UT_hash_handle hh;
uint32_t id;
GLuint prog;
GLint uniform_opacity;
GLint uniform_invert_color;
GLint uniform_tex;
GLint uniform_effective_size;
GLint uniform_dim;
GLint uniform_brightness;
GLint uniform_max_brightness;
@@ -77,7 +76,7 @@ typedef struct {
GLint color_loc;
} gl_fill_shader_t;
/// @brief Wrapper of a binded GL texture.
/// @brief Wrapper of a bound GL texture.
struct gl_texture {
int refcount;
bool has_alpha;
@@ -111,6 +110,7 @@ struct gl_data {
GLuint frame_timing[2];
int current_frame_timing;
GLuint present_prog;
GLuint dummy_prog;
bool dithered_present;
@@ -144,14 +144,14 @@ 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);
image_handle image, void *args);
bool gl_last_render_time(backend_t *backend_data, struct timespec *time);
/**
* @brief Render a region with texture data.
*/
void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp);
void gl_compose(backend_t *, image_handle image, coord_t image_dst, image_handle mask,
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible);
void gl_resize(struct gl_data *, int width, int height);
@@ -160,32 +160,32 @@ void gl_deinit(struct gl_data *gd);
GLuint gl_new_texture(GLenum target);
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
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_release_image(backend_t *base, image_handle image);
image_handle 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);
image_handle gl_clone(backend_t *base, image_handle image, 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, bool high_precision);
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle 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,
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
bool high_precision);
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
void gl_destroy_blur_context(backend_t *base, void *ctx);
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius);
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);
image_handle gl_shadow_from_mask(backend_t *base, image_handle 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);
/**
@@ -264,26 +264,6 @@ static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb)
#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
/**
* Check if a GL extension exists.
*/
static inline bool gl_has_extension(const char *ext) {
int nexts = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
for (int i = 0; i < nexts || !nexts; i++) {
const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
if (exti == NULL) {
break;
}
if (strcmp(ext, exti) == 0) {
return true;
}
}
gl_clear_err();
log_info("Missing GL extension %s.", ext);
return false;
}
static const GLuint vert_coord_loc = 0;
static const GLuint vert_in_texcoord_loc = 1;

View File

@@ -17,8 +17,10 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <uthash.h>
#include <xcb/composite.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
@@ -44,6 +46,13 @@ struct _glx_data {
struct gl_data gl;
xcb_window_t target_win;
GLXContext ctx;
struct glx_fbconfig_cache *cached_fbconfigs;
};
struct glx_fbconfig_cache {
UT_hash_handle hh;
struct xvisual_info visual_info;
struct glx_fbconfig_info info;
};
#define glXGetFBConfigAttribChecked(a, b, attr, c) \
@@ -54,9 +63,12 @@ struct _glx_data {
} \
} while (0)
struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *c, struct xvisual_info m) {
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size,
m.blue_size, m.green_size, m.alpha_size, m.visual_depth);
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
struct glx_fbconfig_info *info) {
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth: %d, visual id: %#x", m.red_size,
m.blue_size, m.green_size, m.alpha_size, m.visual_depth, m.visual);
info->cfg = NULL;
int ncfg;
// clang-format off
@@ -114,7 +126,8 @@ struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *c, struct xvisu
int visual;
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_VISUAL_ID, &visual);
if (m.visual_depth != -1 &&
x_get_visual_depth(c, (xcb_visualid_t)visual) != m.visual_depth) {
xcb_aux_get_depth_of_visual(c->screen_info, (xcb_visualid_t)visual) !=
m.visual_depth) {
// FBConfig and the correspondent X Visual might not have the same
// depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
// quite common, seen in both open source and proprietary drivers.
@@ -143,16 +156,13 @@ struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *c, struct xvisu
min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
}
free(cfg);
if (!found) {
return NULL;
if (found) {
info->cfg = ret;
info->texture_tgts = texture_tgts;
info->texture_fmt = texture_fmt;
info->y_inverted = y_inverted;
}
auto info = cmalloc(struct glx_fbconfig_info);
info->cfg = ret;
info->texture_tgts = texture_tgts;
info->texture_fmt = texture_fmt;
info->y_inverted = y_inverted;
return info;
return found;
}
/**
@@ -197,6 +207,12 @@ void glx_deinit(backend_t *base) {
gd->ctx = 0;
}
struct glx_fbconfig_cache *cached_fbconfig = NULL, *tmp = NULL;
HASH_ITER(hh, gd->cached_fbconfigs, cached_fbconfig, tmp) {
HASH_DEL(gd->cached_fbconfigs, cached_fbconfig);
free(cached_fbconfig);
}
free(gd);
}
@@ -364,9 +380,10 @@ end:
return &gd->gl.base;
}
static void *
static image_handle
glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
struct _glx_pixmap *glxpixmap = NULL;
auto gd = (struct _glx_data *)base;
// Retrieve pixmap parameters, if they aren't provided
if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
log_error("Requested depth %d higher than max possible depth %d.",
@@ -394,38 +411,52 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
wd->inner = (struct backend_image_inner_base *)inner;
free(r);
auto fbcfg = glx_find_fbconfig(base->c, fmt);
if (!fbcfg) {
log_error("Couldn't find FBConfig with requested visual %x", fmt.visual);
goto err;
struct glx_fbconfig_cache *cached_fbconfig = NULL;
HASH_FIND(hh, gd->cached_fbconfigs, &fmt, sizeof(fmt), cached_fbconfig);
if (!cached_fbconfig) {
struct glx_fbconfig_info fbconfig;
if (!glx_find_fbconfig(base->c, fmt, &fbconfig)) {
log_error("Couldn't find FBConfig with requested visual %#x",
fmt.visual);
goto err;
}
cached_fbconfig = cmalloc(struct glx_fbconfig_cache);
cached_fbconfig->visual_info = fmt;
cached_fbconfig->info = fbconfig;
HASH_ADD(hh, gd->cached_fbconfigs, visual_info, sizeof(fmt), cached_fbconfig);
} else {
log_debug("Found cached FBConfig for RGBA%d%d%d%d, depth: %d, visual id: "
"%#x",
fmt.red_size, fmt.blue_size, fmt.green_size, fmt.alpha_size,
fmt.visual_depth, fmt.visual);
}
struct glx_fbconfig_info *fbconfig = &cached_fbconfig->info;
// Choose a suitable texture target for our pixmap.
// Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
// of the bits in texture_tgts
if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
if (!(fbconfig->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
goto err;
}
log_debug("depth %d, rgba %d", fmt.visual_depth,
(fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
(fbconfig->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
GLint attrs[] = {
GLX_TEXTURE_FORMAT_EXT,
fbcfg->texture_fmt,
fbconfig->texture_fmt,
GLX_TEXTURE_TARGET_EXT,
GLX_TEXTURE_2D_EXT,
0,
};
inner->y_inverted = fbcfg->y_inverted;
inner->y_inverted = fbconfig->y_inverted;
glxpixmap = cmalloc(struct _glx_pixmap);
glxpixmap->pixmap = pixmap;
glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbcfg->cfg, pixmap, attrs);
glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbconfig->cfg, pixmap, attrs);
glxpixmap->owned = owned;
free(fbcfg);
if (!glxpixmap->glpixmap) {
log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
@@ -444,7 +475,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return wd;
return (image_handle)wd;
err:
if (glxpixmap && glxpixmap->glpixmap) {
glXDestroyPixmap(base->c->dpy, glxpixmap->glpixmap);
@@ -545,62 +576,17 @@ struct backend_operations glx_ops = {
.max_buffer_age = 5, // Why?
};
/**
* Check if a GLX extension exists.
*/
static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) {
const char *glx_exts = glXQueryExtensionsString(dpy, screen);
if (!glx_exts) {
log_error("Failed get GLX extension list.");
return false;
}
auto inlen = strlen(ext);
const char *curr = glx_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 GLX extension %s.", ext);
} else {
log_info("Found GLX extension %s.", ext);
}
return match;
}
struct glxext_info glxext = {0};
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *dpy, int screen) {
if (glxext.initialized) {
return;
}
glxext.initialized = true;
#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name)
#define check_ext(name) \
glxext.has_##name = epoxy_has_glx_extension(dpy, screen, #name); \
log_info("Extension " #name " - %s", glxext.has_##name ? "present" : "absent")
check_ext(GLX_SGI_video_sync);
check_ext(GLX_SGI_swap_control);
check_ext(GLX_OML_sync_control);
@@ -614,36 +600,4 @@ void glxext_init(Display *dpy, int screen) {
check_ext(GLX_MESA_query_renderer);
#endif
#undef check_ext
#define lookup(name) ((name) = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since glXGetProcAddress might always return
// something. We are doing it just for completeness' sake.
if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
glxext.has_GLX_SGI_video_sync = false;
}
if (!lookup(glXSwapIntervalEXT)) {
glxext.has_GLX_EXT_swap_control = false;
}
if (!lookup(glXSwapIntervalMESA)) {
glxext.has_GLX_MESA_swap_control = false;
}
if (!lookup(glXSwapIntervalSGI)) {
glxext.has_GLX_SGI_swap_control = false;
}
if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
glxext.has_GLX_OML_sync_control = false;
}
if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
glxext.has_GLX_EXT_texture_from_pixmap = false;
}
if (!lookup(glXCreateContextAttribsARB)) {
glxext.has_GLX_ARB_create_context = false;
}
#ifdef GLX_MESA_query_renderer
if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
glxext.has_GLX_MESA_query_renderer = false;
}
#endif
#undef lookup
}

View File

@@ -1,17 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <stdbool.h>
// Older version of glx.h defines function prototypes for these extensions...
// Rename them to avoid conflicts
#define glXSwapIntervalMESA glXSwapIntervalMESA_
#define glXBindTexImageEXT glXBindTexImageEXT_
#define glXReleaseTexImageEXT glXReleaseTexImageEXT
#include <GL/glx.h>
#undef glXSwapIntervalMESA
#undef glXBindTexImageEXT
#undef glXReleaseTexImageEXT
#include <X11/Xlib.h>
#include <epoxy/glx.h>
#include <stdbool.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
@@ -27,21 +19,8 @@ struct glx_fbconfig_info {
int y_inverted;
};
/// The search criteria for glx_find_fbconfig
struct glx_fbconfig_criteria {
/// Bit width of the red component
int red_size;
/// Bit width of the green component
int green_size;
/// Bit width of the blue component
int blue_size;
/// Bit width of the alpha component
int alpha_size;
/// The depth of X visual
int visual_depth;
};
struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *, struct xvisual_info);
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
struct glx_fbconfig_info *info);
struct glxext_info {
bool initialized;
@@ -59,19 +38,4 @@ struct glxext_info {
extern struct glxext_info glxext;
extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *, int screen);

View File

@@ -97,6 +97,7 @@ const char win_shader_glsl[] = GLSL(330,
uniform bool invert_color;
in vec2 texcoord;
uniform sampler2D tex;
uniform vec2 effective_size;
uniform sampler2D brightness;
uniform float max_brightness;
// Signed distance field for rectangle center at (0, 0), with size of
@@ -130,7 +131,7 @@ const char win_shader_glsl[] = GLSL(330,
// 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 outer_size = effective_size;
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;
@@ -157,7 +158,8 @@ const char win_shader_default[] = GLSL(330,
uniform sampler2D tex;
vec4 default_post_processing(vec4 c);
vec4 window_shader() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
vec2 texsize = textureSize(tex, 0);
vec4 c = texture2D(tex, texcoord / texsize, 0);
return default_post_processing(c);
}
);

View File

@@ -56,6 +56,10 @@ typedef struct _xrender_data {
int target_width, target_height;
xcb_special_event_t *present_event;
/// Cache an X region to avoid creating and destroying it every frame. A
/// workaround for yshui/picom#1166.
xcb_xfixes_region_t present_region;
} xrender_data;
struct _xrender_blur_context {
@@ -355,10 +359,12 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
pixman_region32_fini(&reg);
}
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, bool lerp attr_unused) {
static void compose(backend_t *base, image_handle image_, coord_t dst, image_handle mask_,
coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible) {
struct _xrender_data *xd = (void *)base;
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
auto image = (struct xrender_image *)image_;
auto mask = (struct xrender_image *)mask_;
return compose_impl(xd, image, dst, mask, mask_dst, reg_paint, reg_visible,
xd->back[2]);
}
@@ -380,9 +386,10 @@ 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_, void *mask,
static bool blur(backend_t *backend_data, double opacity, void *ctx_, image_handle mask_,
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
struct _xrender_blur_context *bctx = ctx_;
auto bctx = (struct _xrender_blur_context *)ctx_;
auto mask = (struct xrender_image *)mask_;
if (bctx->method == BLUR_METHOD_NONE) {
return true;
}
@@ -513,7 +520,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
return true;
}
static void *
static image_handle
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
xcb_generic_error_t *e;
auto r = xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), &e);
@@ -548,7 +555,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
free(img);
return NULL;
}
return img;
return (image_handle)img;
}
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
x_free_picture(base->c, inner->pict);
@@ -572,13 +579,13 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c
}
}
static void release_image(backend_t *base, void *image) {
struct xrender_image *img = image;
static void release_image(backend_t *base, image_handle image) {
auto img = (struct xrender_image *)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);
release_image_inner(base, (struct _xrender_image_data_inner *)img->base.inner);
}
free(img);
}
@@ -597,6 +604,7 @@ static void deinit(backend_t *backend_data) {
xcb_free_pixmap(xd->base.c->c, xd->back_pixmap[i]);
}
}
x_destroy_region(xd->base.c, xd->present_region);
if (xd->present_event) {
xcb_unregister_for_special_event(xd->base.c->c, xd->present_event);
}
@@ -624,16 +632,15 @@ static void present(backend_t *base, const region_t *region) {
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
0, orig_x, orig_y, region_width, region_height);
auto xregion = x_create_region(base->c, region);
// Make sure we got reply from PresentPixmap before waiting for events,
// to avoid deadlock
auto e = xcb_request_check(
base->c->c, xcb_present_pixmap_checked(
xd->base.c->c, xd->target_win,
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0,
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
x_destroy_region(base->c, xregion);
base->c->c,
xcb_present_pixmap_checked(
base->c->c, xd->target_win, xd->back_pixmap[xd->curr_back], 0, XCB_NONE,
x_set_region(base->c, xd->present_region, region) ? xd->present_region
: XCB_NONE,
0, 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
if (e) {
log_error("Failed to present pixmap");
free(e);
@@ -707,7 +714,7 @@ 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) {
static image_handle 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.
@@ -750,7 +757,7 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
img->base.dim = 0;
img->base.inner = (struct backend_image_inner_base *)inner;
img->rounded_rectangle = NULL;
return img;
return (image_handle)img;
}
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
@@ -778,10 +785,10 @@ static bool decouple_image(backend_t *base, struct backend_image *img, const reg
return true;
}
static bool image_op(backend_t *base, enum image_operations op, void *image,
static bool image_op(backend_t *base, enum image_operations op, image_handle image,
const region_t *reg_op, const region_t *reg_visible, void *arg) {
struct _xrender_data *xd = (void *)base;
struct backend_image *img = image;
auto img = (struct backend_image *)image;
region_t reg;
double *dargs = arg;
@@ -929,6 +936,10 @@ static backend_t *backend_xrender_init(session_t *ps, xcb_window_t target) {
xd->vsync = false;
}
if (xd->vsync) {
xd->present_region = x_create_region(&ps->c, &ps->screen_reg);
}
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
// double buffering.
int first_buffer_index = xd->vsync ? 0 : 2;
@@ -956,19 +967,19 @@ err:
return NULL;
}
void *clone_image(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
image_handle clone_image(backend_t *base attr_unused, image_handle image,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct xrender_image);
*new_img = *(struct xrender_image *)image_data;
*new_img = *(struct xrender_image *)image;
new_img->base.inner->refcount++;
if (new_img->rounded_rectangle) {
new_img->rounded_rectangle->refcount++;
}
return new_img;
return (image_handle)new_img;
}
static bool
set_image_property(backend_t *base, enum image_properties op, void *image, void *args) {
static bool set_image_property(backend_t *base, enum image_properties op,
image_handle image, void *args) {
auto xrimg = (struct xrender_image *)image;
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
((double *)args)[0] != xrimg->base.corner_radius) {

View File

@@ -1380,14 +1380,10 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
case C2_L_PWIDTHB: predef_target = w->widthb; break;
case C2_L_PHEIGHTB: predef_target = w->heightb; break;
case C2_L_PBDW: predef_target = w->g.border_width; break;
case C2_L_PFULLSCREEN:
predef_target = win_is_fullscreen(ps, w);
break;
case C2_L_PFULLSCREEN: predef_target = w->is_fullscreen; break;
case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
case C2_L_PARGB: predef_target = win_has_alpha(w); break;
case C2_L_PFOCUSED:
predef_target = win_is_focused_raw(ps, w);
break;
case C2_L_PFOCUSED: predef_target = win_is_focused_raw(w); break;
case C2_L_PWMWIN: predef_target = w->wmwin; break;
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;

View File

@@ -17,20 +17,19 @@ struct cache {
struct cache_entry *entries;
};
void cache_set(struct cache *c, const char *key, void *data) {
struct cache_entry *e = NULL;
HASH_FIND_STR(c->entries, key, e);
CHECK(!e);
e = ccalloc(1, struct cache_entry);
e->key = strdup(key);
e->value = data;
HASH_ADD_STR(c->entries, key, e);
}
void *cache_get(struct cache *c, const char *key, int *err) {
static inline struct cache_entry *cache_get_entry(struct cache *c, const char *key) {
struct cache_entry *e;
HASH_FIND_STR(c->entries, key, e);
return e;
}
void *cache_get(struct cache *c, const char *key) {
struct cache_entry *e = cache_get_entry(c, key);
return e ? e->value : NULL;
}
void *cache_get_or_fetch(struct cache *c, const char *key, int *err) {
struct cache_entry *e = cache_get_entry(c, key);
if (e) {
return e->value;
}
@@ -54,7 +53,7 @@ void *cache_get(struct cache *c, const char *key, int *err) {
return e->value;
}
static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
static inline void cache_invalidate_impl(struct cache *c, struct cache_entry *e) {
if (c->free) {
c->free(c->user_data, e->value);
}
@@ -68,14 +67,14 @@ void cache_invalidate(struct cache *c, const char *key) {
HASH_FIND_STR(c->entries, key, e);
if (e) {
_cache_invalidate(c, e);
cache_invalidate_impl(c, e);
}
}
void cache_invalidate_all(struct cache *c) {
struct cache_entry *e, *tmpe;
HASH_ITER(hh, c->entries, e, tmpe) {
_cache_invalidate(c, e);
cache_invalidate_impl(c, e);
}
}

View File

@@ -11,9 +11,13 @@ typedef void (*cache_free_t)(void *user_data, void *data);
/// `user_data` will be passed to `getter` and `f` when they are called.
struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
/// Fetch a value from the cache. If the value doesn't present in the cache yet, the
/// Get a value from the cache. If the value doesn't present in the cache yet, the
/// getter will be called, and the returned value will be stored into the cache.
void *cache_get(struct cache *, const char *key, int *err);
void *cache_get_or_fetch(struct cache *, const char *key, int *err);
/// Get a value from the cache. If the value doesn't present in the cache, NULL will be
/// returned.
void *cache_get(struct cache *, const char *key);
/// Invalidate a value in the cache.
void cache_invalidate(struct cache *, const char *key);
@@ -24,9 +28,3 @@ void cache_invalidate_all(struct cache *);
/// Invalidate all values in the cache and free it. Returns the user data passed to
/// `new_cache`
void *cache_free(struct cache *);
/// Insert a key-value pair into the cache. Only used for internal testing. Takes
/// ownership of `data`
///
/// If `key` already exists in the cache, this function will abort the program.
void cache_set(struct cache *c, const char *key, void *data);

View File

@@ -143,11 +143,6 @@ typedef struct session {
ev_timer unredir_timer;
/// Timer for fading
ev_timer fade_timer;
/// Timer for animations
ev_timer animation_timer;
/// Use an ev_idle callback for drawing
/// So we only start drawing when events are processed
ev_idle draw_idle;
/// Use an ev_timer callback for drawing
ev_timer draw_timer;
/// Called every time we have timeouts or new data on socket,
@@ -180,6 +175,7 @@ typedef struct session {
bool server_grabbed;
/// Width of root window.
int root_width;
/// Height of root window.
int root_height;
/// X Composite overlay window.
xcb_window_t overlay;
@@ -190,7 +186,7 @@ typedef struct session {
/// Picture of the root window background.
paint_t root_tile_paint;
/// The backend data the root pixmap bound to
void *root_image;
image_handle root_image;
/// A region of the size of the screen.
region_t screen_reg;
/// Picture of root window. Destination of painting in no-DBE painting
@@ -208,7 +204,7 @@ typedef struct session {
/// Custom GLX program used for painting window.
// XXX should be in struct glx_session
glx_prog_main_t glx_prog_win;
struct glx_fbconfig_info *argb_fbconfig;
struct glx_fbconfig_info argb_fbconfig;
#endif
/// Sync fence to sync draw operations
xcb_sync_fence_t sync_fence;
@@ -244,8 +240,8 @@ typedef struct session {
/// Either the backend is currently rendering a frame, or a frame has been
/// rendered but has yet to be presented. In either case, we should not start
/// another render right now. As if we start issuing rendering commands now, we
/// will have to wait for either the the current render to finish, or the current
/// back buffer to be become available again. In either case, we will be wasting
/// will have to wait for either the current render to finish, or the current
/// back buffer to become available again. In either case, we will be wasting
/// time.
bool backend_busy;
/// Whether a render is queued. This generally means there are pending updates
@@ -268,18 +264,11 @@ typedef struct session {
xcb_render_picture_t *alpha_picts;
/// Time of last fading. In milliseconds.
long long fade_time;
/// Time of last window animation step. In milliseconds.
long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time
/// Head pointer of the error ignore linked list.
pending_reply_t *pending_reply_head;
/// Pointer to the <code>next</code> member of tail element of the error
/// ignore linked list.
pending_reply_t **pending_reply_tail;
// Cached blur convolution kernels.
struct x_convolution_kernel **blur_kerns_cache;
/// If we should quit
bool quit : 1;
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
// TODO(yshui) use separate flags for different kinds of updates so we don't
// waste our time.
/// Whether there are pending updates, like window creation, etc.
bool pending_updates : 1;

View File

@@ -52,7 +52,7 @@
#else
# define attr_warn_unused_result
#endif
// An alias for conveninence
// An alias for convenience
#define must_use attr_warn_unused_result
#if __has_attribute(nonnull)
@@ -105,9 +105,9 @@
#ifndef unreachable
# if defined(__GNUC__) || defined(__clang__)
# define unreachable() __builtin_unreachable()
# define unreachable() assert(false); __builtin_unreachable()
# else
# define unreachable() do {} while(0)
# define unreachable() assert(false); do {} while(0)
# endif
#endif

View File

@@ -43,10 +43,7 @@ const char *xdg_config_home(void) {
return NULL;
}
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
strcpy(xdgh, home);
strcat(xdgh, default_dir);
xdgh = mstrjoin(home, default_dir);
} else {
xdgh = strdup(xdgh);
}
@@ -563,7 +560,7 @@ static char *locate_auxiliary_file_at(const char *base, const char *scope, const
}
/**
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
* Get a path of an auxiliary file to read, could be a shader file, or any supplementary
* file.
*
* Follows the XDG specification to search for the shader file in configuration locations.
@@ -598,15 +595,17 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
// 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;
if (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);
char *ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
if (ret) {
free(config_dirs);
return ret;
@@ -614,7 +613,7 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
}
free(config_dirs);
return ret;
return NULL;
}
struct debug_options_entry {
@@ -797,10 +796,6 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
// opacity logic is complicated, and needs an "unset" state
opt->wintype_option[i].opacity = NAN;
}
if (!mask[i].animation) {
mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].clip_shadow_above) {
mask[i].clip_shadow_above = true;
opt->wintype_option[i].clip_shadow_above = false;
@@ -808,40 +803,6 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
}
}
enum open_window_animation parse_open_window_animation(const char *src) {
if (strcmp(src, "none") == 0) {
return OPEN_WINDOW_ANIMATION_NONE;
} else if (strcmp(src, "fly-in") == 0) {
return OPEN_WINDOW_ANIMATION_FLYIN;
} else if (strcmp(src, "zoom") == 0) {
return OPEN_WINDOW_ANIMATION_ZOOM;
} else if (strcmp(src, "slide-up") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_UP;
} else if (strcmp(src, "slide-down") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_DOWN;
} else if (strcmp(src, "slide-left") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_LEFT;
} else if (strcmp(src, "slide-right") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT;
} else if (strcmp(src, "slide-out") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT;
} else if (strcmp(src, "slide-in") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN;
} else if (strcmp(src, "slide-out-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER;
} else if (strcmp(src, "slide-in-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER;
} else if (strcmp(src, "minimize") == 0 || strcmp(src, "maximize") == 0) {
return OPEN_WINDOW_ANIMATION_MINIMIZE;
} else if (strcmp(src, "squeeze") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE;
} else if (strcmp(src, "squeeze-bottom") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM;
}
return OPEN_WINDOW_ANIMATION_INVALID;
}
char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
// clang-format off
@@ -887,18 +848,6 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.no_fading_destroyed_argb = false,
.fade_blacklist = NULL,
.animations = false,
.animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_unmap_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_next_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_prev_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_stiffness = 200.0,
.animation_stiffness_tag_change = 200.0,
.animation_window_mass = 1.0,
.animation_dampening = 25,
.animation_clamping = true,
.inactive_opacity = 1.0,
.inactive_opacity_override = false,
.active_opacity = 1.0,
@@ -930,8 +879,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.track_leader = false,
.rounded_corners_blacklist = NULL,
.animation_blacklist = NULL
.rounded_corners_blacklist = NULL
};
// clang-format on

View File

@@ -40,24 +40,6 @@ enum backend {
NUM_BKEND,
};
enum open_window_animation {
OPEN_WINDOW_ANIMATION_NONE = 0,
OPEN_WINDOW_ANIMATION_FLYIN,
OPEN_WINDOW_ANIMATION_SLIDE_UP,
OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
OPEN_WINDOW_ANIMATION_SLIDE_IN,
OPEN_WINDOW_ANIMATION_SLIDE_OUT,
OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER,
OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER,
OPEN_WINDOW_ANIMATION_ZOOM,
OPEN_WINDOW_ANIMATION_MINIMIZE,
OPEN_WINDOW_ANIMATION_SQUEEZE,
OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM,
OPEN_WINDOW_ANIMATION_INVALID,
};
typedef struct win_option_mask {
bool shadow : 1;
bool fade : 1;
@@ -67,7 +49,6 @@ typedef struct win_option_mask {
bool redir_ignore : 1;
bool opacity : 1;
bool clip_shadow_above : 1;
enum open_window_animation animation;
} win_option_mask_t;
typedef struct win_option {
@@ -79,7 +60,6 @@ typedef struct win_option {
bool redir_ignore;
double opacity;
bool clip_shadow_above;
enum open_window_animation animation;
} win_option_t;
enum blur_method {
@@ -215,33 +195,6 @@ typedef struct options {
/// Fading blacklist. A linked list of conditions.
c2_lptr_t *fade_blacklist;
// === Animations ===
/// Whether to do window animations
bool animations;
/// Which animation to run when opening a window
enum open_window_animation animation_for_open_window;
/// Which animation to run when opening a transient window
enum open_window_animation animation_for_transient_window;
/// Which animation to run when unmapping a window
enum open_window_animation animation_for_unmap_window;
/// Which animation to run when swapping to new tag
enum open_window_animation animation_for_next_tag;
/// Which animation to run for old tag
enum open_window_animation animation_for_prev_tag;
/// Spring stiffness for animation
double animation_stiffness;
/// Spring stiffness for current tag animation
double animation_stiffness_tag_change;
/// Window mass for animation
double animation_window_mass;
/// Animation dampening
double animation_dampening;
/// Whether to clamp animations
bool animation_clamping;
/// Animation blacklist. A linked list of conditions.
c2_lptr_t *animation_blacklist;
/// TODO: open/close animations
// === Opacity ===
/// Default opacity for inactive windows.
/// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY.
@@ -325,13 +278,6 @@ typedef struct options {
// Make transparent windows clip other windows, instead of blending on top of
// them
bool transparent_clipping;
// Enable fading for next tag
bool enable_fading_next_tag;
// Enable fading for prev tag
bool enable_fading_prev_tag;
/// A list of conditions of windows to which transparent clipping
/// should not apply
c2_lptr_t *transparent_clipping_blacklist;
@@ -352,7 +298,6 @@ bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *)
char *must_use locate_auxiliary_file(const char *scope, const char *path,
const char *include_dir);
enum blur_method must_use parse_blur_method(const char *src);
enum open_window_animation must_use parse_open_window_animation(const char *src);
/**
* Add a pattern to a condition linked list.
@@ -366,7 +311,7 @@ char **xdg_config_dirs(void);
/// Parse a configuration file
/// Returns the actually config_file name used, allocated on heap
/// Outputs:
/// shadow_enable = whether shaodw is enabled globally
/// shadow_enable = whether shadow is enabled globally
/// fading_enable = whether fading is enabled globally
/// win_option_mask = whether option overrides for specific window type is set for given
/// options

View File

@@ -81,17 +81,19 @@ FILE *open_config_file(const char *cpath, char **ppath) {
// First search for config file in user config directory
auto config_home = xdg_config_home();
auto ret = open_config_file_at(config_home, ppath);
free((void *)config_home);
if (ret) {
return ret;
if (config_home) {
auto ret = open_config_file_at(config_home, ppath);
free((void *)config_home);
if (ret) {
return ret;
}
}
// Fall back to legacy config file in user home directory
const char *home = getenv("HOME");
if (home && strlen(home)) {
auto path = mstrjoin(home, config_filename_legacy);
ret = fopen(path, "r");
auto ret = fopen(path, "r");
if (ret && ppath) {
*ppath = path;
} else {
@@ -105,7 +107,7 @@ FILE *open_config_file(const char *cpath, char **ppath) {
// Fall back to config file in system config directory
auto config_dirs = xdg_config_dirs();
for (int i = 0; config_dirs[i]; i++) {
ret = open_config_file_at(config_dirs[i], ppath);
auto ret = open_config_file_at(config_dirs[i], ppath);
if (ret) {
free(config_dirs);
return ret;
@@ -262,15 +264,6 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
o->clip_shadow_above = ival;
mask->clip_shadow_above = true;
}
const char *sval = NULL;
if (config_setting_lookup_string(setting, "animation", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation = animation;
mask->animation = animation;
}
double fval;
if (config_setting_lookup_float(setting, "opacity", &fval)) {
@@ -485,7 +478,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
}
// --sw-opti
if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) {
log_warn("The sw-opti %s", deprecation_message);
log_error("The sw-opti %s", deprecation_message);
goto err;
}
// --use-ewmh-active-win
lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);
@@ -520,70 +514,6 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above");
// --fade-exclude
parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude");
// --animations
lcfg_lookup_bool(&cfg, "animations", &opt->animations);
// --animation-for-open-window
if (config_lookup_string(&cfg, "animation-for-open-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_open_window = animation;
}
// --animation-for-transient-window
if (config_lookup_string(&cfg, "animation-for-transient-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_transient_window = animation;
}
// --animation-for-unmap-window
if (config_lookup_string(&cfg, "animation-for-unmap-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid unmap-window animation %s", sval);
goto err;
}
opt->animation_for_unmap_window = animation;
}
// --animation-for-next-tag
if (config_lookup_string(&cfg, "animation-for-next-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid next-tag animation %s", sval);
goto err;
}
opt->animation_for_next_tag = animation;
}
// --animation-for-prev-tag
if (config_lookup_string(&cfg, "animation-for-prev-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid prev-tag animation %s", sval);
goto err;
}
opt->animation_for_prev_tag = animation;
}
// --animations-exclude
parse_cfg_condlst(&cfg, &opt->animation_blacklist, "animation-exclude");
// --animation-stiffness
config_lookup_float(&cfg, "animation-stiffness-in-tag", &opt->animation_stiffness);
// --animation-stiffness-tag-change
config_lookup_float(&cfg, "animation-stiffness-tag-change", &opt->animation_stiffness_tag_change);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-next-tag", &opt->enable_fading_next_tag);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-prev-tag", &opt->enable_fading_prev_tag);
// --animation-window-mass
config_lookup_float(&cfg, "animation-window-mass", &opt->animation_window_mass);
// --animation-dampening
config_lookup_float(&cfg, "animation-dampening", &opt->animation_dampening);
// --animation-clamping
lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
// --focus-exclude
parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude");
// --invert-color-include

View File

@@ -906,7 +906,7 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
}
if (!strcmp("RawFocused", target)) {
cdbus_reply(ps, msg, cdbus_append_bool_variant,
(bool[]){win_is_focused_raw(ps, w)});
(bool[]){win_is_focused_raw(w)});
return true;
}
@@ -976,7 +976,7 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
cdbus_m_win_get_do(wmwin, cdbus_reply_bool);
cdbus_m_win_get_do(leader, cdbus_reply_wid);
if (!strcmp("focused_raw", target)) {
cdbus_reply_bool(ps, msg, win_is_focused_raw(ps, w));
cdbus_reply_bool(ps, msg, win_is_focused_raw(w));
return true;
}
cdbus_m_win_get_do(fade_force, cdbus_reply_enum);

View File

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
#include <stdint.h>
#include <stdio.h>
#include <X11/Xlibint.h>
@@ -29,7 +30,7 @@
/// made the query when those events were already in the queue. so the reply you got is
/// more up-to-date than the events). Also, handling events when other client are making
/// concurrent requests is not good. Because the server states are changing without you
/// knowning them. This is super racy, and can cause lots of potential problems.
/// knowing them. This is super racy, and can cause lots of potential problems.
///
/// All of above mandates we do these things:
/// 1. Grab server when handling events
@@ -47,8 +48,14 @@
/// When top half finished, we enter the render stage, where no server state should be
/// queried. All rendering should be done with our internal knowledge of the server state.
///
/// P.S. There is another reason to avoid sending any request to the server as much as
/// possible. To make sure requests are sent, flushes are needed. And `xcb_flush`/`XFlush`
/// functions may read more events from the server into their queues. This is
/// undesirable, see the comments on `handle_queued_x_events` in picom.c for more details.
// TODO(yshui) the things described above
// TODO(yshui) the things described above. This is mostly done, maybe some of
// the functions here is still making unnecessary queries, we need
// to do some auditing to be sure.
/**
* Get a window's name from window ID.
@@ -324,7 +331,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
if (ev->parent == ps->c.screen_info->root) {
// X will generate reparent notifiy even if the parent didn't actually
// X will generate reparent notify even if the parent didn't actually
// change (i.e. reparent again to current parent). So we check if that's
// the case
auto w = find_win(ps, ev->window);
@@ -350,19 +357,14 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
// Reset event mask in case something wrong happens
xcb_change_window_attributes(
ps->c.c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)});
uint32_t evmask = determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) {
log_debug("Window %#010x doesn't have WM_STATE property, it is "
"probably not a client window. But we will listen for "
"property change in case it gains one.",
ev->window);
xcb_change_window_attributes(
ps->c.c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) |
XCB_EVENT_MASK_PROPERTY_CHANGE});
evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE;
} else {
auto w_real_top = find_managed_window_or_parent(ps, ev->parent);
if (w_real_top && w_real_top->state != WSTATE_UNMAPPED &&
@@ -386,6 +388,8 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
}
}
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
}
}
@@ -465,7 +469,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
}
// Unconcerned about any other proprties on root window
// Unconcerned about any other properties on root window
return;
}
@@ -475,9 +479,10 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
// Check whether it could be a client window
if (!find_toplevel(ps, ev->window)) {
// Reset event mask anyway
xcb_change_window_attributes(ps->c.c, ev->window, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(
ps, ev->window, WIN_EVMODE_UNKNOWN)});
const uint32_t evmask =
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
auto w_top = find_managed_window_or_parent(ps, ev->window);
// ev->window might have not been managed yet, in that case w_top
@@ -492,14 +497,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
// there are always some stupid applications. (#144)
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
struct managed_win *w = NULL;
if ((w = find_toplevel(ps, ev->window))) {
struct managed_win *w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
// Unnecessay until we remove the queue_redraw in ev_handle
// Unnecessary until we remove the queue_redraw in ev_handle
queue_redraw(ps);
}
@@ -560,6 +565,13 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
}
if (!ps->o.no_ewmh_fullscreen && ev->atom == ps->atoms->a_NET_WM_STATE) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// Check for other atoms we are tracking
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
if (platom->atom == ev->atom) {
@@ -586,16 +598,28 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
region_t parts;
pixman_region32_init(&parts);
// If this is the first time this window is damaged, we would redraw the
// whole window, so we don't need to fetch the damage region. But we still need
// to make sure the X server receives the DamageSubtract request, hence the
// `xcb_request_check` here.
// Otherwise, we fetch the damage regions. That means we will receive a reply
// from the X server, which implies it has received our DamageSubtract request.
if (!w->ever_damaged) {
win_extents(w, &parts);
if (!ps->o.show_all_xerrors) {
set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage,
XCB_NONE, XCB_NONE));
auto e = xcb_request_check(
ps->c.c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, XCB_NONE));
if (e) {
if (ps->o.show_all_xerrors) {
x_print_error(e->sequence, e->major_code, e->minor_code,
e->error_code);
}
free(e);
}
win_extents(w, &parts);
} else {
auto cookie =
xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, ps->damaged_region);
if (!ps->o.show_all_xerrors) {
set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE,
ps->damaged_region));
set_ignore_cookie(&ps->c, cookie);
}
x_fetch_region(&ps->c, ps->damaged_region, &parts);
pixman_region32_translate(&parts, w->g.x + w->g.border_width,

View File

@@ -158,7 +158,7 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *
fflags |= NOTE_CLOSE_WRITE;
#else
// NOTE_WRITE will receive notification more frequent than necessary, so is less
// preferrable
// preferable
fflags |= NOTE_WRITE;
#endif
struct kevent ev = {

View File

@@ -96,7 +96,7 @@ static inline double estimate_first_row_sum(double size, double r) {
// `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 squared. So the sum of the first row is a * factor / factor^2 = a /
// factor
return a / factor;
}

View File

@@ -9,7 +9,7 @@
#include <unistd.h>
#ifdef CONFIG_OPENGL
#include <GL/gl.h>
#include <epoxy/gl.h>
#include "backend/gl/gl_common.h"
#include "backend/gl/glx.h"
#endif
@@ -338,21 +338,14 @@ struct log_target *stderr_logger_new(void) {
}
#ifdef CONFIG_OPENGL
/// An opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct gl_string_marker_logger {
struct log_target tgt;
PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
};
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;
static void gl_string_marker_logger_write(struct log_target *tgt attr_unused,
const char *str, size_t len) {
// strip newlines at the end of the string
while (len > 0 && str[len - 1] == '\n') {
len--;
}
g->gl_string_marker((GLsizei)len, str);
glStringMarkerGREMEDY((GLsizei)len, str);
}
static const struct log_ops gl_string_marker_logger_ops = {
@@ -361,20 +354,16 @@ static const struct log_ops gl_string_marker_logger_ops = {
.destroy = logger_trivial_destroy,
};
/// Create an opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct log_target *gl_string_marker_logger_new(void) {
if (!gl_has_extension("GL_GREMEDY_string_marker")) {
if (!epoxy_has_gl_extension("GL_GREMEDY_string_marker")) {
return NULL;
}
void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
if (!fnptr) {
return NULL;
}
auto ret = cmalloc(struct gl_string_marker_logger);
ret->tgt.ops = &gl_string_marker_logger_ops;
ret->gl_string_marker = fnptr;
return &ret->tgt;
auto ret = cmalloc(struct log_target);
ret->ops = &gl_string_marker_logger_ops;
return ret;
}
#else

View File

@@ -23,7 +23,7 @@ required_xcb_packages = [
# Some XCB packages are here because their versioning differs (see check below).
required_packages = [
'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util',
'xext'
'xext', 'threads',
]
foreach i : required_packages
@@ -58,8 +58,8 @@ if get_option('vsync_drm')
endif
if get_option('opengl')
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)]
cflags += ['-DCONFIG_OPENGL']
deps += [dependency('epoxy', required: true)]
srcs += [ 'opengl.c' ]
endif

View File

@@ -181,7 +181,7 @@ bool glx_init(session_t *ps, bool need_render) {
// must precede FBConfig fetching
if (need_render) {
psglx->has_texture_non_power_of_two =
gl_has_extension("GL_ARB_texture_non_power_of_two");
epoxy_has_gl_extension("GL_ARB_texture_non_power_of_two");
}
// Render preparations
@@ -279,7 +279,7 @@ void glx_destroy(session_t *ps) {
free(ps->psglx);
ps->psglx = NULL;
ps->argb_fbconfig = NULL;
ps->argb_fbconfig = (struct glx_fbconfig_info){0};
}
/**
@@ -1129,7 +1129,7 @@ glx_blur_dst_end:
// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border
// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in
// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the
// documentation for the xcb_xrender extension is literaly non existent...
// documentation for the xcb_xrender extension is literally non existent...
//
// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
// the WM's perspective there are multiple ways to implement window borders. Using

View File

@@ -18,9 +18,9 @@
#include "render.h"
#include "win.h"
#include <GL/gl.h>
#include <GL/glx.h>
#include <ctype.h>
#include <epoxy/gl.h>
#include <epoxy/glx.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
@@ -75,7 +75,7 @@ typedef struct glx_session {
glx_round_pass_t *round_passes;
} glx_session_t;
/// @brief Wrapper of a binded GLX texture.
/// @brief Wrapper of a bound GLX texture.
typedef struct _glx_texture {
GLuint texture;
GLXPixmap glpixmap;
@@ -121,9 +121,9 @@ bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int wi
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
/**
* Check if a texture is binded, or is binded to the given pixmap.
* Check if a texture is bound, or is bound to the given pixmap.
*/
static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
}
@@ -228,10 +228,7 @@ static inline void free_texture(session_t *ps, glx_texture_t **pptex) {
*/
static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
free_texture(ps, &ppaint->ptex);
#ifdef CONFIG_OPENGL
free(ppaint->fbcfg);
#endif
ppaint->fbcfg = NULL;
ppaint->fbcfg = (struct glx_fbconfig_info){0};
}
/**

View File

@@ -184,15 +184,6 @@ static const struct picom_option picom_options[] = {
"you want to attach a debugger to picom"},
{"no-ewmh-fullscreen" , no_argument , 803, NULL , "Do not use EWMH to detect fullscreen windows. Reverts to checking if a "
"window is fullscreen based only on its size and coordinates."},
{"animations" ,no_argument , 804, NULL , "Enable/disable animations."},
{"animation-stiffness-in-tag" , required_argument, 805, NULL , "Animation speed in current tag (float)."},
{"animation-stiffness-tag-change", required_argument, 806, NULL , "Animation speed when tag changes (change to a new desktop)."},
{"animation-dampening" , required_argument, 807, NULL , "Animation dampening ratio (spring dampening as an example)."},
{"animation-window-mass" , required_argument, 808, NULL , "Animation window mass (lower mass makes animations bumpy)."},
{"animation-clamping" , no_argument , 809, NULL , "Enable/disable animation clamping. Disabling increases performance"},
{"animation-for-open-window" , required_argument, 810, NULL , "Set animation for opening window (Check sample.conf for options)."},
{"animation-for-transient-window", required_argument, 811, NULL , "Set animation for transient (child) windows."},
};
// clang-format on
@@ -315,7 +306,7 @@ static void usage(const char *argv0, int ret) {
}
}
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb";
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfCazGb";
/// Get config options that are needed to parse the rest of the options
/// Return true if we should quit
@@ -325,9 +316,9 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all
int o = 0, longopt_idx = -1;
// Pre-parse the commandline arguments to check for --config and invalid
// Pre-parse the command line arguments to check for --config and invalid
// switches
// Must reset optind to 0 here in case we reread the commandline
// Must reset optind to 0 here in case we reread the command line
// arguments
optind = 1;
*config_file = NULL;
@@ -379,7 +370,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
// instead of commas in atof().
setlocale(LC_NUMERIC, "C");
// Parse commandline arguments. Range checking will be done later.
// Parse command line arguments. Range checking will be done later.
bool failed = false;
const char *deprecation_message attr_unused =
@@ -435,7 +426,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp;
break;
case 'f':
case 'F':
fading_enable = true;
break;
P_CASEINT('r', shadow_radius);
@@ -520,8 +510,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
"--crop-shadow-to-monitor instead.");
break;
case 274:
log_warn("--sw-opti has been deprecated, please remove it from the "
log_error("--sw-opti has been deprecated, please remove it from the "
"command line options");
failed = true;
break;
case 275:
// --vsync-aggressive
@@ -532,9 +523,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
P_CASEBOOL(276, use_ewmh_active_win);
case 277:
// --respect-prop-shadow
log_warn("--respect-prop-shadow option has been deprecated, its "
log_error("--respect-prop-shadow option has been deprecated, its "
"functionality will always be enabled. Please remove it "
"from the command line options");
failed = true;
break;
P_CASEBOOL(278, unredir_if_possible);
case 279:
@@ -730,7 +722,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->blur_strength = atoi(optarg);
break;
case 333:
// --cornor-radius
// --corner-radius
opt->corner_radius = atoi(optarg);
break;
case 334:
@@ -759,52 +751,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
break;
P_CASEBOOL(802, debug_mode);
P_CASEBOOL(803, no_ewmh_fullscreen);
P_CASEBOOL(804, animations);
case 805:
// --animation-stiffness
opt->animation_stiffness = atof(optarg);
break;
case 806:
// --animation-stiffness-for-tags
opt->animation_stiffness_tag_change = atof(optarg);
break;
case 807:
// --animation-dampening
opt->animation_dampening = atof(optarg);
break;
case 808:
// --animation-window-masss
opt->animation_window_mass = atof(optarg);
break;
case 809:
// --animation-clamping
opt->animation_clamping = true;
break;
case 810: {
// --animation-for-open-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid open-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_open_window = animation;
}
break;
}
case 811: {
// --animation-for-transient-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid transient-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_transient_window = animation;
}
break;
}
case 812: {
// --animation-exclude
condlst_add(&opt->animation_blacklist, optarg);
break;
}
default: usage(argv[0], 1); break;
#undef P_CASEBOOL
}

View File

@@ -1,10 +1,12 @@
// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
* picom - a compositor for X11
*
* Based on `compton` - Copyright (c) 2011-2013, Christopher Jeffrey
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* Copyright (c) 2019-2023, Yuxuan Shui
*
* See LICENSE-mit for more information.
*
*/
@@ -16,6 +18,8 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
@@ -31,6 +35,7 @@
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb_aux.h>
#include <xcb/xfixes.h>
#include <ev.h>
@@ -144,10 +149,10 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t
return w;
}
void check_render_finish(struct vblank_event *e attr_unused, void *ud) {
enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unused, void *ud) {
auto ps = (session_t *)ud;
if (!ps->backend_busy) {
return;
return VBLANK_CALLBACK_DONE;
}
struct timespec render_time;
@@ -159,8 +164,7 @@ void check_render_finish(struct vblank_event *e attr_unused, void *ud) {
log_debug("Last render did not complete during vblank, msc: "
"%" PRIu64,
ps->last_msc);
vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ud);
return;
return VBLANK_CALLBACK_AGAIN;
}
// The frame has been finished and presented, record its render time.
@@ -174,22 +178,20 @@ void check_render_finish(struct vblank_event *e attr_unused, void *ud) {
render_time_us, (int)ps->last_schedule_delay, ps->last_msc);
}
ps->backend_busy = false;
return VBLANK_CALLBACK_DONE;
}
void collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
enum vblank_callback_action
collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
double vblank_interval = NAN;
assert(ps->frame_pacing);
assert(ps->vblank_scheduler);
if (ps->last_msc == e->msc) {
// Already collected statistics for this vblank
return;
}
if (!ps->o.debug_options.smart_frame_pacing) {
// We don't need to collect statistics if we are not doing smart frame
// pacing.
return;
return VBLANK_CALLBACK_DONE;
}
// TODO(yshui): this naive method of estimating vblank interval does not handle
@@ -200,29 +202,44 @@ void collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
// samples when the monitor is off, but I had a hard time to get it
// working reliably, there are just too many corner cases.
if (ps->last_msc_instant != 0) {
auto frame_count = e->msc - ps->last_msc;
int frame_time = (int)((e->ust - ps->last_msc_instant) / frame_count);
if (frame_count == 1) {
render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time);
log_trace("Frame count %lu, frame time: %d us, ust: %" PRIu64 "",
frame_count, frame_time, e->ust);
} else {
log_trace("Frame count %lu, frame time: %d us, msc: %" PRIu64
", not adding sample.",
frame_count, frame_time, e->ust);
// Don't add sample again if we already collected statistics for this vblank
if (ps->last_msc < e->msc) {
if (ps->last_msc_instant != 0) {
auto frame_count = e->msc - ps->last_msc;
auto frame_time =
(int)((e->ust - ps->last_msc_instant) / frame_count);
if (frame_count == 1) {
render_statistics_add_vblank_time_sample(
&ps->render_stats, frame_time);
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"ust: "
"%" PRIu64,
frame_count, frame_time, e->ust);
} else {
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"msc: "
"%" PRIu64 ", not adding sample.",
frame_count, frame_time, e->ust);
}
}
ps->last_msc_instant = e->ust;
ps->last_msc = e->msc;
} else if (ps->last_msc > e->msc) {
log_warn("PresentCompleteNotify msc is going backwards, last_msc: "
"%" PRIu64 ", current msc: %" PRIu64,
ps->last_msc, e->msc);
ps->last_msc_instant = 0;
ps->last_msc = 0;
}
ps->last_msc_instant = e->ust;
ps->last_msc = e->msc;
double vblank_interval = render_statistics_get_vblank_time(&ps->render_stats);
vblank_interval = render_statistics_get_vblank_time(&ps->render_stats);
log_trace("Vblank interval estimate: %f us", vblank_interval);
if (vblank_interval == 0) {
// We don't have enough data for vblank interval estimate, schedule
// another vblank event.
vblank_scheduler_schedule(ps->vblank_scheduler,
collect_vblank_interval_statistics, ud);
return VBLANK_CALLBACK_AGAIN;
}
return VBLANK_CALLBACK_DONE;
}
void schedule_render(session_t *ps, bool triggered_by_vblank);
@@ -230,7 +247,7 @@ void schedule_render(session_t *ps, bool triggered_by_vblank);
/// vblank callback scheduled by schedule_render, when a render is ongoing.
///
/// Check if previously queued render has finished, and reschedule render if it has.
void reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
assert(ps->frame_pacing);
assert(ps->render_queued);
@@ -242,12 +259,11 @@ void reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
check_render_finish(e, ud);
if (ps->backend_busy) {
vblank_scheduler_schedule(ps->vblank_scheduler,
reschedule_render_at_vblank, ud);
return;
return VBLANK_CALLBACK_AGAIN;
}
schedule_render(ps, false);
return VBLANK_CALLBACK_DONE;
}
/// How many seconds into the future should we start rendering the next frame.
@@ -258,7 +274,7 @@ void reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
/// is no render currently scheduled. i.e. render_queued == false.
/// 2. then, we need to figure out the best time to start rendering. we need to
/// at least know when the next vblank will start, as we can't start render
/// before the current rendered frame is diplayed on screen. we have this
/// before the current rendered frame is displayed on screen. we have this
/// information from the vblank scheduler, it will notify us when that happens.
/// we might also want to delay the rendering even further to reduce latency,
/// this is discussed below, in FUTURE WORKS.
@@ -463,27 +479,29 @@ static double fade_timeout(session_t *ps) {
*/
static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
auto w = *_w;
log_trace("Process fading for window %s (%#010x), steps: %lld", w->name,
w->base.id, steps);
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
// We are not fading
assert(w->opacity_target == w->opacity);
log_trace("|- not fading");
return false;
}
if (!win_should_fade(ps, w)) {
log_debug("Window %#010x %s doesn't need fading", w->base.id, w->name);
log_trace("|- in transition but doesn't need fading");
w->opacity = w->opacity_target;
}
if (w->opacity == w->opacity_target) {
// We have reached target opacity.
// We don't call win_check_fade_finished here because that could destroy
// the window, but we still need the damage info from this window
log_debug("Fading finished for window %#010x %s", w->base.id, w->name);
log_trace("|- was fading but finished");
return false;
}
log_trace("|- fading, opacity: %lf", w->opacity);
if (steps) {
log_trace("Window %#010x (%s) opacity was: %lf", w->base.id, w->name,
w->opacity);
if (w->opacity < w->opacity_target) {
w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps,
0.0, w->opacity_target);
@@ -491,7 +509,7 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps,
w->opacity_target, 1);
}
log_trace("... updated to: %lf", w->opacity);
log_trace("|- opacity updated: %lf", w->opacity);
}
// Note even if opacity == opacity_target here, we still want to run preprocess
@@ -773,6 +791,8 @@ err:
/// Handle configure event of the root window
static void configure_root(session_t *ps) {
// TODO(yshui) re-initializing backend should be done outside of the
// critical section. Probably set a flag and do it in draw_callback_impl.
auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
if (!r) {
log_fatal("Failed to fetch root geometry");
@@ -812,6 +832,13 @@ static void configure_root(session_t *ps) {
top_w->reg_ignore_valid = false;
}
// Whether a window is fullscreen depends on the new screen
// size. So we need to refresh the fullscreen state of all
// windows.
win_stack_foreach_managed(w, &ps->window_stack) {
win_update_is_fullscreen(ps, w);
}
if (ps->redirected) {
for (int i = 0; i < ps->ndamage; i++) {
pixman_region32_clear(&ps->damage_ring[i]);
@@ -861,258 +888,85 @@ static void handle_root_flags(session_t *ps) {
}
}
static struct managed_win *
paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
/**
* Go through the window stack and calculate some parameters for rendering.
*
* @return whether the operation succeeded
*/
static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
struct managed_win **out_bottom) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered.
// 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;
auto now = get_time_ms();
*animation = false;
*out_bottom = NULL;
// Fading step calculation
long long steps = 0L;
auto now = get_time_ms();
if (ps->fade_time) {
assert(now >= ps->fade_time);
steps = (now - ps->fade_time) / ps->o.fade_delta;
} else {
// Reset fade_time if unset
ps->fade_time = now;
ps->fade_time = get_time_ms();
steps = 0L;
}
ps->fade_time += steps * ps->o.fade_delta;
if (ps->o.animations && !ps->animation_time)
ps->animation_time = now;
double delta_secs = (double)(now - ps->animation_time) / 1000;
// First, let's process fading
// 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;
const double opacity_old = w->opacity;
// IMPORTANT: These window animation steps must happen before any other
// [pre]processing. This is because it changes the window's geometry.
if (ps->o.animations &&
!isnan(w->animation_progress) && w->animation_progress != 1.0 &&
ps->o.wintype_option[w->window_type].animation != 0 &&
win_is_mapped_in_x(w))
{
double neg_displacement_x =
w->animation_dest_center_x - w->animation_center_x;
double neg_displacement_y =
w->animation_dest_center_y - w->animation_center_y;
double neg_displacement_w = w->animation_dest_w - w->animation_w;
double neg_displacement_h = w->animation_dest_h - w->animation_h;
double animation_stiffness = ps->o.animation_stiffness;
if (!(w->animation_is_tag & ANIM_IN_TAG)) {
if (w->animation_is_tag & ANIM_SLOW)
animation_stiffness = ps->o.animation_stiffness_tag_change;
else if (w->animation_is_tag & ANIM_FAST)
animation_stiffness = ps->o.animation_stiffness_tag_change * 1.5;
}
if (w->state == WSTATE_FADING && !(w->animation_is_tag & ANIM_FADE))
w->opacity_target = win_calc_opacity_target(ps, w);
double acceleration_x =
(animation_stiffness * neg_displacement_x -
ps->o.animation_dampening * w->animation_velocity_x) /
ps->o.animation_window_mass;
double acceleration_y =
(animation_stiffness * neg_displacement_y -
ps->o.animation_dampening * w->animation_velocity_y) /
ps->o.animation_window_mass;
double acceleration_w =
(animation_stiffness * neg_displacement_w -
ps->o.animation_dampening * w->animation_velocity_w) /
ps->o.animation_window_mass;
double acceleration_h =
(animation_stiffness * neg_displacement_h -
ps->o.animation_dampening * w->animation_velocity_h) /
ps->o.animation_window_mass;
w->animation_velocity_x += acceleration_x * delta_secs;
w->animation_velocity_y += acceleration_y * delta_secs;
w->animation_velocity_w += acceleration_w * delta_secs;
w->animation_velocity_h += acceleration_h * delta_secs;
// Animate window geometry
double new_animation_x =
w->animation_center_x + w->animation_velocity_x * delta_secs;
double new_animation_y =
w->animation_center_y + w->animation_velocity_y * delta_secs;
double new_animation_w =
w->animation_w + w->animation_velocity_w * delta_secs;
double new_animation_h =
w->animation_h + w->animation_velocity_h * delta_secs;
// Negative new width/height causes segfault and it can happen
// when clamping disabled and shading a window
if (new_animation_h < 0)
new_animation_h = 0;
if (new_animation_w < 0)
new_animation_w = 0;
if (ps->o.animation_clamping) {
w->animation_center_x = clamp(
new_animation_x,
min2(w->animation_center_x, w->animation_dest_center_x),
max2(w->animation_center_x, w->animation_dest_center_x));
w->animation_center_y = clamp(
new_animation_y,
min2(w->animation_center_y, w->animation_dest_center_y),
max2(w->animation_center_y, w->animation_dest_center_y));
w->animation_w =
clamp(new_animation_w,
min2(w->animation_w, w->animation_dest_w),
max2(w->animation_w, w->animation_dest_w));
w->animation_h =
clamp(new_animation_h,
min2(w->animation_h, w->animation_dest_h),
max2(w->animation_h, w->animation_dest_h));
} else {
w->animation_center_x = new_animation_x;
w->animation_center_y = new_animation_y;
w->animation_w = new_animation_w;
w->animation_h = new_animation_h;
}
// Now we are done doing the math; we just need to submit our
// changes (if there are any).
struct win_geometry old_g = w->g;
double old_animation_progress = w->animation_progress;
new_animation_x = round(w->animation_center_x - w->animation_w * 0.5);
new_animation_y = round(w->animation_center_y - w->animation_h * 0.5);
new_animation_w = round(w->animation_w);
new_animation_h = round(w->animation_h);
bool position_changed =
new_animation_x != old_g.x || new_animation_y != old_g.y;
bool size_changed =
new_animation_w != old_g.width || new_animation_h != old_g.height;
bool geometry_changed = position_changed || size_changed;
// Mark past window region with damage
if (was_painted && geometry_changed)
add_damage_from_win(ps, w);
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_progress =
1.0 - w->animation_inv_og_distance *
sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
// When clamping disabled we don't want the overlayed image to
// fade in again because process is moving to negative value
if (w->animation_progress < old_animation_progress)
w->animation_progress = old_animation_progress;
w->g.x = (int16_t)new_animation_x;
w->g.y = (int16_t)new_animation_y;
w->g.width = (uint16_t)new_animation_w;
w->g.height = (uint16_t)new_animation_h;
if (w->animation_is_tag > ANIM_IN_TAG && (((w->animation_is_tag & ANIM_FADE) && w->opacity_target == w->opacity) || ((w->g.width == 0 || w->g.height == 0) && (w->animation_dest_w == 0 || w->animation_dest_h == 0)))) {
w->g.x = w->pending_g.x;
w->g.y = w->pending_g.y;
if (ps->o.animation_for_next_tag < OPEN_WINDOW_ANIMATION_ZOOM) {
w->g.width = w->pending_g.width;
w->g.height = w->pending_g.height;
} else {
w->g.width = 0;
w->g.height = 0;
}
}
// Submit window size change
if (size_changed) {
win_on_win_size_change(ps, w);
pixman_region32_clear(&w->bounding_shape);
pixman_region32_fini(&w->bounding_shape);
pixman_region32_init_rect(&w->bounding_shape, 0, 0,
(uint)w->widthb, (uint)w->heightb);
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
win_process_image_flags(ps, w);
}
// Mark new window region with damage
if (was_painted && geometry_changed) {
add_damage_from_win(ps, w);
w->reg_ignore_valid = false;
}
// We can't check for 1 here as sometimes 1 = 0.999999999999999
// in case of floating numbers
if (w->animation_progress >= 0.999999999) {
w->animation_progress = 1;
w->animation_velocity_x = 0.0;
w->animation_velocity_y = 0.0;
w->animation_velocity_w = 0.0;
w->animation_velocity_h = 0.0;
w->opacity = win_calc_opacity_target(ps, w);
}
*animation_running = true;
}
if (win_should_dim(ps, w) != w->dim) {
w->dim = win_should_dim(ps, w);
add_damage_from_win(ps, w);
}
if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
add_damage_from_win(ps, w);
*animation = true;
}
if (w->opacity != w->opacity_target) {
// Run fading
if (run_fade(ps, &w, steps)) {
*fade_running = true;
}
// Run fading
if (run_fade(ps, &w, steps)) {
*fade_running = true;
}
// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->opacity != opacity_old) {
add_damage_from_win(ps, w);
}
// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->opacity != opacity_old) {
add_damage_from_win(ps, w);
}
if (win_check_fade_finished(ps, w)) {
// the window has been destroyed because fading finished
continue;
}
if (win_check_fade_finished(ps, w)) {
// the window has been destroyed because fading finished
continue;
}
if (win_has_frame(w)) {
w->frame_opacity = ps->o.frame_opacity;
} else {
w->frame_opacity = 1.0;
}
if (win_has_frame(w)) {
w->frame_opacity = ps->o.frame_opacity;
} else {
w->frame_opacity = 1.0;
}
// Update window mode
w->mode = win_calc_mode(w);
// Update window mode
w->mode = win_calc_mode(w);
// Destroy all reg_ignore above when frame opaque state changes on
// SOLID mode
if (was_painted && w->mode != mode_old) {
w->reg_ignore_valid = false;
}
// Destroy all reg_ignore above when frame opaque state changes on
// SOLID mode
if (was_painted && w->mode != mode_old) {
w->reg_ignore_valid = false;
}
}
if (animation_running)
ps->animation_time = now;
// Opacity will not change, from now on.
rc_region_t *last_reg_ignore = rc_region_new();
@@ -1132,13 +986,19 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
}
// log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
log_trace("Checking whether window %#010x (%s) should be painted",
w->base.id, w->name);
// Give up if it's not damaged or invisible, or it's unmapped and its
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if (w->state == WSTATE_UNMAPPED ||
unlikely(w->base.id == ps->debug_window ||
w->client_win == ps->debug_window)) {
if (w->state == WSTATE_UNMAPPED) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
(w->base.id == ps->debug_window ||
w->client_win == ps->debug_window)) {
log_trace("|- is the debug window");
to_paint = false;
} else if (!w->ever_damaged && w->state != WSTATE_UNMAPPING &&
w->state != WSTATE_DESTROYING) {
@@ -1146,33 +1006,23 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// is fading out means it must have been damaged when it was still
// mapped (because unmap_win_start will skip fading if it wasn't),
// so we still need to paint it.
log_trace("Window %#010x (%s) will not be painted because it has "
"not received any damages",
w->base.id, w->name);
log_trace("|- has not received any damages");
to_paint = false;
} else if (unlikely(w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 ||
w->g.x >= ps->root_width || w->g.y >= ps->root_height)) {
log_trace("Window %#010x (%s) will not be painted because it is "
"positioned outside of the screen",
w->base.id, w->name);
log_trace("|- is positioned outside of the screen");
to_paint = false;
} else if (unlikely((double)w->opacity * MAX_ALPHA < 1 && !w->blur_background)) {
/* TODO(yshui) for consistency, even a window has 0 opacity, we
* still probably need to blur its background, so to_paint
* shouldn't be false for them. */
log_trace("Window %#010x (%s) will not be painted because it has "
"0 opacity",
w->base.id, w->name);
log_trace("|- has 0 opacity");
to_paint = false;
} else if (w->paint_excluded) {
log_trace("Window %#010x (%s) will not be painted because it is "
"excluded from painting",
w->base.id, w->name);
log_trace("|- is excluded from painting");
to_paint = false;
} else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) {
log_trace("Window %#010x (%s) will not be painted because it has "
"image errors",
w->base.id, w->name);
log_trace("|- has image errors");
to_paint = false;
}
// log_trace("%s %d %d %d", w->name, to_paint, w->opacity,
@@ -1187,10 +1037,12 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// to_paint will never change after this point
if (!to_paint) {
log_trace("|- will not be painted");
goto skip_window;
}
log_trace("Window %#010x (%s) will be painted", w->base.id, w->name);
log_trace("|- will be painted");
log_verbose("Window %#010x (%s) will be painted", w->base.id, w->name);
// Calculate shadow opacity
w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity;
@@ -1229,7 +1081,7 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// is not correctly set.
if (ps->o.unredir_if_possible && is_highest) {
if (w->mode == WMODE_SOLID && !ps->o.force_win_blend &&
win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) {
w->is_fullscreen && !w->unredir_if_possible_excluded) {
unredir_possible = true;
}
}
@@ -1299,7 +1151,8 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
}
}
return bottom;
*out_bottom = bottom;
return true;
}
void root_damaged(session_t *ps) {
@@ -1318,14 +1171,43 @@ void root_damaged(session_t *ps) {
}
auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
if (pixmap != XCB_NONE) {
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(
ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
if (!r) {
goto err;
}
// We used to assume that pixmaps pointed by the root background
// pixmap atoms are owned by the root window and have the same
// depth and hence the same visual that we can use to bind them.
// However, some applications break this assumption, e.g. the
// Xfce's desktop manager xfdesktop that sets the _XROOTPMAP_ID
// atom to a pixmap owned by it that seems to always have 32 bpp
// depth when the common root window's depth is 24 bpp. So use the
// root window's visual only if the root background pixmap's depth
// matches the root window's depth. Otherwise, find a suitable
// visual for the root background pixmap's depth and use it.
//
// We can't obtain a suitable visual for the root background
// pixmap the same way as the win_bind_pixmap function because it
// requires a window and we have only a pixmap. We also can't not
// bind the root background pixmap in case of depth mismatch
// because some options rely on it's content, e.g.
// transparent-clipping.
xcb_visualid_t visual =
r->depth == ps->c.screen_info->root_depth
? ps->c.screen_info->root_visual
: x_get_visual_for_depth(ps->c.screen_info, r->depth);
free(r);
ps->root_image = ps->backend_data->ops->bind_pixmap(
ps->backend_data, pixmap,
x_get_visual_info(&ps->c, ps->c.screen_info->root_visual), false);
ps->backend_data, pixmap, x_get_visual_info(&ps->c, visual), false);
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 {
err:
log_error("Failed to bind root back pixmap");
}
}
@@ -1445,11 +1327,10 @@ static int register_cm(session_t *ps) {
}
// Set COMPTON_VERSION
e = xcb_request_check(
ps->c.c, xcb_change_property_checked(
ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win,
get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8,
(uint32_t)strlen(PICOM_VERSION), PICOM_VERSION));
e = xcb_request_check(ps->c.c, xcb_change_property_checked(
ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win,
ps->atoms->aCOMPTON_VERSION, XCB_ATOM_STRING, 8,
(uint32_t)strlen(PICOM_VERSION), PICOM_VERSION));
if (e) {
log_error_x_error(e, "Failed to set COMPTON_VERSION.");
free(e);
@@ -1629,7 +1510,7 @@ static bool redirect_start(session_t *ps) {
return false;
}
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
if (!initialize_backend(ps)) {
return false;
@@ -1648,8 +1529,7 @@ static bool redirect_start(session_t *ps) {
pixman_region32_init(&ps->damage_ring[i]);
}
ps->frame_pacing = !ps->o.no_frame_pacing;
// if ((ps->o.legacy_backends || ps->o.animations || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
ps->frame_pacing = !ps->o.no_frame_pacing && ps->o.vsync;
if ((ps->o.legacy_backends || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
ps->frame_pacing) {
// Disable frame pacing if we are using a legacy backend or if we are in
@@ -1689,7 +1569,7 @@ static bool redirect_start(session_t *ps) {
}
// Must call XSync() here
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
ps->redirected = true;
ps->first_frame = true;
@@ -1732,15 +1612,38 @@ static void unredirect(session_t *ps) {
}
// Must call XSync() here
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
ps->redirected = false;
log_debug("Screen unredirected.");
}
// Handle queued events before we go to sleep
/// Handle queued events before we go to sleep.
///
/// This function is called by ev_prepare watcher, which is called just before
/// the event loop goes to sleep. X damage events are incremental, which means
/// if we don't handle the ones X server already sent us, we won't get new ones.
/// And if we don't get new ones, we won't render, i.e. we would freeze. libxcb
/// keeps an internal queue of events, so we have to be 100% sure no events are
/// left in that queue before we go to sleep.
static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) {
session_t *ps = session_ptr(w, event_check);
// Flush because if we go into sleep when there is still requests in the
// outgoing buffer, they will not be sent for an indefinite amount of
// time. Use XFlush here too, we might still use some Xlib functions
// because OpenGL.
//
// Also note, after we have flushed here, we won't flush again in this
// function before going into sleep. This is because `xcb_flush`/`XFlush`
// may _read_ more events from the server (yes, this is ridiculous, I
// know). And we can't have that, see the comments above this function.
//
// This means if functions called ev_handle need to send some events,
// they need to carefully make sure those events are flushed, one way or
// another.
XFlush(ps->c.dpy);
xcb_flush(ps->c.c);
if (ps->vblank_scheduler) {
vblank_handle_x_events(ps->vblank_scheduler);
}
@@ -1750,13 +1653,6 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents
ev_handle(ps, ev);
free(ev);
};
// Flush because if we go into sleep when there is still
// requests in the outgoing buffer, they will not be sent
// for an indefinite amount of time.
// Use XFlush here too, we might still use some Xlib functions
// because OpenGL.
XFlush(ps->c.dpy);
xcb_flush(ps->c.c);
int err = xcb_connection_has_error(ps->c.c);
if (err) {
log_fatal("X11 server connection broke (error %d)", err);
@@ -1812,11 +1708,6 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_
queue_redraw(ps);
}
static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
session_t *ps = session_ptr(w, animation_timer);
queue_redraw(ps);
}
static void handle_pending_updates(EV_P_ struct session *ps) {
if (ps->pending_updates) {
log_debug("Delayed handling of events, entering critical section");
@@ -1922,9 +1813,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
* screen is not redirected. its sole purpose should be to decide whether the
* screen should be redirected. */
bool fade_running = false;
bool animation_running = false;
bool animation = false;
bool was_redirected = ps->redirected;
auto bottom = paint_preprocess(ps, &fade_running, &animation_running);
struct managed_win *bottom = NULL;
if (!paint_preprocess(ps, &fade_running, &animation, &bottom)) {
log_fatal("Pre-render preparation has failed, exiting...");
exit(1);
}
ps->tmout_unredir_hit = false;
if (!was_redirected && ps->redirected) {
@@ -1946,13 +1842,6 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0);
ev_timer_start(EV_A_ & ps->fade_timer);
}
// Start/stop animation timer depends on whether windows are animating
if (!animation_running && ev_is_active(&ps->animation_timer)) {
ev_timer_stop(EV_A_ & ps->animation_timer);
} else if (animation_running && !ev_is_active(&ps->animation_timer)) {
ev_timer_set(&ps->animation_timer, 0, 0);
ev_timer_start(EV_A_ & ps->animation_timer);
}
int64_t after_preprocess_us;
clock_gettime(CLOCK_MONOTONIC, &now);
@@ -1993,12 +1882,17 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
if (!fade_running) {
ps->fade_time = 0L;
}
if (!animation_running) {
ps->animation_time = 0L;
}
ps->render_queued = false;
// TODO(yshui) Investigate how big the X critical section needs to be. There are
// suggestions that rendering should be in the critical section as well.
// Queue redraw if animation is running. This should be picked up by next present
// event.
if (animation) {
queue_redraw(ps);
}
if (ps->vblank_scheduler) {
// Even if we might not want to render during next vblank, we want to keep
// `backend_busy` up to date, so when the next render comes, we can
@@ -2032,7 +1926,7 @@ static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused
/**
* Turn on the program reset flag.
*
* This will result in the compostior resetting itself after next paint.
* This will result in the compositor resetting itself after next paint.
*/
static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) {
log_info("picom is resetting...");
@@ -2109,11 +2003,11 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data)
/**
* Initialize a session.
*
* @param argc number of commandline arguments
* @param argv commandline arguments
* @param argc number of command line arguments
* @param argv command line arguments
* @param dpy the X Display
* @param config_file the path to the config file
* @param all_xerros whether we should report all X errors
* @param all_xerrors whether we should report all X errors
* @param fork whether we will fork after initialization
*/
static session_t *session_init(int argc, char **argv, Display *dpy,
@@ -2135,9 +2029,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
.redirected = false,
.alpha_picts = NULL,
.fade_time = 0L,
.animation_time = 0L,
.pending_reply_head = NULL,
.pending_reply_tail = NULL,
.quit = false,
.expose_rects = NULL,
@@ -2386,8 +2277,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
c2_list_postprocess(ps, ps->o.window_shader_fg_rules) &&
c2_list_postprocess(ps, ps->o.opacity_rules) &&
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
c2_list_postprocess(ps, ps->o.focus_blacklist) &&
c2_list_postprocess(ps, ps->o.animation_blacklist))) {
c2_list_postprocess(ps, ps->o.corner_radius_rules) &&
c2_list_postprocess(ps, ps->o.focus_blacklist))) {
log_error("Post-processing of conditionals failed, some of your rules "
"might not work");
}
@@ -2583,7 +2474,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
// Monitor screen changes if vsync_sw is enabled and we are using
// an auto-detected refresh rate, or when X RandR features are enabled
if (ps->randr_exists) {
if (ps->randr_exists && ps->o.crop_shadow_to_monitor) {
xcb_randr_select_input(ps->c.c, ps->c.screen_info->root,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
x_update_monitors(&ps->c, &ps->monitors);
@@ -2612,8 +2503,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ev_init(&ps->draw_timer, draw_callback);
ev_init(&ps->fade_timer, fade_timer_callback);
ev_init(&ps->animation_timer, animation_timer_callback);
// Set up SIGUSR1 signal handler to reset program
ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1);
@@ -2665,7 +2554,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ps->server_grabbed = true;
// We are going to pull latest information from X server now, events sent by X
// earlier is irrelavant at this point.
// earlier is irrelevant at this point.
// A better solution is probably grabbing the server from the very start. But I
// think there still could be race condition that mandates discarding the events.
x_discard_events(&ps->c);
@@ -2726,21 +2615,24 @@ void set_rr_scheduling(void) {
int ret;
struct sched_param param;
ret = sched_getparam(0, &param);
int old_policy;
ret = pthread_getschedparam(pthread_self(), &old_policy, &param);
if (ret != 0) {
log_debug("Failed to get old scheduling priority");
return;
}
param.sched_priority = priority;
ret = sched_setscheduler(0, SCHED_RR, &param);
ret = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
if (ret != 0) {
log_info("Failed to set real-time scheduling priority to %d. Consider "
"giving picom the CAP_SYS_NICE capability",
"giving picom the CAP_SYS_NICE capability or equivalent "
"support.",
priority);
return;
}
log_info("Set real-time scheduling priority to %d", priority);
}
@@ -2757,11 +2649,6 @@ static void session_destroy(session_t *ps) {
unredirect(ps);
}
#ifdef CONFIG_OPENGL
free(ps->argb_fbconfig);
ps->argb_fbconfig = NULL;
#endif
file_watch_destroy(ps->loop, ps->file_watch_handle);
ps->file_watch_handle = NULL;
@@ -2804,6 +2691,7 @@ static void session_destroy(session_t *ps) {
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_radius_rules, NULL);
c2_list_free(&ps->o.window_shader_fg_rules, free);
// Free tracked atom list
@@ -2905,7 +2793,7 @@ static void session_destroy(session_t *ps) {
#endif
// Flush all events
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
ev_io_stop(ps->loop, &ps->xiow);
if (ps->o.legacy_backends) {
free_conv((conv *)ps->shadow_context);
@@ -2920,7 +2808,6 @@ static void session_destroy(session_t *ps) {
// Stop libev event handlers
ev_timer_stop(ps->loop, &ps->unredir_timer);
ev_timer_stop(ps->loop, &ps->fade_timer);
ev_timer_stop(ps->loop, &ps->animation_timer);
ev_timer_stop(ps->loop, &ps->draw_timer);
ev_prepare_stop(ps->loop, &ps->event_check);
ev_signal_stop(ps->loop, &ps->usr1_signal);

View File

@@ -6,6 +6,7 @@
#include <xcb/composite.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb_renderutil.h>
@@ -55,19 +56,20 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h
struct glx_fbconfig_info *fbcfg;
if (!visual) {
assert(depth == 32);
if (!ps->argb_fbconfig) {
ps->argb_fbconfig = glx_find_fbconfig(
&ps->c, (struct xvisual_info){.red_size = 8,
.green_size = 8,
.blue_size = 8,
.alpha_size = 8,
.visual_depth = 32});
if (!ps->argb_fbconfig.cfg) {
glx_find_fbconfig(&ps->c,
(struct xvisual_info){.red_size = 8,
.green_size = 8,
.blue_size = 8,
.alpha_size = 8,
.visual_depth = 32},
&ps->argb_fbconfig);
}
if (!ps->argb_fbconfig) {
if (!ps->argb_fbconfig.cfg) {
log_error("Failed to find appropriate FBConfig for 32 bit depth");
return false;
}
fbcfg = ps->argb_fbconfig;
fbcfg = &ps->argb_fbconfig;
} else {
auto m = x_get_visual_info(&ps->c, visual);
if (m.visual_depth < 0) {
@@ -79,17 +81,17 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h
return false;
}
if (!ppaint->fbcfg) {
ppaint->fbcfg = glx_find_fbconfig(&ps->c, m);
if (!ppaint->fbcfg.cfg) {
glx_find_fbconfig(&ps->c, m, &ppaint->fbcfg);
}
if (!ppaint->fbcfg) {
if (!ppaint->fbcfg.cfg) {
log_error("Failed to find appropriate FBConfig for X pixmap");
return false;
}
fbcfg = ppaint->fbcfg;
fbcfg = &ppaint->fbcfg;
}
if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) {
if (force || !glx_tex_bound(ppaint->ptex, ppaint->pixmap)) {
return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei,
repeat, fbcfg);
}
@@ -378,7 +380,7 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) {
}
#ifdef CONFIG_OPENGL
if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) {
if (BKEND_GLX == ps->o.backend && !glx_tex_bound(ppaint->ptex, XCB_NONE)) {
return false;
}
#endif
@@ -604,20 +606,27 @@ static bool get_root_tile(session_t *ps) {
bool fill = false;
xcb_pixmap_t pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
// Make sure the pixmap we got is valid
if (pixmap && !x_validate_pixmap(&ps->c, pixmap)) {
pixmap = XCB_NONE;
xcb_get_geometry_reply_t *r;
if (pixmap) {
r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
}
// Create a pixmap if there isn't any
if (!pixmap) {
xcb_visualid_t visual;
if (!pixmap || !r) {
pixmap =
x_create_pixmap(&ps->c, (uint8_t)ps->c.screen_info->root_depth, 1, 1);
if (pixmap == XCB_NONE) {
log_error("Failed to create pixmaps for root tile.");
return false;
}
visual = ps->c.screen_info->root_visual;
fill = true;
} else {
visual = r->depth == ps->c.screen_info->root_depth
? ps->c.screen_info->root_visual
: x_get_visual_for_depth(ps->c.screen_info, r->depth);
free(r);
}
// Create Picture
@@ -625,7 +634,7 @@ static bool get_root_tile(session_t *ps) {
.repeat = true,
};
ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap(
&ps->c, ps->c.screen_info->root_visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
&ps->c, visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
// Fill pixmap if needed
if (fill) {
@@ -646,8 +655,7 @@ static bool get_root_tile(session_t *ps) {
ps->root_tile_paint.pixmap = pixmap;
#ifdef CONFIG_OPENGL
if (BKEND_GLX == ps->o.backend) {
return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0,
ps->c.screen_info->root_visual, false);
return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, visual, false);
}
#endif
@@ -1223,7 +1231,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
if (ps->o.vsync) {
// Make sure all previous requests are processed to achieve best
// effect
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
#ifdef CONFIG_OPENGL
if (glx_has_context(ps)) {
if (ps->o.vsync_use_glfinish) {
@@ -1282,7 +1290,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
break;
#ifdef CONFIG_OPENGL
case BKEND_XR_GLX_HYBRID:
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
if (ps->o.vsync_use_glfinish) {
glFinish();
} else {
@@ -1307,7 +1315,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
default: assert(0);
}
x_sync(&ps->c);
xcb_aux_sync(ps->c.c);
#ifdef CONFIG_OPENGL
if (glx_has_context(ps)) {
@@ -1522,7 +1530,7 @@ void deinit_render(session_t *ps) {
free_root_tile(ps);
#ifdef CONFIG_OPENGL
free(ps->root_tile_paint.fbcfg);
ps->root_tile_paint.fbcfg = (struct glx_fbconfig_info){0};
if (bkend_use_glx(ps)) {
glx_destroy(ps);
}

View File

@@ -21,7 +21,7 @@ typedef struct paint {
xcb_render_picture_t pict;
glx_texture_t *ptex;
#ifdef CONFIG_OPENGL
struct glx_fbconfig_info *fbcfg;
struct glx_fbconfig_info fbcfg;
#endif
} paint_t;

View File

@@ -51,10 +51,6 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int
}
/// How much time budget we should give to the backend for rendering, in microseconds.
///
/// A `divisor` is also returned, indicating the target framerate. The divisor is
/// the number of vblanks we should wait between each frame. A divisor of 1 means
/// full framerate, 2 means half framerate, etc.
unsigned int render_statistics_get_budget(struct render_statistics *rs) {
if (rs->render_times.nelem < rs->render_times.window_size) {
// No valid render time estimates yet. Assume maximum budget.

View File

@@ -141,10 +141,10 @@ void rolling_max_pop_front(struct rolling_max *rm, int front) {
}
void rolling_max_push_back(struct rolling_max *rm, int val) {
// Update the prority queue.
// Update the priority queue.
// Remove all elements smaller than the new element from the queue. Because
// the new element will become the maximum element before them, and since they
// come b1efore the new element, they will have been popped before the new
// come before the new element, they will have been popped before the new
// element, so they will never become the maximum element.
while (rm->np) {
int p_tail = IDX(rm->p_head + rm->np - 1);

View File

@@ -21,7 +21,6 @@
#include "types.h"
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define CLEAR_MASK(x) x = 0;
#ifdef __FAST_MATH__
#warning Use of -ffast-math can cause rendering error or artifacts, \
@@ -136,19 +135,6 @@ static inline int attr_const attr_unused normalize_i_range(int i, int min, int m
return i;
}
/**
* Linearly interpolate from a range into another.
*
* @param a,b first range
* @param c,d second range
* @param value value to interpolate, should be in range [a,b]
* @return interpolated value in range [c,d]
*/
static inline int attr_const lerp_range(int a, int b, int c, int d, int value) {
ASSERT_IN_RANGE(value, a, b);
return (d-c)*(value-a)/(b-a) + c;
}
/// Generic integer abs()
#define iabs(val) \
({ \
@@ -236,7 +222,7 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
})
/// @brief Wrapper of ealloc().
/// @brief Wrapper of realloc().
#define crealloc(ptr, nmemb) \
({ \
auto tmp = (nmemb); \

View File

@@ -11,14 +11,13 @@
#ifdef CONFIG_OPENGL
// Enable sgi_video_sync_vblank_scheduler
#include <GL/glx.h>
#include <X11/X.h>
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <epoxy/glx.h>
#include <pthread.h>
#include "backend/gl/glx.h"
#endif
#include "compiler.h"
@@ -27,7 +26,7 @@
#include "vblank.h"
#include "x.h"
struct vblank_callback {
struct vblank_closure {
vblank_callback_t fn;
void *user_data;
};
@@ -37,7 +36,7 @@ struct vblank_callback {
struct vblank_scheduler {
struct x_connection *c;
size_t callback_capacity, callback_count;
struct vblank_callback *callbacks;
struct vblank_closure *callbacks;
struct ev_loop *loop;
/// Request extra vblank events even when no callbacks are scheduled.
/// This is because when callbacks are scheduled too close to a vblank,
@@ -63,9 +62,9 @@ struct present_vblank_scheduler {
struct vblank_scheduler_ops {
size_t size;
void (*init)(struct vblank_scheduler *self);
bool (*init)(struct vblank_scheduler *self);
void (*deinit)(struct vblank_scheduler *self);
void (*schedule)(struct vblank_scheduler *self);
bool (*schedule)(struct vblank_scheduler *self);
bool (*handle_x_events)(struct vblank_scheduler *self);
};
@@ -78,13 +77,14 @@ struct sgi_video_sync_vblank_scheduler {
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
// ... and all the thread shenanigans that come with it.
_Atomic unsigned int last_msc;
_Atomic uint64_t last_ust;
_Atomic unsigned int current_msc;
_Atomic uint64_t current_ust;
ev_async notify;
pthread_t sync_thread;
bool running, error;
unsigned int last_msc;
/// Protects `running`, `error` and `base.vblank_event_requested`
/// Protects `running`, and `base.vblank_event_requested`
pthread_mutex_t vblank_requested_mtx;
pthread_cond_t vblank_requested_cnd;
};
@@ -110,11 +110,6 @@ static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
return false;
}
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
(const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXWaitVideoSyncSGI) {
return false;
}
return true;
}
@@ -207,8 +202,8 @@ static void *sgi_video_sync_thread(void *data) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
atomic_store(&self->last_msc, last_msc);
atomic_store(&self->last_ust,
atomic_store(&self->current_msc, last_msc);
atomic_store(&self->current_ust,
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
ev_async_send(self->base.loop, &self->notify);
pthread_mutex_lock(&self->vblank_requested_mtx);
@@ -239,34 +234,30 @@ cleanup:
return NULL;
}
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
static bool sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
log_verbose("Requesting vblank event for msc %d", self->last_msc + 1);
if (self->error) {
return false;
}
log_verbose("Requesting vblank event for msc %d", self->current_msc + 1);
pthread_mutex_lock(&self->vblank_requested_mtx);
assert(!base->vblank_event_requested);
base->vblank_event_requested = true;
pthread_cond_signal(&self->vblank_requested_cnd);
pthread_mutex_unlock(&self->vblank_requested_mtx);
return true;
}
static void
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
auto event = (struct vblank_event){
.msc = atomic_load(&sched->last_msc),
.ust = atomic_load(&sched->last_ust),
};
sched->base.vblank_event_requested = false;
log_verbose("Received vblank event for msc %lu", event.msc);
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents);
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
static bool sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
auto args = (struct sgi_video_sync_thread_args){
.self = self,
.start_status = -1,
};
bool succeeded = true;
pthread_mutex_init(&args.start_mtx, NULL);
pthread_cond_init(&args.start_cnd, NULL);
@@ -286,11 +277,15 @@ static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
if (args.start_status != 0) {
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
args.start_status);
abort();
succeeded = false;
} else {
log_info("Started sgi_video_sync_thread");
}
self->error = !succeeded;
self->last_msc = 0;
pthread_mutex_destroy(&args.start_mtx);
pthread_cond_destroy(&args.start_cnd);
log_info("Started sgi_video_sync_thread");
return succeeded;
}
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
@@ -306,15 +301,45 @@ static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
pthread_mutex_destroy(&self->vblank_requested_mtx);
pthread_cond_destroy(&self->vblank_requested_cnd);
}
static void
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
auto msc = atomic_load(&sched->current_msc);
if (sched->last_msc == msc) {
// NVIDIA spams us with duplicate vblank events after a suspend/resume
// cycle. Recreating the X connection and GLX context seems to fix this.
// Oh NVIDIA.
log_warn("Duplicate vblank event found with msc %d. Possible NVIDIA bug?", msc);
log_warn("Resetting the vblank scheduler");
sgi_video_sync_scheduler_deinit(&sched->base);
sched->base.vblank_event_requested = false;
if (!sgi_video_sync_scheduler_init(&sched->base)) {
log_error("Failed to reset the vblank scheduler");
} else {
sgi_video_sync_scheduler_schedule(&sched->base);
}
return;
}
auto event = (struct vblank_event){
.msc = msc,
.ust = atomic_load(&sched->current_ust),
};
sched->base.vblank_event_requested = false;
sched->last_msc = msc;
log_verbose("Received vblank event for msc %" PRIu64, event.msc);
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
#endif
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
static bool present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
base->target_window, self->last_msc + 1);
assert(!base->vblank_event_requested);
x_request_vblank_event(base->c, base->target_window, self->last_msc + 1);
base->vblank_event_requested = true;
return true;
}
static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) {
@@ -327,7 +352,7 @@ static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unus
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
static void present_vblank_scheduler_init(struct vblank_scheduler *base) {
static bool present_vblank_scheduler_init(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
base->type = VBLANK_SCHEDULER_PRESENT;
ev_timer_init(&self->callback_timer, present_vblank_callback, 0, 0);
@@ -339,6 +364,7 @@ static void present_vblank_scheduler_init(struct vblank_scheduler *base) {
set_cant_fail_cookie(base->c, select_input);
self->event =
xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL);
return true;
}
static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
@@ -385,7 +411,7 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
double delay_sec = 0.0;
if (now_us < cne->ust) {
log_trace("The end of this vblank is %lu us into the "
log_trace("The end of this vblank is %" PRIu64 " us into the "
"future",
cne->ust - now_us);
delay_sec = (double)(cne->ust - now_us) / 1000000.0;
@@ -439,17 +465,19 @@ static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDU
#endif
};
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
static bool vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
assert(self->type < LAST_VBLANK_SCHEDULER);
auto fn = vblank_scheduler_ops[self->type].schedule;
assert(fn != NULL);
fn(self);
return fn(self);
}
bool vblank_scheduler_schedule(struct vblank_scheduler *self,
vblank_callback_t vblank_callback, void *user_data) {
if (self->callback_count == 0 && self->wind_down == 0) {
vblank_scheduler_schedule_internal(self);
if (!vblank_scheduler_schedule_internal(self)) {
return false;
}
}
if (self->callback_count == self->callback_capacity) {
size_t new_capacity =
@@ -462,7 +490,7 @@ bool vblank_scheduler_schedule(struct vblank_scheduler *self,
self->callbacks = new_buffer;
self->callback_capacity = new_capacity;
}
self->callbacks[self->callback_count++] = (struct vblank_callback){
self->callbacks[self->callback_count++] = (struct vblank_closure){
.fn = vblank_callback,
.user_data = user_data,
};
@@ -473,19 +501,30 @@ static void
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) {
// callbacks might be added during callback invocation, so we need to
// copy the callback_count.
size_t count = self->callback_count;
size_t count = self->callback_count, write_head = 0;
if (count == 0) {
self->wind_down--;
} else {
self->wind_down = VBLANK_WIND_DOWN;
}
for (size_t i = 0; i < count; i++) {
self->callbacks[i].fn(event, self->callbacks[i].user_data);
auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data);
switch (action) {
case VBLANK_CALLBACK_AGAIN:
if (i != write_head) {
self->callbacks[write_head] = self->callbacks[i];
}
write_head++;
case VBLANK_CALLBACK_DONE:
default: // nothing to do
break;
}
}
// remove the callbacks that we have called, keep the newly added ones.
memmove(self->callbacks, self->callbacks + count,
(self->callback_count - count) * sizeof(*self->callbacks));
self->callback_count -= count;
memset(self->callbacks + write_head, 0,
(count - write_head) * sizeof(*self->callbacks));
assert(count == self->callback_count && "callbacks should not be added when "
"callbacks are being invoked.");
self->callback_count = write_head;
if (self->callback_count || self->wind_down) {
vblank_scheduler_schedule_internal(self);
}

View File

@@ -19,7 +19,15 @@ struct vblank_event {
uint64_t ust;
};
typedef void (*vblank_callback_t)(struct vblank_event *event, void *user_data);
enum vblank_callback_action {
/// The callback should be called again in the next vblank.
VBLANK_CALLBACK_AGAIN,
/// The callback is done and should not be called again.
VBLANK_CALLBACK_DONE,
};
typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event,
void *user_data);
/// Schedule a vblank event.
///

499
src/win.c
View File

@@ -73,6 +73,11 @@ static void
win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client);
static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w);
static void win_update_prop_shadow(session_t *ps, struct managed_win *w);
/**
* Update window EWMH fullscreen state.
*/
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
struct managed_win *w);
/**
* Update leader of a window.
*/
@@ -134,10 +139,10 @@ static inline bool attr_pure win_is_real_visible(const struct managed_win *w) {
* Update focused state of a window.
*/
static void win_update_focused(session_t *ps, struct managed_win *w) {
if (UNSET != w->focused_force) {
if (w->focused_force != UNSET) {
w->focused = w->focused_force;
} else {
w->focused = win_is_focused_raw(ps, w);
w->focused = win_is_focused_raw(w);
// Use wintype_focus, and treat WM windows and override-redirected
// windows specially
@@ -205,7 +210,7 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) {
continue;
}
auto mw = (struct managed_win *)w;
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(ps, mw)) {
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(mw)) {
return true;
}
}
@@ -302,13 +307,6 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) {
w->flags |= WIN_FLAGS_PIXMAP_NONE;
}
}
static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) {
log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name);
if (w->old_win_image) {
base->ops->release_image(base, w->old_win_image);
w->old_win_image = NULL;
}
}
static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name);
assert(w->shadow_image);
@@ -405,7 +403,6 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
win_release_pixmap(backend, w);
win_release_oldpixmap(backend, w);
}
if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) {
@@ -465,6 +462,12 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_update_prop_shadow(ps, w);
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_STATE)) {
if (win_update_prop_fullscreen(&ps->c, ps->atoms, w)) {
win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
}
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) ||
win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) {
win_update_leader(ps, w);
@@ -473,179 +476,14 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_clear_all_properties_stale(w);
}
static void init_animation(session_t *ps, struct managed_win *w) {
CLEAR_MASK(w->animation_is_tag)
static int32_t randr_mon_center_x, randr_mon_center_y;
if (w->randr_monitor != -1) {
auto e = pixman_region32_extents(&ps->monitors.regions[w->randr_monitor]);
randr_mon_center_x = (e->x2 + e->x1) / 2, randr_mon_center_y = (e->y2 + e->y1) / 2;
} else {
randr_mon_center_x = ps->root_width / 2, randr_mon_center_y = ps->root_height / 2;
}
static double *anim_x, *anim_y, *anim_w, *anim_h;
enum open_window_animation animation;
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
&& !w->dwm_mask) {
animation = ps->o.wintype_option[w->window_type].animation;
}
else
animation = ps->o.animation_for_open_window;
if (w->window_type != WINTYPE_TOOLTIP &&
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
animation = ps->o.animation_for_transient_window;
}
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
anim_w = &w->animation_w, anim_h = &w->animation_h;
if (w->dwm_mask & ANIM_PREV_TAG) {
animation = ps->o.animation_for_prev_tag;
if (ps->o.enable_fading_prev_tag) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->state = WSTATE_FADING;
w->animation_is_tag |= ANIM_FADE;
}
if (ps->o.animation_for_prev_tag >= OPEN_WINDOW_ANIMATION_ZOOM) {
w->animation_is_tag |= ANIM_FAST;
w->dwm_mask |= ANIM_SPECIAL_MINIMIZE;
goto revert;
}
w->animation_is_tag |= ANIM_SLOW;
} else if (w->dwm_mask & ANIM_NEXT_TAG) {
animation = ps->o.animation_for_next_tag;
w->animation_is_tag |= animation >= OPEN_WINDOW_ANIMATION_ZOOM ? ANIM_FAST : ANIM_SLOW;
if (ps->o.enable_fading_next_tag) {
w->opacity = 0.0;
w->state = WSTATE_FADING;
}
} else if (w->dwm_mask & ANIM_UNMAP) {
animation = ps->o.animation_for_unmap_window;
revert:
anim_x = &w->animation_dest_center_x, anim_y = &w->animation_dest_center_y;
anim_w = &w->animation_dest_w, anim_h = &w->animation_dest_h;
}
double angle;
switch (animation) {
case OPEN_WINDOW_ANIMATION_NONE: // No animation
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5;
w->animation_w = w->pending_g.width;
w->animation_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_FLYIN: // Fly-in from a random point outside the screen
// Compute random point off screen
angle = 2 * M_PI * ((double)rand() / RAND_MAX);
const double radius =
sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height);
// Set animation
*anim_x = randr_mon_center_x + radius * cos(angle);
*anim_y = randr_mon_center_y + radius * sin(angle);
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_UP: // Slide up the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: // Slide down the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: // Slide left the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: // Slide right the image, without changing its location
*anim_x = w->pending_g.x;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_IN:
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER:
*anim_x = randr_mon_center_x;
*anim_y = w->g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_OUT:
w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER:
w->animation_dest_center_x = randr_mon_center_x;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_ZOOM: // Zoom-in the image, without changing its location
if (w->dwm_mask & ANIM_SPECIAL_MINIMIZE) {
*anim_x = w->g.x + w->g.width * 0.5;
*anim_y = w->g.y + w->g.height * 0.5;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
}
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_MINIMIZE:
*anim_x = randr_mon_center_x;
*anim_y = randr_mon_center_y;
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SQUEEZE:
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_h = 0;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = 0;
}
break;
case OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM:
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_y = w->g.y + w->g.height;
*anim_h = 0;
} else {
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height;
w->animation_w = w->pending_g.width;
*anim_h = 0;
*anim_y = w->pending_g.y + w->pending_g.height;
}
break;
case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break;
}
}
/// Handle non-image flags. This phase might set IMAGES_STALE flags
void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Whether the window was visible before we process the mapped flag. i.e.
// 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);
log_trace("Processing flags for window %#010x (%s), was visible: %d, flags: "
"%#" PRIx64,
w->base.id, w->name, was_visible, w->flags);
if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) {
map_win_start(ps, w);
@@ -679,79 +517,11 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
add_damage_from_win(ps, w);
}
// Update window geometry
w->g = w->pending_g;
// Determine if a window should animate
if (win_should_animate(ps, w)) {
if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
w->dwm_mask = ANIM_PREV_TAG;
else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
w->dwm_mask = ANIM_NEXT_TAG;
if (!was_visible || w->dwm_mask) {
// Set window-open animation
init_animation(ps, w);
if (!(w->dwm_mask & ANIM_SPECIAL_MINIMIZE)) {
w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
w->g.x = (int16_t)round(w->animation_center_x -
w->animation_w * 0.5);
w->g.y = (int16_t)round(w->animation_center_y -
w->animation_h * 0.5);
w->g.width = (uint16_t)round(w->animation_w);
w->g.height = (uint16_t)round(w->animation_h);
}
} else {
w->animation_is_tag = ANIM_IN_TAG;
w->animation_dest_center_x =
w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y =
w->pending_g.y + w->pending_g.height * 0.5;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
}
CLEAR_MASK(w->dwm_mask)
w->g.border_width = w->pending_g.border_width;
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_inv_og_distance =
1.0 / sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
if (isinf(w->animation_inv_og_distance))
w->animation_inv_og_distance = 0;
// We only grab images if w->reg_ignore_valid is true as
// there's an ev_shape_notify() event fired quickly on new windows
// for e.g. in case of Firefox main menu and ev_shape_notify()
// sets the win_set_flags(w, WIN_FLAGS_SIZE_STALE); which
// brakes the new image captured and because this same event
// also sets w->reg_ignore_valid = false; too we check for it
if (w->reg_ignore_valid) {
if (w->old_win_image) {
ps->backend_data->ops->release_image(ps->backend_data,
w->old_win_image);
w->old_win_image = NULL;
}
// We only grab
if (w->win_image) {
w->old_win_image = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &w->bounding_shape);
}
}
w->animation_progress = 0.0;
} else {
w->g = w->pending_g;
}
// Whether a window is fullscreen changes based on its geometry
win_update_is_fullscreen(ps, w);
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
win_on_win_size_change(ps, w);
@@ -1063,10 +833,6 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) {
return 0;
}
if ((w->state == WSTATE_FADING && (w->animation_is_tag & ANIM_FADE))) {
return 0;
}
// Try obeying opacity property and window type opacity firstly
if (w->has_opacity_prop) {
opacity = ((double)w->opacity_prop) / OPAQUE;
@@ -1077,7 +843,7 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
} else {
// Respect active_opacity only when the window is physically
// focused
if (win_is_focused_raw(ps, w)) {
if (win_is_focused_raw(w)) {
opacity = ps->o.active_opacity;
} else if (!w->focused) {
// Respect inactive_opacity in some cases
@@ -1131,24 +897,6 @@ bool win_should_fade(session_t *ps, const struct managed_win *w) {
return ps->o.wintype_option[w->window_type].fade;
}
/**
* Determine if a window should animate.
*/
bool win_should_animate(session_t *ps, const struct managed_win *w) {
if (!ps->o.animations) {
return false;
}
if (ps->o.wintype_option[w->window_type].animation == 0) {
log_debug("Animation disabled by window_type");
return false;
}
if (c2_match(ps, w, ps->o.animation_blacklist, NULL)) {
log_debug("Animation disabled by animation_exclude");
return false;
}
return true;
}
/**
* Reread _COMPTON_SHADOW property from a window.
*
@@ -1221,7 +969,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
// 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
// re-create or release the shadow in based on whether w->shadow
// is set.
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
@@ -1278,6 +1026,30 @@ void win_update_prop_shadow(session_t *ps, struct managed_win *w) {
}
}
/**
* Update window EWMH fullscreen state.
*/
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
struct managed_win *w) {
auto prop = x_get_prop(c, w->client_win, atoms->a_NET_WM_STATE, 12, XCB_ATOM_ATOM, 0);
if (!prop.nitems) {
return false;
}
bool is_fullscreen = false;
for (uint32_t i = 0; i < prop.nitems; i++) {
if (prop.atom[i] == atoms->a_NET_WM_STATE_FULLSCREEN) {
is_fullscreen = true;
break;
}
}
free_winprop(&prop);
bool changed = w->is_ewmh_fullscreen != is_fullscreen;
w->is_ewmh_fullscreen = is_fullscreen;
return changed;
}
static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) {
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
c2_match(ps, w, ps->o.shadow_clip_list, NULL));
@@ -1406,19 +1178,19 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
* Determine if a window should have rounded corners.
*/
static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
if (ps->o.corner_radius == 0) {
w->corner_radius = 0;
return;
}
void *radius_override = NULL;
if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) {
log_debug("Matched corner rule! %d", w->corner_radius);
}
if (ps->o.corner_radius == 0 && !radius_override) {
w->corner_radius = 0;
return;
}
// Don't round full screen windows & excluded windows,
// unless we find a corner override in corner_radius_rules
if (!radius_override && ((w && win_is_fullscreen(ps, w)) ||
if (!radius_override && ((w && w->is_fullscreen) ||
c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL))) {
w->corner_radius = 0;
log_debug("Not rounding corners for window %#010x", w->base.id);
@@ -1485,9 +1257,10 @@ 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 and is_fullscreen needs to be updated first, as other rules might depend
// on the focused state of the window
win_update_focused(ps, w);
win_update_is_fullscreen(ps, w);
win_determine_shadow(ps, w);
win_determine_clip_shadow_above(ps, w);
@@ -1520,6 +1293,10 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
* Update cache data in struct _win that depends on window size.
*/
void win_on_win_size_change(session_t *ps, struct managed_win *w) {
log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", w->base.id,
w->name, w->widthb, w->heightb, w->g.width + w->g.border_width * 2,
w->g.height + w->g.border_width * 2);
w->widthb = w->g.width + w->g.border_width * 2;
w->heightb = w->g.height + w->g.border_width * 2;
w->shadow_dx = ps->o.shadow_offset_x;
@@ -1793,19 +1570,14 @@ 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
.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
.pixmap_damaged = false, // updated by damage events
.state = WSTATE_UNMAPPED, // updated by window state changes
.in_openclose = true, // set to false after first map is done,
// true here because window is just created
.reg_ignore_valid = false, // set to true when damaged
.flags = WIN_FLAGS_IMAGES_NONE, // updated by
// property/attributes/etc
// change
.stale_props = NULL,
.stale_props_capacity = 0,
@@ -1831,7 +1603,6 @@ struct win *fill_win(session_t *ps, struct win *w) {
// have no meaning or have no use until the window
// is mapped
.win_image = NULL,
.old_win_image = NULL,
.shadow_image = NULL,
.mask_image = NULL,
.prev_trans = NULL,
@@ -1991,6 +1762,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS,
ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW,
ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR,
ps->atoms->a_NET_WM_STATE,
};
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
@@ -2020,7 +1792,7 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
// 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) {
if (win_is_focused_raw(w) && cache_leader_old != cache_leader) {
ps->active_leader = cache_leader;
group_on_factor_change(ps, cache_leader_old);
@@ -2133,7 +1905,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
xcb_window_t leader = win_get_leader(ps, w);
// If the window gets focused, replace the old active_leader
if (win_is_focused_raw(ps, w) && leader != ps->active_leader) {
if (win_is_focused_raw(w) && leader != ps->active_leader) {
xcb_window_t active_leader_old = ps->active_leader;
ps->active_leader = leader;
@@ -2142,7 +1914,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
group_on_factor_change(ps, leader);
}
// If the group get unfocused, remove it from active_leader
else if (!win_is_focused_raw(ps, w) && leader &&
else if (!win_is_focused_raw(w) && leader &&
leader == ps->active_leader && !group_is_focused(ps, leader)) {
ps->active_leader = XCB_NONE;
group_on_factor_change(ps, leader);
@@ -2155,7 +1927,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
#ifdef CONFIG_DBUS
// Send D-Bus signal
if (ps->o.dbus) {
if (win_is_focused_raw(ps, w)) {
if (win_is_focused_raw(w)) {
cdbus_ev_win_focusin(ps, &w->base);
} else {
cdbus_ev_win_focusout(ps, &w->base);
@@ -2173,15 +1945,18 @@ void win_set_focused(session_t *ps, struct managed_win *w) {
return;
}
if (win_is_focused_raw(ps, w)) {
if (w->is_ewmh_focused) {
assert(ps->active_win == w);
return;
}
auto old_active_win = ps->active_win;
ps->active_win = w;
assert(win_is_focused_raw(ps, w));
w->is_ewmh_focused = true;
if (old_active_win) {
assert(old_active_win->is_ewmh_focused);
old_active_win->is_ewmh_focused = false;
win_on_focus_change(ps, old_active_win);
}
win_on_focus_change(ps, w);
@@ -2254,7 +2029,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// Add border width because we are using a different origin.
// X thinks the top left of the inner window is the origin
// (for the bounding shape, althought xcb_get_geometry thinks
// (for the bounding shape, although xcb_get_geometry thinks
// the outer top left (outer means outside of the window
// border) is the origin),
// We think the top left of the border is the origin
@@ -2274,14 +2049,12 @@ 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_set_flags(w, WIN_FLAGS_IMAGES_STALE | WIN_FLAGS_FACTOR_CHANGED);
win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->paint);
free_paint(ps, &w->shadow_paint);
win_on_factor_change(ps, w);
}
/**
@@ -2394,31 +2167,17 @@ static void unmap_win_finish(session_t *ps, struct managed_win *w) {
// Shadow image can be preserved.
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
win_release_pixmap(ps->backend_data, w);
win_release_oldpixmap(ps->backend_data, w);
}
} else {
assert(!w->win_image);
assert(!w->old_win_image);
assert(!w->shadow_image);
}
// Force animation to completed position
w->animation_velocity_x = 0;
w->animation_velocity_y = 0;
w->animation_velocity_w = 0;
w->animation_velocity_h = 0;
w->animation_progress = 1.0;
free_paint(ps, &w->paint);
free_paint(ps, &w->shadow_paint);
// Try again at binding images when the window is mapped next time
win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR);
// Flag window so that it gets animated when it reapears
// in case it wasn't destroyed
win_set_flags(w, WIN_FLAGS_POSITION_STALE);
win_set_flags(w, WIN_FLAGS_SIZE_STALE);
}
/// Finish the destruction of a window (e.g. after fading has finished).
@@ -2597,7 +2356,7 @@ bool destroy_win_start(session_t *ps, struct win *w) {
HASH_DEL(ps->windows, w);
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
// Window is already unmapped, or is an unmanged window, just
// Window is already unmapped, or is an unmanaged window, just
// destroy it
destroy_win_finish(ps, w);
return true;
@@ -2622,6 +2381,13 @@ bool destroy_win_start(session_t *ps, struct win *w) {
add_damage_from_win(ps, mw);
}
if (win_check_flags_all(mw, WIN_FLAGS_CLIENT_STALE)) {
mw->client_win = mw->base.id;
mw->wmwin = !mw->a.override_redirect;
log_debug("(%#010x): client self (%s)", mw->base.id,
(mw->wmwin ? "wmwin" : "override-redirected"));
}
// 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 |
@@ -2687,30 +2453,6 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->opacity_target = win_calc_opacity_target(ps, w);
if (ps->o.animations && ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && ps->o.wintype_option[w->window_type].animation) {
w->dwm_mask = ANIM_UNMAP;
init_animation(ps, w);
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_inv_og_distance =
1.0 / sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
if (isinf(w->animation_inv_og_distance))
w->animation_inv_og_distance = 0;
w->animation_progress = 0.0;
if (w->old_win_image) {
ps->backend_data->ops->release_image(ps->backend_data,
w->old_win_image);
w->old_win_image = NULL;
}
}
#ifdef CONFIG_DBUS
// Send D-Bus signal
if (ps->o.dbus) {
@@ -2756,7 +2498,7 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
///
/// @return whether the window is destroyed and freed
bool win_skip_fading(session_t *ps, struct managed_win *w) {
if ((w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED)) {
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
assert(w->opacity_target == w->opacity);
return false;
}
@@ -2768,12 +2510,11 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) {
// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to
// the x.c.
void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
mw->randr_monitor = -1;
for (int i = 0; i < monitors->count; i++) {
auto e = pixman_region32_extents(&monitors->regions[i]);
if (((e->x1 <= mw->g.x || e->x1 <= mw->pending_g.x) &&
(e->x2 >= mw->g.x + mw->widthb || e->x2 >= mw->pending_g.x + mw->widthb)) &&
(e->y1 <= mw->g.y || e->y1 <= mw->pending_g.y) &&
(e->y2 >= mw->g.y + mw->heightb || e->y2 >= mw->pending_g.y + mw->heightb)) {
if (e->x1 <= mw->g.x && e->y1 <= mw->g.y &&
e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) {
mw->randr_monitor = i;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the "
"monitor %d (%dx%d+%dx%d)",
@@ -2782,7 +2523,6 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
return;
}
}
mw->randr_monitor = -1;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor",
mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb);
}
@@ -3011,41 +2751,6 @@ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wi
return (struct managed_win *)w;
}
/**
* Check if a rectangle includes the whole screen.
*/
static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) {
return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height);
}
/**
* Check if a window is fulscreen using EWMH
*
* TODO(yshui) cache this property
*/
static inline bool
win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) {
xcb_get_property_cookie_t prop =
xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12);
xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL);
if (!reply) {
return false;
}
if (reply->length) {
xcb_atom_t *val = xcb_get_property_value(reply);
for (uint32_t i = 0; i < reply->length; i++) {
if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) {
continue;
}
free(reply);
return true;
}
}
free(reply);
return false;
}
/// Set flags on a window. Some sanity checks are performed
void win_set_flags(struct managed_win *w, uint64_t flags) {
log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name);
@@ -3130,13 +2835,15 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) {
*
* It's not using w->border_size for performance measures.
*/
bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) {
if (!ps->o.no_ewmh_fullscreen &&
win_is_fullscreen_xcb(ps->c.c, ps->atoms, w->client_win)) {
return true;
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w) {
if (!ps->o.no_ewmh_fullscreen && w->is_ewmh_fullscreen) {
w->is_fullscreen = true;
return;
}
return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) &&
(!w->bounding_shaped || w->rounded_corners);
w->is_fullscreen = w->g.x <= 0 && w->g.y <= 0 &&
(w->g.x + w->widthb) >= ps->root_width &&
(w->g.y + w->heightb) >= ps->root_height &&
(!w->bounding_shaped || w->rounded_corners);
}
/**
@@ -3162,8 +2869,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
*/
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;
bool win_is_focused_raw(const struct managed_win *w) {
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused;
}
// Find the managed window immediately below `i` in the window stack
@@ -3182,5 +2889,5 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *w) {
/// Return whether this window is mapped on the X server side
bool win_is_mapped_in_x(const struct managed_win *w) {
return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING ||
w->state == WSTATE_MAPPED || w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED);
w->state == WSTATE_MAPPED || (w->flags & WIN_FLAGS_MAPPED);
}

View File

@@ -96,27 +96,13 @@ struct win_geometry {
uint16_t border_width;
};
enum {
// dwm_mask
ANIM_PREV_TAG = 1,
ANIM_NEXT_TAG = (1 << 1),
ANIM_UNMAP = (1 << 2),
ANIM_SPECIAL_MINIMIZE = (1 << 3),
// animation_is_tag
ANIM_IN_TAG = 1,
ANIM_SLOW = (1 << 1),
ANIM_FAST = (1 << 2),
ANIM_FADE = (1 << 3),
};
struct managed_win {
struct win base;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
void *win_image;
void *old_win_image; // Old window image for interpolating window contents during animations
void *shadow_image;
void *mask_image;
image_handle win_image;
image_handle shadow_image;
image_handle mask_image;
/// Pointer to the next higher window to paint.
struct managed_win *prev_trans;
/// Number of windows above this window
@@ -166,8 +152,6 @@ struct managed_win {
/// opacity state, window geometry, window mapped/unmapped state,
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
/// NULL means reg_ignore has not been calculated for this window.
/// 1 = tag prev , 2 = tag next, 4 = unmap
uint32_t dwm_mask;
rc_region_t *reg_ignore;
/// Whether the reg_ignore of all windows beneath this window are valid
bool reg_ignore_valid;
@@ -185,24 +169,6 @@ struct managed_win {
bool unredir_if_possible_excluded;
/// Whether this window is in open/close state.
bool in_openclose;
/// Current position and destination, for animation
double animation_center_x, animation_center_y;
double animation_dest_center_x, animation_dest_center_y;
double animation_w, animation_h;
double animation_dest_w, animation_dest_h;
/// Spring animation velocity
double animation_velocity_x, animation_velocity_y;
double animation_velocity_w, animation_velocity_h;
/// Track animation progress; goes from 0 to 1
double animation_progress;
/// Inverse of the window distance at the start of animation, for
/// tracking animation progress
double animation_inv_og_distance;
/// Animation info if it is a tag change & check if its changing window sizes
/// 0: no tag change
/// 1: normal tag change animation
/// 2: tag change animation that effects window size
uint16_t animation_is_tag;
// Client window related members
/// ID of the top-level client window of the window.
@@ -233,6 +199,14 @@ struct managed_win {
char *class_general;
/// <code>WM_WINDOW_ROLE</code> value of the window.
char *role;
/// Whether the window sets the EWMH fullscreen property.
bool is_ewmh_fullscreen;
/// Whether the window should be considered fullscreen. Based on
/// `is_ewmh_fullscreen`, or the windows spatial relation with the
/// root window. Which one is used is determined by user configuration.
bool is_fullscreen;
/// Whether the window is the EWMH active window.
bool is_ewmh_focused;
// Opacity-related members
/// Current window opacity.
@@ -278,13 +252,13 @@ struct managed_win {
switch_t shadow_force;
/// Opacity of the shadow. Affected by window opacity and frame opacity.
double shadow_opacity;
/// X offset of shadow. Affected by commandline argument.
/// X offset of shadow. Affected by command line argument.
int shadow_dx;
/// Y offset of shadow. Affected by commandline argument.
/// Y offset of shadow. Affected by command line argument.
int shadow_dy;
/// Width of shadow. Affected by window size and commandline argument.
/// Width of shadow. Affected by window size and command line argument.
int shadow_width;
/// Height of shadow. Affected by window size and commandline argument.
/// Height of shadow. Affected by window size and command line argument.
int shadow_height;
/// Picture to render shadow. Affected by window size.
paint_t shadow_paint;
@@ -383,6 +357,8 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw);
*/
// XXX was win_border_size
void win_update_bounding_shape(session_t *ps, struct managed_win *w);
/// Recheck if a window is fullscreen
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w);
/**
* Check if a window has BYPASS_COMPOSITOR property set
*/
@@ -458,17 +434,10 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id);
*/
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid);
/**
* Check if a window is a fullscreen window.
*
* It's not using w->border_size for performance measures.
*/
bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w);
/**
* Check if a window is focused, without using any focus rules or forced focus settings
*/
bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w);
bool attr_pure win_is_focused_raw(const struct managed_win *w);
/// check if window has ARGB visual
bool attr_pure win_has_alpha(const struct managed_win *w);
@@ -493,9 +462,6 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags);
/// Mark properties as stale for a window
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
/// Determine if a window should animate
bool attr_pure win_should_animate(session_t *ps, const struct managed_win *w);
static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}

View File

@@ -68,7 +68,7 @@ typedef enum {
} winstate_t;
enum win_flags {
// Note: *_NONE flags are mostly redudant and meant for detecting logical errors
// Note: *_NONE flags are mostly redundant and meant for detecting logical errors
// in the code
/// pixmap is out of date, will be update in win_process_flags

102
src/x.c
View File

@@ -16,6 +16,7 @@
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_renderutil.h>
#include <xcb/xfixes.h>
@@ -94,7 +95,7 @@ void x_connection_init(struct x_connection *c, Display *dpy) {
c->previous_xerror_handler = XSetErrorHandler(xerror);
c->screen = DefaultScreen(dpy);
c->screen_info = x_screen_of_display(c, c->screen);
c->screen_info = xcb_aux_get_screen(c->c, c->screen);
}
/**
@@ -321,6 +322,17 @@ xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standa
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
}
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth) {
xcb_depth_iterator_t depth_it = xcb_screen_allowed_depths_iterator(screen);
for (; depth_it.rem; xcb_depth_next(&depth_it)) {
if (depth_it.data->depth == depth) {
return xcb_depth_visuals_iterator(depth_it.data).data->visual_id;
}
}
return XCB_NONE;
}
xcb_render_pictformat_t
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
x_get_server_pictfmts(c);
@@ -330,24 +342,6 @@ x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
return pictfmt->id;
}
int x_get_visual_depth(struct x_connection *c, xcb_visualid_t visual) {
auto setup = xcb_get_setup(c->c);
for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
xcb_screen_next(&screen)) {
for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
depth.rem; xcb_depth_next(&depth)) {
const int len = xcb_depth_visuals_length(depth.data);
const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
for (int i = 0; i < len; i++) {
if (visual == visuals[i].visual_id) {
return depth.data->depth;
}
}
}
}
return -1;
}
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *c,
const xcb_render_pictforminfo_t *pictfmt,
@@ -459,6 +453,34 @@ bool x_fetch_region(struct x_connection *c, xcb_xfixes_region_t r, pixman_region
return ret;
}
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src) {
if (!src || dst == XCB_NONE) {
return false;
}
int32_t nrects = 0;
const rect_t *rects = pixman_region32_rectangles((region_t *)src, &nrects);
if (!rects || nrects < 1) {
return false;
}
xcb_rectangle_t *xrects = ccalloc(nrects, xcb_rectangle_t);
for (int32_t i = 0; i < nrects; i++) {
xrects[i] =
(xcb_rectangle_t){.x = to_i16_checked(rects[i].x1),
.y = to_i16_checked(rects[i].y1),
.width = to_u16_checked(rects[i].x2 - rects[i].x1),
.height = to_u16_checked(rects[i].y2 - rects[i].y1)};
}
bool success =
XCB_AWAIT_VOID(xcb_xfixes_set_region, c->c, dst, to_u32_checked(nrects), xrects);
free(xrects);
return success;
}
uint32_t x_create_region(struct x_connection *c, const region_t *reg) {
if (!reg) {
return XCB_NONE;
@@ -562,9 +584,7 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c
const char *name = "Unknown";
#define CASESTRRET(s) \
case s: \
name = #s; \
break
case s: name = #s; break
#define CASESTRRET2(s) \
case XCB_##s: name = #s; break
@@ -689,27 +709,6 @@ xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, i
return XCB_NONE;
}
/**
* Validate a pixmap.
*
* Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
* are better ways.
*/
bool x_validate_pixmap(struct x_connection *c, xcb_pixmap_t pixmap) {
if (pixmap == XCB_NONE) {
return false;
}
auto r = xcb_get_geometry_reply(c->c, xcb_get_geometry(c->c, pixmap), NULL);
if (!r) {
return false;
}
bool ret = r->width && r->height;
free(r);
return ret;
}
/// We don't use the _XSETROOT_ID root window property as a source of the background
/// pixmap because it most likely points to a dummy pixmap used to keep the colormap
/// associated with the background pixmap alive but we listen for it's changes and update
@@ -856,8 +855,8 @@ void x_create_convolution_kernel(const conv *kernel, double center,
/// Returns {-1, -1, -1, -1, -1, 0} on failure
struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual) {
auto pictfmt = x_get_pictform_for_visual(c, visual);
auto depth = x_get_visual_depth(c, visual);
if (!pictfmt || depth == -1) {
auto depth = xcb_aux_get_depth_of_visual(c->screen_info, visual);
if (!pictfmt || depth == 0) {
log_error("Invalid visual %#03x", visual);
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
}
@@ -882,19 +881,6 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
};
}
xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen) {
xcb_screen_iterator_t iter;
iter = xcb_setup_roots_iterator(xcb_get_setup(c->c));
for (; iter.rem; --screen, xcb_screen_next(&iter)) {
if (screen == 0) {
return iter.data;
}
}
return NULL;
}
void x_update_monitors(struct x_connection *c, struct x_monitors *m) {
x_free_monitor_info(m);

22
src/x.h
View File

@@ -27,6 +27,7 @@ typedef struct winprop {
int16_t *p16;
int32_t *p32;
uint32_t *c32; // 32bit cardinal
xcb_atom_t *atom;
};
unsigned long nitems;
xcb_atom_t type;
@@ -207,17 +208,6 @@ void x_discard_pending(struct x_connection *c, uint32_t sequence);
/// This function logs X errors, or aborts the program based on severity of the error.
void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev);
/**
* Send a request to X server and get the reply to make sure all previous
* requests are processed, and their replies received
*
* xcb_get_input_focus is used here because it is the same request used by
* libX11
*/
static inline void x_sync(struct x_connection *c) {
free(xcb_get_input_focus_reply(c->c, xcb_get_input_focus(c->c), NULL));
}
/**
* Get a specific attribute of a window.
*
@@ -276,7 +266,6 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char **
const xcb_render_pictforminfo_t *
x_get_pictform_for_visual(struct x_connection *, xcb_visualid_t);
int x_get_visual_depth(struct x_connection *, xcb_visualid_t);
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *,
@@ -321,6 +310,9 @@ x_create_picture_with_visual(struct x_connection *, int w, int h, xcb_visualid_t
/// Fetch a X region and store it in a pixman region
bool x_fetch_region(struct x_connection *, xcb_xfixes_region_t r, region_t *res);
/// Set an X region to a pixman region
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src);
/// Create a X region from a pixman region
uint32_t x_create_region(struct x_connection *c, const region_t *reg);
@@ -356,8 +348,6 @@ const char *x_strerror(xcb_generic_error_t *e);
xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height);
bool x_validate_pixmap(struct x_connection *, xcb_pixmap_t pxmap);
/**
* Free a <code>winprop_t</code>.
*
@@ -408,11 +398,11 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std);
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth);
xcb_render_pictformat_t
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen);
/// Populates a `struct x_monitors` with the current monitor configuration.
void x_update_monitors(struct x_connection *, struct x_monitors *);
/// Free memory allocated for a `struct x_monitors`.

View File

@@ -58,6 +58,14 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
} \
} while (0)
#define TEST_NOTEQUAL(a, b) \
do { \
if ((a) == (b)) { \
SET_FAILURE(#a " == " #b, false); \
return; \
} \
} while (0)
#define TEST_TRUE(a) \
do { \
if (!(a)) { \
@@ -69,11 +77,13 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#define TEST_STREQUAL(a, b) \
do { \
if (strcmp(a, b) != 0) { \
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); \
const char *test_strequal__part2 = " != " #b; \
size_t test_strequal__len = \
strlen(a) + strlen(test_strequal__part2) + 3; \
char *test_strequal__buf = malloc(test_strequal__len); \
snprintf(test_strequal__buf, test_strequal__len, "\"%s\"%s", a, \
test_strequal__part2); \
SET_FAILURE(test_strequal__buf, true); \
return; \
} \
} while (0)
@@ -81,11 +91,27 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#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); \
const char *test_strnequal__part2 = " != " #b; \
size_t test_strnequal__len2 = \
len + strlen(test_strnequal__part2) + 3; \
char *test_strnequal__buf = malloc(test_strnequal__len2); \
snprintf(test_strnequal__buf, test_strnequal__len2, \
"\"%.*s\"%s", (int)len, a, test_strnequal__part2); \
SET_FAILURE(test_strnequal__buf, true); \
return; \
} \
} while (0)
#define TEST_STREQUAL3(str, expected, len) \
do { \
if (len != strlen(expected) || strncmp(str, expected, len) != 0) { \
const char *test_strequal3__part2 = " != " #expected; \
size_t test_strequal3__len2 = \
len + strlen(test_strequal3__part2) + 3; \
char *test_strequal3__buf = malloc(test_strequal3__len2); \
snprintf(test_strequal3__buf, test_strequal3__len2, \
"\"%.*s\"%s", (int)len, str, test_strequal3__part2); \
SET_FAILURE(test_strequal3__buf, true); \
return; \
} \
} while (0)
@@ -199,6 +225,9 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
#define TEST_EQUAL(a, b) \
(void)(a); \
(void)(b)
#define TEST_NOTEQUAL(a, b) \
(void)(a); \
(void)(b)
#define TEST_TRUE(a) (void)(a)
#define TEST_STREQUAL(a, b) \
(void)(a); \
@@ -207,5 +236,8 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
(void)(a); \
(void)(b); \
(void)(len)
#define TEST_STREQUAL3(str, expected, len) \
(void)(str); \
(void)(expected); \
(void)(len)
#endif