143 Commits

Author SHA1 Message Date
Arda Atci
d4f3ffb258 remove queue draw, does not work with animations
Some checks failed
coding-style / check (push) Has been cancelled
2023-12-19 22:41:55 +03:00
Arda Atci
7b2fa9d09e Merge remote-tracking branch 'yshui/pacing-fixes' into pacing-fixes 2023-12-19 17:44:35 +03:00
Arda Atci
cd203473fe Merge remote-tracking branch 'yshui/pacing-fixes' into pacing-fixes 2023-12-19 17:40:52 +03:00
Yuxuan Shui
a5b2ceb55c core: disable frame pacing when vsync is disabled
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 11:07:55 +00:00
Yuxuan Shui
c9840c1a7b 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 10:58:20 +00:00
Yuxuan Shui
bd1c6e600e driver: choose sgi_video_sync scheduler for NVIDIA
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:19 +00:00
Yuxuan Shui
4080a8d254 core: add debug options to override the vblank scheduler
Useful for debugging.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:17 +00:00
Yuxuan Shui
5ee7b09542 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 10:58:16 +00:00
Yuxuan Shui
75f89caf04 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 10:58:14 +00:00
Yuxuan Shui
057c8265b0 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 10:58:13 +00:00
Yuxuan Shui
4574977287 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 10:58:11 +00:00
Yuxuan Shui
58150dfe55 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 10:58:10 +00:00
Yuxuan Shui
91a0ccc391 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 10:58:09 +00:00
Yuxuan Shui
8e35b33458 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 10:58:07 +00:00
Yuxuan Shui
5bcd34449c 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 10:57:56 +00:00
Yuxuan Shui
5d94b2a054 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 10:36:03 +00:00
Arda Atci
c178be2874 Merge remote-tracking branch 'yshui/next' into next 2023-12-19 13:07:09 +03:00
Arda Atci
7da885e6c0 index on next: 5a8c61da revert opacity rule 2023-12-19 13:00:17 +03:00
Yuxuan Shui
ff691e6295 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 10:00:07 +00:00
Yuxuan Shui
fc62f6a4ed config: make naming consistent
*_VBLANK_SCHEDULER -> VBLANK_SCHEDULER_*

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:05 +00:00
Yuxuan Shui
0b45b3415b driver: choose sgi_video_sync scheduler for NVIDIA
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:04 +00:00
Yuxuan Shui
d7a2f8ade6 core: add a few debug logs
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:03 +00:00
Yuxuan Shui
ffaa42a019 core: add debug options to override the vblank scheduler
Useful for debugging.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:02 +00:00
Yuxuan Shui
e7c00108d1 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 10:00:01 +00:00
Yuxuan Shui
72693b7550 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 09:59:59 +00:00
Yuxuan Shui
6986ba919a 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 09:59:58 +00:00
Yuxuan Shui
79bf7cd5c3 vblank: use table of function pointers instead of switch
Makes conditional compilation easier in the future.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:57 +00:00
Yuxuan Shui
91667d7747 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 09:59:56 +00:00
Yuxuan Shui
c6b48d7cbc 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 09:59:54 +00:00
Yuxuan Shui
8384890d3a 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 09:59:53 +00:00
Yuxuan Shui
f7b578dd54 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 09:59:52 +00:00
Yuxuan Shui
40ca6d7146 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 09:59:50 +00:00
Yuxuan Shui
b3c12b8724 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 09:59:48 +00:00
Yuxuan Shui
3e8af9fb88 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 09:59:47 +00:00
Yuxuan Shui
2bc180c2a7 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 09:59:46 +00:00
Yuxuan Shui
580889488f 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 09:59:43 +00:00
Yuxuan Shui
ce160cf432 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-18 04:37:03 +00:00
Arda Atci
5a8c61daed revert opacity rule 2023-11-24 00:25:12 +03:00
Arda Atci
b1fce63a26 closes #26 2023-11-24 00:02:49 +03:00
Arda Atci
b73e6a0c45 Merge branch 'yshui:next' into next 2023-11-10 22:11:03 +03:00
Arda Atci
98e842e83b fix for vertical stacked monitors 2023-09-30 01:35:52 +03:00
Arda Atci
2241c7d87a Merge branch 'yshui:next' into next 2023-09-30 01:04:35 +03:00
Arda Atci
21c2da549a multi monitor desktop switch fixed 2023-09-20 01:19:14 +03:00
Arda Atci
2b74392ec7 Merge pull request #27 from yshui/next
Merged with picom upstream
2023-09-20 00:04:29 +03:00
Arda Atci
367643e98a Merge branch 'yshui:next' into next 2023-08-16 05:24:15 +03:00
Arda Atci
a5c4095082 Merge remote-tracking branch 'yshui/next' into next 2023-07-14 02:45:02 +03:00
Arda Atci
15aa5014a6 Merge branch 'yshui:next' into next 2023-06-20 22:36:05 +03:00
Arda Atci
4d624bbee5 Merge remote-tracking branch 'yshui/next' into next 2023-06-14 22:02:43 +03:00
Arda Atci
dc9d1fe248 Merge branch 'yshui:next' into next 2023-04-01 23:12:29 +03:00
Arda Atci
1c7466c07c pos fix 2023-02-15 17:29:55 +03:00
Arda Atci
44cfa0b0ef randr_mon position fix 2023-02-14 23:24:51 +03:00
Arda Atci
bb2b4801f7 randr anim fix 2023-02-06 23:12:45 +03:00
Arda Atci
36e6b73cde randr extension 2023-02-04 23:10:18 +03:00
Arda Atci
90f57e9b1a shadow fix 2023-02-04 15:40:21 +03:00
Arda Atci
157ecd2077 shadow fix 2023-02-04 07:11:23 +03:00
Arda Atci
3a791ab559 Merge branch 'next' into nextt
Some checks failed
coding-style / check (push) Has been cancelled
2023-02-04 07:10:45 +03:00
Arda Atci
f5e8351507 shadow fix 2023-02-04 07:09:20 +03:00
Arda Atci
9f14c43989 Merge pull request #18 from yshui/next
merge yshui/next
2023-02-04 05:49:58 +03:00
Arda Atci
97d9c960e0 animations added, picom upstreamed yshui/next 2023-01-23 07:36:50 +03:00
Arda Atci
d41a291fd6 fix lerping on shadows, closes #4 2023-01-23 07:34:35 +03:00
Arda Atci
c3a7cf29ab AUR pkg, closes #15, thanks fxzzi 2023-01-23 07:34:35 +03:00
Arda Atci
3b1930d2c6 fixed open window type anim, closes #12 2023-01-23 07:34:35 +03:00
Yuxuan Shui
92a32808ae doc: list cases that trigger unredirect
unredir-if-possible doesn't just happen for fullscreen windows, be more
accurate.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Maxim Solovyov
d3c467f4c0 opengl: fix segfault in ensure_glx_context 2023-01-23 07:34:35 +03:00
Maxim Solovyov
4be96a92c7 backend: egl: fix creating eglpixmap
according to the specification of the EGL_KHR_image_pixmap extension,
if target is EGL_NATIVE_PIXMAP_KHR then ctx must be EGL_NO_CONTEXT,
otherwise, the EGL_BAD_PARAMETER error is generated. source:
https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_image_pixmap.txt

fixes #981
2023-01-23 07:34:35 +03:00
Maxim Solovyov
ab766b6386 picom: fix xcb_request_check memory leaks 2023-01-23 07:34:35 +03:00
Maxim Solovyov
7e975cdc5d picom: free root image properly
fixes #982
2023-01-23 07:34:35 +03:00
Maxim Solovyov
349f2f37db backend: egl: simplify getting a framebuffer configuration
we're using the first available framebuffer configuration that
meets our needs so there is no need to ask for more than one
2023-01-23 07:34:35 +03:00
Maxim Solovyov
7846b17c54 use _checked functions with xcb_request_check 2023-01-23 07:34:35 +03:00
Maxim Solovyov
a667886959 xrender: make corner-radius respect inactive-dim
for the new xrender backend it's enough to clip with a rounded rectangle after dimming, not before

partially fixes #867
2023-01-23 07:34:35 +03:00
Maxim Solovyov
6c08650f3c xrender: fix leak in release_rounded_corner_cache
calling wrong free function did nothing and produced ton of x errors

fixes at least #892
2023-01-23 07:34:35 +03:00
Maxim Solovyov
7f533c0b23 xrender: fix leak in deinit
and check should we actually free something

fixes at least #960
2023-01-23 07:34:35 +03:00
Maxim Solovyov
efa3d9c227 xrender: fix leak in bind_pixmap 2023-01-23 07:34:35 +03:00
Maxim Solovyov
0b965b2e5a x: fix leak in x_create_picture_with_pictfmt_and_pixmap 2023-01-23 07:34:35 +03:00
Yuxuan Shui
ba2e24af3e core: detect screen off
Use the DPMS extension to detect if screen is turned off, and unredirect
if it is. This also helps working around the problem where OpenGL
buffers lose data when screen is turned off, causing screen to flicker
later when it turns back on if use-damage is enabled.

