Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "Compositor.h"
28#include "Event.h"
29#include "EventLoop.h"
30#include "Screen.h"
31#include "Window.h"
32#include "WindowManager.h"
33#include <LibCore/Timer.h>
34#include <LibGfx/Font.h>
35#include <LibGfx/Painter.h>
36#include <LibThread/BackgroundAction.h>
37
38// #define COMPOSITOR_DEBUG
39
40namespace WindowServer {
41
42Compositor& Compositor::the()
43{
44 static Compositor s_the;
45 return s_the;
46}
47
48WallpaperMode mode_to_enum(const String& name)
49{
50 if (name == "simple")
51 return WallpaperMode::Simple;
52 if (name == "tile")
53 return WallpaperMode::Tile;
54 if (name == "center")
55 return WallpaperMode::Center;
56 if (name == "scaled")
57 return WallpaperMode::Scaled;
58 return WallpaperMode::Simple;
59}
60
61Compositor::Compositor()
62{
63 m_compose_timer = add<Core::Timer>();
64 m_immediate_compose_timer = add<Core::Timer>();
65
66 m_screen_can_set_buffer = Screen::the().can_set_buffer();
67
68 init_bitmaps();
69
70 m_compose_timer->on_timeout = [&]() {
71#if defined(COMPOSITOR_DEBUG)
72 dbgprintf("Compositor: delayed frame callback: %d rects\n", m_dirty_rects.size());
73#endif
74 compose();
75 };
76 m_compose_timer->set_single_shot(true);
77 m_compose_timer->set_interval(1000 / 60);
78 m_immediate_compose_timer->on_timeout = [this]() {
79#if defined(COMPOSITOR_DEBUG)
80 dbgprintf("Compositor: immediate frame callback: %d rects\n", m_dirty_rects.size());
81#endif
82 compose();
83 };
84 m_immediate_compose_timer->set_single_shot(true);
85 m_immediate_compose_timer->set_interval(0);
86}
87
88void Compositor::init_bitmaps()
89{
90 auto& screen = Screen::the();
91 auto size = screen.size();
92
93 m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(0));
94
95 if (m_screen_can_set_buffer)
96 m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(size.height()));
97 else
98 m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, size);
99
100 m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
101 m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
102
103 m_buffers_are_flipped = false;
104
105 invalidate();
106}
107
108void Compositor::compose()
109{
110 auto& wm = WindowManager::the();
111 if (m_wallpaper_mode == WallpaperMode::Unchecked)
112 m_wallpaper_mode = mode_to_enum(wm.wm_config()->read_entry("Background", "Mode", "simple"));
113 auto& ws = Screen::the();
114
115 auto dirty_rects = move(m_dirty_rects);
116
117 if (dirty_rects.size() == 0) {
118 // nothing dirtied since the last compose pass.
119 return;
120 }
121
122 dirty_rects.add(Gfx::Rect::intersection(m_last_geometry_label_rect, Screen::the().rect()));
123 dirty_rects.add(Gfx::Rect::intersection(m_last_cursor_rect, Screen::the().rect()));
124 dirty_rects.add(Gfx::Rect::intersection(m_last_dnd_rect, Screen::the().rect()));
125 dirty_rects.add(Gfx::Rect::intersection(current_cursor_rect(), Screen::the().rect()));
126#ifdef DEBUG_COUNTERS
127 dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.rects().size());
128#endif
129
130 auto any_dirty_rect_intersects_window = [&dirty_rects](const Window& window) {
131 auto window_frame_rect = window.frame().rect();
132 for (auto& dirty_rect : dirty_rects.rects()) {
133 if (dirty_rect.intersects(window_frame_rect))
134 return true;
135 }
136 return false;
137 };
138
139 // Paint the wallpaper.
140 for (auto& dirty_rect : dirty_rects.rects()) {
141 if (wm.any_opaque_window_contains_rect(dirty_rect))
142 continue;
143 // FIXME: If the wallpaper is opaque, no need to fill with color!
144 m_back_painter->fill_rect(dirty_rect, wm.palette().desktop_background());
145 if (m_wallpaper) {
146 if (m_wallpaper_mode == WallpaperMode::Simple) {
147 m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
148 } else if (m_wallpaper_mode == WallpaperMode::Center) {
149 Gfx::Point offset { ws.size().width() / 2 - m_wallpaper->size().width() / 2,
150 ws.size().height() / 2 - m_wallpaper->size().height() / 2 };
151 m_back_painter->blit_offset(dirty_rect.location(), *m_wallpaper,
152 dirty_rect, offset);
153 } else if (m_wallpaper_mode == WallpaperMode::Tile) {
154 m_back_painter->draw_tiled_bitmap(dirty_rect, *m_wallpaper);
155 } else if (m_wallpaper_mode == WallpaperMode::Scaled) {
156 float hscale = (float)m_wallpaper->size().width() / (float)ws.size().width();
157 float vscale = (float)m_wallpaper->size().height() / (float)ws.size().height();
158
159 m_back_painter->blit_scaled(dirty_rect, *m_wallpaper, dirty_rect, hscale, vscale);
160 } else {
161 ASSERT_NOT_REACHED();
162 }
163 }
164 }
165
166 auto compose_window = [&](Window& window) -> IterationDecision {
167 if (!any_dirty_rect_intersects_window(window))
168 return IterationDecision::Continue;
169 Gfx::PainterStateSaver saver(*m_back_painter);
170 m_back_painter->add_clip_rect(window.frame().rect());
171 RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
172 for (auto& dirty_rect : dirty_rects.rects()) {
173 if (wm.any_opaque_window_above_this_one_contains_rect(window, dirty_rect))
174 continue;
175 Gfx::PainterStateSaver saver(*m_back_painter);
176 m_back_painter->add_clip_rect(dirty_rect);
177 if (!backing_store)
178 m_back_painter->fill_rect(dirty_rect, wm.palette().window());
179 if (!window.is_fullscreen())
180 window.frame().paint(*m_back_painter);
181 if (!backing_store)
182 continue;
183
184 // Decide where we would paint this window's backing store.
185 // This is subtly different from widow.rect(), because window
186 // size may be different from its backing store size. This
187 // happens when the window has been resized and the client
188 // has not yet attached a new backing store. In this case,
189 // we want to try to blit the backing store at the same place
190 // it was previously, and fill the rest of the window with its
191 // background color.
192 Gfx::Rect backing_rect;
193 backing_rect.set_size(backing_store->size());
194 switch (WindowManager::the().resize_direction_of_window(window)) {
195 case ResizeDirection::None:
196 case ResizeDirection::Right:
197 case ResizeDirection::Down:
198 case ResizeDirection::DownRight:
199 backing_rect.set_location(window.rect().location());
200 break;
201 case ResizeDirection::Left:
202 case ResizeDirection::Up:
203 case ResizeDirection::UpLeft:
204 backing_rect.set_right_without_resize(window.rect().right());
205 backing_rect.set_bottom_without_resize(window.rect().bottom());
206 break;
207 case ResizeDirection::UpRight:
208 backing_rect.set_left(window.rect().left());
209 backing_rect.set_bottom_without_resize(window.rect().bottom());
210 break;
211 case ResizeDirection::DownLeft:
212 backing_rect.set_right_without_resize(window.rect().right());
213 backing_rect.set_top(window.rect().top());
214 break;
215 }
216
217 Gfx::Rect dirty_rect_in_backing_coordinates = dirty_rect
218 .intersected(window.rect())
219 .intersected(backing_rect)
220 .translated(-backing_rect.location());
221
222 if (dirty_rect_in_backing_coordinates.is_empty())
223 continue;
224 auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
225
226 m_back_painter->blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
227 for (auto background_rect : window.rect().shatter(backing_rect))
228 m_back_painter->fill_rect(background_rect, wm.palette().window());
229 }
230 return IterationDecision::Continue;
231 };
232
233 // Paint the window stack.
234 if (auto* fullscreen_window = wm.active_fullscreen_window()) {
235 compose_window(*fullscreen_window);
236 } else {
237 wm.for_each_visible_window_from_back_to_front([&](Window& window) {
238 return compose_window(window);
239 });
240
241 draw_geometry_label();
242 }
243
244 run_animations();
245
246 draw_cursor();
247
248 if (m_flash_flush) {
249 for (auto& rect : dirty_rects.rects())
250 m_front_painter->fill_rect(rect, Color::Yellow);
251 }
252
253 if (m_screen_can_set_buffer)
254 flip_buffers();
255
256 for (auto& r : dirty_rects.rects())
257 flush(r);
258}
259
260void Compositor::flush(const Gfx::Rect& a_rect)
261{
262 auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
263
264#ifdef DEBUG_COUNTERS
265 dbgprintf("[WM] flush #%u (%d,%d %dx%d)\n", ++m_flush_count, rect.x(), rect.y(), rect.width(), rect.height());
266#endif
267
268 Gfx::RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
269 Gfx::RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
270 size_t pitch = m_back_bitmap->pitch();
271
272 // NOTE: The meaning of a flush depends on whether we can flip buffers or not.
273 //
274 // If flipping is supported, flushing means that we've flipped, and now we
275 // copy the changed bits from the front buffer to the back buffer, to keep
276 // them in sync.
277 //
278 // If flipping is not supported, flushing means that we copy the changed
279 // rects from the backing bitmap to the display framebuffer.
280
281 Gfx::RGBA32* to_ptr;
282 const Gfx::RGBA32* from_ptr;
283
284 if (m_screen_can_set_buffer) {
285 to_ptr = back_ptr;
286 from_ptr = front_ptr;
287 } else {
288 to_ptr = front_ptr;
289 from_ptr = back_ptr;
290 }
291
292 for (int y = 0; y < rect.height(); ++y) {
293 fast_u32_copy(to_ptr, from_ptr, rect.width());
294 from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
295 to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
296 }
297}
298
299void Compositor::invalidate()
300{
301 m_dirty_rects.clear_with_capacity();
302 invalidate(Screen::the().rect());
303}
304
305void Compositor::invalidate(const Gfx::Rect& a_rect)
306{
307 auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
308 if (rect.is_empty())
309 return;
310
311 m_dirty_rects.add(rect);
312
313 // We delay composition by a timer interval, but to not affect latency too
314 // much, if a pending compose is not already scheduled, we also schedule an
315 // immediate compose the next spin of the event loop.
316 if (!m_compose_timer->is_active()) {
317#if defined(COMPOSITOR_DEBUG)
318 dbgprintf("Invalidated (starting immediate frame): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
319#endif
320 m_compose_timer->start();
321 m_immediate_compose_timer->start();
322 } else {
323#if defined(COMPOSITOR_DEBUG)
324 dbgprintf("Invalidated (frame callback pending): %dx%d %dx%d\n", a_rect.x(), a_rect.y(), a_rect.width(), a_rect.height());
325#endif
326 }
327}
328
329bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
330{
331 LibThread::BackgroundAction<RefPtr<Gfx::Bitmap>>::create(
332 [path] {
333 return Gfx::Bitmap::load_from_file(path);
334 },
335
336 [this, path, callback = move(callback)](RefPtr<Gfx::Bitmap> bitmap) {
337 if (!bitmap) {
338 callback(false);
339 return;
340 }
341 m_wallpaper_path = path;
342 m_wallpaper = move(bitmap);
343 invalidate();
344 callback(true);
345 });
346 return true;
347}
348
349void Compositor::flip_buffers()
350{
351 ASSERT(m_screen_can_set_buffer);
352 swap(m_front_bitmap, m_back_bitmap);
353 swap(m_front_painter, m_back_painter);
354 Screen::the().set_buffer(m_buffers_are_flipped ? 0 : 1);
355 m_buffers_are_flipped = !m_buffers_are_flipped;
356}
357
358void Compositor::run_animations()
359{
360 static const int minimize_animation_steps = 10;
361
362 WindowManager::the().for_each_window([&](Window& window) {
363 if (window.in_minimize_animation()) {
364 int animation_index = window.minimize_animation_index();
365
366 auto from_rect = window.is_minimized() ? window.frame().rect() : window.taskbar_rect();
367 auto to_rect = window.is_minimized() ? window.taskbar_rect() : window.frame().rect();
368
369 float x_delta_per_step = (float)(from_rect.x() - to_rect.x()) / minimize_animation_steps;
370 float y_delta_per_step = (float)(from_rect.y() - to_rect.y()) / minimize_animation_steps;
371 float width_delta_per_step = (float)(from_rect.width() - to_rect.width()) / minimize_animation_steps;
372 float height_delta_per_step = (float)(from_rect.height() - to_rect.height()) / minimize_animation_steps;
373
374 Gfx::Rect rect {
375 from_rect.x() - (int)(x_delta_per_step * animation_index),
376 from_rect.y() - (int)(y_delta_per_step * animation_index),
377 from_rect.width() - (int)(width_delta_per_step * animation_index),
378 from_rect.height() - (int)(height_delta_per_step * animation_index)
379 };
380
381#ifdef MINIMIZE_ANIMATION_DEBUG
382 dbg() << "Minimize animation from " << from_rect << " to " << to_rect << " frame# " << animation_index << " " << rect;
383#endif
384
385 m_back_painter->draw_rect(rect, Color::White);
386
387 window.step_minimize_animation();
388 if (window.minimize_animation_index() >= minimize_animation_steps)
389 window.end_minimize_animation();
390
391 invalidate(rect);
392 }
393 return IterationDecision::Continue;
394 });
395}
396
397void Compositor::set_resolution(int desired_width, int desired_height)
398{
399 auto screen_rect = Screen::the().rect();
400 if (screen_rect.width() == desired_width && screen_rect.height() == desired_height)
401 return;
402
403 // Make sure it's impossible to set an invalid resolution
404 ASSERT(desired_width >= 640 && desired_height >= 480);
405 Screen::the().set_resolution(desired_width, desired_height);
406 init_bitmaps();
407 compose();
408}
409
410Gfx::Rect Compositor::current_cursor_rect() const
411{
412 auto& wm = WindowManager::the();
413 return { Screen::the().cursor_location().translated(-wm.active_cursor().hotspot()), wm.active_cursor().size() };
414}
415
416void Compositor::invalidate_cursor()
417{
418 auto& wm = WindowManager::the();
419 if (wm.dnd_client())
420 invalidate(wm.dnd_rect());
421 invalidate(current_cursor_rect());
422}
423
424void Compositor::draw_geometry_label()
425{
426 auto& wm = WindowManager::the();
427 auto* window_being_moved_or_resized = wm.m_move_window ? wm.m_move_window.ptr() : (wm.m_resize_window ? wm.m_resize_window.ptr() : nullptr);
428 if (!window_being_moved_or_resized) {
429 m_last_geometry_label_rect = {};
430 return;
431 }
432 auto geometry_string = window_being_moved_or_resized->rect().to_string();
433 if (!window_being_moved_or_resized->size_increment().is_null()) {
434 int width_steps = (window_being_moved_or_resized->width() - window_being_moved_or_resized->base_size().width()) / window_being_moved_or_resized->size_increment().width();
435 int height_steps = (window_being_moved_or_resized->height() - window_being_moved_or_resized->base_size().height()) / window_being_moved_or_resized->size_increment().height();
436 geometry_string = String::format("%s (%dx%d)", geometry_string.characters(), width_steps, height_steps);
437 }
438 auto geometry_label_rect = Gfx::Rect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
439 geometry_label_rect.center_within(window_being_moved_or_resized->rect());
440 m_back_painter->fill_rect(geometry_label_rect, wm.palette().window());
441 m_back_painter->draw_rect(geometry_label_rect, wm.palette().threed_shadow2());
442 m_back_painter->draw_text(geometry_label_rect, geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
443 m_last_geometry_label_rect = geometry_label_rect;
444}
445
446void Compositor::draw_cursor()
447{
448 auto& wm = WindowManager::the();
449 Gfx::Rect cursor_rect = current_cursor_rect();
450 m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
451
452 if (wm.dnd_client()) {
453 auto dnd_rect = wm.dnd_rect();
454 m_back_painter->fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
455 if (!wm.dnd_text().is_empty()) {
456 auto text_rect = dnd_rect;
457 if (wm.dnd_bitmap())
458 text_rect.move_by(wm.dnd_bitmap()->width(), 0);
459 m_back_painter->draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
460 }
461 if (wm.dnd_bitmap()) {
462 m_back_painter->blit(dnd_rect.top_left(), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
463 }
464 m_last_dnd_rect = dnd_rect;
465 } else {
466 m_last_dnd_rect = {};
467 }
468 m_last_cursor_rect = cursor_rect;
469}
470
471}