Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Compositor.h"
8#include "Animation.h"
9#include "ConnectionFromClient.h"
10#include "Event.h"
11#include "EventLoop.h"
12#include "MultiScaleBitmaps.h"
13#include "Screen.h"
14#include "Window.h"
15#include "WindowManager.h"
16#include "WindowSwitcher.h"
17#include <AK/Debug.h>
18#include <AK/Memory.h>
19#include <AK/ScopeGuard.h>
20#include <LibCore/Timer.h>
21#include <LibGfx/AntiAliasingPainter.h>
22#include <LibGfx/Font/Font.h>
23#include <LibGfx/Painter.h>
24#include <LibGfx/StylePainter.h>
25#include <LibThreading/BackgroundAction.h>
26
27namespace WindowServer {
28
29Compositor& Compositor::the()
30{
31 static Compositor s_the;
32 return s_the;
33}
34
35static WallpaperMode mode_to_enum(DeprecatedString const& name)
36{
37 if (name == "Tile")
38 return WallpaperMode::Tile;
39 if (name == "Stretch")
40 return WallpaperMode::Stretch;
41 if (name == "Center")
42 return WallpaperMode::Center;
43 return WallpaperMode::Center;
44}
45
46Compositor::Compositor()
47{
48 m_display_link_notify_timer = add<Core::Timer>(
49 1000 / 60, [this] {
50 notify_display_links();
51 });
52
53 m_compose_timer = Core::Timer::create_single_shot(
54 1000 / 60,
55 [this] {
56 compose();
57 },
58 this)
59 .release_value_but_fixme_should_propagate_errors();
60 m_compose_timer->start();
61
62 m_immediate_compose_timer = Core::Timer::create_single_shot(
63 0,
64 [this] {
65 compose();
66 },
67 this)
68 .release_value_but_fixme_should_propagate_errors();
69 m_compose_timer->start();
70
71 init_bitmaps();
72}
73
74Gfx::Bitmap const* Compositor::cursor_bitmap_for_screenshot(Badge<ConnectionFromClient>, Screen& screen) const
75{
76 if (!m_current_cursor)
77 return nullptr;
78 return &m_current_cursor->bitmap(screen.scale_factor());
79}
80
81Gfx::Bitmap const& Compositor::front_bitmap_for_screenshot(Badge<ConnectionFromClient>, Screen& screen) const
82{
83 return *screen.compositor_screen_data().m_front_bitmap;
84}
85
86Gfx::Color Compositor::color_at_position(Badge<ConnectionFromClient>, Screen& screen, Gfx::IntPoint position) const
87{
88 return screen.compositor_screen_data().m_front_bitmap->get_pixel(position);
89}
90
91void CompositorScreenData::init_bitmaps(Compositor& compositor, Screen& screen)
92{
93 // Recreate the screen-number overlay as the Screen instances may have changed, or get rid of it if we no longer need it
94 if (compositor.showing_screen_numbers()) {
95 m_screen_number_overlay = compositor.create_overlay<ScreenNumberOverlay>(screen);
96 m_screen_number_overlay->set_enabled(true);
97 } else {
98 m_screen_number_overlay = nullptr;
99 }
100
101 m_has_flipped = false;
102 m_have_flush_rects = false;
103 m_buffers_are_flipped = false;
104 m_screen_can_set_buffer = screen.can_set_buffer();
105
106 m_flush_rects.clear_with_capacity();
107 m_flush_transparent_rects.clear_with_capacity();
108 m_flush_special_rects.clear_with_capacity();
109
110 auto size = screen.size();
111 m_front_bitmap = nullptr;
112 m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0, 0)).release_value_but_fixme_should_propagate_errors();
113 m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
114 m_front_painter->translate(-screen.rect().location());
115
116 m_back_bitmap = nullptr;
117 if (m_screen_can_set_buffer)
118 m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(1, 0)).release_value_but_fixme_should_propagate_errors();
119 else
120 m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()).release_value_but_fixme_should_propagate_errors();
121 m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
122 m_back_painter->translate(-screen.rect().location());
123
124 m_temp_bitmap = nullptr;
125 m_temp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()).release_value_but_fixme_should_propagate_errors();
126 m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap);
127 m_temp_painter->translate(-screen.rect().location());
128
129 clear_wallpaper_bitmap();
130}
131
132void Compositor::init_bitmaps()
133{
134 Screen::for_each([&](auto& screen) {
135 screen.compositor_screen_data().init_bitmaps(*this, screen);
136 return IterationDecision::Continue;
137 });
138
139 invalidate_screen();
140}
141
142void Compositor::did_construct_window_manager(Badge<WindowManager>)
143{
144 auto& wm = WindowManager::the();
145
146 m_current_window_stack = &wm.current_window_stack();
147
148 m_wallpaper_mode = mode_to_enum(g_config->read_entry("Background", "Mode", "Center"));
149 m_custom_background_color = Color::from_string(g_config->read_entry("Background", "Color", ""));
150
151 invalidate_screen();
152 invalidate_occlusions();
153 compose();
154}
155
156Gfx::IntPoint Compositor::window_transition_offset(Window& window)
157{
158 if (WindowManager::is_stationary_window_type(window.type()))
159 return {};
160
161 if (window.is_moving_to_another_stack())
162 return {};
163
164 return window.window_stack().transition_offset();
165}
166
167void Compositor::compose()
168{
169 auto& wm = WindowManager::the();
170
171 {
172 auto& current_cursor = wm.active_cursor();
173 if (m_current_cursor != ¤t_cursor) {
174 change_cursor(¤t_cursor);
175 m_invalidated_cursor = m_invalidated_any = true;
176 }
177 }
178
179 if (!m_invalidated_any) {
180 // nothing dirtied since the last compose pass.
181 return;
182 }
183
184 if (m_occlusions_dirty) {
185 m_occlusions_dirty = false;
186 recompute_occlusions();
187 }
188
189 // We should have recomputed occlusions if any overlay rects were changed
190 VERIFY(!m_overlay_rects_changed);
191
192 auto dirty_screen_rects = move(m_dirty_screen_rects);
193
194 bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr;
195
196 // Mark window regions as dirty that need to be re-rendered
197 wm.for_each_visible_window_from_back_to_front([&](Window& window) {
198 auto transition_offset = window_transition_offset(window);
199 auto frame_rect = window.frame().render_rect();
200 auto frame_rect_on_screen = frame_rect.translated(transition_offset);
201 for (auto& dirty_rect : dirty_screen_rects.rects()) {
202 auto invalidate_rect = dirty_rect.intersected(frame_rect_on_screen);
203 if (!invalidate_rect.is_empty()) {
204 auto inner_rect_offset = window.rect().location() - frame_rect.location();
205 invalidate_rect.translate_by(-(frame_rect.location() + inner_rect_offset + transition_offset));
206 window.invalidate_no_notify(invalidate_rect);
207 m_invalidated_window = true;
208 }
209 }
210 window.prepare_dirty_rects();
211 if (window_stack_transition_in_progress)
212 window.dirty_rects().translate_by(transition_offset);
213 return IterationDecision::Continue;
214 });
215
216 // Any dirty rects in transparency areas may require windows above or below
217 // to also be marked dirty in these areas
218 wm.for_each_visible_window_from_back_to_front([&](Window& window) {
219 auto& dirty_rects = window.dirty_rects(); // dirty rects have already been adjusted for transition offset!
220 if (dirty_rects.is_empty())
221 return IterationDecision::Continue;
222 auto& affected_transparency_rects = window.affected_transparency_rects();
223 if (affected_transparency_rects.is_empty())
224 return IterationDecision::Continue;
225 // If we have transparency rects that affect others, we better have transparency rects ourselves...
226 auto& transparency_rects = window.transparency_rects();
227 VERIFY(!transparency_rects.is_empty());
228 for (auto& it : affected_transparency_rects) {
229 auto& affected_window_dirty_rects = it.key->dirty_rects();
230 auto& affected_rects = it.value;
231 affected_rects.for_each_intersected(dirty_rects, [&](auto& dirty_rect) {
232 affected_window_dirty_rects.add(dirty_rect);
233 return IterationDecision::Continue;
234 });
235 }
236 return IterationDecision::Continue;
237 });
238
239 Color background_color = wm.palette().desktop_background();
240 if (m_custom_background_color.has_value())
241 background_color = m_custom_background_color.value();
242
243 if constexpr (COMPOSE_DEBUG) {
244 dbgln("COMPOSE: invalidated: window: {} cursor: {}, any: {}", m_invalidated_window, m_invalidated_cursor, m_invalidated_any);
245 for (auto& r : dirty_screen_rects.rects())
246 dbgln("dirty screen: {}", r);
247 }
248
249 auto& cursor_screen = ScreenInput::the().cursor_location_screen();
250
251 Screen::for_each([&](auto& screen) {
252 auto& screen_data = screen.compositor_screen_data();
253 screen_data.m_have_flush_rects = false;
254 screen_data.m_flush_rects.clear_with_capacity();
255 screen_data.m_flush_transparent_rects.clear_with_capacity();
256 screen_data.m_flush_special_rects.clear_with_capacity();
257 return IterationDecision::Continue;
258 });
259
260 auto cursor_rect = current_cursor_rect();
261
262 bool need_to_draw_cursor = false;
263 Gfx::IntRect previous_cursor_rect;
264 Screen* previous_cursor_screen = nullptr;
265 auto check_restore_cursor_back = [&](Screen& screen, Gfx::IntRect const& rect) {
266 if (&screen == &cursor_screen && !previous_cursor_screen && !need_to_draw_cursor && rect.intersects(cursor_rect)) {
267 // Restore what's behind the cursor if anything touches the area of the cursor
268 need_to_draw_cursor = true;
269 if (cursor_screen.compositor_screen_data().restore_cursor_back(cursor_screen, previous_cursor_rect))
270 previous_cursor_screen = &screen;
271 }
272 };
273
274 if (&cursor_screen != m_current_cursor_screen) {
275 // Cursor moved to another screen, restore on the cursor's background on the previous screen
276 need_to_draw_cursor = true;
277 if (m_current_cursor_screen) {
278 if (m_current_cursor_screen->compositor_screen_data().restore_cursor_back(*m_current_cursor_screen, previous_cursor_rect))
279 previous_cursor_screen = m_current_cursor_screen;
280 }
281 m_current_cursor_screen = &cursor_screen;
282 }
283
284 auto prepare_rect = [&](Screen& screen, Gfx::IntRect const& rect) {
285 auto& screen_data = screen.compositor_screen_data();
286 dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect);
287 VERIFY(!screen_data.m_flush_rects.intersects(rect));
288 VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect));
289 screen_data.m_have_flush_rects = true;
290 screen_data.m_flush_rects.add(rect);
291 check_restore_cursor_back(screen, rect);
292 };
293
294 auto prepare_transparency_rect = [&](Screen& screen, Gfx::IntRect const& rect) {
295 auto& screen_data = screen.compositor_screen_data();
296 dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect);
297 VERIFY(!screen_data.m_flush_rects.intersects(rect));
298 for (auto& r : screen_data.m_flush_transparent_rects.rects()) {
299 if (r == rect)
300 return;
301 }
302
303 screen_data.m_have_flush_rects = true;
304 screen_data.m_flush_transparent_rects.add(rect);
305 check_restore_cursor_back(screen, rect);
306 };
307
308 if (!cursor_screen.compositor_screen_data().m_cursor_back_bitmap || m_invalidated_cursor)
309 check_restore_cursor_back(cursor_screen, cursor_rect);
310
311 auto paint_wallpaper = [&](Screen& screen, Gfx::Painter& painter, Gfx::IntRect const& rect, Gfx::IntRect const& screen_rect) {
312 if (m_wallpaper) {
313 if (m_wallpaper_mode == WallpaperMode::Center) {
314 Gfx::IntPoint offset { (screen.width() - m_wallpaper->width()) / 2, (screen.height() - m_wallpaper->height()) / 2 };
315
316 // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color!
317 painter.fill_rect(rect, background_color);
318 painter.blit_offset(rect.location(), *m_wallpaper, rect.translated(-screen_rect.location()), offset);
319 } else if (m_wallpaper_mode == WallpaperMode::Tile) {
320 painter.draw_tiled_bitmap(rect, *m_wallpaper);
321 } else if (m_wallpaper_mode == WallpaperMode::Stretch) {
322 VERIFY(screen.compositor_screen_data().m_wallpaper_bitmap);
323 painter.blit(rect.location(), *screen.compositor_screen_data().m_wallpaper_bitmap, rect.translated(-screen.location()));
324 } else {
325 VERIFY_NOT_REACHED();
326 }
327 } else {
328 painter.fill_rect(rect, background_color);
329 }
330 };
331
332 {
333 // Paint any desktop wallpaper rects that are not somehow underneath any window transparency
334 // rects and outside of any opaque window areas
335 m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) {
336 Screen::for_each([&](auto& screen) {
337 auto screen_rect = screen.rect();
338 auto screen_render_rect = screen_rect.intersected(render_rect);
339 if (!screen_render_rect.is_empty()) {
340 dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {} on screen #{}", screen_render_rect, screen.index());
341 prepare_rect(screen, render_rect);
342 auto& back_painter = *screen.compositor_screen_data().m_back_painter;
343 paint_wallpaper(screen, back_painter, render_rect, screen_rect);
344 }
345 return IterationDecision::Continue;
346 });
347 return IterationDecision::Continue;
348 });
349 m_transparent_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) {
350 Screen::for_each([&](auto& screen) {
351 auto screen_rect = screen.rect();
352 auto screen_render_rect = screen_rect.intersected(render_rect);
353 if (!screen_render_rect.is_empty()) {
354 dbgln_if(COMPOSE_DEBUG, " render wallpaper transparent: {} on screen #{}", screen_render_rect, screen.index());
355 prepare_transparency_rect(screen, render_rect);
356 auto& temp_painter = *screen.compositor_screen_data().m_temp_painter;
357 paint_wallpaper(screen, temp_painter, render_rect, screen_rect);
358 }
359 return IterationDecision::Continue;
360 });
361 return IterationDecision::Continue;
362 });
363 }
364
365 auto compose_window = [&](Window& window) -> IterationDecision {
366 if (window.screens().is_empty()) {
367 // This window doesn't intersect with any screens, so there's nothing to render
368 return IterationDecision::Continue;
369 }
370 auto transition_offset = window_transition_offset(window);
371 auto frame_rect = window.frame().render_rect().translated(transition_offset);
372 auto window_rect = window.rect().translated(transition_offset);
373 auto frame_rects = frame_rect.shatter(window_rect);
374
375 dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect);
376
377 RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
378 auto compose_window_rect = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) {
379 if (!window.is_fullscreen()) {
380 rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) {
381 Gfx::PainterStateSaver saver(painter);
382 painter.add_clip_rect(intersected_rect);
383 painter.translate(transition_offset);
384 dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect);
385 window.frame().paint(screen, painter, intersected_rect.translated(-transition_offset));
386 return IterationDecision::Continue;
387 });
388 }
389
390 auto update_window_rect = window_rect.intersected(rect);
391 if (update_window_rect.is_empty())
392 return;
393
394 auto clear_window_rect = [&](const Gfx::IntRect& clear_rect) {
395 auto fill_color = wm.palette().window();
396 if (!window.is_opaque())
397 fill_color.set_alpha(255 * window.opacity());
398 painter.fill_rect(clear_rect, fill_color);
399 };
400
401 if (!backing_store) {
402 clear_window_rect(update_window_rect);
403 return;
404 }
405
406 // Decide where we would paint this window's backing store.
407 // This is subtly different from widow.rect(), because window
408 // size may be different from its backing store size. This
409 // happens when the window has been resized and the client
410 // has not yet attached a new backing store. In this case,
411 // we want to try to blit the backing store at the same place
412 // it was previously, and fill the rest of the window with its
413 // background color.
414 Gfx::IntRect backing_rect;
415 backing_rect.set_size(window.backing_store_visible_size());
416 switch (WindowManager::the().resize_direction_of_window(window)) {
417 case ResizeDirection::None:
418 case ResizeDirection::Right:
419 case ResizeDirection::Down:
420 case ResizeDirection::DownRight:
421 backing_rect.set_location(window_rect.location());
422 break;
423 case ResizeDirection::Left:
424 case ResizeDirection::Up:
425 case ResizeDirection::UpLeft:
426 backing_rect.set_right_without_resize(window_rect.right());
427 backing_rect.set_bottom_without_resize(window_rect.bottom());
428 break;
429 case ResizeDirection::UpRight:
430 backing_rect.set_left(window.rect().left());
431 backing_rect.set_bottom_without_resize(window_rect.bottom());
432 break;
433 case ResizeDirection::DownLeft:
434 backing_rect.set_right_without_resize(window_rect.right());
435 backing_rect.set_top(window_rect.top());
436 break;
437 default:
438 VERIFY_NOT_REACHED();
439 break;
440 }
441
442 Gfx::IntRect dirty_rect_in_backing_coordinates = update_window_rect.intersected(backing_rect)
443 .translated(-backing_rect.location());
444
445 if (!dirty_rect_in_backing_coordinates.is_empty()) {
446 auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
447
448 if (window.client() && window.client()->is_unresponsive()) {
449 if (window.is_opaque()) {
450 painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [](Color src) {
451 return src.to_grayscale().darkened(0.75f);
452 });
453 } else {
454 u8 alpha = 255 * window.opacity();
455 painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [&](Color src) {
456 auto color = src.to_grayscale().darkened(0.75f);
457 color.set_alpha(alpha);
458 return color;
459 });
460 }
461 } else {
462 painter.blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
463 }
464 }
465
466 for (auto background_rect : update_window_rect.shatter(backing_rect))
467 clear_window_rect(background_rect);
468 };
469
470 auto& dirty_rects = window.dirty_rects();
471
472 if constexpr (COMPOSE_DEBUG) {
473 for (auto& dirty_rect : dirty_rects.rects())
474 dbgln(" dirty: {}", dirty_rect);
475 for (auto& r : window.opaque_rects().rects())
476 dbgln(" opaque: {}", r);
477 for (auto& r : window.transparency_rects().rects())
478 dbgln(" transparent: {}", r);
479 }
480
481 // Render opaque portions directly to the back buffer
482 auto& opaque_rects = window.opaque_rects();
483 if (!opaque_rects.is_empty()) {
484 opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
485 for (auto* screen : window.screens()) {
486 auto screen_render_rect = render_rect.intersected(screen->rect());
487 if (screen_render_rect.is_empty())
488 continue;
489 dbgln_if(COMPOSE_DEBUG, " render opaque: {} on screen #{}", screen_render_rect, screen->index());
490
491 prepare_rect(*screen, screen_render_rect);
492 auto& back_painter = *screen->compositor_screen_data().m_back_painter;
493 Gfx::PainterStateSaver saver(back_painter);
494 back_painter.add_clip_rect(screen_render_rect);
495 compose_window_rect(*screen, back_painter, screen_render_rect);
496 }
497 return IterationDecision::Continue;
498 });
499 }
500
501 // Render the wallpaper for any transparency directly covering
502 // the wallpaper
503 auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects();
504 if (!transparency_wallpaper_rects.is_empty()) {
505 transparency_wallpaper_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
506 for (auto* screen : window.screens()) {
507 auto screen_rect = screen->rect();
508 auto screen_render_rect = render_rect.intersected(screen_rect);
509 if (screen_render_rect.is_empty())
510 continue;
511 dbgln_if(COMPOSE_DEBUG, " render wallpaper: {} on screen #{}", screen_render_rect, screen->index());
512
513 auto& temp_painter = *screen->compositor_screen_data().m_temp_painter;
514 prepare_transparency_rect(*screen, screen_render_rect);
515 paint_wallpaper(*screen, temp_painter, screen_render_rect, screen_rect);
516 }
517 return IterationDecision::Continue;
518 });
519 }
520 auto& transparency_rects = window.transparency_rects();
521 if (!transparency_rects.is_empty()) {
522 transparency_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
523 for (auto* screen : window.screens()) {
524 auto screen_rect = screen->rect();
525 auto screen_render_rect = render_rect.intersected(screen_rect);
526 if (screen_render_rect.is_empty())
527 continue;
528 dbgln_if(COMPOSE_DEBUG, " render transparent: {} on screen #{}", screen_render_rect, screen->index());
529
530 prepare_transparency_rect(*screen, screen_render_rect);
531 auto& temp_painter = *screen->compositor_screen_data().m_temp_painter;
532 Gfx::PainterStateSaver saver(temp_painter);
533 temp_painter.add_clip_rect(screen_render_rect);
534 compose_window_rect(*screen, temp_painter, screen_render_rect);
535 }
536 return IterationDecision::Continue;
537 });
538 }
539 return IterationDecision::Continue;
540 };
541
542 // Paint the window stack.
543 if (m_invalidated_window) {
544 auto* fullscreen_window = wm.active_fullscreen_window();
545 // FIXME: Remove the !WindowSwitcher::the().is_visible() check when WindowSwitcher is an overlay
546 if (fullscreen_window && fullscreen_window->is_opaque() && !WindowSwitcher::the().is_visible()) {
547 compose_window(*fullscreen_window);
548 fullscreen_window->clear_dirty_rects();
549 } else {
550 wm.for_each_visible_window_from_back_to_front([&](Window& window) {
551 compose_window(window);
552 window.clear_dirty_rects();
553 return IterationDecision::Continue;
554 });
555 }
556
557 // Check that there are no overlapping transparent and opaque flush rectangles
558 VERIFY(![&]() {
559 bool is_overlapping = false;
560 Screen::for_each([&](auto& screen) {
561 auto& screen_data = screen.compositor_screen_data();
562 auto& flush_transparent_rects = screen_data.m_flush_transparent_rects;
563 auto& flush_rects = screen_data.m_flush_rects;
564 for (auto& rect_transparent : flush_transparent_rects.rects()) {
565 for (auto& rect_opaque : flush_rects.rects()) {
566 if (rect_opaque.intersects(rect_transparent)) {
567 dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent));
568 is_overlapping = true;
569 return IterationDecision::Break;
570 }
571 }
572 }
573 return IterationDecision::Continue;
574 });
575 return is_overlapping;
576 }());
577
578 if (!m_overlay_list.is_empty()) {
579 // Render everything to the temporary buffer before we copy it back
580 render_overlays();
581 }
582
583 // Copy anything rendered to the temporary buffer to the back buffer
584 Screen::for_each([&](auto& screen) {
585 auto screen_rect = screen.rect();
586 auto& screen_data = screen.compositor_screen_data();
587 for (auto& rect : screen_data.m_flush_transparent_rects.rects())
588 screen_data.m_back_painter->blit(rect.location(), *screen_data.m_temp_bitmap, rect.translated(-screen_rect.location()));
589 return IterationDecision::Continue;
590 });
591 }
592
593 m_invalidated_any = false;
594 m_invalidated_window = false;
595 m_invalidated_cursor = false;
596
597 if (!m_animations.is_empty()) {
598 Screen::for_each([&](auto& screen) {
599 auto& screen_data = screen.compositor_screen_data();
600 update_animations(screen, screen_data.m_flush_special_rects);
601 if (!screen_data.m_flush_special_rects.is_empty())
602 screen_data.m_have_flush_rects = true;
603 return IterationDecision::Continue;
604 });
605 // As long as animations are running make sure we keep rendering frames
606 m_invalidated_any = true;
607 start_compose_async_timer();
608 }
609
610 if (need_to_draw_cursor) {
611 auto& screen_data = cursor_screen.compositor_screen_data();
612 screen_data.draw_cursor(cursor_screen, cursor_rect);
613 }
614
615 Screen::for_each([&](auto& screen) {
616 flush(screen);
617 return IterationDecision::Continue;
618 });
619}
620
621void Compositor::flush(Screen& screen)
622{
623 auto& screen_data = screen.compositor_screen_data();
624
625 bool device_can_flush_buffers = screen.can_device_flush_buffers();
626 if (!screen_data.m_have_flush_rects && (!screen_data.m_screen_can_set_buffer || screen_data.m_has_flipped)) {
627 dbgln_if(COMPOSE_DEBUG, "Nothing to flush on screen #{} {}", screen.index(), screen_data.m_have_flush_rects);
628 return;
629 }
630 screen_data.m_have_flush_rects = false;
631
632 auto screen_rect = screen.rect();
633 if (m_flash_flush) {
634 Gfx::IntRect bounding_flash;
635 for (auto& rect : screen_data.m_flush_rects.rects()) {
636 screen_data.m_front_painter->fill_rect(rect, Color::Yellow);
637 bounding_flash = bounding_flash.united(rect);
638 }
639 for (auto& rect : screen_data.m_flush_transparent_rects.rects()) {
640 screen_data.m_front_painter->fill_rect(rect, Color::Green);
641 bounding_flash = bounding_flash.united(rect);
642 }
643 if (!bounding_flash.is_empty()) {
644 if (screen.can_device_flush_entire_buffer()) {
645 screen.flush_display_entire_framebuffer();
646 } else if (device_can_flush_buffers) {
647 // If the device needs a flush we need to let it know that we
648 // modified the front buffer!
649 bounding_flash.translate_by(-screen_rect.location());
650 screen.flush_display_front_buffer((!screen_data.m_screen_can_set_buffer || !screen_data.m_buffers_are_flipped) ? 0 : 1, bounding_flash);
651 }
652 usleep(10000);
653 }
654 }
655
656 if (device_can_flush_buffers && screen_data.m_screen_can_set_buffer) {
657 if (!screen_data.m_has_flipped) {
658 // If we have not flipped any buffers before, we should be flushing
659 // the entire buffer to make sure that the device has all the bits we wrote
660 screen_data.m_flush_rects = { screen.rect() };
661 }
662
663 // If we also support buffer flipping we need to make sure we transfer all
664 // updated areas to the device before we flip. We already modified the framebuffer
665 // memory, but the device needs to know what areas we actually did update.
666 for (auto& rect : screen_data.m_flush_rects.rects())
667 screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
668 for (auto& rect : screen_data.m_flush_transparent_rects.rects())
669 screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
670 for (auto& rect : screen_data.m_flush_special_rects.rects())
671 screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
672
673 screen.flush_display((!screen_data.m_screen_can_set_buffer || screen_data.m_buffers_are_flipped) ? 0 : 1);
674 }
675
676 if (screen_data.m_screen_can_set_buffer) {
677 screen_data.flip_buffers(screen);
678 screen_data.m_has_flipped = true;
679 }
680
681 auto do_flush = [&](Gfx::IntRect rect) {
682 VERIFY(screen_rect.contains(rect));
683 rect.translate_by(-screen_rect.location());
684
685 // Almost everything in Compositor is in logical coordinates, with the painters having
686 // a scale applied. But this routine accesses the backbuffer pixels directly, so it
687 // must work in physical coordinates.
688 auto scaled_rect = rect * screen.scale_factor();
689 Gfx::ARGB32* front_ptr = screen_data.m_front_bitmap->scanline(scaled_rect.y()) + scaled_rect.x();
690 Gfx::ARGB32* back_ptr = screen_data.m_back_bitmap->scanline(scaled_rect.y()) + scaled_rect.x();
691 size_t pitch = screen_data.m_back_bitmap->pitch();
692
693 // NOTE: The meaning of a flush depends on whether we can flip buffers or not.
694 //
695 // If flipping is supported, flushing means that we've flipped, and now we
696 // copy the changed bits from the front buffer to the back buffer, to keep
697 // them in sync.
698 //
699 // If flipping is not supported, flushing means that we copy the changed
700 // rects from the backing bitmap to the display framebuffer.
701
702 Gfx::ARGB32* to_ptr;
703 const Gfx::ARGB32* from_ptr;
704
705 if (screen_data.m_screen_can_set_buffer) {
706 to_ptr = back_ptr;
707 from_ptr = front_ptr;
708 } else {
709 to_ptr = front_ptr;
710 from_ptr = back_ptr;
711 }
712
713 for (int y = 0; y < scaled_rect.height(); ++y) {
714 fast_u32_copy(to_ptr, from_ptr, scaled_rect.width());
715 from_ptr = (const Gfx::ARGB32*)((const u8*)from_ptr + pitch);
716 to_ptr = (Gfx::ARGB32*)((u8*)to_ptr + pitch);
717 }
718 if (device_can_flush_buffers) {
719 // Whether or not we need to flush buffers, we need to at least track what we modified
720 // so that we can flush these areas next time before we flip buffers. Or, if we don't
721 // support buffer flipping then we will flush them shortly.
722 screen.queue_flush_display_rect(rect);
723 }
724 };
725 for (auto& rect : screen_data.m_flush_rects.rects())
726 do_flush(rect);
727 for (auto& rect : screen_data.m_flush_transparent_rects.rects())
728 do_flush(rect);
729 for (auto& rect : screen_data.m_flush_special_rects.rects())
730 do_flush(rect);
731 if (device_can_flush_buffers && !screen_data.m_screen_can_set_buffer) {
732 // If we also support flipping buffers we don't really need to flush these areas right now.
733 // Instead, we skip this step and just keep track of them until shortly before the next flip.
734 // If we however don't support flipping buffers then we need to flush the changed areas right
735 // now so that they can be sent to the device.
736 screen.flush_display(screen_data.m_buffers_are_flipped ? 1 : 0);
737 }
738}
739
740void Compositor::invalidate_screen()
741{
742 invalidate_screen(Screen::bounding_rect());
743}
744
745void Compositor::invalidate_screen(Gfx::IntRect const& screen_rect)
746{
747 m_dirty_screen_rects.add(screen_rect.intersected(Screen::bounding_rect()));
748
749 if (m_invalidated_any)
750 return;
751
752 m_invalidated_any = true;
753 m_invalidated_window = true;
754 start_compose_async_timer();
755}
756
757void Compositor::invalidate_screen(Gfx::DisjointIntRectSet const& rects)
758{
759 m_dirty_screen_rects.add(rects.intersected(Screen::bounding_rect()));
760
761 if (m_invalidated_any)
762 return;
763
764 m_invalidated_any = true;
765 m_invalidated_window = true;
766 start_compose_async_timer();
767}
768
769void Compositor::invalidate_window()
770{
771 if (m_invalidated_window)
772 return;
773 m_invalidated_window = true;
774 m_invalidated_any = true;
775
776 start_compose_async_timer();
777}
778
779void Compositor::start_compose_async_timer()
780{
781 // We delay composition by a timer interval, but to not affect latency too
782 // much, if a pending compose is not already scheduled, we also schedule an
783 // immediate compose the next spin of the event loop.
784 if (!m_compose_timer->is_active()) {
785 m_compose_timer->start();
786 m_immediate_compose_timer->start();
787 }
788}
789
790bool Compositor::set_background_color(DeprecatedString const& background_color)
791{
792 auto color = Color::from_string(background_color);
793 if (!color.has_value())
794 return false;
795
796 m_custom_background_color = color;
797
798 g_config->write_entry("Background", "Color", background_color);
799 bool succeeded = !g_config->sync().is_error();
800
801 if (succeeded) {
802 update_wallpaper_bitmap();
803 Compositor::invalidate_screen();
804 }
805
806 return succeeded;
807}
808
809bool Compositor::set_wallpaper_mode(DeprecatedString const& mode)
810{
811 g_config->write_entry("Background", "Mode", mode);
812 bool succeeded = !g_config->sync().is_error();
813
814 if (succeeded) {
815 m_wallpaper_mode = mode_to_enum(mode);
816 update_wallpaper_bitmap();
817 Compositor::invalidate_screen();
818 }
819
820 return succeeded;
821}
822
823bool Compositor::set_wallpaper(RefPtr<Gfx::Bitmap const> bitmap)
824{
825 if (!bitmap)
826 m_wallpaper = nullptr;
827 else
828 m_wallpaper = bitmap;
829 update_wallpaper_bitmap();
830 invalidate_screen();
831
832 return true;
833}
834
835void Compositor::update_wallpaper_bitmap()
836{
837 Screen::for_each([&](Screen& screen) {
838 auto& screen_data = screen.compositor_screen_data();
839 if (m_wallpaper_mode != WallpaperMode::Stretch || !m_wallpaper) {
840 screen_data.clear_wallpaper_bitmap();
841 return IterationDecision::Continue;
842 }
843
844 // See if there is another screen with the same resolution and scale.
845 // If so, we can use the same bitmap.
846 bool share_bitmap_with_other_screen = false;
847 Screen::for_each([&](Screen& screen2) {
848 if (&screen == &screen2) {
849 // Stop iterating here, we haven't updated wallpaper bitmaps for
850 // this screen and the following screens.
851 return IterationDecision::Break;
852 }
853
854 if (screen.size() == screen2.size() && screen.scale_factor() == screen2.scale_factor()) {
855 auto& screen2_data = screen2.compositor_screen_data();
856
857 // Use the same bitmap as the other screen
858 screen_data.m_wallpaper_bitmap = screen2_data.m_wallpaper_bitmap;
859 share_bitmap_with_other_screen = true;
860 return IterationDecision::Break;
861 }
862 return IterationDecision::Continue;
863 });
864
865 if (share_bitmap_with_other_screen)
866 return IterationDecision::Continue;
867
868 if (screen.size() == m_wallpaper->size() && screen.scale_factor() == m_wallpaper->scale()) {
869 // If the screen size is equal to the wallpaper size, we don't actually need to scale it
870 screen_data.m_wallpaper_bitmap = m_wallpaper;
871 } else {
872 auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, screen.size(), screen.scale_factor()).release_value_but_fixme_should_propagate_errors();
873
874 Gfx::Painter painter(*bitmap);
875 painter.draw_scaled_bitmap(bitmap->rect(), *m_wallpaper, m_wallpaper->rect());
876
877 screen_data.m_wallpaper_bitmap = move(bitmap);
878 }
879 return IterationDecision::Continue;
880 });
881}
882
883void CompositorScreenData::clear_wallpaper_bitmap()
884{
885 m_wallpaper_bitmap = nullptr;
886}
887
888void CompositorScreenData::flip_buffers(Screen& screen)
889{
890 VERIFY(m_screen_can_set_buffer);
891 swap(m_front_bitmap, m_back_bitmap);
892 swap(m_front_painter, m_back_painter);
893 screen.set_buffer(m_buffers_are_flipped ? 0 : 1);
894 m_buffers_are_flipped = !m_buffers_are_flipped;
895}
896
897void Compositor::screen_resolution_changed()
898{
899 // Screens may be gone now, invalidate any references to them
900 m_current_cursor_screen = nullptr;
901
902 init_bitmaps();
903 invalidate_occlusions();
904 overlay_rects_changed();
905 update_wallpaper_bitmap();
906 compose();
907}
908
909Gfx::IntRect Compositor::current_cursor_rect() const
910{
911 auto& wm = WindowManager::the();
912 auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor();
913 Gfx::IntRect cursor_rect { ScreenInput::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() };
914 if (wm.is_cursor_highlight_enabled()) {
915 auto highlight_diameter = wm.cursor_highlight_radius() * 2;
916 auto inflate_w = highlight_diameter - cursor_rect.width();
917 auto inflate_h = highlight_diameter - cursor_rect.height();
918 cursor_rect.inflate(inflate_w, inflate_h);
919 // Ensures cursor stays in the same location when highlighting is enabled.
920 cursor_rect.translate_by(-(inflate_w % 2), -(inflate_h % 2));
921 }
922 return cursor_rect;
923}
924
925void Compositor::invalidate_cursor(bool compose_immediately)
926{
927 if (m_invalidated_cursor && !compose_immediately)
928 return;
929 m_invalidated_cursor = true;
930 m_invalidated_any = true;
931
932 if (compose_immediately)
933 compose();
934 else
935 start_compose_async_timer();
936}
937
938void Compositor::change_cursor(Cursor const* cursor)
939{
940 if (m_current_cursor == cursor)
941 return;
942 m_current_cursor = cursor;
943 m_current_cursor_frame = 0;
944 if (m_cursor_timer) {
945 m_cursor_timer->stop();
946 m_cursor_timer = nullptr;
947 }
948 if (cursor && cursor->params().frames() > 1 && cursor->params().frame_ms() != 0) {
949 m_cursor_timer = add<Core::Timer>(
950 cursor->params().frame_ms(), [this, cursor] {
951 if (m_current_cursor != cursor)
952 return;
953 auto frames = cursor->params().frames();
954 if (++m_current_cursor_frame >= frames)
955 m_current_cursor_frame = 0;
956 invalidate_cursor(true);
957 });
958 m_cursor_timer->start();
959 }
960}
961
962void Compositor::render_overlays()
963{
964 // NOTE: overlays should always be rendered to the temporary buffer!
965 for (auto& overlay : m_overlay_list) {
966 for (auto* screen : overlay.m_screens) {
967 auto& screen_data = screen->compositor_screen_data();
968 auto& painter = screen_data.overlay_painter();
969
970 auto render_overlay_rect = [&](auto& intersected_overlay_rect) {
971 Gfx::PainterStateSaver saver(painter);
972 painter.add_clip_rect(intersected_overlay_rect);
973 painter.translate(overlay.m_current_rect.location());
974 overlay.render(painter, *screen);
975 return IterationDecision::Continue;
976 };
977
978 auto const& render_rect = overlay.current_render_rect();
979 screen_data.for_each_intersected_flushing_rect(render_rect, render_overlay_rect);
980
981 // Now render any areas that are not somehow underneath any window or transparency area
982 m_transparent_wallpaper_rects.for_each_intersected(render_rect, render_overlay_rect);
983 }
984 }
985}
986
987void Compositor::add_overlay(Overlay& overlay)
988{
989 VERIFY(!overlay.m_list_node.is_in_list());
990 auto zorder = overlay.zorder();
991 bool did_insert = false;
992 for (auto& other_overlay : m_overlay_list) {
993 if (other_overlay.zorder() > zorder) {
994 m_overlay_list.insert_before(other_overlay, overlay);
995 did_insert = true;
996 break;
997 }
998 }
999 if (!did_insert)
1000 m_overlay_list.append(overlay);
1001
1002 overlay.clear_invalidated();
1003 overlay_rects_changed();
1004 auto& rect = overlay.rect();
1005 if (!rect.is_empty())
1006 invalidate_screen(rect);
1007}
1008
1009void Compositor::remove_overlay(Overlay& overlay)
1010{
1011 auto& current_render_rect = overlay.current_render_rect();
1012 if (!current_render_rect.is_empty())
1013 invalidate_screen(current_render_rect);
1014 m_overlay_list.remove(overlay);
1015 overlay_rects_changed();
1016}
1017
1018void CompositorScreenData::draw_cursor(Screen& screen, Gfx::IntRect const& cursor_rect)
1019{
1020 auto& wm = WindowManager::the();
1021
1022 if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size() || m_cursor_back_bitmap->scale() != screen.scale_factor()) {
1023 m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor()).release_value_but_fixme_should_propagate_errors();
1024 m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap);
1025 }
1026
1027 auto& compositor = Compositor::the();
1028 auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor();
1029 auto screen_rect = screen.rect();
1030 m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, cursor_rect.intersected(screen_rect).translated(-screen_rect.location()));
1031 auto cursor_src_rect = current_cursor.source_rect(compositor.m_current_cursor_frame);
1032 auto cursor_blit_pos = current_cursor.rect().centered_within(cursor_rect).location();
1033
1034 if (wm.is_cursor_highlight_enabled()) {
1035 Gfx::AntiAliasingPainter aa_back_painter { *m_back_painter };
1036 aa_back_painter.fill_ellipse(cursor_rect, wm.cursor_highlight_color());
1037 }
1038 m_back_painter->blit(cursor_blit_pos, current_cursor.bitmap(screen.scale_factor()), cursor_src_rect);
1039
1040 m_flush_special_rects.add(Gfx::IntRect(cursor_rect.location(), cursor_rect.size()).intersected(screen.rect()));
1041 m_have_flush_rects = true;
1042 m_last_cursor_rect = cursor_rect;
1043 VERIFY(compositor.m_current_cursor_screen == &screen);
1044 m_cursor_back_is_valid = true;
1045}
1046
1047bool CompositorScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect)
1048{
1049 if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale())
1050 return false;
1051
1052 last_cursor_rect = m_last_cursor_rect.intersected(screen.rect());
1053 m_back_painter->blit(last_cursor_rect.location(), *m_cursor_back_bitmap, { { 0, 0 }, last_cursor_rect.size() });
1054 m_flush_special_rects.add(last_cursor_rect.intersected(screen.rect()));
1055 m_have_flush_rects = true;
1056 m_cursor_back_is_valid = false;
1057 return true;
1058}
1059
1060void Compositor::update_fonts()
1061{
1062 ScreenNumberOverlay::pick_font();
1063}
1064
1065void Compositor::notify_display_links()
1066{
1067 ConnectionFromClient::for_each_client([](auto& client) {
1068 client.notify_display_link({});
1069 });
1070}
1071
1072void Compositor::increment_display_link_count(Badge<ConnectionFromClient>)
1073{
1074 ++m_display_link_count;
1075 if (m_display_link_count == 1)
1076 m_display_link_notify_timer->start();
1077}
1078
1079void Compositor::decrement_display_link_count(Badge<ConnectionFromClient>)
1080{
1081 VERIFY(m_display_link_count);
1082 --m_display_link_count;
1083 if (!m_display_link_count)
1084 m_display_link_notify_timer->stop();
1085}
1086
1087void Compositor::invalidate_current_screen_number_rects()
1088{
1089 Screen::for_each([&](auto& screen) {
1090 auto& screen_data = screen.compositor_screen_data();
1091 if (screen_data.m_screen_number_overlay)
1092 screen_data.m_screen_number_overlay->invalidate();
1093 return IterationDecision::Continue;
1094 });
1095}
1096
1097void Compositor::increment_show_screen_number(Badge<ConnectionFromClient>)
1098{
1099 if (m_show_screen_number_count++ == 0) {
1100 Screen::for_each([&](auto& screen) {
1101 auto& screen_data = screen.compositor_screen_data();
1102 VERIFY(!screen_data.m_screen_number_overlay);
1103 screen_data.m_screen_number_overlay = create_overlay<ScreenNumberOverlay>(screen);
1104 screen_data.m_screen_number_overlay->set_enabled(true);
1105 return IterationDecision::Continue;
1106 });
1107 }
1108}
1109void Compositor::decrement_show_screen_number(Badge<ConnectionFromClient>)
1110{
1111 if (--m_show_screen_number_count == 0) {
1112 invalidate_current_screen_number_rects();
1113 Screen::for_each([&](auto& screen) {
1114 screen.compositor_screen_data().m_screen_number_overlay = nullptr;
1115 return IterationDecision::Continue;
1116 });
1117 }
1118}
1119
1120void Compositor::overlays_theme_changed()
1121{
1122 for (auto& overlay : m_overlay_list)
1123 overlay.theme_changed();
1124 overlay_rects_changed();
1125}
1126
1127void Compositor::overlay_rects_changed()
1128{
1129 if (m_overlay_rects_changed)
1130 return;
1131
1132 m_overlay_rects_changed = true;
1133 m_invalidated_any = true;
1134 invalidate_occlusions();
1135 for (auto& rect : m_overlay_rects.rects())
1136 invalidate_screen(rect);
1137 start_compose_async_timer();
1138}
1139
1140void Compositor::recompute_overlay_rects()
1141{
1142 // The purpose of this is to gather all areas that we will render over
1143 // regular window contents. This effectively just forces those areas to
1144 // be rendered as transparency areas, which allows us to render these
1145 // flicker-free.
1146 m_overlay_rects.clear_with_capacity();
1147 for (auto& overlay : m_overlay_list) {
1148 auto& render_rect = overlay.rect();
1149 m_overlay_rects.add(render_rect);
1150
1151 // Save the rectangle we are using for rendering from now on
1152 overlay.did_recompute_occlusions();
1153
1154 // Cache which screens this overlay are rendered on
1155 overlay.m_screens.clear_with_capacity();
1156 Screen::for_each([&](auto& screen) {
1157 if (render_rect.intersects(screen.rect()))
1158 overlay.m_screens.append(&screen);
1159 return IterationDecision::Continue;
1160 });
1161
1162 invalidate_screen(render_rect);
1163 }
1164}
1165
1166void Compositor::recompute_occlusions()
1167{
1168 auto& wm = WindowManager::the();
1169 bool is_switcher_visible = wm.m_switcher->is_visible();
1170 auto never_occlude = [&](WindowStack& window_stack) {
1171 if (is_switcher_visible) {
1172 switch (wm.m_switcher->mode()) {
1173 case WindowSwitcher::Mode::ShowCurrentDesktop:
1174 // Any window on the currently rendered desktop should not be occluded, even if it's behind
1175 // another window entirely.
1176 return &window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack;
1177 case WindowSwitcher::Mode::ShowAllWindows:
1178 // The window switcher wants to know about all windows, even those on other desktops
1179 return true;
1180 }
1181 }
1182 return false;
1183 };
1184
1185 wm.for_each_window_stack([&](WindowStack& window_stack) {
1186 if (&window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack) {
1187 // We'll calculate precise occlusions for these further down. Changing occlusions right now
1188 // may trigger an additional unnecessary notification
1189 } else {
1190 window_stack.set_all_occluded(!never_occlude(window_stack));
1191 }
1192 return IterationDecision::Continue;
1193 });
1194
1195 if (m_overlay_rects_changed) {
1196 m_overlay_rects_changed = false;
1197 recompute_overlay_rects();
1198 }
1199
1200 if constexpr (OCCLUSIONS_DEBUG) {
1201 dbgln("OCCLUSIONS:");
1202 for (auto& rect : m_overlay_rects.rects())
1203 dbgln(" overlay: {}", rect);
1204 }
1205
1206 bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr;
1207 auto& main_screen = Screen::main();
1208 auto* fullscreen_window = wm.active_fullscreen_window();
1209 // FIXME: Remove the !WindowSwitcher::the().is_visible() check when WindowSwitcher is an overlay
1210 if (fullscreen_window && !WindowSwitcher::the().is_visible()) {
1211 // TODO: support fullscreen windows on all screens
1212 auto screen_rect = main_screen.rect();
1213 wm.for_each_visible_window_from_front_to_back([&](Window& w) {
1214 auto& visible_opaque = w.opaque_rects();
1215 auto& transparency_rects = w.transparency_rects();
1216 auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
1217 w.affected_transparency_rects().clear();
1218 w.screens().clear_with_capacity();
1219 if (&w == fullscreen_window) {
1220 w.screens().append(&main_screen);
1221 if (w.is_opaque()) {
1222 visible_opaque = screen_rect;
1223 transparency_rects.clear();
1224 transparency_wallpaper_rects.clear();
1225 } else {
1226 visible_opaque.clear();
1227 transparency_rects = screen_rect;
1228 transparency_wallpaper_rects = screen_rect;
1229 }
1230 } else {
1231 visible_opaque.clear();
1232 transparency_rects.clear();
1233 transparency_wallpaper_rects.clear();
1234 }
1235 return IterationDecision::Continue;
1236 });
1237
1238 m_opaque_wallpaper_rects.clear();
1239 }
1240 // FIXME: Remove the WindowSwitcher::the().is_visible() check when WindowSwitcher is an overlay
1241 if (!fullscreen_window || WindowSwitcher::the().is_visible() || (fullscreen_window && !fullscreen_window->is_opaque())) {
1242 Gfx::DisjointIntRectSet remaining_visible_screen_rects;
1243 remaining_visible_screen_rects.add_many(Screen::rects());
1244 bool have_transparent = false;
1245 wm.for_each_visible_window_from_front_to_back([&](Window& w) {
1246 VERIFY(!w.is_minimized());
1247 w.transparency_wallpaper_rects().clear();
1248 auto previous_visible_opaque = move(w.opaque_rects());
1249 auto previous_visible_transparency = move(w.transparency_rects());
1250
1251 auto invalidate_previous_render_rects = [&](Gfx::IntRect const& new_render_rect) {
1252 if (!previous_visible_opaque.is_empty()) {
1253 if (new_render_rect.is_empty())
1254 invalidate_screen(previous_visible_opaque);
1255 else
1256 invalidate_screen(previous_visible_opaque.shatter(new_render_rect));
1257 }
1258 if (!previous_visible_transparency.is_empty()) {
1259 if (new_render_rect.is_empty())
1260 invalidate_screen(previous_visible_transparency);
1261 else
1262 invalidate_screen(previous_visible_transparency.shatter(new_render_rect));
1263 }
1264 };
1265
1266 auto& visible_opaque = w.opaque_rects();
1267 auto& transparency_rects = w.transparency_rects();
1268 bool should_invalidate_old = w.should_invalidate_last_rendered_screen_rects();
1269
1270 auto& affected_transparency_rects = w.affected_transparency_rects();
1271 affected_transparency_rects.clear();
1272
1273 w.screens().clear_with_capacity();
1274
1275 auto transition_offset = window_transition_offset(w);
1276 auto transparent_frame_render_rects = w.frame().transparent_render_rects();
1277 auto opaque_frame_render_rects = w.frame().opaque_render_rects();
1278 if (window_stack_transition_in_progress) {
1279 transparent_frame_render_rects.translate_by(transition_offset);
1280 opaque_frame_render_rects.translate_by(transition_offset);
1281 }
1282 if (should_invalidate_old) {
1283 for (auto& rect : opaque_frame_render_rects.rects())
1284 invalidate_previous_render_rects(rect);
1285 for (auto& rect : transparent_frame_render_rects.rects())
1286 invalidate_previous_render_rects(rect);
1287 }
1288
1289 if (auto transparent_render_rects = transparent_frame_render_rects.intersected(remaining_visible_screen_rects); !transparent_render_rects.is_empty())
1290 transparency_rects = move(transparent_render_rects);
1291 if (auto opaque_render_rects = opaque_frame_render_rects.intersected(remaining_visible_screen_rects); !opaque_render_rects.is_empty())
1292 visible_opaque = move(opaque_render_rects);
1293
1294 auto render_rect_on_screen = w.frame().render_rect().translated(transition_offset);
1295 auto visible_window_rects = remaining_visible_screen_rects.intersected(w.rect().translated(transition_offset));
1296 Gfx::DisjointIntRectSet opaque_covering;
1297 Gfx::DisjointIntRectSet transparent_covering;
1298 bool found_this_window = false;
1299 wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
1300 if (!found_this_window) {
1301 if (&w == &w2)
1302 found_this_window = true;
1303 return IterationDecision::Continue;
1304 }
1305
1306 VERIFY(!w2.is_minimized());
1307
1308 auto w2_render_rect = w2.frame().render_rect();
1309 auto w2_render_rect_on_screen = w2_render_rect;
1310 auto w2_transition_offset = window_transition_offset(w2);
1311 if (window_stack_transition_in_progress)
1312 w2_render_rect_on_screen.translate_by(w2_transition_offset);
1313 if (!render_rect_on_screen.intersects(w2_render_rect_on_screen))
1314 return IterationDecision::Continue;
1315
1316 auto opaque_rects = w2.frame().opaque_render_rects();
1317 auto transparent_rects = w2.frame().transparent_render_rects();
1318 if (window_stack_transition_in_progress) {
1319 auto transition_offset_2 = window_transition_offset(w2);
1320 opaque_rects.translate_by(transition_offset_2);
1321 transparent_rects.translate_by(transition_offset_2);
1322 }
1323 opaque_rects = opaque_rects.intersected(render_rect_on_screen);
1324 transparent_rects = transparent_rects.intersected(render_rect_on_screen);
1325 if (opaque_rects.is_empty() && transparent_rects.is_empty())
1326 return IterationDecision::Continue;
1327 VERIFY(!opaque_rects.intersects(transparent_rects));
1328 for (auto& covering : opaque_rects.rects()) {
1329 opaque_covering.add(covering);
1330 if (!visible_window_rects.is_empty())
1331 visible_window_rects = visible_window_rects.shatter(covering);
1332 if (!visible_opaque.is_empty()) {
1333 auto uncovered_opaque = visible_opaque.shatter(covering);
1334 visible_opaque = move(uncovered_opaque);
1335 }
1336 if (!transparency_rects.is_empty()) {
1337 auto uncovered_transparency = transparency_rects.shatter(covering);
1338 transparency_rects = move(uncovered_transparency);
1339 }
1340 if (!transparent_covering.is_empty()) {
1341 auto uncovered_transparency = transparent_covering.shatter(covering);
1342 transparent_covering = move(uncovered_transparency);
1343 }
1344 }
1345 if (!transparent_rects.is_empty())
1346 transparent_covering.add(transparent_rects.shatter(opaque_covering));
1347 VERIFY(!transparent_covering.intersects(opaque_covering));
1348 return IterationDecision::Continue;
1349 });
1350 VERIFY(opaque_covering.is_empty() || render_rect_on_screen.contains(opaque_covering.rects()));
1351 if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(visible_opaque)) {
1352 // In order to render overlays flicker-free we need to force this area into the
1353 // temporary transparency rendering buffer
1354 transparent_covering.add(m_overlay_rects.intersected(visible_opaque));
1355 }
1356 if (!transparent_covering.is_empty()) {
1357 VERIFY(!transparent_covering.intersects(opaque_covering));
1358 transparency_rects.add(transparent_covering);
1359 if (!visible_opaque.is_empty()) {
1360 auto uncovered_opaque = visible_opaque.shatter(transparent_covering);
1361 visible_opaque = move(uncovered_opaque);
1362 }
1363
1364 // Now that we know what transparency rectangles are immediately covering our window
1365 // figure out what windows they belong to and add them to the affected transparency rects.
1366 // We can't do the same with the windows below as we haven't gotten to those yet. These
1367 // will be determined after we're done with this pass.
1368 found_this_window = false;
1369 wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
1370 if (!found_this_window) {
1371 if (&w == &w2)
1372 found_this_window = true;
1373 return IterationDecision::Continue;
1374 }
1375
1376 auto affected_transparency = transparent_covering.intersected(w2.transparency_rects());
1377 if (!affected_transparency.is_empty()) {
1378 auto result = affected_transparency_rects.set(&w2, move(affected_transparency));
1379 VERIFY(result == AK::HashSetResult::InsertedNewEntry);
1380 }
1381 return IterationDecision::Continue;
1382 });
1383 }
1384
1385 // This window should not be occluded while the window switcher is interested in it (depending
1386 // on the mode it's in). If it isn't then determine occlusions based on whether the window
1387 // rect has any visible areas at all.
1388 w.set_occluded(never_occlude(w.window_stack()) ? false : visible_window_rects.is_empty());
1389
1390 bool have_opaque = !visible_opaque.is_empty();
1391 if (!transparency_rects.is_empty())
1392 have_transparent = true;
1393 if (have_transparent || have_opaque) {
1394 // Figure out what screens this window is rendered on
1395 // We gather this information so we can more quickly
1396 // render the window on each of the screens that it
1397 // needs to be rendered on.
1398 Screen::for_each([&](auto& screen) {
1399 auto screen_rect = screen.rect();
1400 for (auto& r : visible_opaque.rects()) {
1401 if (r.intersects(screen_rect)) {
1402 w.screens().append(&screen);
1403 return IterationDecision::Continue;
1404 }
1405 }
1406 for (auto& r : transparency_rects.rects()) {
1407 if (r.intersects(screen_rect)) {
1408 w.screens().append(&screen);
1409 return IterationDecision::Continue;
1410 }
1411 }
1412 return IterationDecision::Continue;
1413 });
1414 }
1415
1416 if (!visible_opaque.is_empty()) {
1417 VERIFY(!visible_opaque.intersects(transparency_rects));
1418
1419 // Determine visible area for the window below
1420 remaining_visible_screen_rects = remaining_visible_screen_rects.shatter(visible_opaque);
1421 }
1422 return IterationDecision::Continue;
1423 });
1424
1425 if (have_transparent) {
1426 // Also, now that we have completed the first pass we can determine the affected
1427 // transparency rects below a given window
1428 wm.for_each_visible_window_from_back_to_front([&](Window& w) {
1429 // Any area left in remaining_visible_screen_rects will need to be rendered with the wallpaper first
1430 auto& transparency_rects = w.transparency_rects();
1431 auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
1432 if (transparency_rects.is_empty()) {
1433 VERIFY(transparency_wallpaper_rects.is_empty()); // Should have been cleared in the first pass
1434 } else {
1435 transparency_wallpaper_rects = remaining_visible_screen_rects.intersected(transparency_rects);
1436
1437 if (!transparency_wallpaper_rects.is_empty()) {
1438 auto remaining_visible = remaining_visible_screen_rects.shatter(transparency_wallpaper_rects);
1439 remaining_visible_screen_rects = move(remaining_visible);
1440 }
1441 }
1442
1443 // Figure out the affected transparency rects underneath. First figure out if any transparency is visible at all
1444 Gfx::DisjointIntRectSet transparent_underneath;
1445 wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
1446 if (&w == &w2)
1447 return IterationDecision::Break;
1448 auto& opaque_rects2 = w2.opaque_rects();
1449 if (!opaque_rects2.is_empty()) {
1450 auto uncovered_transparency = transparent_underneath.shatter(opaque_rects2);
1451 transparent_underneath = move(uncovered_transparency);
1452 }
1453 w2.transparency_rects().for_each_intersected(transparency_rects, [&](auto& rect) {
1454 transparent_underneath.add(rect);
1455 return IterationDecision::Continue;
1456 });
1457
1458 return IterationDecision::Continue;
1459 });
1460 if (!transparent_underneath.is_empty()) {
1461 // Now that we know there are some transparency rects underneath that are visible
1462 // figure out what windows they belong to
1463 auto& affected_transparency_rects = w.affected_transparency_rects();
1464 wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
1465 if (&w == &w2)
1466 return IterationDecision::Break;
1467 auto& transparency_rects2 = w2.transparency_rects();
1468 if (transparency_rects2.is_empty())
1469 return IterationDecision::Continue;
1470
1471 auto affected_transparency = transparent_underneath.intersected(transparency_rects2);
1472 if (!affected_transparency.is_empty()) {
1473 auto result = affected_transparency_rects.set(&w2, move(affected_transparency));
1474 VERIFY(result == AK::HashSetResult::InsertedNewEntry);
1475 }
1476 return IterationDecision::Continue;
1477 });
1478 }
1479 return IterationDecision::Continue;
1480 });
1481 }
1482
1483 m_transparent_wallpaper_rects.clear_with_capacity();
1484 if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(remaining_visible_screen_rects)) {
1485 // Check if any overlay rects are remaining that are not somehow above any windows
1486 m_transparent_wallpaper_rects = m_overlay_rects.intersected(remaining_visible_screen_rects);
1487 auto remaining_visible_not_covered = remaining_visible_screen_rects.shatter(m_overlay_rects);
1488 remaining_visible_screen_rects = move(remaining_visible_not_covered);
1489 }
1490
1491 m_opaque_wallpaper_rects = move(remaining_visible_screen_rects);
1492 }
1493
1494 if constexpr (OCCLUSIONS_DEBUG) {
1495 for (auto& r : m_opaque_wallpaper_rects.rects())
1496 dbgln(" wallpaper opaque: {}", r);
1497 }
1498
1499 wm.for_each_visible_window_from_back_to_front([&](Window& w) {
1500 auto window_frame_rect = w.frame().render_rect();
1501 if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty())
1502 return IterationDecision::Continue;
1503
1504 if constexpr (OCCLUSIONS_DEBUG) {
1505 dbgln(" Window {} frame rect: {} rendered on screens: {}", w.title(), window_frame_rect, w.screens().size());
1506 for (auto& s : w.screens())
1507 dbgln(" screen: #{}", s->index());
1508 for (auto& r : w.opaque_rects().rects())
1509 dbgln(" opaque: {}", r);
1510 for (auto& r : w.transparency_wallpaper_rects().rects())
1511 dbgln(" transparent wallpaper: {}", r);
1512 for (auto& r : w.transparency_rects().rects())
1513 dbgln(" transparent: {}", r);
1514 for (auto& it : w.affected_transparency_rects()) {
1515 dbgln(" affects {}:", it.key->title());
1516 for (auto& r : it.value.rects())
1517 dbgln(" transparent: {}", r);
1518 }
1519 }
1520
1521 VERIFY(!w.opaque_rects().intersects(m_opaque_wallpaper_rects));
1522 VERIFY(!w.transparency_rects().intersects(m_opaque_wallpaper_rects));
1523 VERIFY(!w.transparency_wallpaper_rects().intersects(m_opaque_wallpaper_rects));
1524 return IterationDecision::Continue;
1525 });
1526}
1527
1528void Compositor::register_animation(Badge<Animation>, Animation& animation)
1529{
1530 bool was_empty = m_animations.is_empty();
1531 auto result = m_animations.set(&animation);
1532 VERIFY(result == AK::HashSetResult::InsertedNewEntry);
1533 if (was_empty)
1534 start_compose_async_timer();
1535}
1536
1537void Compositor::animation_started(Badge<Animation>)
1538{
1539 m_invalidated_any = true;
1540 start_compose_async_timer();
1541}
1542
1543void Compositor::unregister_animation(Badge<Animation>, Animation& animation)
1544{
1545 bool was_removed = m_animations.remove(&animation);
1546 VERIFY(was_removed);
1547}
1548
1549void Compositor::update_animations(Screen& screen, Gfx::DisjointIntRectSet& flush_rects)
1550{
1551 auto& painter = *screen.compositor_screen_data().m_back_painter;
1552 // Iterating over the animations using remove_all_matching we can iterate
1553 // and immediately remove finished animations without having to keep track
1554 // of them in a separate container.
1555 m_animations.remove_all_matching([&](auto* animation) {
1556 if (!animation->update({}, painter, screen, flush_rects)) {
1557 // Mark it as removed so that the Animation::on_stop handler doesn't
1558 // trigger the Animation object from being destroyed, causing it to
1559 // unregister while we still loop over them.
1560 animation->was_removed({});
1561
1562 // Temporarily bump the ref count so that if the Animation::on_stop
1563 // handler clears its own reference, it doesn't immediately destroy
1564 // itself while we're still in the Function<> call
1565 NonnullRefPtr<Animation> protect_animation(*animation);
1566 animation->stop();
1567 return true;
1568 }
1569 return false;
1570 });
1571}
1572
1573void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack)
1574{
1575 stop_window_stack_switch_overlay_timer();
1576 Screen::for_each([&](auto& screen) {
1577 auto& screen_data = screen.compositor_screen_data();
1578 screen_data.m_window_stack_switch_overlay = nullptr; // delete it first
1579 screen_data.m_window_stack_switch_overlay = create_overlay<WindowStackSwitchOverlay>(screen, target_stack);
1580 screen_data.m_window_stack_switch_overlay->set_enabled(true);
1581 return IterationDecision::Continue;
1582 });
1583}
1584
1585void Compositor::remove_window_stack_switch_overlays()
1586{
1587 Screen::for_each([&](auto& screen) {
1588 screen.compositor_screen_data().m_window_stack_switch_overlay = nullptr;
1589 return IterationDecision::Continue;
1590 });
1591}
1592
1593void Compositor::stop_window_stack_switch_overlay_timer()
1594{
1595 if (m_stack_switch_overlay_timer) {
1596 // Cancel any timer, we're going to delete the overlay
1597 m_stack_switch_overlay_timer->stop();
1598 m_stack_switch_overlay_timer = nullptr;
1599 }
1600}
1601
1602void Compositor::start_window_stack_switch_overlay_timer()
1603{
1604 if (m_stack_switch_overlay_timer) {
1605 m_stack_switch_overlay_timer->stop();
1606 m_stack_switch_overlay_timer = nullptr;
1607 }
1608 bool have_overlay = false;
1609 Screen::for_each([&](auto& screen) {
1610 if (screen.compositor_screen_data().m_window_stack_switch_overlay) {
1611 have_overlay = true;
1612 return IterationDecision::Break;
1613 }
1614 return IterationDecision::Continue;
1615 });
1616 if (!have_overlay)
1617 return;
1618 m_stack_switch_overlay_timer = Core::Timer::create_single_shot(
1619 500,
1620 [this] {
1621 remove_window_stack_switch_overlays();
1622 },
1623 this)
1624 .release_value_but_fixme_should_propagate_errors();
1625 m_stack_switch_overlay_timer->start();
1626}
1627
1628void Compositor::finish_window_stack_switch()
1629{
1630 VERIFY(m_transitioning_to_window_stack);
1631 VERIFY(m_current_window_stack);
1632 VERIFY(m_transitioning_to_window_stack != m_current_window_stack);
1633
1634 m_current_window_stack->set_transition_offset({}, {});
1635 m_transitioning_to_window_stack->set_transition_offset({}, {});
1636
1637 auto* previous_window_stack = m_current_window_stack;
1638 m_current_window_stack = m_transitioning_to_window_stack;
1639 m_transitioning_to_window_stack = nullptr;
1640
1641 m_window_stack_transition_animation = nullptr;
1642
1643 auto& wm = WindowManager::the();
1644 if (!wm.m_switcher->is_visible())
1645 previous_window_stack->set_all_occluded(true);
1646 wm.did_switch_window_stack({}, *previous_window_stack, *m_current_window_stack);
1647
1648 invalidate_occlusions();
1649
1650 // Rather than invalidating the entire we could invalidate all render rectangles
1651 // that are affected by the transition offset before and after changing it.
1652 invalidate_screen();
1653
1654 start_window_stack_switch_overlay_timer();
1655}
1656
1657void Compositor::set_current_window_stack_no_transition(WindowStack& new_window_stack)
1658{
1659 if (m_transitioning_to_window_stack) {
1660 finish_window_stack_switch();
1661 VERIFY(!m_window_stack_transition_animation);
1662 VERIFY(!m_transitioning_to_window_stack);
1663 }
1664 if (m_current_window_stack == &new_window_stack)
1665 return;
1666 m_current_window_stack = &new_window_stack;
1667 invalidate_for_window_stack_merge_or_change();
1668}
1669
1670void Compositor::invalidate_for_window_stack_merge_or_change()
1671{
1672 invalidate_occlusions();
1673 invalidate_screen();
1674}
1675
1676void Compositor::switch_to_window_stack(WindowStack& new_window_stack, bool show_overlay)
1677{
1678 if (m_transitioning_to_window_stack) {
1679 if (m_transitioning_to_window_stack == &new_window_stack)
1680 return;
1681 // A switch is in progress, but the user is impatient. Finish the transition instantly
1682 finish_window_stack_switch();
1683 VERIFY(!m_window_stack_transition_animation);
1684 // Now switch to the next target as usual
1685 }
1686 VERIFY(m_current_window_stack);
1687
1688 if (&new_window_stack == m_current_window_stack) {
1689 // So that the user knows which stack they're on, show the overlay briefly
1690 if (show_overlay) {
1691 create_window_stack_switch_overlay(*m_current_window_stack);
1692 start_window_stack_switch_overlay_timer();
1693 } else {
1694 stop_window_stack_switch_overlay_timer();
1695 remove_window_stack_switch_overlays();
1696 }
1697 return;
1698 }
1699 VERIFY(!m_transitioning_to_window_stack);
1700 m_transitioning_to_window_stack = &new_window_stack;
1701
1702 auto window_stack_size = Screen::bounding_rect().size();
1703
1704 int delta_x = 0;
1705 if (new_window_stack.column() < m_current_window_stack->column())
1706 delta_x = window_stack_size.width();
1707 else if (new_window_stack.column() > m_current_window_stack->column())
1708 delta_x = -window_stack_size.width();
1709 int delta_y = 0;
1710 if (new_window_stack.row() < m_current_window_stack->row())
1711 delta_y = window_stack_size.height();
1712 else if (new_window_stack.row() > m_current_window_stack->row()) {
1713 delta_y = -window_stack_size.height();
1714 }
1715
1716 m_transitioning_to_window_stack->set_transition_offset({}, { -delta_x, -delta_y });
1717 m_current_window_stack->set_transition_offset({}, {});
1718
1719 if (show_overlay) {
1720 // We start the timer when the animation ends!
1721 create_window_stack_switch_overlay(*m_transitioning_to_window_stack);
1722 } else {
1723 stop_window_stack_switch_overlay_timer();
1724 remove_window_stack_switch_overlays();
1725 }
1726
1727 VERIFY(!m_window_stack_transition_animation);
1728 m_window_stack_transition_animation = Animation::create();
1729 m_window_stack_transition_animation->set_duration(250);
1730 m_window_stack_transition_animation->on_update = [this, delta_x, delta_y](float progress, Gfx::Painter&, Screen&, Gfx::DisjointIntRectSet&) {
1731 VERIFY(m_transitioning_to_window_stack);
1732 VERIFY(m_current_window_stack);
1733
1734 // Set transition offset for the window stack we're transitioning out of
1735 auto previous_transition_offset_from = m_current_window_stack->transition_offset();
1736 Gfx::IntPoint transition_offset_from { (float)delta_x * progress, (float)delta_y * progress };
1737 if (previous_transition_offset_from == transition_offset_from)
1738 return;
1739
1740 {
1741 // we need to render both, the existing dirty rectangles as well as where we're shifting to
1742 auto translated_dirty_rects = m_dirty_screen_rects.clone();
1743 auto transition_delta = transition_offset_from - previous_transition_offset_from;
1744 translated_dirty_rects.translate_by(transition_delta);
1745 m_dirty_screen_rects.add(translated_dirty_rects.intersected(Screen::bounding_rect()));
1746 }
1747 m_current_window_stack->set_transition_offset({}, transition_offset_from);
1748
1749 // Set transition offset for the window stack we're transitioning to
1750 Gfx::IntPoint transition_offset_to { (float)-delta_x * (1.0f - progress), (float)-delta_y * (1.0f - progress) };
1751 m_transitioning_to_window_stack->set_transition_offset({}, transition_offset_to);
1752
1753 invalidate_occlusions();
1754
1755 // Rather than invalidating the entire we could invalidate all render rectangles
1756 // that are affected by the transition offset before and after changing it.
1757 invalidate_screen();
1758 };
1759
1760 m_window_stack_transition_animation->on_stop = [this] {
1761 finish_window_stack_switch();
1762 };
1763 m_window_stack_transition_animation->start();
1764}
1765
1766}