Unfortunately the DPMS extension doesn't define an event, so we have to
periodically poll the screen state.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
0278a1fb0b backend: xrender: fix using of invalid picture when vsync is disabled
Fixes #974

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
931c1b8bf6 x: fix missing _checked
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
6906e6694a core: add set_cant_fail_cookie
Enables picom to abort when certain requests fail.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
f1bff49b1c core: expand X error handling
We used to have a list of X errors we should ignore in case they do
occur. This commit expands that functionality to also allow us aborting
on certain errors.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
fe3f53f3a4 x: fix CI build failure
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
57956fb219 general: fix compiler warning about unused results
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
c51020aef7 event: restore event sequence number after passing it to Xlib handlers
We set event sequence number to the last sequence xlib knows about to
silence its complaint about missing sequence numbers, but we forgot to
restore it back afterwards.

This used to break error ignoring mechanism in `should_ignore`. In the
last commit we updated it to use full_sequence which incidently fixed
this problem. But let's restore the sequence number anyway for good
measure.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
eec8bf79d2 core: print error with full_sequence
ev->sequence was just the lower 16 bits of the sequence number.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
227cb55ca5 backend: xrender: set update region for PresentPixmap request
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
e9ff18e1bd x: add x_{create,destroy}_region
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
43d8d3ed5d backend: add a comment
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
bf0832cd4c utils: add rolling_avg
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
9c3204cc72 utils: add rolling_max
For tracking rolling max of a stream of integers.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
bc1f99f2ae backend: gl: fix use-after-scope
'format' was pointing to an array with a shorter lifetime

suggested by @tryone144

Co-authored-by: Bernd Busse <bernd@busse-net.de>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
d21fb34e55 backend: gl: try different back buffer formats
Prefer RGB formats first, because they use less memory; but fallback to
RGBA formats, as they are formats required by OpenGL.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
7f18e74b8f backend: gl: don't add dither where it's not needed
If a pixel is perfectly representable as an 8-bit number, don't add
dither. Reduce artifacts where dither is unnecessary.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
2173654fbd options: add dithered-present option
See also 0a2cd0f14e

Related: #602

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
123ef51210 backend: gl: add dither
Add bayer ordered dithering when presenting to screen. Reduce banding
when using a strong blur. Also use 16-bit intermediary textures to
preserve precision in the rendering pipeline.

Related: #602

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
4ecc3e65a9 man: fix typo
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
4a7a9e8079 Update README.md
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Maxim Solovyov
dacadb9fc3 README: fix meson's warnings about setup commands
this fixes `WARNING: Running the setup command as `meson [options]` instead of `meson setup [options]` is ambiguous and deprecated.` warning while setting up the project
2023-01-23 07:34:35 +03:00
Jake
144e78fd5d Change dreaw -> draw 2023-01-23 07:34:35 +03:00
Yuxuan Shui
d647ccca16 README: update pcre requirements
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
9fe7e65e3e c2: replace pcre with pcre2
Because pcre has been deprecated.[1]

There are subtle changes from pcre to pcre2, so this could be a breaking
change.

Closes #895

[1]: https://www.pcre.org/original/changelog.txt

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Evgeniy Baskov
ad5a042803 win: consider border when creating the mask image
With rounded corners, X11 native border and blur enabled, left and
bottom 2*border_width pixels were not blurred, since mask did not
include border_width, only content width and height.

Create mask image with dimensions that include border width.

Signed-off-by: Evgeniy Baskov <j-basevgser@yandex.ru>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
0a82f460e0 backend: egl: fix warning 2023-01-23 07:34:35 +03:00
Yuxuan Shui
49490ab99f backend: egl: fix undefined symbols on old systems
Users with an old EGL version won't be able to use the egl backend. OTOH
we shouldn't prevent them from running picom because of a feature they
won't even use.

Don't assume the existence of EGL 1.5 symbols.

Fixes #945

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
h7x4
9bf39b8d1d picom.sample.conf: Add egl to backend option doc 2023-01-23 07:34:35 +03:00
Omar Polo
11a195747a backend: egl: don't assume glEGLImageTargetTexStorage exists
Use eglGetProcAddress instead.

Fixes #932
2023-01-23 07:34:35 +03:00
Yuxuan Shui
89690c9843 win: fix leaking of the mask image
destroy_win_finish doesn't call win_release_images to free the images,
so we need to add a release_mask call there.

Related: #892

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Omar Polo
935885d396 fix log_debug call 2023-01-23 07:34:35 +03:00
Yuxuan Shui
611f8b80c2 backend: gl: handle blur context creation failure
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
8f848c2b1d backend: gl: fix crash when shadow radius is 0
Fixes #927

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
a10a64f984 Fix typo
Fixes #922

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
1434881567 win: assert we won't clobber existing mask
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
fa21c44ee7 backend: gl: fix shadow from mask
The intermediate texture used for shadow from mask calculation did not
properly set the min/mag filter to linear, which is required by the blur
methods. Because they use texture interpolation to accelerate
the convolution calculation.

Fixes #916

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
dbb81b5116 doc: remove mention of raw string pattern
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Alp
f7596fd43a rounded corner delay fix 2023-01-23 07:34:35 +03:00
Yuxuan Shui
f7a950a638 Bump version number
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
fbe7ed5699 options: improve usage message printing
Laying the usage message out by hand is tedious, also error prone
because the option names are duplicated at 2 places and have to be
consistent.

Create a struct to hold the option names and help messages, and
do layout programmatically.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Yuxuan Shui
0004173ecd Rename COMPTON_VERSION to PICOM_VERSION
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:34:35 +03:00
Stefan Radziuk
1e5de4067b picom upto date sync with yshui, full anim support 2023-01-23 07:34:35 +03:00
Yuxuan Shui
04b80760d8 win: fix leak in win_bind_mask
Related: #905

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-01-23 07:33:56 +03:00
Arda Atci
a88393a7df fix lerping on shadows, closes #4 2023-01-23 06:00:50 +03:00
Arda Atci
8e8a62ae29 AUR pkg, closes #15, thanks fxzzi 2023-01-23 02:46:38 +03:00
Arda Atci
223533acec fixed open window type anim, closes #12 2023-01-23 02:31:51 +03:00
Arda Atci
bcc7d37546 Merge branch 'yshui:next' into next 2023-01-20 05:44:39 +03:00
Arda Atci
c9aee893d2 Merge pull request #14 from yshui/next
Merge yshui/next
2023-01-13 23:02:18 +03:00
Arda Atci
a1dcadf81b Merge pull request #13 from yshui/next
Merge yshui/next
2023-01-12 20:19:37 +03:00
Arda Atci
c86652a5c4 Merge remote-tracking branch 'yshui/next' into next 2022-12-25 21:37:18 +03:00
Arda Atci
157cb57ea7 Merge remote-tracking branch 'yshui/next' into next 2022-12-20 03:07:52 +03:00
Arda Atci
ad8feaad12 Merge branch 'yshui:next' into next 2022-12-05 21:07:53 +03:00
Arda Atci
f54315b51c Merge branch 'yshui:next' into next 2022-11-29 19:17:45 +03:00
Arda Atci
fd18476f4b Merge branch 'yshui:next' into next 2022-11-24 15:42:17 +03:00
Arda Atci
8e3ff3d63b Merge branch 'yshui:next' into next 2022-11-23 01:22:09 +03:00
Arda Atci
04b027d495 Merge pull request #9 from yshui/next
Merge yshui/next
2022-11-18 18:13:19 +03:00
Arda Atci
b93ad16e5a Merge remote-tracking branch 'yshui/next' into next 2022-11-11 20:18:17 +03:00
Arda Atci
68b48617c7 Merge pull request #8 from Alpcho/next
rounded corner delay fix
2022-10-31 22:26:41 +03:00
Alp
59d5b95483 rounded corner delay fix 2022-10-31 22:24:45 +03:00
Arda Atci
0a49da1b91 Merge remote-tracking branch 'yshui/next' into next 2022-10-30 02:27:37 +03:00
Arda Atci
260adcdc15 Merge branch 'yshui-next' into next 2022-10-25 01:31:35 +03:00
Arda Atci
4fbc9b33ad small changes 2022-10-25 01:30:51 +03:00
Arda Atci
9e397ef4e9 small changes 2022-10-25 01:30:51 +03:00
Arda Atci
70c729d389 picom upto date sync with yshui, full anim support 2022-10-25 01:30:50 +03:00
Arda Atci
23c151c8dc small changes 2022-10-25 01:24:44 +03:00
Arda Atci
b7434c7b76 small changes 2022-10-24 17:29:52 +03:00
Arda Atci
5a8da46952 Merge pull request #1 from yshui/next
win: fix leak in win_bind_mask
2022-10-10 20:01:50 +03:00
Arda Atci
f6b0b04f5b picom upto date sync with yshui, full anim support 2022-10-04 00:24:05 +03:00
28 changed files with 2138 additions and 531 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ compton
build/
compile_commands.json
build.ninja
make.sh
# language servers
.ccls-cache

View File

@@ -85,6 +85,11 @@ $ ninja -C build
### To install
#### AUR (arch)
- picom-ftlabs-git
Thanks to @Fxzzi for maintaining the package.
``` bash
$ ninja -C build install
```

View File

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

View File

