Serenity Operating System
at hosted 491 lines 19 kB view raw
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}