Serenity Operating System
at portability 471 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 "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}