@@ -56,6 +56,58 @@ region_t get_damage(session_t *ps, bool all_damage) {
return region;
}
static void process_window_for_painting(session_t *ps, struct managed_win *w,
void *win_image, double additional_alpha,
region_t *reg_bound, region_t *reg_visible,
region_t *reg_paint, region_t *reg_paint_in_bound) {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
coord_t dest_coord = {.x = w->g.x + w->widthb, .y = w->g.y + w->heightb};
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&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
@@ -82,13 +134,17 @@ void handle_device_reset(session_t *ps) {
}
/// paint all windows
void paint_all_new(session_t *ps, struct managed_win *t) {
///
/// Returns if any render command is issued. IOW if nothing on the screen has changed,
/// this function will return false.
bool paint_all_new(session_t *ps, struct managed_win *const t) {
struct timespec now = get_time_timespec();
auto paint_all_start_us =
(uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
if (ps->backend_data->ops->device_status &&
ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
return handle_device_reset(ps);
handle_device_reset(ps);
return false;
}
if (ps->o.xrender_sync_fence) {
if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) {
@@ -114,7 +170,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
if (!pixman_region32_not_empty(&reg_damage)) {
pixman_region32_fini(&reg_damage);
return;
return false;
}
#ifdef DEBUG_REPAINT
@@ -190,16 +246,15 @@ void paint_all_new(session_t *ps, struct managed_win *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_trace("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld",
labs((long)after_damage_us - (long)ps->next_render),
after_damage_us < ps->next_render ? "early" : "late",
after_damage_us, ps->next_render);
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld",
labs((long)after_damage_us - (long)ps->next_render),
after_damage_us < ps->next_render ? "early" : "late",
after_damage_us, ps->next_render);
ps->last_schedule_delay = 0;
if (after_damage_us > ps->next_render) {
ps->last_schedule_delay = after_damage_us - ps->next_render;
}
}
ps->did_render = true;
if (ps->backend_data->ops->prepare) {
ps->backend_data->ops->prepare(ps->backend_data, &reg_paint);
@@ -207,8 +262,8 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
(coord_t){0}, NULL, (coord_t){0},
&reg_paint, &reg_visible);
(coord_t){0}, NULL, (coord_t){.x = ps->root_width, .y = ps->root_height},
&reg_paint, &reg_visible, true);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
&reg_paint);
@@ -219,7 +274,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
// on top of that window. This is used to reduce the number of pixels painted.
//
// Whether this is beneficial is to be determined XXX
for (auto w = t; w; w = w->prev_trans) {
for (struct managed_win *w = t; w; w = w->prev_trans) {
pixman_region32_subtract(&reg_visible, &ps->screen_reg, w->reg_ignore);
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
@@ -259,6 +314,7 @@ void paint_all_new(session_t *ps, struct managed_win *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 ||
@@ -387,7 +443,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
}
ps->backend_data->ops->compose(
ps->backend_data, w->shadow_image, shadow_coord,
inverted_mask, window_coord, &reg_shadow, &reg_visible);
inverted_mask, window_coord, &reg_shadow, &reg_visible, false);
if (inverted_mask) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
@@ -431,6 +487,17 @@ void paint_all_new(session_t *ps, struct managed_win *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(
@@ -453,53 +520,43 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
}
// Draw window on target
if (w->frame_opacity == 1) {
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
if (w->frame_opacity == 1 && !is_animating) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
window_coord, NULL, dest_coord,
&reg_paint_in_bound, &reg_visible, true);
} else {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
if (is_animating && w->old_win_image) {
bool is_focused = win_is_focused_raw(ps, w);
if (!is_focused && w->focused && w->opacity_is_set)
is_focused = true;
assert(w->old_win_image);
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
pixman_region32_init(&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);
// 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);
}
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);
@@ -541,6 +598,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) {
for (win *w = t; w; w = w->prev_trans)
log_trace(" %#010lx", w->id);
#endif
return true;
}
// vim: set noet sw=8 ts=8 :

View File

@@ -176,7 +176,12 @@ struct backend_operations {
*/
void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst,
void *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible);
const region_t *reg_visible, bool lerp);
void (*_compose)(backend_t *backend_data, void *image_data,
int dst_x1, int dst_y1, int dst_x2, int dst_y2,
const region_t *reg_paint, const region_t *reg_visible);
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
@@ -366,4 +371,8 @@ struct backend_operations {
extern struct backend_operations *backend_list[];
void paint_all_new(session_t *ps, struct managed_win *const t) attr_nonnull(1);
/// paint all windows
///
/// Returns if any render command is issued. IOW if nothing on the screen has changed,
/// this function will return false.
bool paint_all_new(session_t *ps, struct managed_win *t) attr_nonnull(1);

View File

@@ -19,6 +19,13 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) {
}
}
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) {
if (driver & DRIVER_NVIDIA) {
return VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
}
return VBLANK_SCHEDULER_PRESENT;
}
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
enum driver ret = 0;
// First we try doing backend agnostic detection using RANDR

View File

@@ -7,6 +7,7 @@
#include <stdio.h>
#include <xcb/xcb.h>
#include "config.h"
#include "utils.h"
struct session;
@@ -41,6 +42,8 @@ enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_
/// Apply driver specified global workarounds. It's safe to call this multiple times.
void apply_driver_workarounds(struct session *ps, enum driver);
/// Choose a vblank scheduler based on the driver.
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver);
// Print driver names to stdout, for diagnostics
static inline void print_drivers(enum driver drivers) {

View File

@@ -67,7 +67,7 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag
void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused,
void *mask attr_unused, coord_t mask_dst attr_unused,
const region_t *reg_paint attr_unused,
const region_t *reg_visible attr_unused) {
const region_t *reg_visible attr_unused, bool lerp attr_unused) {
auto dummy attr_unused = (struct dummy_data *)base;
dummy_check_image(base, image);
assert(mask == NULL || mask == &dummy->mask);

View File

@@ -417,7 +417,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,6 +499,7 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
image_dst.y = root_height - image_dst.y;
image_dst.y -= extent_height;
for (int i = 0; i < nrects; i++) {
// Y-flip. Note after this, crect.y1 > crect.y2
rect_t crect = rects[i];
@@ -550,7 +551,7 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
// TODO(yshui) make use of reg_visible
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused) {
const region_t *reg_visible attr_unused, bool lerp) {
auto gd = (struct gl_data *)base;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
@@ -576,6 +577,14 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask
coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y};
x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height,
gd->height, inner->y_inverted, coord, indices);
if (lerp) {
for (unsigned int i = 2; i < 16; i+=4) {
coord[i+0] = lerp_range(0, mask_offset.x, 0, inner->width, coord[i+0]);
coord[i+1] = lerp_range(0, mask_offset.y, 0, inner->height, coord[i+1]);
}
}
_gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);

View File

@@ -151,7 +151,7 @@ bool gl_last_render_time(backend_t *backend_data, struct timespec *time);
* @brief Render a region with texture data.
*/
void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible);
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp);
void gl_resize(struct gl_data *, int width, int height);

View File

@@ -356,7 +356,7 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
}
static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst,
const region_t *reg_paint, const region_t *reg_visible) {
const region_t *reg_paint, const region_t *reg_visible, bool lerp attr_unused) {
struct _xrender_data *xd = (void *)base;
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
xd->back[2]);

View File

