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 "ClientConnection.h"
29#include "Event.h"
30#include "EventLoop.h"
31#include "Screen.h"
32#include "Window.h"
33#include "WindowManager.h"
34#include <AK/Memory.h>
35#include <LibCore/Timer.h>
36#include <LibGfx/Font.h>
37#include <LibGfx/Painter.h>
38#include <LibThread/BackgroundAction.h>
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 = Core::Timer::create_single_shot(
64 1000 / 60,
65 [this] {
66 notify_display_links();
67 compose();
68 },
69 this);
70
71 m_immediate_compose_timer = Core::Timer::create_single_shot(
72 0,
73 [this] {
74 compose();
75 },
76 this);
77
78 m_screen_can_set_buffer = Screen::the().can_set_buffer();
79 init_bitmaps();
80}
81
82void Compositor::init_bitmaps()
83{
84 auto& screen = Screen::the();
85 auto size = screen.size();
86
87 m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(0));
88
89 if (m_screen_can_set_buffer)
90 m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(size.height()));
91 else
92 m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, size);
93
94 m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
95 m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
96
97 m_buffers_are_flipped = false;
98
99 invalidate();
100}
101
102void Compositor::compose()
103{
104 auto& wm = WindowManager::the();
105 if (m_wallpaper_mode == WallpaperMode::Unchecked)
106 m_wallpaper_mode = mode_to_enum(wm.wm_config()->read_entry("Background", "Mode", "simple"));
107 auto& ws = Screen::the();
108
109 auto dirty_rects = move(m_dirty_rects);
110
111 if (dirty_rects.size() == 0) {
112 // nothing dirtied since the last compose pass.
113 return;
114 }
115
116 dirty_rects.add(Gfx::Rect::intersection(m_last_geometry_label_rect, Screen::the().rect()));
117 dirty_rects.add(Gfx::Rect::intersection(m_last_cursor_rect, Screen::the().rect()));
118 dirty_rects.add(Gfx::Rect::intersection(m_last_dnd_rect, Screen::the().rect()));
119 dirty_rects.add(Gfx::Rect::intersection(current_cursor_rect(), Screen::the().rect()));
120
121 auto any_dirty_rect_intersects_window = [&dirty_rects](const Window& window) {
122 auto window_frame_rect = window.frame().rect();
123 for (auto& dirty_rect : dirty_rects.rects()) {
124 if (dirty_rect.intersects(window_frame_rect))
125 return true;
126 }
127 return false;
128 };
129
130 Color background_color = wm.palette().desktop_background();
131 String background_color_entry = wm.wm_config()->read_entry("Background", "Color", "");
132 if (!background_color_entry.is_empty()) {
133 background_color = Color::from_string(background_color_entry).value_or(background_color);
134 }
135
136 // Paint the wallpaper.
137 for (auto& dirty_rect : dirty_rects.rects()) {
138 if (wm.any_opaque_window_contains_rect(dirty_rect))
139 continue;
140 // FIXME: If the wallpaper is opaque, no need to fill with color!
141 m_back_painter->fill_rect(dirty_rect, background_color);
142 if (m_wallpaper) {
143 if (m_wallpaper_mode == WallpaperMode::Simple) {
144 m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
145 } else if (m_wallpaper_mode == WallpaperMode::Center) {
146 Gfx::Point offset { ws.size().width() / 2 - m_wallpaper->size().width() / 2,
147 ws.size().height() / 2 - m_wallpaper->size().height() / 2 };
148 m_back_painter->blit_offset(dirty_rect.location(), *m_wallpaper,
149 dirty_rect, offset);
150 } else if (m_wallpaper_mode == WallpaperMode::Tile) {
151 m_back_painter->draw_tiled_bitmap(dirty_rect, *m_wallpaper);
152 } else if (m_wallpaper_mode == WallpaperMode::Scaled) {
153 float hscale = (float)m_wallpaper->size().width() / (float)ws.size().width();
154 float vscale = (float)m_wallpaper->size().height() / (float)ws.size().height();
155
156 m_back_painter->blit_scaled(dirty_rect, *m_wallpaper, dirty_rect, hscale, vscale);
157 } else {
158 ASSERT_NOT_REACHED();
159 }
160 }
161 }
162
163 auto compose_window = [&](Window& window) -> IterationDecision {
164 if (!any_dirty_rect_intersects_window(window))
165 return IterationDecision::Continue;
166 Gfx::PainterStateSaver saver(*m_back_painter);
167 m_back_painter->add_clip_rect(window.frame().rect());
168 RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
169 for (auto& dirty_rect : dirty_rects.rects()) {
170 if (wm.any_opaque_window_above_this_one_contains_rect(window, dirty_rect))
171 continue;
172 Gfx::PainterStateSaver saver(*m_back_painter);
173 m_back_painter->add_clip_rect(dirty_rect);
174 if (!backing_store)
175 m_back_painter->fill_rect(dirty_rect, wm.palette().window());
176 if (!window.is_fullscreen())
177 window.frame().paint(*m_back_painter);
178 if (!backing_store)
179 continue;
180
181 // Decide where we would paint this window's backing store.
182 // This is subtly different from widow.rect(), because window
183 // size may be different from its backing store size. This
184 // happens when the window has been resized and the client
185 // has not yet attached a new backing store. In this case,
186 // we want to try to blit the backing store at the same place
187 // it was previously, and fill the rest of the window with its
188 // background color.
189 Gfx::Rect backing_rect;
190 backing_rect.set_size(backing_store->size());
191 switch (WindowManager::the().resize_direction_of_window(window)) {
192 case ResizeDirection::None:
193 case ResizeDirection::Right:
194 case ResizeDirection::Down:
195 case ResizeDirection::DownRight:
196 backing_rect.set_location(window.rect().location());
197 break;
198 case ResizeDirection::Left:
199 case ResizeDirection::Up:
200 case ResizeDirection::UpLeft:
201 backing_rect.set_right_without_resize(window.rect().right());
202 backing_rect.set_bottom_without_resize(window.rect().bottom());
203 break;
204 case ResizeDirection::UpRight:
205 backing_rect.set_left(window.rect().left());
206 backing_rect.set_bottom_without_resize(window.rect().bottom());
207 break;
208 case ResizeDirection::DownLeft:
209 backing_rect.set_right_without_resize(window.rect().right());
210 backing_rect.set_top(window.rect().top());
211 break;
212 }
213
214 Gfx::Rect dirty_rect_in_backing_coordinates = dirty_rect
215 .intersected(window.rect())
216 .intersected(backing_rect)
217 .translated(-backing_rect.location());
218
219 if (dirty_rect_in_backing_coordinates.is_empty())
220 continue;
221 auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
222
223 m_back_painter->blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
224 for (auto background_rect : window.rect().shatter(backing_rect))
225 m_back_painter->fill_rect(background_rect, wm.palette().window());
226 }
227 return IterationDecision::Continue;
228 };
229
230 // Paint the window stack.
231 if (auto* fullscreen_window = wm.active_fullscreen_window()) {
232 compose_window(*fullscreen_window);
233 } else {
234 wm.for_each_visible_window_from_back_to_front([&](Window& window) {
235 return compose_window(window);
236 });
237
238 draw_geometry_label();
239 }
240
241 run_animations();
242
243 draw_cursor();
244
245 if (m_flash_flush) {
246 for (auto& rect : dirty_rects.rects())
247 m_front_painter->fill_rect(rect, Color::Yellow);
248 }
249
250 if (m_screen_can_set_buffer)
251 flip_buffers();
252
253 for (auto& r : dirty_rects.rects())
254 flush(r);
255}
256
257void Compositor::flush(const Gfx::Rect& a_rect)
258{
259 auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
260
261 Gfx::RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
262 Gfx::RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
263 size_t pitch = m_back_bitmap->pitch();
264
265 // NOTE: The meaning of a flush depends on whether we can flip buffers or not.
266 //
267 // If flipping is supported, flushing means that we've flipped, and now we
268 // copy the changed bits from the front buffer to the back buffer, to keep
269 // them in sync.
270 //
271 // If flipping is not supported, flushing means that we copy the changed
272 // rects from the backing bitmap to the display framebuffer.
273
274 Gfx::RGBA32* to_ptr;
275 const Gfx::RGBA32* from_ptr;
276
277 if (m_screen_can_set_buffer) {
278 to_ptr = back_ptr;
279 from_ptr = front_ptr;
280 } else {
281 to_ptr = front_ptr;
282 from_ptr = back_ptr;
283 }
284
285 for (int y = 0; y < rect.height(); ++y) {
286 fast_u32_copy(to_ptr, from_ptr, rect.width());
287 from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
288 to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
289 }
290}
291
292void Compositor::invalidate()
293{
294 m_dirty_rects.clear_with_capacity();
295 invalidate(Screen::the().rect());
296}
297
298void Compositor::invalidate(const Gfx::Rect& a_rect)
299{
300 auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
301 if (rect.is_empty())
302 return;
303
304 m_dirty_rects.add(rect);
305
306 // We delay composition by a timer interval, but to not affect latency too
307 // much, if a pending compose is not already scheduled, we also schedule an
308 // immediate compose the next spin of the event loop.
309 if (!m_compose_timer->is_active()) {
310 m_compose_timer->start();
311 m_immediate_compose_timer->start();
312 }
313}
314
315bool Compositor::set_backgound_color(const String& background_color)
316{
317 auto& wm = WindowManager::the();
318 wm.wm_config()->write_entry("Background", "Color", background_color);
319 bool ret_val = wm.wm_config()->sync();
320
321 if (ret_val)
322 Compositor::invalidate();
323
324 return ret_val;
325}
326
327bool Compositor::set_wallpaper_mode(const String& mode)
328{
329 auto& wm = WindowManager::the();
330 wm.wm_config()->write_entry("Background", "Mode", mode);
331 bool ret_val = wm.wm_config()->sync();
332
333 if (ret_val) {
334 m_wallpaper_mode = mode_to_enum(mode);
335 Compositor::invalidate();
336 }
337
338 return ret_val;
339}
340
341bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
342{
343 LibThread::BackgroundAction<RefPtr<Gfx::Bitmap>>::create(
344 [path] {
345 return Gfx::Bitmap::load_from_file(path);
346 },
347
348 [this, path, callback = move(callback)](RefPtr<Gfx::Bitmap> bitmap) {
349 if (!bitmap) {
350 callback(false);
351 return;
352 }
353 m_wallpaper_path = path;
354 m_wallpaper = move(bitmap);
355 invalidate();
356 callback(true);
357 });
358 return true;
359}
360
361void Compositor::flip_buffers()
362{
363 ASSERT(m_screen_can_set_buffer);
364 swap(m_front_bitmap, m_back_bitmap);
365 swap(m_front_painter, m_back_painter);
366 Screen::the().set_buffer(m_buffers_are_flipped ? 0 : 1);
367 m_buffers_are_flipped = !m_buffers_are_flipped;
368}
369
370void Compositor::run_animations()
371{
372 static const int minimize_animation_steps = 10;
373
374 WindowManager::the().for_each_window([&](Window& window) {
375 if (window.in_minimize_animation()) {
376 int animation_index = window.minimize_animation_index();
377
378 auto from_rect = window.is_minimized() ? window.frame().rect() : window.taskbar_rect();
379 auto to_rect = window.is_minimized() ? window.taskbar_rect() : window.frame().rect();
380
381 float x_delta_per_step = (float)(from_rect.x() - to_rect.x()) / minimize_animation_steps;
382 float y_delta_per_step = (float)(from_rect.y() - to_rect.y()) / minimize_animation_steps;
383 float width_delta_per_step = (float)(from_rect.width() - to_rect.width()) / minimize_animation_steps;
384 float height_delta_per_step = (float)(from_rect.height() - to_rect.height()) / minimize_animation_steps;
385
386 Gfx::Rect rect {
387 from_rect.x() - (int)(x_delta_per_step * animation_index),
388 from_rect.y() - (int)(y_delta_per_step * animation_index),
389 from_rect.width() - (int)(width_delta_per_step * animation_index),
390 from_rect.height() - (int)(height_delta_per_step * animation_index)
391 };
392
393#ifdef MINIMIZE_ANIMATION_DEBUG
394 dbg() << "Minimize animation from " << from_rect << " to " << to_rect << " frame# " << animation_index << " " << rect;
395#endif
396
397 m_back_painter->draw_rect(rect, Color::White);
398
399 window.step_minimize_animation();
400 if (window.minimize_animation_index() >= minimize_animation_steps)
401 window.end_minimize_animation();
402
403 invalidate(rect);
404 }
405 return IterationDecision::Continue;
406 });
407}
408
409bool Compositor::set_resolution(int desired_width, int desired_height)
410{
411 auto screen_rect = Screen::the().rect();
412 if (screen_rect.width() == desired_width && screen_rect.height() == desired_height)
413 return true;
414
415 // Make sure it's impossible to set an invalid resolution
416 ASSERT(desired_width >= 640 && desired_height >= 480);
417 bool success = Screen::the().set_resolution(desired_width, desired_height);
418 init_bitmaps();
419 compose();
420 return success;
421}
422
423Gfx::Rect Compositor::current_cursor_rect() const
424{
425 auto& wm = WindowManager::the();
426 return { Screen::the().cursor_location().translated(-wm.active_cursor().hotspot()), wm.active_cursor().size() };
427}
428
429void Compositor::invalidate_cursor()
430{
431 auto& wm = WindowManager::the();
432 if (wm.dnd_client())
433 invalidate(wm.dnd_rect());
434 invalidate(current_cursor_rect());
435}
436
437void Compositor::draw_geometry_label()
438{
439 auto& wm = WindowManager::the();
440 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);
441 if (!window_being_moved_or_resized) {
442 m_last_geometry_label_rect = {};
443 return;
444 }
445 auto geometry_string = window_being_moved_or_resized->rect().to_string();
446 if (!window_being_moved_or_resized->size_increment().is_null()) {
447 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();
448 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();
449 geometry_string = String::format("%s (%dx%d)", geometry_string.characters(), width_steps, height_steps);
450 }
451 auto geometry_label_rect = Gfx::Rect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
452 geometry_label_rect.center_within(window_being_moved_or_resized->rect());
453 m_back_painter->fill_rect(geometry_label_rect, wm.palette().window());
454 m_back_painter->draw_rect(geometry_label_rect, wm.palette().threed_shadow2());
455 m_back_painter->draw_text(geometry_label_rect, geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
456 m_last_geometry_label_rect = geometry_label_rect;
457}
458
459void Compositor::draw_cursor()
460{
461 auto& wm = WindowManager::the();
462 Gfx::Rect cursor_rect = current_cursor_rect();
463 m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
464
465 if (wm.dnd_client()) {
466 auto dnd_rect = wm.dnd_rect();
467 m_back_painter->fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
468 if (!wm.dnd_text().is_empty()) {
469 auto text_rect = dnd_rect;
470 if (wm.dnd_bitmap())
471 text_rect.move_by(wm.dnd_bitmap()->width(), 0);
472 m_back_painter->draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
473 }
474 if (wm.dnd_bitmap()) {
475 m_back_painter->blit(dnd_rect.top_left(), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
476 }
477 m_last_dnd_rect = dnd_rect;
478 } else {
479 m_last_dnd_rect = {};
480 }
481 m_last_cursor_rect = cursor_rect;
482}
483
484void Compositor::notify_display_links()
485{
486 ClientConnection::for_each_client([](auto& client) {
487 client.notify_display_link({});
488 });
489}
490
491}