@@ -139,12 +139,15 @@ typedef struct session {
// === Event handlers ===
/// ev_io for X connection
ev_io xiow;
/// Timer for checking DPMS power level
ev_timer dpms_check_timer;
/// Timeout for delayed unredirection.
ev_timer unredir_timer;
/// Timer for fading
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,
@@ -177,7 +180,6 @@ typedef struct session {
bool server_grabbed;
/// Width of root window.
int root_width;
/// Height of root window.
int root_height;
/// X Composite overlay window.
xcb_window_t overlay;
@@ -214,26 +216,19 @@ typedef struct session {
bool first_frame;
/// Whether screen has been turned off
bool screen_is_off;
/// Event context for X Present extension.
uint32_t present_event_id;
xcb_special_event_t *present_event;
/// When last MSC event happened, in useconds.
uint64_t last_msc_instant;
/// The last MSC number
uint64_t last_msc;
/// When the currently rendered frame will be displayed.
/// 0 means there is no pending frame.
uint64_t target_msc;
/// The delay between when the last frame was scheduled to be rendered, and when
/// the render actually started.
uint64_t last_schedule_delay;
/// When do we want our next frame to start rendering.
uint64_t next_render;
/// Did we actually render the last frame. Sometimes redraw will be scheduled only
/// to find out nothing has changed. In which case this will be set to false.
bool did_render;
/// Whether we can perform frame pacing.
bool frame_pacing;
/// Vblank event scheduler
struct vblank_scheduler *vblank_scheduler;
/// Render statistics
struct render_statistics render_stats;
@@ -245,8 +240,18 @@ typedef struct session {
options_t o;
/// Whether we have hit unredirection timeout.
bool tmout_unredir_hit;
/// Whether we need to redraw the screen
bool redraw_needed;
/// If the backend is busy. This means two things:
/// 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
/// time.
bool backend_busy;
/// Whether a render is queued. This generally means there are pending updates
/// to the screen that's neither included in the current render, nor on the
/// screen.
bool render_queued;
/// Cache a xfixes region so we don't need to allocate it every time.
/// A workaround for yshui/picom#301
@@ -263,6 +268,13 @@ typedef struct session {
xcb_render_picture_t *alpha_picts;
/// Time of last fading. In milliseconds.
long long fade_time;
/// Time of last window animation step. In milliseconds.
long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time
/// Head pointer of the error ignore linked list.
pending_reply_t *pending_reply_head;
/// Pointer to the <code>next</code> member of tail element of the error
/// ignore linked list.
pending_reply_t **pending_reply_tail;
// Cached blur convolution kernels.
struct x_convolution_kernel **blur_kerns_cache;
/// If we should quit

View File

@@ -8,6 +8,8 @@
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -615,6 +617,82 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
return ret;
}
struct debug_options_entry {
const char *name;
const char **choices;
size_t offset;
};
// clang-format off
const char *vblank_scheduler_str[] = {
[VBLANK_SCHEDULER_PRESENT] = "present",
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] = "sgi_video_sync",
[LAST_VBLANK_SCHEDULER] = NULL
};
static const struct debug_options_entry debug_options_entries[] = {
{"smart_frame_pacing", NULL, offsetof(struct debug_options, smart_frame_pacing)},
{"force_vblank_sched", vblank_scheduler_str, offsetof(struct debug_options, force_vblank_scheduler)},
};
// clang-format on
void parse_debug_option_single(char *setting, struct debug_options *debug_options) {
char *equal = strchr(setting, '=');
size_t name_len = equal ? (size_t)(equal - setting) : strlen(setting);
for (size_t i = 0; i < ARR_SIZE(debug_options_entries); i++) {
if (strncmp(setting, debug_options_entries[i].name, name_len) != 0) {
continue;
}
if (debug_options_entries[i].name[name_len] != '\0') {
continue;
}
auto value = (int *)((void *)debug_options + debug_options_entries[i].offset);
if (equal) {
const char *const arg = equal + 1;
if (debug_options_entries[i].choices != NULL) {
for (size_t j = 0; debug_options_entries[i].choices[j]; j++) {
if (strcmp(arg, debug_options_entries[i].choices[j]) ==
0) {
*value = (int)j;
return;
}
}
}
if (!parse_int(arg, value)) {
log_error("Invalid value for debug option %s: %s, it "
"will be ignored.",
debug_options_entries[i].name, arg);
}
} else if (debug_options_entries[i].choices == NULL) {
*value = 1;
} else {
log_error(
"Missing value for debug option %s, it will be ignored.", setting);
}
return;
}
log_error("Invalid debug option: %s", setting);
}
/// Parse debug options from environment variable `PICOM_DEBUG`.
void parse_debug_options(struct debug_options *debug_options) {
const char *debug = getenv("PICOM_DEBUG");
const struct debug_options default_debug_options = {
.force_vblank_scheduler = LAST_VBLANK_SCHEDULER,
};
*debug_options = default_debug_options;
if (!debug) {
return;
}
scoped_charp debug_copy = strdup(debug);
char *tmp, *needle = strtok_r(debug_copy, ";", &tmp);
while (needle) {
parse_debug_option_single(needle, debug_options);
needle = strtok_r(NULL, ";", &tmp);
}
}
/**
* Parse a list of window shader rules.
*/
@@ -719,6 +797,10 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
// opacity logic is complicated, and needs an "unset" state
opt->wintype_option[i].opacity = NAN;
}
if (!mask[i].animation) {
mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
}
if (!mask[i].clip_shadow_above) {
mask[i].clip_shadow_above = true;
opt->wintype_option[i].clip_shadow_above = false;
@@ -726,6 +808,40 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en
}
}
enum open_window_animation parse_open_window_animation(const char *src) {
if (strcmp(src, "none") == 0) {
return OPEN_WINDOW_ANIMATION_NONE;
} else if (strcmp(src, "fly-in") == 0) {
return OPEN_WINDOW_ANIMATION_FLYIN;
} else if (strcmp(src, "zoom") == 0) {
return OPEN_WINDOW_ANIMATION_ZOOM;
} else if (strcmp(src, "slide-up") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_UP;
} else if (strcmp(src, "slide-down") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_DOWN;
} else if (strcmp(src, "slide-left") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_LEFT;
} else if (strcmp(src, "slide-right") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT;
} else if (strcmp(src, "slide-out") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT;
} else if (strcmp(src, "slide-in") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN;
} else if (strcmp(src, "slide-out-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER;
} else if (strcmp(src, "slide-in-center") == 0) {
return OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER;
} else if (strcmp(src, "minimize") == 0 || strcmp(src, "maximize") == 0) {
return OPEN_WINDOW_ANIMATION_MINIMIZE;
} else if (strcmp(src, "squeeze") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE;
} else if (strcmp(src, "squeeze-bottom") == 0) {
return OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM;
}
return OPEN_WINDOW_ANIMATION_INVALID;
}
char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
// clang-format off
@@ -771,6 +887,18 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.no_fading_destroyed_argb = false,
.fade_blacklist = NULL,
.animations = false,
.animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_unmap_window = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_next_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_for_prev_tag = OPEN_WINDOW_ANIMATION_NONE,
.animation_stiffness = 200.0,
.animation_stiffness_tag_change = 200.0,
.animation_window_mass = 1.0,
.animation_dampening = 25,
.animation_clamping = true,
.inactive_opacity = 1.0,
.inactive_opacity_override = false,
.active_opacity = 1.0,
@@ -802,7 +930,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.track_leader = false,
.rounded_corners_blacklist = NULL
.rounded_corners_blacklist = NULL,
.animation_blacklist = NULL
};
// clang-format on
@@ -817,5 +946,6 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
(void)hasneg;
(void)winopt_mask;
#endif
parse_debug_options(&opt->debug_options);
return ret;
}

View File

@@ -40,6 +40,24 @@ enum backend {
NUM_BKEND,
};
enum open_window_animation {
OPEN_WINDOW_ANIMATION_NONE = 0,
OPEN_WINDOW_ANIMATION_FLYIN,
OPEN_WINDOW_ANIMATION_SLIDE_UP,
OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
OPEN_WINDOW_ANIMATION_SLIDE_IN,
OPEN_WINDOW_ANIMATION_SLIDE_OUT,
OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER,
OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER,
OPEN_WINDOW_ANIMATION_ZOOM,
OPEN_WINDOW_ANIMATION_MINIMIZE,
OPEN_WINDOW_ANIMATION_SQUEEZE,
OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM,
OPEN_WINDOW_ANIMATION_INVALID,
};
typedef struct win_option_mask {
bool shadow : 1;
bool fade : 1;
@@ -49,6 +67,7 @@ typedef struct win_option_mask {
bool redir_ignore : 1;
bool opacity : 1;
bool clip_shadow_above : 1;
enum open_window_animation animation;
} win_option_mask_t;
typedef struct win_option {
@@ -60,6 +79,7 @@ typedef struct win_option {
bool redir_ignore;
double opacity;
bool clip_shadow_above;
enum open_window_animation animation;
} win_option_t;
enum blur_method {
@@ -73,6 +93,27 @@ enum blur_method {
typedef struct _c2_lptr c2_lptr_t;
enum vblank_scheduler_type {
/// X Present extension based vblank events
VBLANK_SCHEDULER_PRESENT,
/// GLX_SGI_video_sync based vblank events
VBLANK_SCHEDULER_SGI_VIDEO_SYNC,
/// An invalid scheduler, served as a scheduler count, and
/// as a sentinel value.
LAST_VBLANK_SCHEDULER,
};
extern const char *vblank_scheduler_str[];
/// Internal, private options for debugging and development use.
struct debug_options {
/// Try to reduce frame latency by using vblank interval and render time
/// estimates. Right now it's not working well across drivers.
int smart_frame_pacing;
/// Override the vblank scheduler chosen by the compositor.
int force_vblank_scheduler;
};
/// Structure representing all options.
typedef struct options {
// === Debugging ===
@@ -174,6 +215,33 @@ typedef struct options {
/// Fading blacklist. A linked list of conditions.
c2_lptr_t *fade_blacklist;
// === Animations ===
/// Whether to do window animations
bool animations;
/// Which animation to run when opening a window
enum open_window_animation animation_for_open_window;
/// Which animation to run when opening a transient window
enum open_window_animation animation_for_transient_window;
/// Which animation to run when unmapping a window
enum open_window_animation animation_for_unmap_window;
/// Which animation to run when swapping to new tag
enum open_window_animation animation_for_next_tag;
/// Which animation to run for old tag
enum open_window_animation animation_for_prev_tag;
/// Spring stiffness for animation
double animation_stiffness;
/// Spring stiffness for current tag animation
double animation_stiffness_tag_change;
/// Window mass for animation
double animation_window_mass;
/// Animation dampening
double animation_dampening;
/// Whether to clamp animations
bool animation_clamping;
/// Animation blacklist. A linked list of conditions.
c2_lptr_t *animation_blacklist;
/// TODO: open/close animations
// === Opacity ===
/// Default opacity for inactive windows.
/// 32-bit integer with the format of _NET_WM_WINDOW_OPACITY.
@@ -257,11 +325,20 @@ 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;
bool dithered_present;
struct debug_options debug_options;
} options_t;
extern const char *const BACKEND_STRS[NUM_BKEND + 1];
@@ -275,6 +352,7 @@ bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *)
char *must_use locate_auxiliary_file(const char *scope, const char *path,
const char *include_dir);
enum blur_method must_use parse_blur_method(const char *src);
enum open_window_animation must_use parse_open_window_animation(const char *src);
/**
* Add a pattern to a condition linked list.

View File

@@ -262,6 +262,15 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_
o->clip_shadow_above = ival;
mask->clip_shadow_above = true;
}
const char *sval = NULL;
if (config_setting_lookup_string(setting, "animation", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
animation = OPEN_WINDOW_ANIMATION_NONE;
o->animation = animation;
mask->animation = animation;
}
double fval;
if (config_setting_lookup_float(setting, "opacity", &fval)) {
@@ -511,6 +520,70 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above");
// --fade-exclude
parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude");
// --animations
lcfg_lookup_bool(&cfg, "animations", &opt->animations);
// --animation-for-open-window
if (config_lookup_string(&cfg, "animation-for-open-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_open_window = animation;
}
// --animation-for-transient-window
if (config_lookup_string(&cfg, "animation-for-transient-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid open-window animation %s", sval);
goto err;
}
opt->animation_for_transient_window = animation;
}
// --animation-for-unmap-window
if (config_lookup_string(&cfg, "animation-for-unmap-window", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid unmap-window animation %s", sval);
goto err;
}
opt->animation_for_unmap_window = animation;
}
// --animation-for-next-tag
if (config_lookup_string(&cfg, "animation-for-next-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid next-tag animation %s", sval);
goto err;
}
opt->animation_for_next_tag = animation;
}
// --animation-for-prev-tag
if (config_lookup_string(&cfg, "animation-for-prev-tag", &sval)) {
enum open_window_animation animation = parse_open_window_animation(sval);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_fatal("Invalid prev-tag animation %s", sval);
goto err;
}
opt->animation_for_prev_tag = animation;
}
// --animations-exclude
parse_cfg_condlst(&cfg, &opt->animation_blacklist, "animation-exclude");
// --animation-stiffness
config_lookup_float(&cfg, "animation-stiffness-in-tag", &opt->animation_stiffness);
// --animation-stiffness-tag-change
config_lookup_float(&cfg, "animation-stiffness-tag-change", &opt->animation_stiffness_tag_change);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-next-tag", &opt->enable_fading_next_tag);
// --enable-fading-next-tag
lcfg_lookup_bool(&cfg, "enable-fading-prev-tag", &opt->enable_fading_prev_tag);
// --animation-window-mass
config_lookup_float(&cfg, "animation-window-mass", &opt->animation_window_mass);
// --animation-dampening
config_lookup_float(&cfg, "animation-dampening", &opt->animation_dampening);
// --animation-clamping
lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
// --focus-exclude
parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude");
// --invert-color-include

View File

@@ -716,7 +716,8 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
// XXX redraw needs to be more fine grained
queue_redraw(ps);
// the events sent from SendEvent will be ignored
// We intentionally ignore events sent via SendEvent. Those events has the 8th bit
// of response_type set, meaning they will match none of the cases below.
switch (ev->response_type) {
case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break;
case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break;

View File

@@ -9,7 +9,8 @@ base_deps = [
srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c') ]
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c',
'vblank.c') ]
picom_inc = include_directories('.')
cflags = []
@@ -58,7 +59,7 @@ endif
if get_option('opengl')
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
deps += [dependency('gl', required: true), dependency('egl', required: true)]
deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)]
srcs += [ 'opengl.c' ]
endif

View File

@@ -184,6 +184,15 @@ static const struct picom_option picom_options[] = {
"you want to attach a debugger to picom"},
{"no-ewmh-fullscreen" , no_argument , 803, NULL , "Do not use EWMH to detect fullscreen windows. Reverts to checking if a "
"window is fullscreen based only on its size and coordinates."},
{"animations" ,no_argument , 804, NULL , "Enable/disable animations."},
{"animation-stiffness-in-tag" , required_argument, 805, NULL , "Animation speed in current tag (float)."},
{"animation-stiffness-tag-change", required_argument, 806, NULL , "Animation speed when tag changes (change to a new desktop)."},
{"animation-dampening" , required_argument, 807, NULL , "Animation dampening ratio (spring dampening as an example)."},
{"animation-window-mass" , required_argument, 808, NULL , "Animation window mass (lower mass makes animations bumpy)."},
{"animation-clamping" , no_argument , 809, NULL , "Enable/disable animation clamping. Disabling increases performance"},
{"animation-for-open-window" , required_argument, 810, NULL , "Set animation for opening window (Check sample.conf for options)."},
{"animation-for-transient-window", required_argument, 811, NULL , "Set animation for transient (child) windows."},
};
// clang-format on
@@ -750,6 +759,52 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
break;
P_CASEBOOL(802, debug_mode);
P_CASEBOOL(803, no_ewmh_fullscreen);
P_CASEBOOL(804, animations);
case 805:
// --animation-stiffness
opt->animation_stiffness = atof(optarg);
break;
case 806:
// --animation-stiffness-for-tags
opt->animation_stiffness_tag_change = atof(optarg);
break;
case 807:
// --animation-dampening
opt->animation_dampening = atof(optarg);
break;
case 808:
// --animation-window-masss
opt->animation_window_mass = atof(optarg);
break;
case 809:
// --animation-clamping
opt->animation_clamping = true;
break;
case 810: {
// --animation-for-open-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid open-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_open_window = animation;
}
break;
}
case 811: {
// --animation-for-transient-window
enum open_window_animation animation = parse_open_window_animation(optarg);
if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
log_warn("Invalid transient-window animation %s, ignoring.", optarg);
} else {
opt->animation_for_transient_window = animation;
}
break;
}
case 812: {
// --animation-exclude
condlst_add(&opt->animation_blacklist, optarg);
break;
}
default: usage(argv[0], 1); break;
#undef P_CASEBOOL
}

File diff suppressed because it is too large Load Diff

View File

@@ -55,26 +55,15 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int
/// A `divisor` is also returned, indicating the target framerate. The divisor is
/// the number of vblanks we should wait between each frame. A divisor of 1 means
/// full framerate, 2 means half framerate, etc.
unsigned int
render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor) {
unsigned int render_statistics_get_budget(struct render_statistics *rs) {
if (rs->render_times.nelem < rs->render_times.window_size) {
// No valid render time estimates yet. Assume maximum budget.
*divisor = 1;
return UINT_MAX;
}
// N-th percentile of render times, see render_statistics_init for N.
auto render_time_percentile =
rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times);
auto vblank_time_us = render_statistics_get_vblank_time(rs);
if (vblank_time_us == 0) {
// We don't have a good estimate of the vblank time yet, so we
// assume we can finish in one vblank.
*divisor = 1;
} else {
*divisor =
(unsigned int)(render_time_percentile / rs->vblank_time_us.mean + 1);
}
return (unsigned int)render_time_percentile;
}

View File

@@ -23,11 +23,8 @@ void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int
void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us);
/// How much time budget we should give to the backend for rendering, in microseconds.
///
/// A `divisor` is also returned, indicating the target framerate. The divisor is
/// the number of vblanks we should wait between each frame. A divisor of 1 means
/// full framerate, 2 means half framerate, etc.
unsigned int
render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor);
unsigned int render_statistics_get_budget(struct render_statistics *rs);
/// Return the measured vblank interval in microseconds. Returns 0 if not enough
/// samples have been collected yet.
unsigned int render_statistics_get_vblank_time(struct render_statistics *rs);

View File

@@ -21,6 +21,7 @@
#include "types.h"
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define CLEAR_MASK(x) x = 0;
#ifdef __FAST_MATH__
#warning Use of -ffast-math can cause rendering error or artifacts, \
@@ -125,14 +126,35 @@ safe_isnan(double a) {
* @param max maximum value
* @return normalized value
*/
static inline int attr_const normalize_i_range(int i, int min, int max) {
if (i > max)
static inline int attr_const attr_unused normalize_i_range(int i, int min, int max) {
if (i > max) {
return max;
if (i < min)
}
if (i < min) {
return min;
}
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) \
({ \
__auto_type __tmp = (val); \
__tmp > 0 ? __tmp : -__tmp; \
})
#define min2(a, b) ((a) > (b) ? (b) : (a))
#define max2(a, b) ((a) > (b) ? (a) : (b))
#define min3(a, b, c) min2(a, min2(b, c))
@@ -149,10 +171,12 @@ static inline int attr_const normalize_i_range(int i, int min, int max) {
* @return normalized value
*/
static inline double attr_const normalize_d_range(double d, double min, double max) {
if (d > max)
if (d > max) {
return max;
if (d < min)
}
if (d < min) {
return min;
}
return d;
}
@@ -162,7 +186,7 @@ static inline double attr_const normalize_d_range(double d, double min, double m
* @param d double value to normalize
* @return normalized value
*/
static inline double attr_const normalize_d(double d) {
static inline double attr_const attr_unused normalize_d(double d) {
return normalize_d_range(d, 0.0, 1.0);
}

530
src/vblank.c Normal file
View File

@@ -0,0 +1,530 @@
#include <assert.h>
#include <ev.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <string.h>
#include <time.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
#include "config.h"
#ifdef CONFIG_OPENGL
// Enable sgi_video_sync_vblank_scheduler
#include <GL/glx.h>
#include <X11/X.h>
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <pthread.h>
#include "backend/gl/glx.h"
#endif
#include "compiler.h"
#include "list.h" // for container_of
#include "log.h"
#include "vblank.h"
#include "x.h"
struct vblank_callback {
vblank_callback_t fn;
void *user_data;
};
#define VBLANK_WIND_DOWN 4
struct vblank_scheduler {
struct x_connection *c;
size_t callback_capacity, callback_count;
struct vblank_callback *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,
/// we might send PresentNotifyMsc request too late and miss the vblank event.
/// So we request extra vblank events right after the last vblank event
/// to make sure this doesn't happen.
unsigned int wind_down;
xcb_window_t target_window;
enum vblank_scheduler_type type;
bool vblank_event_requested;
};
struct present_vblank_scheduler {
struct vblank_scheduler base;
uint64_t last_msc;
/// The timestamp for the end of last vblank.
uint64_t last_ust;
ev_timer callback_timer;
xcb_present_event_t event_id;
xcb_special_event_t *event;
};
struct vblank_scheduler_ops {
size_t size;
void (*init)(struct vblank_scheduler *self);
void (*deinit)(struct vblank_scheduler *self);
void (*schedule)(struct vblank_scheduler *self);
bool (*handle_x_events)(struct vblank_scheduler *self);
};
static void
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event);
#ifdef CONFIG_OPENGL
struct sgi_video_sync_vblank_scheduler {
struct vblank_scheduler base;
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
// ... and all the thread shenanigans that come with it.
_Atomic unsigned int last_msc;
_Atomic uint64_t last_ust;
ev_async notify;
pthread_t sync_thread;
bool running, error;
/// Protects `running`, `error` and `base.vblank_event_requested`
pthread_mutex_t vblank_requested_mtx;
pthread_cond_t vblank_requested_cnd;
};
struct sgi_video_sync_thread_args {
struct sgi_video_sync_vblank_scheduler *self;
int start_status;
pthread_mutex_t start_mtx;
pthread_cond_t start_cnd;
};
static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
const char *glx_ext = glXQueryExtensionsString(dpy, screen);
const char *needle = "GLX_SGI_video_sync";
char *found = strstr(glx_ext, needle);
if (!found) {
return false;
}
if (found != glx_ext && found[-1] != ' ') {
return false;
}
if (found[strlen(needle)] != ' ' && found[strlen(needle)] != '\0') {
return false;
}
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
(const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXWaitVideoSyncSGI) {
return false;
}
return true;
}
static void *sgi_video_sync_thread(void *data) {
auto args = (struct sgi_video_sync_thread_args *)data;
auto self = args->self;
Display *dpy = XOpenDisplay(NULL);
int error_code = 0;
if (!dpy) {
error_code = 1;
goto start_failed;
}
Window root = DefaultRootWindow(dpy), dummy = None;
int screen = DefaultScreen(dpy);
int ncfg = 0;
GLXFBConfig *cfg_ = glXChooseFBConfig(
dpy, screen,
(int[]){GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 0},
&ncfg);
GLXContext ctx = NULL;
GLXDrawable drawable = None;
if (!cfg_) {
error_code = 2;
goto start_failed;
}
GLXFBConfig cfg = cfg_[0];
XFree(cfg_);
XVisualInfo *vi = glXGetVisualFromFBConfig(dpy, cfg);
if (!vi) {
error_code = 3;
goto start_failed;
}
Visual *visual = vi->visual;
const int depth = vi->depth;
XFree(vi);
Colormap colormap = XCreateColormap(dpy, root, visual, AllocNone);
XSetWindowAttributes attributes;
attributes.colormap = colormap;
dummy = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual,
CWColormap, &attributes);
XFreeColormap(dpy, colormap);
if (dummy == None) {
error_code = 4;
goto start_failed;
}
drawable = glXCreateWindow(dpy, cfg, dummy, NULL);
if (drawable == None) {
error_code = 5;
goto start_failed;
}
ctx = glXCreateNewContext(dpy, cfg, GLX_RGBA_TYPE, 0, true);
if (ctx == NULL) {
error_code = 6;
goto start_failed;
}
if (!glXMakeContextCurrent(dpy, drawable, drawable, ctx)) {
error_code = 7;
goto start_failed;
}
if (!check_sgi_video_sync_extension(dpy, screen)) {
error_code = 8;
goto start_failed;
}
pthread_mutex_lock(&args->start_mtx);
args->start_status = 0;
pthread_cond_signal(&args->start_cnd);
pthread_mutex_unlock(&args->start_mtx);
pthread_mutex_lock(&self->vblank_requested_mtx);
while (self->running) {
if (!self->base.vblank_event_requested) {
pthread_cond_wait(&self->vblank_requested_cnd,
&self->vblank_requested_mtx);
continue;
}
pthread_mutex_unlock(&self->vblank_requested_mtx);
unsigned int last_msc;
glXWaitVideoSyncSGI(1, 0, &last_msc);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
atomic_store(&self->last_msc, last_msc);
atomic_store(&self->last_ust,
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
ev_async_send(self->base.loop, &self->notify);
pthread_mutex_lock(&self->vblank_requested_mtx);
}
pthread_mutex_unlock(&self->vblank_requested_mtx);
goto cleanup;
start_failed:
pthread_mutex_lock(&args->start_mtx);
args->start_status = error_code;
pthread_cond_signal(&args->start_cnd);
pthread_mutex_unlock(&args->start_mtx);
cleanup:
if (dpy) {
glXMakeCurrent(dpy, None, NULL);
if (ctx) {
glXDestroyContext(dpy, ctx);
}
if (drawable) {
glXDestroyWindow(dpy, drawable);
}
if (dummy) {
XDestroyWindow(dpy, dummy);
}
XCloseDisplay(dpy);
}
return NULL;
}
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
log_verbose("Requesting vblank event for msc %d", self->last_msc + 1);
pthread_mutex_lock(&self->vblank_requested_mtx);
assert(!base->vblank_event_requested);
base->vblank_event_requested = true;
pthread_cond_signal(&self->vblank_requested_cnd);
pthread_mutex_unlock(&self->vblank_requested_mtx);
}
static void
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
auto event = (struct vblank_event){
.msc = atomic_load(&sched->last_msc),
.ust = atomic_load(&sched->last_ust),
};
sched->base.vblank_event_requested = false;
log_verbose("Received vblank event for msc %lu", event.msc);
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
auto args = (struct sgi_video_sync_thread_args){
.self = self,
.start_status = -1,
};
pthread_mutex_init(&args.start_mtx, NULL);
pthread_cond_init(&args.start_cnd, NULL);
base->type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
ev_async_init(&self->notify, sgi_video_sync_scheduler_callback);
ev_async_start(base->loop, &self->notify);
pthread_mutex_init(&self->vblank_requested_mtx, NULL);
pthread_cond_init(&self->vblank_requested_cnd, NULL);
self->running = true;
pthread_create(&self->sync_thread, NULL, sgi_video_sync_thread, &args);
pthread_mutex_lock(&args.start_mtx);
while (args.start_status == -1) {
pthread_cond_wait(&args.start_cnd, &args.start_mtx);
}
if (args.start_status != 0) {
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
args.start_status);
abort();
}
pthread_mutex_destroy(&args.start_mtx);
pthread_cond_destroy(&args.start_cnd);
log_info("Started sgi_video_sync_thread");
}
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
ev_async_stop(base->loop, &self->notify);
pthread_mutex_lock(&self->vblank_requested_mtx);
self->running = false;
pthread_cond_signal(&self->vblank_requested_cnd);
pthread_mutex_unlock(&self->vblank_requested_mtx);
pthread_join(self->sync_thread, NULL);
pthread_mutex_destroy(&self->vblank_requested_mtx);
pthread_cond_destroy(&self->vblank_requested_cnd);
}
#endif
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
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;
}
static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) {
auto sched = container_of(w, struct present_vblank_scheduler, callback_timer);
auto event = (struct vblank_event){
.msc = sched->last_msc,
.ust = sched->last_ust,
};
sched->base.vblank_event_requested = false;
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
static void 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);
self->event_id = x_new_id(base->c);
auto select_input =
xcb_present_select_input(base->c->c, self->event_id, base->target_window,
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
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);
}
static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
ev_timer_stop(base->loop, &self->callback_timer);
auto select_input =
xcb_present_select_input(base->c->c, self->event_id, base->target_window, 0);
set_cant_fail_cookie(base->c, select_input);
xcb_unregister_for_special_event(base->c->c, self->event);
}
/// Handle PresentCompleteNotify events
///
/// Schedule the registered callback to be called when the current vblank ends.
static void handle_present_complete_notify(struct present_vblank_scheduler *self,
xcb_present_complete_notify_event_t *cne) {
assert(self->base.type == VBLANK_SCHEDULER_PRESENT);
if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) {
return;
}
assert(self->base.vblank_event_requested);
// X sometimes sends duplicate/bogus MSC events, when screen has just been turned
// off. Don't use the msc value in these events. We treat this as not receiving a
// vblank event at all, and try to get a new one.
//
// See:
// https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418
bool event_is_invalid = cne->msc <= self->last_msc || cne->ust == 0;
if (event_is_invalid) {
log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64,
cne->msc, cne->ust);
x_request_vblank_event(self->base.c, cne->window, self->last_msc + 1);
return;
}
self->last_ust = cne->ust;
self->last_msc = cne->msc;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
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 "
"future",
cne->ust - now_us);
delay_sec = (double)(cne->ust - now_us) / 1000000.0;
}
// Wait until the end of the current vblank to invoke callbacks. If we
// call it too early, it can mistakenly think the render missed the
// vblank, and doesn't schedule render for the next vblank, causing frame
// drops.
assert(!ev_is_active(&self->callback_timer));
ev_timer_set(&self->callback_timer, delay_sec, 0);
ev_timer_start(self->base.loop, &self->callback_timer);
}
static bool handle_present_events(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
xcb_present_generic_event_t *ev;
while ((ev = (void *)xcb_poll_for_special_event(base->c->c, self->event))) {
if (ev->event != self->event_id) {
// This event doesn't have the right event context, it's not meant
// for us.
goto next;
}
// We only subscribed to the complete notify event.
assert(ev->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY);
handle_present_complete_notify(self, (void *)ev);
next:
free(ev);
}
return true;
}
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
[VBLANK_SCHEDULER_PRESENT] =
{
.size = sizeof(struct present_vblank_scheduler),
.init = present_vblank_scheduler_init,
.deinit = present_vblank_scheduler_deinit,
.schedule = present_vblank_scheduler_schedule,
.handle_x_events = handle_present_events,
},
#ifdef CONFIG_OPENGL
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] =
{
.size = sizeof(struct sgi_video_sync_vblank_scheduler),
.init = sgi_video_sync_scheduler_init,
.deinit = sgi_video_sync_scheduler_deinit,
.schedule = sgi_video_sync_scheduler_schedule,
.handle_x_events = NULL,
},
#endif
};
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
assert(self->type < LAST_VBLANK_SCHEDULER);
auto fn = vblank_scheduler_ops[self->type].schedule;
assert(fn != NULL);
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 (self->callback_count == self->callback_capacity) {
size_t new_capacity =
self->callback_capacity ? self->callback_capacity * 2 : 1;
void *new_buffer =
realloc(self->callbacks, new_capacity * sizeof(*self->callbacks));
if (!new_buffer) {
return false;
}
self->callbacks = new_buffer;
self->callback_capacity = new_capacity;
}
self->callbacks[self->callback_count++] = (struct vblank_callback){
.fn = vblank_callback,
.user_data = user_data,
};
return true;
}
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;
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);
}
// 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;
if (self->callback_count || self->wind_down) {
vblank_scheduler_schedule_internal(self);
}
}
void vblank_scheduler_free(struct vblank_scheduler *self) {
assert(self->type < LAST_VBLANK_SCHEDULER);
auto fn = vblank_scheduler_ops[self->type].deinit;
if (fn != NULL) {
fn(self);
}
free(self->callbacks);
free(self);
}
struct vblank_scheduler *
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
xcb_window_t target_window, enum vblank_scheduler_type type) {
size_t object_size = vblank_scheduler_ops[type].size;
auto init_fn = vblank_scheduler_ops[type].init;
if (!object_size || !init_fn) {
log_error("Unsupported or invalid vblank scheduler type: %d", type);
return NULL;
}
assert(object_size >= sizeof(struct vblank_scheduler));
struct vblank_scheduler *self = calloc(1, object_size);
self->target_window = target_window;
self->c = c;
self->loop = loop;
init_fn(self);
return self;
}
bool vblank_handle_x_events(struct vblank_scheduler *self) {
assert(self->type < LAST_VBLANK_SCHEDULER);
auto fn = vblank_scheduler_ops[self->type].handle_x_events;
if (fn != NULL) {
return fn(self);
}
return true;
}

39
src/vblank.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <xcb/present.h>
#include <xcb/xcb.h>
#include <ev.h>
#include <xcb/xproto.h>
#include "config.h"
#include "x.h"
/// An object that schedule vblank events.
struct vblank_scheduler;
struct vblank_event {
uint64_t msc;
uint64_t ust;
};
typedef void (*vblank_callback_t)(struct vblank_event *event, void *user_data);
/// Schedule a vblank event.
///
/// Schedule for `cb` to be called when the current vblank ends. If this is called
/// from a callback function for the current vblank, the newly scheduled callback
/// will be called in the next vblank.
///
/// Returns whether the scheduling is successful. Scheduling can fail if there
/// is not enough memory.
bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb,
void *user_data);
struct vblank_scheduler *
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
xcb_window_t target_window, enum vblank_scheduler_type type);
void vblank_scheduler_free(struct vblank_scheduler *);
bool vblank_handle_x_events(struct vblank_scheduler *self);

343
src/win.c
View File

@@ -302,6 +302,13 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) {
w->flags |= WIN_FLAGS_PIXMAP_NONE;
}
}
static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) {
log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name);
if (w->old_win_image) {
base->ops->release_image(base, w->old_win_image);
w->old_win_image = NULL;
}
}
static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name);
assert(w->shadow_image);
@@ -398,6 +405,7 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
win_release_pixmap(backend, w);
win_release_oldpixmap(backend, w);
}
if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) {
@@ -465,6 +473,172 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_clear_all_properties_stale(w);
}
static void init_animation(session_t *ps, struct managed_win *w) {
CLEAR_MASK(w->animation_is_tag)
static int32_t randr_mon_center_x, randr_mon_center_y;
if (w->randr_monitor != -1) {
auto e = pixman_region32_extents(&ps->monitors.regions[w->randr_monitor]);
randr_mon_center_x = (e->x2 + e->x1) / 2, randr_mon_center_y = (e->y2 + e->y1) / 2;
} else {
randr_mon_center_x = ps->root_width / 2, randr_mon_center_y = ps->root_height / 2;
}
static double *anim_x, *anim_y, *anim_w, *anim_h;
enum open_window_animation animation;
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
&& !w->dwm_mask) {
animation = ps->o.wintype_option[w->window_type].animation;
}
else
animation = ps->o.animation_for_open_window;
if (w->window_type != WINTYPE_TOOLTIP &&
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
animation = ps->o.animation_for_transient_window;
}
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
anim_w = &w->animation_w, anim_h = &w->animation_h;
if (w->dwm_mask & ANIM_PREV_TAG) {
animation = ps->o.animation_for_prev_tag;
if (ps->o.enable_fading_prev_tag) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->state = WSTATE_FADING;
w->animation_is_tag |= ANIM_FADE;
}
if (ps->o.animation_for_prev_tag >= OPEN_WINDOW_ANIMATION_ZOOM) {
w->animation_is_tag |= ANIM_FAST;
w->dwm_mask |= ANIM_SPECIAL_MINIMIZE;
goto revert;
}
w->animation_is_tag |= ANIM_SLOW;
} else if (w->dwm_mask & ANIM_NEXT_TAG) {
animation = ps->o.animation_for_next_tag;
w->animation_is_tag |= animation >= OPEN_WINDOW_ANIMATION_ZOOM ? ANIM_FAST : ANIM_SLOW;
if (ps->o.enable_fading_next_tag) {
w->opacity = 0.0;
w->state = WSTATE_FADING;
}
} else if (w->dwm_mask & ANIM_UNMAP) {
animation = ps->o.animation_for_unmap_window;
revert:
anim_x = &w->animation_dest_center_x, anim_y = &w->animation_dest_center_y;
anim_w = &w->animation_dest_w, anim_h = &w->animation_dest_h;
}
double angle;
switch (animation) {
case OPEN_WINDOW_ANIMATION_NONE: // No animation
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5;
w->animation_w = w->pending_g.width;
w->animation_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_FLYIN: // Fly-in from a random point outside the screen
// Compute random point off screen
angle = 2 * M_PI * ((double)rand() / RAND_MAX);
const double radius =
sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height);
// Set animation
*anim_x = randr_mon_center_x + radius * cos(angle);
*anim_y = randr_mon_center_y + radius * sin(angle);
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_UP: // Slide up the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: // Slide down the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y;
*anim_w = w->pending_g.width;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: // Slide left the image, without changing its location
*anim_x = w->pending_g.x + w->pending_g.width;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: // Slide right the image, without changing its location
*anim_x = w->pending_g.x;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = 0;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_IN:
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER:
*anim_x = randr_mon_center_x;
*anim_y = w->g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_OUT:
w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER:
w->animation_dest_center_x = randr_mon_center_x;
w->animation_dest_center_y = w->pending_g.y;
w->animation_dest_w = w->pending_g.width;
w->animation_dest_h = w->pending_g.height;
break;
case OPEN_WINDOW_ANIMATION_ZOOM: // Zoom-in the image, without changing its location
if (w->dwm_mask & ANIM_SPECIAL_MINIMIZE) {
*anim_x = w->g.x + w->g.width * 0.5;
*anim_y = w->g.y + w->g.height * 0.5;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
}
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_MINIMIZE:
*anim_x = randr_mon_center_x;
*anim_y = randr_mon_center_y;
*anim_w = 0;
*anim_h = 0;
break;
case OPEN_WINDOW_ANIMATION_SQUEEZE:
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_h = 0;
} else {
*anim_x = w->pending_g.x + w->pending_g.width * 0.5;
*anim_y = w->pending_g.y + w->pending_g.height * 0.5;
*anim_w = w->pending_g.width;
*anim_h = 0;
}
break;
case OPEN_WINDOW_ANIMATION_SQUEEZE_BOTTOM:
if (w->dwm_mask & ANIM_PREV_TAG) {
*anim_y = w->g.y + w->g.height;
*anim_h = 0;
} else {
w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5;
w->animation_center_y = w->pending_g.y + w->pending_g.height;
w->animation_w = w->pending_g.width;
*anim_h = 0;
*anim_y = w->pending_g.y + w->pending_g.height;
}
break;
case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break;
}
}
/// Handle non-image flags. This phase might set IMAGES_STALE flags
void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Whether the window was visible before we process the mapped flag. i.e.
@@ -505,8 +679,79 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
add_damage_from_win(ps, w);
}
// Update window geometry
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;
}
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
win_on_win_size_change(ps, w);
@@ -818,6 +1063,10 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) {
return 0;
}
if ((w->state == WSTATE_FADING && (w->animation_is_tag & ANIM_FADE))) {
return 0;
}
// Try obeying opacity property and window type opacity firstly
if (w->has_opacity_prop) {
opacity = ((double)w->opacity_prop) / OPAQUE;
@@ -882,6 +1131,24 @@ bool win_should_fade(session_t *ps, const struct managed_win *w) {
return ps->o.wintype_option[w->window_type].fade;
}
/**
* Determine if a window should animate.
*/
bool win_should_animate(session_t *ps, const struct managed_win *w) {
if (!ps->o.animations) {
return false;
}
if (ps->o.wintype_option[w->window_type].animation == 0) {
log_debug("Animation disabled by window_type");
return false;
}
if (c2_match(ps, w, ps->o.animation_blacklist, NULL)) {
log_debug("Animation disabled by animation_exclude");
return false;
}
return true;
}
/**
* Reread _COMPTON_SHADOW property from a window.
*
@@ -1526,14 +1793,19 @@ struct win *fill_win(session_t *ps, struct win *w) {
.blur_background = false,
.reg_ignore = NULL,
// The following ones are updated for other reasons
.pixmap_damaged = false, // updated by damage events
.state = WSTATE_UNMAPPED, // updated by window state changes
.in_openclose = true, // set to false after first map is done,
// true here because window is just created
.reg_ignore_valid = false, // set to true when damaged
.flags = WIN_FLAGS_IMAGES_NONE, // updated by
// property/attributes/etc
// change
.pixmap_damaged = false, // updated by damage events
.state = WSTATE_UNMAPPED, // updated by window state changes
.in_openclose = true, // set to false after first map is done,
// true here because window is just created
.animation_velocity_x = 0.0, // updated by window geometry changes
.animation_velocity_y = 0.0, // updated by window geometry changes
.animation_velocity_w = 0.0, // updated by window geometry changes
.animation_velocity_h = 0.0, // updated by window geometry changes
.animation_progress = 1.0, // updated by window geometry changes
.animation_inv_og_distance = NAN, // updated by window geometry changes
.reg_ignore_valid = false, // set to true when damaged
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
// change
.stale_props = NULL,
.stale_props_capacity = 0,
@@ -1559,6 +1831,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
// have no meaning or have no use until the window
// is mapped
.win_image = NULL,
.old_win_image = NULL,
.shadow_image = NULL,
.mask_image = NULL,
.prev_trans = NULL,
@@ -2121,17 +2394,31 @@ static void unmap_win_finish(session_t *ps, struct managed_win *w) {
// Shadow image can be preserved.
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
win_release_pixmap(ps->backend_data, w);
win_release_oldpixmap(ps->backend_data, w);
}
} else {
assert(!w->win_image);
assert(!w->old_win_image);
assert(!w->shadow_image);
}
// Force animation to completed position
w->animation_velocity_x = 0;
w->animation_velocity_y = 0;
w->animation_velocity_w = 0;
w->animation_velocity_h = 0;
w->animation_progress = 1.0;
free_paint(ps, &w->paint);
free_paint(ps, &w->shadow_paint);
// Try again at binding images when the window is mapped next time
win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR);
// Flag window so that it gets animated when it reapears
// in case it wasn't destroyed
win_set_flags(w, WIN_FLAGS_POSITION_STALE);
win_set_flags(w, WIN_FLAGS_SIZE_STALE);
}
/// Finish the destruction of a window (e.g. after fading has finished).
@@ -2400,6 +2687,30 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->opacity_target = win_calc_opacity_target(ps, w);
if (ps->o.animations && ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && ps->o.wintype_option[w->window_type].animation) {
w->dwm_mask = ANIM_UNMAP;
init_animation(ps, w);
double x_dist = w->animation_dest_center_x - w->animation_center_x;
double y_dist = w->animation_dest_center_y - w->animation_center_y;
double w_dist = w->animation_dest_w - w->animation_w;
double h_dist = w->animation_dest_h - w->animation_h;
w->animation_inv_og_distance =
1.0 / sqrt(x_dist * x_dist + y_dist * y_dist +
w_dist * w_dist + h_dist * h_dist);
if (isinf(w->animation_inv_og_distance))
w->animation_inv_og_distance = 0;
w->animation_progress = 0.0;
if (w->old_win_image) {
ps->backend_data->ops->release_image(ps->backend_data,
w->old_win_image);
w->old_win_image = NULL;
}
}
#ifdef CONFIG_DBUS
// Send D-Bus signal
if (ps->o.dbus) {
@@ -2445,7 +2756,7 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
///
/// @return whether the window is destroyed and freed
bool win_skip_fading(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
if ((w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED)) {
assert(w->opacity_target == w->opacity);
return false;
}
@@ -2457,11 +2768,12 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) {
// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to
// the x.c.
void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
mw->randr_monitor = -1;
for (int i = 0; i < monitors->count; i++) {
auto e = pixman_region32_extents(&monitors->regions[i]);
if (e->x1 <= mw->g.x && e->y1 <= mw->g.y &&
e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) {
if (((e->x1 <= mw->g.x || e->x1 <= mw->pending_g.x) &&
(e->x2 >= mw->g.x + mw->widthb || e->x2 >= mw->pending_g.x + mw->widthb)) &&
(e->y1 <= mw->g.y || e->y1 <= mw->pending_g.y) &&
(e->y2 >= mw->g.y + mw->heightb || e->y2 >= mw->pending_g.y + mw->heightb)) {
mw->randr_monitor = i;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the "
"monitor %d (%dx%d+%dx%d)",
@@ -2470,6 +2782,7 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
return;
}
}
mw->randr_monitor = -1;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor",
mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb);
}
@@ -2869,5 +3182,5 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *w) {
/// Return whether this window is mapped on the X server side
bool win_is_mapped_in_x(const struct managed_win *w) {
return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING ||
w->state == WSTATE_MAPPED || (w->flags & WIN_FLAGS_MAPPED);
w->state == WSTATE_MAPPED || w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED);
}

View File

@@ -96,11 +96,25 @@ struct win_geometry {
uint16_t border_width;
};
enum {
// dwm_mask
ANIM_PREV_TAG = 1,
ANIM_NEXT_TAG = (1 << 1),
ANIM_UNMAP = (1 << 2),
ANIM_SPECIAL_MINIMIZE = (1 << 3),
// animation_is_tag
ANIM_IN_TAG = 1,
ANIM_SLOW = (1 << 1),
ANIM_FAST = (1 << 2),
ANIM_FADE = (1 << 3),
};
struct managed_win {
struct win base;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
void *win_image;
void *old_win_image; // Old window image for interpolating window contents during animations
void *shadow_image;
void *mask_image;
/// Pointer to the next higher window to paint.
@@ -152,6 +166,8 @@ struct managed_win {
/// opacity state, window geometry, window mapped/unmapped state,
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
/// NULL means reg_ignore has not been calculated for this window.
/// 1 = tag prev , 2 = tag next, 4 = unmap
uint32_t dwm_mask;
rc_region_t *reg_ignore;
/// Whether the reg_ignore of all windows beneath this window are valid
bool reg_ignore_valid;
@@ -169,6 +185,24 @@ struct managed_win {
bool unredir_if_possible_excluded;
/// Whether this window is in open/close state.
bool in_openclose;
/// Current position and destination, for animation
double animation_center_x, animation_center_y;
double animation_dest_center_x, animation_dest_center_y;
double animation_w, animation_h;
double animation_dest_w, animation_dest_h;
/// Spring animation velocity
double animation_velocity_x, animation_velocity_y;
double animation_velocity_w, animation_velocity_h;
/// Track animation progress; goes from 0 to 1
double animation_progress;
/// Inverse of the window distance at the start of animation, for
/// tracking animation progress
double animation_inv_og_distance;
/// Animation info if it is a tag change & check if its changing window sizes
/// 0: no tag change
/// 1: normal tag change animation
/// 2: tag change animation that effects window size
uint16_t animation_is_tag;
// Client window related members
/// ID of the top-level client window of the window.
@@ -459,6 +493,9 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags);
/// Mark properties as stale for a window
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
/// Determine if a window should animate
bool attr_pure win_should_animate(session_t *ps, const struct managed_win *w);
static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}

27
src/x.c
View File

@@ -9,7 +9,9 @@
#include <pixman.h>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/dpms.h>
#include <xcb/glx.h>
#include <xcb/present.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/sync.h>
@@ -777,6 +779,31 @@ err:
return false;
}
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc) {
auto cookie = xcb_present_notify_msc(c->c, window, 0, msc, 1, 0);
set_cant_fail_cookie(c, cookie);
}
static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) {
// state is a bool indicating whether dpms is enabled
return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON);
}
bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off) {
auto r = xcb_dpms_info_reply(c->c, xcb_dpms_info(c->c), NULL);
if (!r) {
log_error("Failed to query DPMS status.");
return false;
}
auto now_screen_is_off = dpms_screen_is_off(r);
if (*screen_is_off != now_screen_is_off) {
log_debug("Screen is now %s", now_screen_is_off ? "off" : "on");
*screen_is_off = now_screen_is_off;
}
free(r);
return true;
}
/**
* Convert a struct conv to a X picture convolution filter, normalizing the kernel
* in the process. Allow the caller to specify the element at the center of the kernel,

View File

@@ -419,3 +419,11 @@ void x_update_monitors(struct x_connection *, struct x_monitors *);
void x_free_monitor_info(struct x_monitors *);
uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
/// Ask X server to send us a notification for the next end of vblank.
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc);
/// Update screen_is_off to reflect the current DPMS state.
///
/// Returns true if the DPMS state was successfully queried, false otherwise.
bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off);