diff options
author | Tom <tomut@yahoo.com> | 2021-06-13 06:16:06 -0600 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-20 14:57:26 +0200 |
commit | 4392da970ac0fc433530579b13030a41ff4a8f59 (patch) | |
tree | cfae0a3ddc1b6c4f2db9f4cf7984eba761f3b5f0 /Userland/Services/WindowServer | |
parent | 499c33ae0cf8892dfdf851c178b2b9f7a8bfb79f (diff) | |
download | serenity-4392da970ac0fc433530579b13030a41ff4a8f59.zip |
WindowServer: Add initial support for rendering on multiple screens
This allows WindowServer to use multiple framebuffer devices and
compose the desktop with any arbitrary layout. Currently, it is assumed
that it is configured contiguous and non-overlapping, but this should
eventually be enforced.
To make rendering efficient, each window now also tracks on which
screens it needs to be rendered. This way we don't have to iterate all
the windows for each screen but instead use the same rendering loop and
then only render to the screen (or screens) that the window actually
uses.
Diffstat (limited to 'Userland/Services/WindowServer')
19 files changed, 1044 insertions, 503 deletions
diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index 3ae815316b..9aa68c523e 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -53,7 +53,7 @@ ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socke s_connections = new HashMap<int, NonnullRefPtr<ClientConnection>>; s_connections->set(client_id, *this); - async_fast_greet(Screen::the().rect(), Gfx::current_system_theme_buffer(), Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query()); + async_fast_greet(Screen::rects(), Screen::main().index(), Gfx::current_system_theme_buffer(), Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query()); } ClientConnection::~ClientConnection() @@ -77,9 +77,9 @@ void ClientConnection::die() }); } -void ClientConnection::notify_about_new_screen_rect(Gfx::IntRect const& rect) +void ClientConnection::notify_about_new_screen_rects(Vector<Gfx::IntRect, 4> const& rects, size_t main_screen_index) { - async_screen_rect_changed(rect); + async_screen_rects_changed(rects, main_screen_index); } void ClientConnection::create_menubar(i32 menubar_id) @@ -297,9 +297,14 @@ Messages::WindowServer::GetWallpaperResponse ClientConnection::get_wallpaper() return Compositor::the().wallpaper_path(); } -Messages::WindowServer::SetResolutionResponse ClientConnection::set_resolution(Gfx::IntSize const& resolution, int scale_factor) +Messages::WindowServer::SetResolutionResponse ClientConnection::set_resolution(u32 screen_index, Gfx::IntSize const& resolution, int scale_factor) { - return { WindowManager::the().set_resolution(resolution.width(), resolution.height(), scale_factor), WindowManager::the().resolution(), WindowManager::the().scale_factor() }; + if (auto* screen = Screen::find_by_index(screen_index)) { + bool success = WindowManager::the().set_resolution(*screen, resolution.width(), resolution.height(), scale_factor); + return { success, screen->size(), screen->scale_factor() }; + } + dbgln("Setting resolution: Invalid screen index {}", screen_index); + return { false, {}, 0 }; } void ClientConnection::set_window_title(i32 window_id, String const& title) @@ -384,7 +389,7 @@ Messages::WindowServer::SetWindowRectResponse ClientConnection::set_window_rect( auto new_rect = rect; window.apply_minimum_size(new_rect); window.set_rect(new_rect); - window.nudge_into_desktop(); + window.nudge_into_desktop(nullptr); window.request_update(window.rect()); return window.rect(); } @@ -419,7 +424,7 @@ void ClientConnection::set_window_minimum_size(i32 window_id, Gfx::IntSize const auto new_rect = window.rect(); bool did_size_clamp = window.apply_minimum_size(new_rect); window.set_rect(new_rect); - window.nudge_into_desktop(); + window.nudge_into_desktop(nullptr); window.request_update(window.rect()); if (did_size_clamp) @@ -498,13 +503,13 @@ void ClientConnection::create_window(i32 window_id, Gfx::IntRect const& rect, window->set_minimum_size(minimum_size); bool did_size_clamp = window->apply_minimum_size(new_rect); window->set_rect(new_rect); - window->nudge_into_desktop(); + window->nudge_into_desktop(nullptr); if (did_size_clamp) window->refresh_client_size(); } if (window->type() == WindowType::Desktop) { - window->set_rect(WindowManager::the().desktop_rect()); + window->set_rect(Screen::bounding_rect()); window->recalculate_rect(); } window->set_opacity(opacity); @@ -706,7 +711,7 @@ void ClientConnection::start_window_resize(i32 window_id) } // FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button. // Maybe the client should be allowed to specify what initiated this request? - WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left); + WindowManager::the().start_window_resize(window, ScreenInput::the().cursor_location(), MouseButton::Left); } Messages::WindowServer::StartDragResponse ClientConnection::start_drag(String const& text, HashMap<String, ByteBuffer> const& mime_data, Gfx::ShareableBitmap const& drag_bitmap) @@ -830,7 +835,7 @@ void ClientConnection::pong() Messages::WindowServer::GetGlobalCursorPositionResponse ClientConnection::get_global_cursor_position() { - return Screen::the().cursor_location(); + return ScreenInput::the().cursor_location(); } void ClientConnection::set_mouse_acceleration(float factor) @@ -845,7 +850,7 @@ void ClientConnection::set_mouse_acceleration(float factor) Messages::WindowServer::GetMouseAccelerationResponse ClientConnection::get_mouse_acceleration() { - return Screen::the().acceleration_factor(); + return ScreenInput::the().acceleration_factor(); } void ClientConnection::set_scroll_step_size(u32 step_size) @@ -859,7 +864,7 @@ void ClientConnection::set_scroll_step_size(u32 step_size) Messages::WindowServer::GetScrollStepSizeResponse ClientConnection::get_scroll_step_size() { - return Screen::the().scroll_step_size(); + return ScreenInput::the().scroll_step_size(); } void ClientConnection::set_double_click_speed(i32 speed) @@ -909,25 +914,27 @@ void ClientConnection::did_become_responsive() Messages::WindowServer::GetScreenBitmapResponse ClientConnection::get_screen_bitmap(Optional<Gfx::IntRect> const& rect) { + auto& screen = Screen::main(); // TODO: implement screenshots from other screens or areas spanning multiple screens if (rect.has_value()) { - auto bitmap = Compositor::the().front_bitmap_for_screenshot({}).cropped(rect.value()); + auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(rect.value()); return bitmap->to_shareable_bitmap(); } - auto& bitmap = Compositor::the().front_bitmap_for_screenshot({}); + auto& bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen); return bitmap.to_shareable_bitmap(); } Messages::WindowServer::GetScreenBitmapAroundCursorResponse ClientConnection::get_screen_bitmap_around_cursor(Gfx::IntSize const& size) { - auto scale_factor = WindowManager::the().scale_factor(); - auto cursor_location = Screen::the().cursor_location(); + auto& screen = Screen::main(); // TODO: implement getting screen bitmaps from other screens or areas spanning multiple screens + auto scale_factor = screen.scale_factor(); + auto cursor_location = ScreenInput::the().cursor_location(); Gfx::Rect rect { (cursor_location.x() * scale_factor) - (size.width() / 2), (cursor_location.y() * scale_factor) - (size.height() / 2), size.width(), size.height() }; // Recompose the screen to make sure the cursor is painted in the location we think it is. // FIXME: This is rather wasteful. We can probably think of a way to avoid this. Compositor::the().compose(); - auto bitmap = Compositor::the().front_bitmap_for_screenshot({}).cropped(rect); + auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(rect); return bitmap->to_shareable_bitmap(); } @@ -942,9 +949,12 @@ Messages::WindowServer::IsWindowModifiedResponse ClientConnection::is_window_mod return window.is_modified(); } -Messages::WindowServer::GetDesktopDisplayScaleResponse ClientConnection::get_desktop_display_scale() +Messages::WindowServer::GetDesktopDisplayScaleResponse ClientConnection::get_desktop_display_scale(u32 screen_index) { - return WindowManager::the().scale_factor(); + if (auto* screen = Screen::find_by_index(screen_index)) + return screen->scale_factor(); + dbgln("GetDesktopDisplayScale: Screen {} does not exist", screen_index); + return 0; } void ClientConnection::set_window_modified(i32 window_id, bool modified) diff --git a/Userland/Services/WindowServer/ClientConnection.h b/Userland/Services/WindowServer/ClientConnection.h index 2564370a69..27ed7fc8f4 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -40,7 +40,7 @@ public: static ClientConnection* from_client_id(int client_id); static void for_each_client(Function<void(ClientConnection&)>); - void notify_about_new_screen_rect(const Gfx::IntRect&); + void notify_about_new_screen_rects(const Vector<Gfx::IntRect, 4>&, size_t); void post_paint_message(Window&, bool ignore_occlusion = false); Menu* find_menu_by_id(int menu_id) @@ -125,7 +125,7 @@ private: virtual void set_background_color(String const&) override; virtual void set_wallpaper_mode(String const&) override; virtual Messages::WindowServer::GetWallpaperResponse get_wallpaper() override; - virtual Messages::WindowServer::SetResolutionResponse set_resolution(Gfx::IntSize const&, int) override; + virtual Messages::WindowServer::SetResolutionResponse set_resolution(u32, Gfx::IntSize const&, int) override; virtual void set_window_cursor(i32, i32) override; virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override; virtual void popup_menu(i32, Gfx::IntPoint const&) override; @@ -153,7 +153,7 @@ private: virtual Messages::WindowServer::GetDoubleClickSpeedResponse get_double_click_speed() override; virtual void set_window_modified(i32, bool) override; virtual Messages::WindowServer::IsWindowModifiedResponse is_window_modified(i32) override; - virtual Messages::WindowServer::GetDesktopDisplayScaleResponse get_desktop_display_scale() override; + virtual Messages::WindowServer::GetDesktopDisplayScaleResponse get_desktop_display_scale(u32) override; Window* window_from_id(i32 window_id); diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 8793e295af..1d329c8767 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -61,28 +61,44 @@ Compositor::Compositor() }, this); - m_screen_can_set_buffer = Screen::the().can_set_buffer(); init_bitmaps(); } -void Compositor::init_bitmaps() +const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const +{ + return *m_screen_data[screen.index()].m_front_bitmap; +} + +void Compositor::ScreenData::init_bitmaps(Screen& screen) { - auto& screen = Screen::the(); auto size = screen.size(); m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0)); m_front_painter = make<Gfx::Painter>(*m_front_bitmap); + m_front_painter->translate(-screen.rect().location()); - if (m_screen_can_set_buffer) + if (screen.can_set_buffer()) m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(screen.physical_height())); else m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_back_painter = make<Gfx::Painter>(*m_back_bitmap); + m_back_painter->translate(-screen.rect().location()); m_temp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap); + m_temp_painter->translate(-screen.rect().location()); m_buffers_are_flipped = false; + m_screen_can_set_buffer = screen.can_set_buffer(); +} + +void Compositor::init_bitmaps() +{ + m_screen_data.resize(Screen::count()); + Screen::for_each([&](auto& screen) { + m_screen_data[screen.index()].init_bitmaps(screen); + return IterationDecision::Continue; + }); invalidate_screen(); } @@ -101,7 +117,6 @@ void Compositor::did_construct_window_manager(Badge<WindowManager>) void Compositor::compose() { auto& wm = WindowManager::the(); - auto& ws = Screen::the(); { auto& current_cursor = wm.active_cursor(); @@ -122,11 +137,26 @@ void Compositor::compose() } auto dirty_screen_rects = move(m_dirty_screen_rects); - dirty_screen_rects.add(m_last_geometry_label_damage_rect.intersected(ws.rect())); - dirty_screen_rects.add(m_last_dnd_rect.intersected(ws.rect())); - if (m_invalidated_cursor) { - if (wm.dnd_client()) - dirty_screen_rects.add(wm.dnd_rect().intersected(ws.rect())); + auto* dnd_client = wm.dnd_client(); + if (!m_last_geometry_label_damage_rect.is_empty() || !m_last_dnd_rect.is_empty() || (m_invalidated_cursor && dnd_client)) { + Screen::for_each([&](auto& screen) { + if (!m_last_geometry_label_damage_rect.is_empty()) { + auto rect = m_last_geometry_label_damage_rect.intersected(screen.rect()); + if (!rect.is_empty()) + dirty_screen_rects.add(rect); + } + if (!m_last_dnd_rect.is_empty()) { + auto rect = m_last_dnd_rect.intersected(screen.rect()); + if (!rect.is_empty()) + dirty_screen_rects.add(rect); + } + if (m_invalidated_cursor && dnd_client) { + auto rect = wm.dnd_rect().intersected(screen.rect()); + if (!rect.is_empty()) + dirty_screen_rects.add(rect); + } + return IterationDecision::Continue; + }); } // Mark window regions as dirty that need to be re-rendered @@ -180,61 +210,81 @@ void Compositor::compose() dbgln("dirty screen: {}", r); } - Gfx::DisjointRectSet flush_rects; - Gfx::DisjointRectSet flush_transparent_rects; - Gfx::DisjointRectSet flush_special_rects; - auto cursor_rect = current_cursor_rect(); - bool need_to_draw_cursor = false; + auto& cursor_screen = ScreenInput::the().cursor_location_screen(); - auto back_painter = *m_back_painter; - auto temp_painter = *m_temp_painter; + for (auto& screen_data : m_screen_data) { + screen_data.m_flush_rects.clear_with_capacity(); + screen_data.m_flush_transparent_rects.clear_with_capacity(); + screen_data.m_flush_special_rects.clear_with_capacity(); + } - auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) { - if (!need_to_draw_cursor && rect.intersects(cursor_rect)) { + auto cursor_rect = current_cursor_rect(); + + bool need_to_draw_cursor = false; + Gfx::IntRect previous_cursor_rect; + Screen* previous_cursor_screen = nullptr; + auto check_restore_cursor_back = [&](Screen& screen, const Gfx::IntRect& rect) { + if (&screen == &cursor_screen && !previous_cursor_screen && !need_to_draw_cursor && rect.intersects(cursor_rect)) { // Restore what's behind the cursor if anything touches the area of the cursor need_to_draw_cursor = true; - restore_cursor_back(); + auto& screen_data = m_screen_data[cursor_screen.index()]; + if (screen_data.restore_cursor_back(cursor_screen, previous_cursor_rect)) + previous_cursor_screen = &screen; } }; - auto prepare_rect = [&](const Gfx::IntRect& rect) { + if (&cursor_screen != m_current_cursor_screen) { + // Cursor moved to another screen, restore on the cursor's background on the previous screen + need_to_draw_cursor = true; + if (m_current_cursor_screen) { + auto& screen_data = m_screen_data[m_current_cursor_screen->index()]; + if (screen_data.restore_cursor_back(*m_current_cursor_screen, previous_cursor_rect)) + previous_cursor_screen = m_current_cursor_screen; + } + m_current_cursor_screen = &cursor_screen; + } + + auto prepare_rect = [&](Screen& screen, const Gfx::IntRect& rect) { + auto& screen_data = m_screen_data[screen.index()]; dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect); - VERIFY(!flush_rects.intersects(rect)); - VERIFY(!flush_transparent_rects.intersects(rect)); - flush_rects.add(rect); - check_restore_cursor_back(rect); + VERIFY(!screen_data.m_flush_rects.intersects(rect)); + VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect)); + screen_data.m_flush_rects.add(rect); + check_restore_cursor_back(screen, rect); }; - auto prepare_transparency_rect = [&](const Gfx::IntRect& rect) { + auto prepare_transparency_rect = [&](Screen& screen, const Gfx::IntRect& rect) { + auto& screen_data = m_screen_data[screen.index()]; dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect); - VERIFY(!flush_rects.intersects(rect)); - for (auto& r : flush_transparent_rects.rects()) { + VERIFY(!screen_data.m_flush_rects.intersects(rect)); + for (auto& r : screen_data.m_flush_transparent_rects.rects()) { if (r == rect) return; } - flush_transparent_rects.add(rect); - check_restore_cursor_back(rect); + screen_data.m_flush_transparent_rects.add(rect); + check_restore_cursor_back(screen, rect); }; - if (!m_cursor_back_bitmap || m_invalidated_cursor) - check_restore_cursor_back(cursor_rect); + if (!m_screen_data[cursor_screen.index()].m_cursor_back_bitmap || m_invalidated_cursor) + check_restore_cursor_back(cursor_screen, cursor_rect); - auto paint_wallpaper = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) { + auto paint_wallpaper = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect, const Gfx::IntRect& screen_rect) { // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color! painter.fill_rect(rect, background_color); if (m_wallpaper) { if (m_wallpaper_mode == WallpaperMode::Center) { - Gfx::IntPoint offset { (ws.width() - m_wallpaper->width()) / 2, (ws.height() - m_wallpaper->height()) / 2 }; - painter.blit_offset(rect.location(), *m_wallpaper, rect, offset); + Gfx::IntPoint offset { (screen.width() - m_wallpaper->width()) / 2, (screen.height() - m_wallpaper->height()) / 2 }; + painter.blit_offset(rect.location(), *m_wallpaper, rect.translated(-screen_rect.location()), offset); } else if (m_wallpaper_mode == WallpaperMode::Tile) { painter.draw_tiled_bitmap(rect, *m_wallpaper); } else if (m_wallpaper_mode == WallpaperMode::Stretch) { - float hscale = (float)m_wallpaper->width() / (float)ws.width(); - float vscale = (float)m_wallpaper->height() / (float)ws.height(); + float hscale = (float)m_wallpaper->width() / (float)screen.width(); + float vscale = (float)m_wallpaper->height() / (float)screen.height(); // TODO: this may look ugly, we should scale to a backing bitmap and then blit - auto src_rect = Gfx::FloatRect { rect.x() * hscale, rect.y() * vscale, rect.width() * hscale, rect.height() * vscale }; + auto relative_rect = rect.translated(-screen_rect.location()); + auto src_rect = Gfx::FloatRect { relative_rect.x() * hscale, relative_rect.y() * vscale, relative_rect.width() * hscale, relative_rect.height() * vscale }; painter.draw_scaled_bitmap(rect, *m_wallpaper, src_rect); } else { VERIFY_NOT_REACHED(); @@ -243,29 +293,39 @@ void Compositor::compose() }; m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](const Gfx::IntRect& render_rect) { - dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {}", render_rect); - prepare_rect(render_rect); - paint_wallpaper(back_painter, render_rect); + Screen::for_each([&](auto& screen) { + auto screen_rect = screen.rect(); + auto screen_render_rect = screen_rect.intersected(render_rect); + if (!screen_render_rect.is_empty()) { + auto& back_painter = *m_screen_data[screen.index()].m_back_painter; + dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {} on screen #{}", screen_render_rect, screen.index()); + prepare_rect(screen, render_rect); + paint_wallpaper(screen, back_painter, render_rect, screen_rect); + } + return IterationDecision::Continue; + }); return IterationDecision::Continue; }); auto compose_window = [&](Window& window) -> IterationDecision { - auto frame_rect = window.frame().render_rect(); - if (!frame_rect.intersects(ws.rect())) + if (window.screens().is_empty()) { + // This window doesn't intersect with any screens, so there's nothing to render return IterationDecision::Continue; + } + auto frame_rect = window.frame().render_rect(); auto window_rect = window.rect(); auto frame_rects = frame_rect.shatter(window_rect); dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect); RefPtr<Gfx::Bitmap> backing_store = window.backing_store(); - auto compose_window_rect = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) { + auto compose_window_rect = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) { if (!window.is_fullscreen()) { rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) { Gfx::PainterStateSaver saver(painter); painter.add_clip_rect(intersected_rect); dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect); - window.frame().paint(painter, intersected_rect); + window.frame().paint(screen, painter, intersected_rect); return IterationDecision::Continue; }); } @@ -359,12 +419,18 @@ void Compositor::compose() auto& opaque_rects = window.opaque_rects(); if (!opaque_rects.is_empty()) { opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { - dbgln_if(COMPOSE_DEBUG, " render opaque: {}", render_rect); - - prepare_rect(render_rect); - Gfx::PainterStateSaver saver(back_painter); - back_painter.add_clip_rect(render_rect); - compose_window_rect(back_painter, render_rect); + for (auto* screen : window.screens()) { + auto screen_render_rect = render_rect.intersected(screen->rect()); + if (screen_render_rect.is_empty()) + continue; + dbgln_if(COMPOSE_DEBUG, " render opaque: {} on screen #{}", screen_render_rect, screen->index()); + + prepare_rect(*screen, screen_render_rect); + auto& back_painter = *m_screen_data[screen->index()].m_back_painter; + Gfx::PainterStateSaver saver(back_painter); + back_painter.add_clip_rect(screen_render_rect); + compose_window_rect(*screen, back_painter, screen_render_rect); + } return IterationDecision::Continue; }); } @@ -374,22 +440,36 @@ void Compositor::compose() auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects(); if (!transparency_wallpaper_rects.is_empty()) { transparency_wallpaper_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { - dbgln_if(COMPOSE_DEBUG, " render wallpaper: {}", render_rect); - - prepare_transparency_rect(render_rect); - paint_wallpaper(temp_painter, render_rect); + for (auto* screen : window.screens()) { + auto screen_rect = screen->rect(); + auto screen_render_rect = render_rect.intersected(screen_rect); + if (screen_render_rect.is_empty()) + continue; + dbgln_if(COMPOSE_DEBUG, " render wallpaper: {} on screen #{}", screen_render_rect, screen->index()); + + auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter; + prepare_transparency_rect(*screen, screen_render_rect); + paint_wallpaper(*screen, temp_painter, screen_render_rect, screen_rect); + } return IterationDecision::Continue; }); } auto& transparency_rects = window.transparency_rects(); if (!transparency_rects.is_empty()) { transparency_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { - dbgln_if(COMPOSE_DEBUG, " render transparent: {}", render_rect); - - prepare_transparency_rect(render_rect); - Gfx::PainterStateSaver saver(temp_painter); - temp_painter.add_clip_rect(render_rect); - compose_window_rect(temp_painter, render_rect); + for (auto* screen : window.screens()) { + auto screen_rect = screen->rect(); + auto screen_render_rect = render_rect.intersected(screen_rect); + if (screen_render_rect.is_empty()) + continue; + dbgln_if(COMPOSE_DEBUG, " render transparent: {} on screen #{}", screen_render_rect, screen->index()); + + prepare_transparency_rect(*screen, screen_render_rect); + auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter; + Gfx::PainterStateSaver saver(temp_painter); + temp_painter.add_clip_rect(screen_render_rect); + compose_window_rect(*screen, temp_painter, screen_render_rect); + } return IterationDecision::Continue; }); } @@ -411,24 +491,35 @@ void Compositor::compose() // Check that there are no overlapping transparent and opaque flush rectangles VERIFY(![&]() { - for (auto& rect_transparent : flush_transparent_rects.rects()) { - for (auto& rect_opaque : flush_rects.rects()) { - if (rect_opaque.intersects(rect_transparent)) { - dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent)); - return true; + bool is_overlapping = false; + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + auto& flush_transparent_rects = screen_data.m_flush_transparent_rects; + auto& flush_rects = screen_data.m_flush_rects; + for (auto& rect_transparent : flush_transparent_rects.rects()) { + for (auto& rect_opaque : flush_rects.rects()) { + if (rect_opaque.intersects(rect_transparent)) { + dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent)); + is_overlapping = true; + return IterationDecision::Break; + } } } - } - return false; + return IterationDecision::Continue; + }); + return is_overlapping; }()); // Copy anything rendered to the temporary buffer to the back buffer - for (auto& rect : flush_transparent_rects.rects()) - back_painter.blit(rect.location(), *m_temp_bitmap, rect); + Screen::for_each([&](auto& screen) { + auto screen_rect = screen.rect(); + auto& screen_data = m_screen_data[screen.index()]; + for (auto& rect : screen_data.m_flush_transparent_rects.rects()) + screen_data.m_back_painter->blit(rect.location(), *screen_data.m_temp_bitmap, rect.translated(-screen_rect.location())); + return IterationDecision::Continue; + }); - Gfx::IntRect geometry_label_damage_rect; - if (draw_geometry_label(geometry_label_damage_rect)) - flush_special_rects.add(geometry_label_damage_rect); + draw_geometry_label(cursor_screen); } m_invalidated_any = false; @@ -438,34 +529,50 @@ void Compositor::compose() if (wm.dnd_client()) { auto dnd_rect = wm.dnd_rect(); - // TODO: render once into a backing bitmap, then just blit... - auto render_dnd = [&]() { - back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200)); - back_painter.draw_rect(dnd_rect, wm.palette().selection()); - if (!wm.dnd_text().is_empty()) { - auto text_rect = dnd_rect; - if (wm.dnd_bitmap()) - text_rect.translate_by(wm.dnd_bitmap()->width() + 8, 0); - back_painter.draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text()); - } - if (wm.dnd_bitmap()) { - back_painter.blit(dnd_rect.top_left().translated(4, 4), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect()); - } - }; + Screen::for_each([&](auto& screen) { + auto screen_rect = screen.rect(); + auto render_dnd_rect = screen_rect.intersected(dnd_rect); + if (render_dnd_rect.is_empty()) + return IterationDecision::Continue; + auto& screen_data = m_screen_data[screen.index()]; + auto& back_painter = *screen_data.m_back_painter; + + // TODO: render once into a backing bitmap, then just blit... + auto render_dnd = [&]() { + back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200)); + back_painter.draw_rect(dnd_rect, wm.palette().selection()); + if (!wm.dnd_text().is_empty()) { + auto text_rect = dnd_rect; + if (wm.dnd_bitmap()) + text_rect.translate_by(wm.dnd_bitmap()->width() + 8, 0); + back_painter.draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text()); + } + if (wm.dnd_bitmap()) { + back_painter.blit(dnd_rect.top_left().translated(4, 4), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect()); + } + }; - dirty_screen_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) { - Gfx::PainterStateSaver saver(back_painter); - back_painter.add_clip_rect(render_rect); - render_dnd(); - return IterationDecision::Continue; - }); - flush_transparent_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) { - Gfx::PainterStateSaver saver(back_painter); - back_painter.add_clip_rect(render_rect); - render_dnd(); + dirty_screen_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) { + auto screen_render_rect = render_rect.intersected(screen_rect); + if (screen_render_rect.is_empty()) + return IterationDecision::Continue; + Gfx::PainterStateSaver saver(back_painter); + back_painter.add_clip_rect(screen_render_rect); + render_dnd(); + return IterationDecision::Continue; + }); + screen_data.m_flush_transparent_rects.for_each_intersected(dnd_rect, [&](const Gfx::IntRect& render_rect) { + auto screen_render_rect = render_rect.intersected(screen_rect); + if (screen_render_rect.is_empty()) + return IterationDecision::Continue; + Gfx::PainterStateSaver saver(back_painter); + back_painter.add_clip_rect(screen_render_rect); + render_dnd(); + return IterationDecision::Continue; + }); + m_last_dnd_rect = dnd_rect; return IterationDecision::Continue; }); - m_last_dnd_rect = dnd_rect; } else { if (!m_last_dnd_rect.is_empty()) { invalidate_screen(m_last_dnd_rect); @@ -473,78 +580,98 @@ void Compositor::compose() } } - run_animations(flush_special_rects); + bool did_render_animation = false; + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + did_render_animation |= render_animation_frame(screen, screen_data.m_flush_special_rects); + return IterationDecision::Continue; + }); if (need_to_draw_cursor) { - flush_rects.add(cursor_rect); - if (cursor_rect != m_last_cursor_rect) - flush_rects.add(m_last_cursor_rect); - draw_cursor(cursor_rect); + auto& screen_data = m_screen_data[cursor_screen.index()]; + screen_data.draw_cursor(cursor_screen, cursor_rect); + screen_data.m_flush_rects.add(cursor_rect); + if (previous_cursor_screen && cursor_rect != previous_cursor_rect) + m_screen_data[previous_cursor_screen->index()].m_flush_rects.add(previous_cursor_rect); } - if (m_flash_flush) { - for (auto& rect : flush_rects.rects()) - m_front_painter->fill_rect(rect, Color::Yellow); - } - - if (m_screen_can_set_buffer) - flip_buffers(); + Screen::for_each([&](auto& screen) { + flush(screen); + return IterationDecision::Continue; + }); - for (auto& rect : flush_rects.rects()) - flush(rect); - for (auto& rect : flush_transparent_rects.rects()) - flush(rect); - for (auto& rect : flush_special_rects.rects()) - flush(rect); + if (did_render_animation) + step_animations(); } -void Compositor::flush(const Gfx::IntRect& a_rect) +void Compositor::flush(Screen& screen) { - auto rect = Gfx::IntRect::intersection(a_rect, Screen::the().rect()); - - // Almost everything in Compositor is in logical coordinates, with the painters having - // a scale applied. But this routine accesses the backbuffer pixels directly, so it - // must work in physical coordinates. - rect = rect * Screen::the().scale_factor(); - Gfx::RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x(); - Gfx::RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x(); - size_t pitch = m_back_bitmap->pitch(); - - // NOTE: The meaning of a flush depends on whether we can flip buffers or not. - // - // If flipping is supported, flushing means that we've flipped, and now we - // copy the changed bits from the front buffer to the back buffer, to keep - // them in sync. - // - // If flipping is not supported, flushing means that we copy the changed - // rects from the backing bitmap to the display framebuffer. - - Gfx::RGBA32* to_ptr; - const Gfx::RGBA32* from_ptr; - - if (m_screen_can_set_buffer) { - to_ptr = back_ptr; - from_ptr = front_ptr; - } else { - to_ptr = front_ptr; - from_ptr = back_ptr; + auto& screen_data = m_screen_data[screen.index()]; + if (m_flash_flush) { + for (auto& rect : screen_data.m_flush_rects.rects()) + screen_data.m_front_painter->fill_rect(rect, Color::Yellow); } - for (int y = 0; y < rect.height(); ++y) { - fast_u32_copy(to_ptr, from_ptr, rect.width()); - from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch); - to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch); - } + if (screen_data.m_screen_can_set_buffer) + screen_data.flip_buffers(screen); + + auto screen_rect = screen.rect(); + auto do_flush = [&](const Gfx::IntRect& a_rect) { + auto rect = Gfx::IntRect::intersection(a_rect, screen_rect); + if (rect.is_empty()) + return; + rect.translate_by(-screen_rect.location()); + + // Almost everything in Compositor is in logical coordinates, with the painters having + // a scale applied. But this routine accesses the backbuffer pixels directly, so it + // must work in physical coordinates. + rect = rect * screen.scale_factor(); + Gfx::RGBA32* front_ptr = screen_data.m_front_bitmap->scanline(rect.y()) + rect.x(); + Gfx::RGBA32* back_ptr = screen_data.m_back_bitmap->scanline(rect.y()) + rect.x(); + size_t pitch = screen_data.m_back_bitmap->pitch(); + + // NOTE: The meaning of a flush depends on whether we can flip buffers or not. + // + // If flipping is supported, flushing means that we've flipped, and now we + // copy the changed bits from the front buffer to the back buffer, to keep + // them in sync. + // + // If flipping is not supported, flushing means that we copy the changed + // rects from the backing bitmap to the display framebuffer. + + Gfx::RGBA32* to_ptr; + const Gfx::RGBA32* from_ptr; + + if (screen_data.m_screen_can_set_buffer) { + to_ptr = back_ptr; + from_ptr = front_ptr; + } else { + to_ptr = front_ptr; + from_ptr = back_ptr; + } + + for (int y = 0; y < rect.height(); ++y) { + fast_u32_copy(to_ptr, from_ptr, rect.width()); + from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch); + to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch); + } + }; + for (auto& rect : screen_data.m_flush_rects.rects()) + do_flush(rect); + for (auto& rect : screen_data.m_flush_transparent_rects.rects()) + do_flush(rect); + for (auto& rect : screen_data.m_flush_special_rects.rects()) + do_flush(rect); } void Compositor::invalidate_screen() { - invalidate_screen(Screen::the().rect()); + invalidate_screen(Screen::bounding_rect()); } void Compositor::invalidate_screen(const Gfx::IntRect& screen_rect) { - m_dirty_screen_rects.add(screen_rect.intersected(Screen::the().rect())); + m_dirty_screen_rects.add(screen_rect.intersected(Screen::bounding_rect())); if (m_invalidated_any) return; @@ -623,19 +750,21 @@ bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callba return true; } -void Compositor::flip_buffers() +void Compositor::ScreenData::flip_buffers(Screen& screen) { VERIFY(m_screen_can_set_buffer); swap(m_front_bitmap, m_back_bitmap); swap(m_front_painter, m_back_painter); - Screen::the().set_buffer(m_buffers_are_flipped ? 0 : 1); + screen.set_buffer(m_buffers_are_flipped ? 0 : 1); m_buffers_are_flipped = !m_buffers_are_flipped; } -void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects) +static const int minimize_animation_steps = 10; + +bool Compositor::render_animation_frame(Screen& screen, Gfx::DisjointRectSet& flush_rects) { - static const int minimize_animation_steps = 10; - auto& painter = *m_back_painter; + bool did_render_any = false; + auto& painter = *m_screen_data[screen.index()].m_back_painter; Gfx::PainterStateSaver saver(painter); painter.set_draw_op(Gfx::Painter::DrawOp::Invert); @@ -658,12 +787,24 @@ void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects) from_rect.height() - (int)(height_delta_per_step * animation_index) }; - dbgln_if(MINIMIZE_ANIMATION_DEBUG, "Minimize animation from {} to {} frame# {} {}", from_rect, to_rect, animation_index, rect); + dbgln_if(MINIMIZE_ANIMATION_DEBUG, "Minimize animation from {} to {} frame# {} {} on screen #{}", from_rect, to_rect, animation_index, rect, screen.index()); painter.draw_rect(rect, Color::Transparent); // Color doesn't matter, we draw inverted flush_rects.add(rect); invalidate_screen(rect); + did_render_any = true; + } + return IterationDecision::Continue; + }); + + return did_render_any; +} + +void Compositor::step_animations() +{ + WindowManager::the().window_stack().for_each_window([&](Window& window) { + if (window.in_minimize_animation()) { window.step_minimize_animation(); if (window.minimize_animation_index() >= minimize_animation_steps) window.end_minimize_animation(); @@ -672,33 +813,18 @@ void Compositor::run_animations(Gfx::DisjointRectSet& flush_rects) }); } -bool Compositor::set_resolution(int desired_width, int desired_height, int scale_factor) +void Compositor::screen_resolution_changed() { - auto screen_rect = Screen::the().rect(); - if (screen_rect.width() == desired_width && screen_rect.height() == desired_height && Screen::the().scale_factor() == scale_factor) - return true; - - // Make sure it's impossible to set an invalid resolution - if (!(desired_width >= 640 && desired_height >= 480 && scale_factor >= 1)) { - dbgln("Compositor: Tried to set invalid resolution: {}x{}", desired_width, desired_height); - return false; - } - - int old_scale_factor = Screen::the().scale_factor(); - bool success = Screen::the().set_resolution(desired_width, desired_height, scale_factor); - if (success && old_scale_factor != scale_factor) - WindowManager::the().reload_icon_bitmaps_after_scale_change(); init_bitmaps(); invalidate_occlusions(); compose(); - return success; } Gfx::IntRect Compositor::current_cursor_rect() const { auto& wm = WindowManager::the(); auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); - return { Screen::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() }; + return { ScreenInput::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() }; } void Compositor::invalidate_cursor(bool compose_immediately) @@ -714,13 +840,13 @@ void Compositor::invalidate_cursor(bool compose_immediately) start_compose_async_timer(); } -bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect) +void Compositor::draw_geometry_label(Screen& screen) { auto& wm = WindowManager::the(); 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); if (!window_being_moved_or_resized) { m_last_geometry_label_damage_rect = {}; - return false; + return; } auto geometry_string = window_being_moved_or_resized->rect().to_string(); if (!window_being_moved_or_resized->size_increment().is_null()) { @@ -731,7 +857,7 @@ bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect) auto geometry_label_rect = Gfx::IntRect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 }; geometry_label_rect.center_within(window_being_moved_or_resized->rect()); - auto desktop_rect = wm.desktop_rect(); + auto desktop_rect = wm.desktop_rect(screen); if (geometry_label_rect.left() < desktop_rect.left()) geometry_label_rect.set_left(desktop_rect.left()); if (geometry_label_rect.top() < desktop_rect.top()) @@ -741,14 +867,17 @@ bool Compositor::draw_geometry_label(Gfx::IntRect& geometry_label_damage_rect) if (geometry_label_rect.bottom() > desktop_rect.bottom()) geometry_label_rect.set_bottom_without_resize(desktop_rect.bottom()); - auto& back_painter = *m_back_painter; + auto& screen_data = m_screen_data[screen.index()]; + auto& back_painter = *screen_data.m_back_painter; + auto geometry_label_damage_rect = geometry_label_rect.inflated(2, 2); + Gfx::PainterStateSaver saver(back_painter); + back_painter.add_clip_rect(geometry_label_damage_rect); + back_painter.fill_rect(geometry_label_rect.translated(1, 1), Color(Color::Black).with_alpha(80)); Gfx::StylePainter::paint_button(back_painter, geometry_label_rect.translated(-1, -1), wm.palette(), Gfx::ButtonStyle::Normal, false); back_painter.draw_text(geometry_label_rect.translated(-1, -1), geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text()); - geometry_label_damage_rect = geometry_label_rect.inflated(2, 2); m_last_geometry_label_damage_rect = geometry_label_damage_rect; - return true; } void Compositor::change_cursor(const Cursor* cursor) @@ -774,28 +903,34 @@ void Compositor::change_cursor(const Cursor* cursor) } } -void Compositor::draw_cursor(const Gfx::IntRect& cursor_rect) +void Compositor::ScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect) { auto& wm = WindowManager::the(); - if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size() || m_cursor_back_bitmap->scale() != Screen::the().scale_factor()) { - m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), Screen::the().scale_factor()); + if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size() || m_cursor_back_bitmap->scale() != screen.scale_factor()) { + m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor()); m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap); } - auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); - m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(Screen::the().rect())); - m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(m_current_cursor_frame)); + auto& compositor = Compositor::the(); + auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor(); + auto screen_rect = screen.rect(); + m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(screen_rect).translated(-screen_rect.location())); + m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(compositor.m_current_cursor_frame)); m_last_cursor_rect = cursor_rect; + VERIFY(compositor.m_current_cursor_screen == &screen); + m_cursor_back_is_valid = true; } -void Compositor::restore_cursor_back() +bool Compositor::ScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect) { - if (!m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale()) - return; + if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale()) + return false; - auto last_cursor_rect = m_last_cursor_rect.intersected(Screen::the().rect()); + last_cursor_rect = m_last_cursor_rect.intersected(screen.rect()); m_back_painter->blit(last_cursor_rect.location(), *m_cursor_back_bitmap, { { 0, 0 }, last_cursor_rect.size() }); + m_cursor_back_is_valid = false; + return true; } void Compositor::notify_display_links() @@ -863,14 +998,17 @@ void Compositor::recompute_occlusions() dbgln_if(OCCLUSIONS_DEBUG, "OCCLUSIONS:"); - auto screen_rect = Screen::the().rect(); - + auto& main_screen = Screen::main(); if (auto* fullscreen_window = wm.active_fullscreen_window()) { + // TODO: support fullscreen windows on all screens + auto screen_rect = main_screen.rect(); WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) { auto& visible_opaque = w.opaque_rects(); auto& transparency_rects = w.transparency_rects(); auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); + w.screens().clear_with_capacity(); if (&w == fullscreen_window) { + w.screens().append(&main_screen); if (w.is_opaque()) { visible_opaque = screen_rect; transparency_rects.clear(); @@ -890,28 +1028,39 @@ void Compositor::recompute_occlusions() m_opaque_wallpaper_rects.clear(); } else { - Gfx::DisjointRectSet visible_rects(screen_rect); + Gfx::DisjointRectSet visible_rects; + visible_rects.add_many(Screen::rects()); bool have_transparent = false; WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) { w.transparency_wallpaper_rects().clear(); auto& visible_opaque = w.opaque_rects(); + visible_opaque.clear(); auto& transparency_rects = w.transparency_rects(); - if (w.is_minimized()) { - visible_opaque.clear(); - transparency_rects.clear(); + transparency_rects.clear(); + w.screens().clear_with_capacity(); + if (w.is_minimized()) return IterationDecision::Continue; - } - auto transparent_render_rects = w.frame().transparent_render_rects().intersected(screen_rect); - auto opaque_render_rects = w.frame().opaque_render_rects().intersected(screen_rect); - if (transparent_render_rects.is_empty() && opaque_render_rects.is_empty()) { - visible_opaque.clear(); - transparency_rects.clear(); + auto transparent_frame_render_rects = w.frame().transparent_render_rects(); + auto opaque_frame_render_rects = w.frame().opaque_render_rects(); + Gfx::DisjointRectSet visible_opaque_rects; + Screen::for_each([&](auto& screen) { + auto screen_rect = screen.rect(); + if (auto transparent_render_rects = transparent_frame_render_rects.intersected(screen_rect); !transparent_render_rects.is_empty()) { + if (transparency_rects.is_empty()) + transparency_rects = move(transparent_render_rects); + else + transparency_rects.add(transparent_render_rects); + } + if (auto opaque_render_rects = opaque_frame_render_rects.intersected(screen_rect); !opaque_render_rects.is_empty()) { + if (visible_opaque_rects.is_empty()) + visible_opaque_rects = move(opaque_render_rects); + else + visible_opaque_rects.add(opaque_render_rects); + } return IterationDecision::Continue; - } - - visible_opaque = visible_rects.intersected(opaque_render_rects); - transparency_rects = move(transparent_render_rects); + }); + visible_opaque = visible_rects.intersected(visible_opaque_rects); auto render_rect = w.frame().render_rect(); @@ -967,8 +1116,31 @@ void Compositor::recompute_occlusions() return IterationDecision::Continue; }); + bool have_opaque = !visible_opaque.is_empty(); if (!transparency_rects.is_empty()) have_transparent = true; + if (have_transparent || have_opaque) { + // Figure out what screens this window is rendered on + // We gather this information so we can more quickly + // render the window on each of the screens that it + // needs to be rendered on. + Screen::for_each([&](auto& screen) { + auto screen_rect = screen.rect(); + for (auto& r : visible_opaque.rects()) { + if (r.intersects(screen_rect)) { + w.screens().append(&screen); + return IterationDecision::Continue; + } + } + for (auto& r : transparency_rects.rects()) { + if (r.intersects(screen_rect)) { + w.screens().append(&screen); + return IterationDecision::Continue; + } + } + return IterationDecision::Continue; + }); + } VERIFY(!visible_opaque.intersects(transparency_rects)); @@ -1008,12 +1180,14 @@ void Compositor::recompute_occlusions() } wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& w) { - auto window_frame_rect = w.frame().render_rect().intersected(screen_rect); - if (w.is_minimized() || window_frame_rect.is_empty()) + auto window_frame_rect = w.frame().render_rect(); + if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty()) return IterationDecision::Continue; if constexpr (OCCLUSIONS_DEBUG) { - dbgln(" Window {} frame rect: {}", w.title(), window_frame_rect); + dbgln(" Window {} frame rect: {} rendered on screens: {}", w.title(), window_frame_rect, w.screens().size()); + for (auto& s : w.screens()) + dbgln(" screen: #{}", s->index()); for (auto& r : w.opaque_rects().rects()) dbgln(" opaque: {}", r); for (auto& r : w.transparency_wallpaper_rects().rects()) diff --git a/Userland/Services/WindowServer/Compositor.h b/Userland/Services/WindowServer/Compositor.h index c22cfdccf9..ea245dfdb8 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -11,6 +11,7 @@ #include <LibCore/Object.h> #include <LibGfx/Color.h> #include <LibGfx/DisjointRectSet.h> +#include <WindowServer/Screen.h> namespace WindowServer { @@ -36,7 +37,7 @@ public: void invalidate_screen(); void invalidate_screen(const Gfx::IntRect&); - bool set_resolution(int desired_width, int desired_height, int scale_factor); + void screen_resolution_changed(); bool set_background_color(const String& background_color); @@ -55,46 +56,58 @@ public: void did_construct_window_manager(Badge<WindowManager>); - const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>) const { return *m_front_bitmap; } + const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const; private: Compositor(); void init_bitmaps(); - void flip_buffers(); - void flush(const Gfx::IntRect&); - void run_animations(Gfx::DisjointRectSet&); + bool render_animation_frame(Screen&, Gfx::DisjointRectSet&); + void step_animations(); void notify_display_links(); void start_compose_async_timer(); void recompute_occlusions(); bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&); void change_cursor(const Cursor*); - void draw_cursor(const Gfx::IntRect&); - void restore_cursor_back(); - bool draw_geometry_label(Gfx::IntRect&); + void draw_geometry_label(Screen&); + void flush(Screen&); RefPtr<Core::Timer> m_compose_timer; RefPtr<Core::Timer> m_immediate_compose_timer; bool m_flash_flush { false }; - bool m_buffers_are_flipped { false }; - bool m_screen_can_set_buffer { false }; bool m_occlusions_dirty { true }; bool m_invalidated_any { true }; bool m_invalidated_window { false }; bool m_invalidated_cursor { false }; - RefPtr<Gfx::Bitmap> m_front_bitmap; - RefPtr<Gfx::Bitmap> m_back_bitmap; - RefPtr<Gfx::Bitmap> m_temp_bitmap; - OwnPtr<Gfx::Painter> m_back_painter; - OwnPtr<Gfx::Painter> m_front_painter; - OwnPtr<Gfx::Painter> m_temp_painter; + struct ScreenData { + RefPtr<Gfx::Bitmap> m_front_bitmap; + RefPtr<Gfx::Bitmap> m_back_bitmap; + RefPtr<Gfx::Bitmap> m_temp_bitmap; + OwnPtr<Gfx::Painter> m_back_painter; + OwnPtr<Gfx::Painter> m_front_painter; + OwnPtr<Gfx::Painter> m_temp_painter; + RefPtr<Gfx::Bitmap> m_cursor_back_bitmap; + OwnPtr<Gfx::Painter> m_cursor_back_painter; + Gfx::IntRect m_last_cursor_rect; + bool m_buffers_are_flipped { false }; + bool m_screen_can_set_buffer { false }; + bool m_cursor_back_is_valid { false }; + + Gfx::DisjointRectSet m_flush_rects; + Gfx::DisjointRectSet m_flush_transparent_rects; + Gfx::DisjointRectSet m_flush_special_rects; + + void init_bitmaps(Screen&); + void flip_buffers(Screen&); + void draw_cursor(Screen&, const Gfx::IntRect&); + bool restore_cursor_back(Screen&, Gfx::IntRect&); + }; + friend class ScreenData; + Vector<ScreenData, default_screen_count> m_screen_data; Gfx::DisjointRectSet m_dirty_screen_rects; Gfx::DisjointRectSet m_opaque_wallpaper_rects; - RefPtr<Gfx::Bitmap> m_cursor_back_bitmap; - OwnPtr<Gfx::Painter> m_cursor_back_painter; - Gfx::IntRect m_last_cursor_rect; Gfx::IntRect m_last_dnd_rect; Gfx::IntRect m_last_geometry_label_damage_rect; @@ -103,6 +116,7 @@ private: RefPtr<Gfx::Bitmap> m_wallpaper; const Cursor* m_current_cursor { nullptr }; + Screen* m_current_cursor_screen { nullptr }; unsigned m_current_cursor_frame { 0 }; RefPtr<Core::Timer> m_cursor_timer; diff --git a/Userland/Services/WindowServer/EventLoop.cpp b/Userland/Services/WindowServer/EventLoop.cpp index 5bc5b4a062..f2cb81f7ea 100644 --- a/Userland/Services/WindowServer/EventLoop.cpp +++ b/Userland/Services/WindowServer/EventLoop.cpp @@ -81,9 +81,9 @@ EventLoop::~EventLoop() void EventLoop::drain_mouse() { - auto& screen = Screen::the(); + auto& screen_input = ScreenInput::the(); MousePacket state; - state.buttons = screen.mouse_button_state(); + state.buttons = screen_input.mouse_button_state(); unsigned buttons = state.buttons; MousePacket packets[32]; @@ -114,7 +114,7 @@ void EventLoop::drain_mouse() if (buttons != state.buttons) { state.buttons = buttons; dbgln_if(WSMESSAGELOOP_DEBUG, "EventLoop: Mouse Button Event"); - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); if (state.is_relative) { state.x = 0; state.y = 0; @@ -123,21 +123,21 @@ void EventLoop::drain_mouse() } } if (state.is_relative && (state.x || state.y || state.z)) - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); if (!state.is_relative) - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); } void EventLoop::drain_keyboard() { - auto& screen = Screen::the(); + auto& screen_input = ScreenInput::the(); for (;;) { ::KeyEvent event; ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent)); if (nread == 0) break; VERIFY(nread == sizeof(::KeyEvent)); - screen.on_receive_keyboard_data(event); + screen_input.on_receive_keyboard_data(event); } } diff --git a/Userland/Services/WindowServer/Menu.cpp b/Userland/Services/WindowServer/Menu.cpp index f878ab1035..942df82120 100644 --- a/Userland/Services/WindowServer/Menu.cpp +++ b/Userland/Services/WindowServer/Menu.cpp @@ -136,7 +136,7 @@ Window& Menu::ensure_menu_window() next_item_location.translate_by(0, height); } - int window_height_available = Screen::the().height() - frame_thickness() * 2; + int window_height_available = Screen::main().height() - frame_thickness() * 2; // TODO: we don't know yet on what screen! int max_window_height = (window_height_available / item_height()) * item_height() + frame_thickness() * 2; int content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness(); int window_height = min(max_window_height, content_height); @@ -584,14 +584,15 @@ void Menu::do_popup(const Gfx::IntPoint& position, bool make_input, bool as_subm redraw_if_theme_changed(); const int margin = 30; + auto& screen = Screen::closest_to_location(position); Gfx::IntPoint adjusted_pos = position; - if (adjusted_pos.x() + window.width() >= Screen::the().width() - margin) { + if (adjusted_pos.x() + window.width() >= screen.width() - margin) { adjusted_pos = adjusted_pos.translated(-window.width(), 0); } else { adjusted_pos.set_x(adjusted_pos.x() + 1); } - if (adjusted_pos.y() + window.height() >= Screen::the().height() - margin) { + if (adjusted_pos.y() + window.height() >= screen.height() - margin) { adjusted_pos = adjusted_pos.translated(0, -min(window.height(), adjusted_pos.y())); if (as_submenu) adjusted_pos = adjusted_pos.translated(0, item_height()); diff --git a/Userland/Services/WindowServer/Screen.cpp b/Userland/Services/WindowServer/Screen.cpp index bc94fa5a7f..e6b7716e9e 100644 --- a/Userland/Services/WindowServer/Screen.cpp +++ b/Userland/Services/WindowServer/Screen.cpp @@ -19,21 +19,37 @@ namespace WindowServer { -static Screen* s_the; +NonnullOwnPtrVector<Screen, default_screen_count> Screen::s_screens; +Screen* Screen::s_main_screen { nullptr }; +Gfx::IntRect Screen::s_bounding_screens_rect {}; -Screen& Screen::the() +ScreenInput& ScreenInput::the() { - VERIFY(s_the); - return *s_the; + static ScreenInput s_the; + return s_the; } -Screen::Screen(unsigned desired_width, unsigned desired_height, int scale_factor) +Screen& ScreenInput::cursor_location_screen() { - VERIFY(!s_the); - s_the = this; - m_framebuffer_fd = open("/dev/fb0", O_RDWR | O_CLOEXEC); + auto* screen = Screen::find_by_location(m_cursor_location); + VERIFY(screen); + return *screen; +} + +const Screen& ScreenInput::cursor_location_screen() const +{ + auto* screen = Screen::find_by_location(m_cursor_location); + VERIFY(screen); + return *screen; +} + +Screen::Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale_factor) + : m_virtual_rect(virtual_rect) + , m_scale_factor(scale_factor) +{ + m_framebuffer_fd = open(device.characters(), O_RDWR | O_CLOEXEC); if (m_framebuffer_fd < 0) { - perror("failed to open /dev/fb0"); + perror(String::formatted("failed to open {}", device).characters()); VERIFY_NOT_REACHED(); } @@ -41,8 +57,10 @@ Screen::Screen(unsigned desired_width, unsigned desired_height, int scale_factor m_can_set_buffer = true; } - set_resolution(desired_width, desired_height, scale_factor); - m_physical_cursor_location = physical_rect().center(); + // If the cursor is not in a valid screen (yet), force it into one + dbgln("Screen() current physical cursor location: {} rect: {}", ScreenInput::the().cursor_location(), rect()); + if (!find_by_location(ScreenInput::the().cursor_location())) + ScreenInput::the().set_cursor_location(rect().center()); } Screen::~Screen() @@ -50,35 +68,84 @@ Screen::~Screen() close(m_framebuffer_fd); } -bool Screen::set_resolution(int width, int height, int new_scale_factor) +void Screen::init() +{ + do_set_resolution(true, m_virtual_rect.width(), m_virtual_rect.height(), m_scale_factor); +} + +Screen& Screen::closest_to_rect(const Gfx::IntRect& rect) { + Screen* best_screen = nullptr; + int best_area = 0; + for (auto& screen : s_screens) { + auto r = screen.rect().intersected(rect); + int area = r.width() * r.height(); + if (!best_screen || area > best_area) { + best_screen = &screen; + best_area = area; + } + } + if (!best_screen) { + // TODO: try to find the best screen in close proximity + best_screen = &Screen::main(); + } + return *best_screen; +} + +Screen& Screen::closest_to_location(const Gfx::IntPoint& point) +{ + for (auto& screen : s_screens) { + if (screen.rect().contains(point)) + return screen; + } + // TODO: guess based on how close the point is to the next screen rectangle + return Screen::main(); +} + +void Screen::update_bounding_rect() +{ + if (!s_screens.is_empty()) { + s_bounding_screens_rect = s_screens[0].rect(); + for (size_t i = 1; i < s_screens.size(); i++) + s_bounding_screens_rect = s_bounding_screens_rect.united(s_screens[i].rect()); + } else { + s_bounding_screens_rect = {}; + } +} + +bool Screen::do_set_resolution(bool initial, int width, int height, int new_scale_factor) +{ + // Remember the screen that the cursor is on. Make sure it stays on the same screen if we change its resolution... + auto& screen_with_cursor = ScreenInput::the().cursor_location_screen(); + int new_physical_width = width * new_scale_factor; int new_physical_height = height * new_scale_factor; - if (physical_width() == new_physical_width && physical_height() == new_physical_height) { - VERIFY(scale_factor() != new_scale_factor); - on_change_resolution(m_pitch, physical_width(), physical_height(), new_scale_factor); + if (!initial && physical_width() == new_physical_width && physical_height() == new_physical_height) { + VERIFY(initial || scale_factor() != new_scale_factor); + on_change_resolution(initial, m_pitch, physical_width(), physical_height(), new_scale_factor, screen_with_cursor); return true; } FBResolution physical_resolution { 0, (unsigned)new_physical_width, (unsigned)new_physical_height }; int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); - dbgln_if(WSSCREEN_DEBUG, "fb_set_resolution() - return code {}", rc); + dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc); if (rc == 0) { - on_change_resolution(physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor); + on_change_resolution(initial, physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor, screen_with_cursor); return true; } if (rc == -1) { - dbgln("Invalid resolution {}x{}", width, height); - on_change_resolution(physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor); + int err = errno; + dbgln("Screen #{}: Failed to set resolution {}x{}: {}", index(), width, height, strerror(err)); + on_change_resolution(initial, physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor, screen_with_cursor); return false; } VERIFY_NOT_REACHED(); } -void Screen::on_change_resolution(int pitch, int new_physical_width, int new_physical_height, int new_scale_factor) +void Screen::on_change_resolution(bool initial, int pitch, int new_physical_width, int new_physical_height, int new_scale_factor, Screen& screen_with_cursor) { - if (physical_width() != new_physical_width || physical_height() != new_physical_height) { + if (initial || physical_width() != new_physical_width || physical_height() != new_physical_height) { if (m_framebuffer) { size_t previous_size_in_bytes = m_size_in_bytes; int rc = munmap(m_framebuffer, previous_size_in_bytes); @@ -93,11 +160,15 @@ void Screen::on_change_resolution(int pitch, int new_physical_width, int new_phy } m_pitch = pitch; - m_width = new_physical_width / new_scale_factor; - m_height = new_physical_height / new_scale_factor; + m_virtual_rect.set_width(new_physical_width / new_scale_factor); + m_virtual_rect.set_height(new_physical_height / new_scale_factor); m_scale_factor = new_scale_factor; + update_bounding_rect(); - m_physical_cursor_location.constrain(physical_rect()); + if (this == &screen_with_cursor) { + auto& screen_input = ScreenInput::the(); + screen_input.set_cursor_location(screen_input.cursor_location().constrained(rect())); + } } void Screen::set_buffer(int index) @@ -107,31 +178,35 @@ void Screen::set_buffer(int index) VERIFY(rc == 0); } -void Screen::set_acceleration_factor(double factor) +void ScreenInput::set_acceleration_factor(double factor) { VERIFY(factor >= mouse_accel_min && factor <= mouse_accel_max); m_acceleration_factor = factor; } -void Screen::set_scroll_step_size(unsigned step_size) +void ScreenInput::set_scroll_step_size(unsigned step_size) { VERIFY(step_size >= scroll_step_size_min); m_scroll_step_size = step_size; } -void Screen::on_receive_mouse_data(const MousePacket& packet) +void ScreenInput::on_receive_mouse_data(const MousePacket& packet) { - auto prev_location = m_physical_cursor_location / m_scale_factor; + auto& current_screen = cursor_location_screen(); + auto prev_location = m_cursor_location; if (packet.is_relative) { - m_physical_cursor_location.translate_by(packet.x * m_acceleration_factor, packet.y * m_acceleration_factor); - dbgln_if(WSSCREEN_DEBUG, "Screen: New Relative mouse point @ {}", m_physical_cursor_location); + m_cursor_location.translate_by(packet.x * m_acceleration_factor, packet.y * m_acceleration_factor); + dbgln_if(WSSCREEN_DEBUG, "Screen: New Relative mouse point @ {}", m_cursor_location); } else { - m_physical_cursor_location = { packet.x * physical_width() / 0xffff, packet.y * physical_height() / 0xffff }; - dbgln_if(WSSCREEN_DEBUG, "Screen: New Absolute mouse point @ {}", m_physical_cursor_location); + m_cursor_location = { packet.x * current_screen.physical_width() / 0xffff, packet.y * current_screen.physical_height() / 0xffff }; + dbgln_if(WSSCREEN_DEBUG, "Screen: New Absolute mouse point @ {}", m_cursor_location); } - m_physical_cursor_location.constrain(physical_rect()); - auto new_location = m_physical_cursor_location / m_scale_factor; + auto* moved_to_screen = Screen::find_by_location(m_cursor_location); + if (!moved_to_screen) { + m_cursor_location = m_cursor_location.constrained(current_screen.rect()); + moved_to_screen = ¤t_screen; + } unsigned buttons = packet.buttons; unsigned prev_buttons = m_mouse_button_state; @@ -140,7 +215,7 @@ void Screen::on_receive_mouse_data(const MousePacket& packet) auto post_mousedown_or_mouseup_if_needed = [&](MouseButton button) { if (!(changed_buttons & (unsigned)button)) return; - auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, new_location, buttons, button, m_modifiers); + auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, m_cursor_location, buttons, button, m_modifiers); Core::EventLoop::current().post_event(WindowManager::the(), move(message)); }; post_mousedown_or_mouseup_if_needed(MouseButton::Left); @@ -148,23 +223,23 @@ void Screen::on_receive_mouse_data(const MousePacket& packet) post_mousedown_or_mouseup_if_needed(MouseButton::Middle); post_mousedown_or_mouseup_if_needed(MouseButton::Back); post_mousedown_or_mouseup_if_needed(MouseButton::Forward); - if (new_location != prev_location) { - auto message = make<MouseEvent>(Event::MouseMove, new_location, buttons, MouseButton::None, m_modifiers); + if (m_cursor_location != prev_location) { + auto message = make<MouseEvent>(Event::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers); if (WindowManager::the().dnd_client()) message->set_mime_data(WindowManager::the().dnd_mime_data()); Core::EventLoop::current().post_event(WindowManager::the(), move(message)); } if (packet.z) { - auto message = make<MouseEvent>(Event::MouseWheel, new_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size); + auto message = make<MouseEvent>(Event::MouseWheel, m_cursor_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size); Core::EventLoop::current().post_event(WindowManager::the(), move(message)); } - if (new_location != prev_location) + if (m_cursor_location != prev_location) Compositor::the().invalidate_cursor(); } -void Screen::on_receive_keyboard_data(::KeyEvent kernel_event) +void ScreenInput::on_receive_keyboard_data(::KeyEvent kernel_event) { m_modifiers = kernel_event.modifiers(); auto message = make<KeyEvent>(kernel_event.is_press() ? Event::KeyDown : Event::KeyUp, kernel_event.key, kernel_event.code_point, kernel_event.modifiers(), kernel_event.scancode); diff --git a/Userland/Services/WindowServer/Screen.h b/Userland/Services/WindowServer/Screen.h index e4885f0238..4e094ee795 100644 --- a/Userland/Services/WindowServer/Screen.h +++ b/Userland/Services/WindowServer/Screen.h @@ -6,6 +6,7 @@ #pragma once +#include <AK/NonnullOwnPtrVector.h> #include <Kernel/API/KeyCode.h> #include <LibGfx/Color.h> #include <LibGfx/Rect.h> @@ -19,12 +20,115 @@ constexpr double mouse_accel_max = 3.5; constexpr double mouse_accel_min = 0.5; constexpr unsigned scroll_step_size_min = 1; +// Most people will probably have 4 screens or less +constexpr size_t default_screen_count = 4; + +class Screen; + +class ScreenInput { +public: + static ScreenInput& the(); + + Screen& cursor_location_screen(); + const Screen& cursor_location_screen() const; + unsigned mouse_button_state() const { return m_mouse_button_state; } + + double acceleration_factor() const { return m_acceleration_factor; } + void set_acceleration_factor(double); + + unsigned scroll_step_size() const { return m_scroll_step_size; } + void set_scroll_step_size(unsigned); + + void on_receive_mouse_data(const MousePacket&); + void on_receive_keyboard_data(::KeyEvent); + + Gfx::IntPoint cursor_location() const { return m_cursor_location; } + void set_cursor_location(const Gfx::IntPoint point) { m_cursor_location = point; } + +private: + Gfx::IntPoint m_cursor_location; + unsigned m_mouse_button_state { 0 }; + unsigned m_modifiers { 0 }; + double m_acceleration_factor { 1.0 }; + unsigned m_scroll_step_size { 1 }; +}; + class Screen { public: - Screen(unsigned width, unsigned height, int scale_factor); + template<typename... Args> + static Screen* create(Args&&... args) + { + auto screen = adopt_own(*new Screen(forward<Args>(args)...)); + if (!screen->is_opened()) + return nullptr; + auto* screen_ptr = screen.ptr(); + s_screens.append(move(screen)); + update_indices(); + update_bounding_rect(); + if (!s_main_screen) + s_main_screen = screen_ptr; + screen_ptr->init(); + return screen_ptr; + } ~Screen(); - bool set_resolution(int width, int height, int scale_factor); + static Screen& main() + { + VERIFY(s_main_screen); + return *s_main_screen; + } + + static Screen& closest_to_rect(const Gfx::IntRect&); + static Screen& closest_to_location(const Gfx::IntPoint&); + + static Screen* find_by_index(size_t index) + { + if (index >= s_screens.size()) + return nullptr; + return &s_screens[index]; + } + + static Vector<Gfx::IntRect, 4> rects() + { + Vector<Gfx::IntRect, 4> rects; + for (auto& screen : s_screens) + rects.append(screen.rect()); + return rects; + } + + static Screen* find_by_location(const Gfx::IntPoint& point) + { + for (auto& screen : s_screens) { + if (screen.rect().contains(point)) + return &screen; + } + return nullptr; + } + + static const Gfx::IntRect& bounding_rect() { return s_bounding_screens_rect; } + + static size_t count() { return s_screens.size(); } + size_t index() const { return m_index; } + + template<typename F> + static IterationDecision for_each(F f) + { + for (auto& screen : s_screens) { + IterationDecision decision = f(screen); + if (decision != IterationDecision::Continue) + return decision; + } + return IterationDecision::Continue; + } + + void make_main_screen() { s_main_screen = this; } + bool is_main_screen() const { return s_main_screen == this; } + + template<typename... Args> + bool set_resolution(Args&&... args) + { + return do_set_resolution(false, forward<Args>(args)...); + } bool can_set_buffer() { return m_can_set_buffer; } void set_buffer(int index); @@ -32,34 +136,36 @@ public: int physical_height() const { return height() * scale_factor(); } size_t pitch() const { return m_pitch; } - int width() const { return m_width; } - int height() const { return m_height; } + int width() const { return m_virtual_rect.width(); } + int height() const { return m_virtual_rect.height(); } int scale_factor() const { return m_scale_factor; } Gfx::RGBA32* scanline(int y); - static Screen& the(); - Gfx::IntSize physical_size() const { return { physical_width(), physical_height() }; } - Gfx::IntRect physical_rect() const { return { { 0, 0 }, physical_size() }; } - Gfx::IntSize size() const { return { width(), height() }; } - Gfx::IntRect rect() const { return { { 0, 0 }, size() }; } + Gfx::IntSize size() const { return { m_virtual_rect.width(), m_virtual_rect.height() }; } + Gfx::IntRect rect() const { return m_virtual_rect; } - Gfx::IntPoint cursor_location() const { return m_physical_cursor_location / m_scale_factor; } - unsigned mouse_button_state() const { return m_mouse_button_state; } +private: + Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale_factor); + void init(); + bool do_set_resolution(bool initial, int width, int height, int scale_factor); + static void update_indices() + { + for (size_t i = 0; i < s_screens.size(); i++) + s_screens[i].m_index = i; + } + static void update_bounding_rect(); - double acceleration_factor() const { return m_acceleration_factor; } - void set_acceleration_factor(double); + bool is_opened() const { return m_framebuffer_fd >= 0; } - unsigned scroll_step_size() const { return m_scroll_step_size; } - void set_scroll_step_size(unsigned); + void on_change_resolution(bool initial, int pitch, int physical_width, int physical_height, int scale_factor, Screen& screen_with_cursor); - void on_receive_mouse_data(const MousePacket&); - void on_receive_keyboard_data(::KeyEvent); - -private: - void on_change_resolution(int pitch, int physical_width, int physical_height, int scale_factor); + static NonnullOwnPtrVector<Screen, default_screen_count> s_screens; + static Screen* s_main_screen; + static Gfx::IntRect s_bounding_screens_rect; + size_t m_index { 0 }; size_t m_size_in_bytes; @@ -67,16 +173,9 @@ private: bool m_can_set_buffer { false }; int m_pitch { 0 }; - int m_width { 0 }; - int m_height { 0 }; + Gfx::IntRect m_virtual_rect; int m_framebuffer_fd { -1 }; int m_scale_factor { 1 }; - - Gfx::IntPoint m_physical_cursor_location; - unsigned m_mouse_button_state { 0 }; - unsigned m_modifiers { 0 }; - double m_acceleration_factor { 1.0 }; - unsigned m_scroll_step_size { 1 }; }; inline Gfx::RGBA32* Screen::scanline(int y) diff --git a/Userland/Services/WindowServer/WMClientConnection.cpp b/Userland/Services/WindowServer/WMClientConnection.cpp index d1174d0c37..e1ca5d2b17 100644 --- a/Userland/Services/WindowServer/WMClientConnection.cpp +++ b/Userland/Services/WindowServer/WMClientConnection.cpp @@ -96,7 +96,7 @@ void WMClientConnection::start_window_resize(i32 client_id, i32 window_id) auto& window = *(*it).value; // FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button. // Maybe the client should be allowed to specify what initiated this request? - WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left); + WindowManager::the().start_window_resize(window, ScreenInput::the().cursor_location(), MouseButton::Left); } void WMClientConnection::set_window_minimized(i32 client_id, i32 window_id, bool minimized) diff --git a/Userland/Services/WindowServer/Window.cpp b/Userland/Services/WindowServer/Window.cpp index 0b89368725..3ec39650ad 100644 --- a/Userland/Services/WindowServer/Window.cpp +++ b/Userland/Services/WindowServer/Window.cpp @@ -174,12 +174,25 @@ bool Window::apply_minimum_size(Gfx::IntRect& rect) return did_size_clamp; } -void Window::nudge_into_desktop(bool force_titlebar_visible) +void Window::nudge_into_desktop(Screen* target_screen, bool force_titlebar_visible) { - Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(type()); + if (!target_screen) { + // If no explicit target screen was supplied, + // guess based on the current frame rectangle + target_screen = &Screen::closest_to_rect(rect()); + } + Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(*target_screen, type()); auto min_visible = 1; - if (type() == WindowType::Normal) + switch (type()) { + case WindowType::Normal: min_visible = 30; + break; + case WindowType::Desktop: + set_rect(arena); + return; + default: + break; + } // Push the frame around such that at least `min_visible` pixels of the *frame* are in the desktop rect. auto old_frame_rect = frame().rect(); @@ -203,6 +216,7 @@ void Window::nudge_into_desktop(bool force_titlebar_visible) width(), height(), }; + set_rect(new_window_rect); } @@ -684,7 +698,7 @@ void Window::handle_window_menu_action(WindowMenuAction action) WindowManager::the().move_to_front_and_make_active(*this); break; case WindowMenuAction::Move: - WindowManager::the().start_window_move(*this, Screen::the().cursor_location()); + WindowManager::the().start_window_move(*this, ScreenInput::the().cursor_location()); break; case WindowMenuAction::Close: request_close(); @@ -746,7 +760,7 @@ void Window::set_fullscreen(bool fullscreen) Gfx::IntRect new_window_rect = m_rect; if (m_fullscreen) { m_saved_nonfullscreen_rect = m_rect; - new_window_rect = Screen::the().rect(); + new_window_rect = Screen::main().rect(); // TODO: We should support fullscreen on any screen } else if (!m_saved_nonfullscreen_rect.is_empty()) { new_window_rect = m_saved_nonfullscreen_rect; } @@ -755,56 +769,73 @@ void Window::set_fullscreen(bool fullscreen) set_rect(new_window_rect); } -Gfx::IntRect Window::tiled_rect(WindowTileType tiled) const +Gfx::IntRect Window::tiled_rect(Screen* target_screen, WindowTileType tiled) const { + if (!target_screen) { + // If no explicit target screen was supplied, + // guess based on the current frame rectangle + target_screen = &Screen::closest_to_rect(frame().rect()); + } + VERIFY(tiled != WindowTileType::None); int frame_width = (m_frame.rect().width() - m_rect.width()) / 2; int titlebar_height = m_frame.titlebar_rect().height(); - int menu_height = WindowManager::the().maximized_window_rect(*this).y(); - int max_height = WindowManager::the().maximized_window_rect(*this).height(); + auto maximized_rect_relative_to_window_screen = WindowManager::the().maximized_window_rect(*this, true); + int menu_height = maximized_rect_relative_to_window_screen.y(); + int max_height = maximized_rect_relative_to_window_screen.height(); + auto& screen = *target_screen; + auto screen_location = screen.rect().location(); switch (tiled) { case WindowTileType::Left: return Gfx::IntRect(0, menu_height, - Screen::the().width() / 2 - frame_width, - max_height); + screen.width() / 2 - frame_width, + max_height) + .translated(screen_location); case WindowTileType::Right: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height, - Screen::the().width() / 2 - frame_width, - max_height); + screen.width() / 2 - frame_width, + max_height) + .translated(screen_location); case WindowTileType::Top: return Gfx::IntRect(0, menu_height, - Screen::the().width(), - (max_height - titlebar_height) / 2 - frame_width); + screen.width(), + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::Bottom: return Gfx::IntRect(0, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width(), - (max_height - titlebar_height) / 2 - frame_width); + screen.width(), + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::TopLeft: return Gfx::IntRect(0, menu_height, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::TopRight: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::BottomLeft: return Gfx::IntRect(0, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::BottomRight: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); default: VERIFY_NOT_REACHED(); } @@ -831,7 +862,7 @@ bool Window::set_untiled(Optional<Gfx::IntPoint> fixed_point) return true; } -void Window::set_tiled(WindowTileType tiled) +void Window::set_tiled(Screen* screen, WindowTileType tiled) { VERIFY(tiled != WindowTileType::None); @@ -845,7 +876,7 @@ void Window::set_tiled(WindowTileType tiled) m_untiled_rect = m_rect; m_tiled = tiled; - set_rect(tiled_rect(tiled)); + set_rect(tiled_rect(screen, tiled)); Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect)); } @@ -861,11 +892,11 @@ void Window::recalculate_rect() bool send_event = true; if (m_tiled != WindowTileType::None) { - set_rect(tiled_rect(m_tiled)); + set_rect(tiled_rect(nullptr, m_tiled)); } else if (is_maximized()) { set_rect(WindowManager::the().maximized_window_rect(*this)); } else if (type() == WindowType::Desktop) { - set_rect(WindowManager::the().desktop_rect()); + set_rect(WindowManager::the().arena_rect_for_type(Screen::main(), WindowType::Desktop)); } else { send_event = false; } @@ -953,7 +984,7 @@ bool Window::is_descendant_of(Window& window) const return false; } -Optional<HitTestResult> Window::hit_test(Gfx::IntPoint const& position, bool include_frame) const +Optional<HitTestResult> Window::hit_test(Gfx::IntPoint const& position, bool include_frame) { if (!m_hit_testing_enabled) return {}; diff --git a/Userland/Services/WindowServer/Window.h b/Userland/Services/WindowServer/Window.h index bf1403ebac..129f7d7281 100644 --- a/Userland/Services/WindowServer/Window.h +++ b/Userland/Services/WindowServer/Window.h @@ -14,6 +14,7 @@ #include <LibGfx/DisjointRectSet.h> #include <LibGfx/Rect.h> #include <WindowServer/Cursor.h> +#include <WindowServer/Screen.h> #include <WindowServer/WindowFrame.h> #include <WindowServer/WindowType.h> @@ -97,7 +98,7 @@ public: void set_fullscreen(bool); WindowTileType tiled() const { return m_tiled; } - void set_tiled(WindowTileType); + void set_tiled(Screen*, WindowTileType); bool set_untiled(Optional<Gfx::IntPoint> fixed_point = {}); bool is_occluded() const { return m_occluded; } @@ -139,7 +140,8 @@ public: { m_alpha_hit_threshold = threshold; } - Optional<HitTestResult> hit_test(const Gfx::IntPoint&, bool include_frame = true) const; + + Optional<HitTestResult> hit_test(const Gfx::IntPoint&, bool include_frame = true); int x() const { return m_rect.x(); } int y() const { return m_rect.y(); } @@ -159,7 +161,7 @@ public: void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); } void set_rect_without_repaint(const Gfx::IntRect&); bool apply_minimum_size(Gfx::IntRect&); - void nudge_into_desktop(bool force_titlebar_visible = true); + void nudge_into_desktop(Screen*, bool force_titlebar_visible = true); Gfx::IntSize minimum_size() const { return m_minimum_size; } void set_minimum_size(const Gfx::IntSize&); @@ -260,7 +262,7 @@ public: void start_minimize_animation(); void end_minimize_animation() { m_minimize_animation_step = -1; } - Gfx::IntRect tiled_rect(WindowTileType) const; + Gfx::IntRect tiled_rect(Screen*, WindowTileType) const; void recalculate_rect(); IntrusiveListNode<Window> m_list_node; @@ -319,6 +321,14 @@ public: WindowStack const* outer_stack() const { return m_outer_stack; } void set_outer_stack(Badge<WindowStack>, WindowStack* stack) { m_outer_stack = stack; } + const Vector<Screen*, default_screen_count>& screens() const { return m_screens; } + Vector<Screen*, default_screen_count>& screens() { return m_screens; } + + void did_construct() + { + frame().window_was_constructed({}); + } + private: Window(ClientConnection&, WindowType, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window = nullptr); Window(Core::Object&, WindowType); @@ -344,6 +354,7 @@ private: Gfx::IntRect m_rect; Gfx::IntRect m_saved_nonfullscreen_rect; Gfx::IntRect m_taskbar_rect; + Vector<Screen*, default_screen_count> m_screens; Gfx::DisjointRectSet m_dirty_rects; Gfx::DisjointRectSet m_opaque_rects; Gfx::DisjointRectSet m_transparency_rects; diff --git a/Userland/Services/WindowServer/WindowClient.ipc b/Userland/Services/WindowServer/WindowClient.ipc index f54b0fccd3..a3219415f0 100644 --- a/Userland/Services/WindowServer/WindowClient.ipc +++ b/Userland/Services/WindowServer/WindowClient.ipc @@ -1,6 +1,6 @@ endpoint WindowClient { - fast_greet(Gfx::IntRect screen_rect, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| + fast_greet(Vector<Gfx::IntRect> screen_rects, u32 main_screen_index, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| paint(i32 window_id, Gfx::IntSize window_size, Vector<Gfx::IntRect> rects) =| mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector<String> mime_types) =| @@ -25,7 +25,7 @@ endpoint WindowClient menu_item_left(i32 menu_id, u32 identifier) =| menu_visibility_did_change(i32 menu_id, bool visible) =| - screen_rect_changed(Gfx::IntRect rect) =| + screen_rects_changed(Vector<Gfx::IntRect> rects, u32 main_screen_index) =| set_wallpaper_finished(bool success) =| diff --git a/Userland/Services/WindowServer/WindowFrame.cpp b/Userland/Services/WindowServer/WindowFrame.cpp index fe799039d5..add33622ef 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -65,6 +65,14 @@ static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& re WindowFrame::WindowFrame(Window& window) : m_window(window) { + // Because Window constructs a WindowFrame during its construction, we need + // to be careful and defer doing initialization that assumes a fully + // constructed Window. It is fully constructed when Window notifies us with + // a call to WindowFrame::window_was_constructed. +} + +void WindowFrame::window_was_constructed(Badge<Window>) +{ { auto button = make<Button>(*this, [this](auto&) { m_window.handle_window_menu_action(WindowMenuAction::Close); @@ -73,7 +81,7 @@ WindowFrame::WindowFrame(Window& window) m_buttons.append(move(button)); } - if (window.is_resizable()) { + if (m_window.is_resizable()) { auto button = make<Button>(*this, [this](auto&) { m_window.handle_window_menu_action(WindowMenuAction::MaximizeOrRestore); }); @@ -84,7 +92,7 @@ WindowFrame::WindowFrame(Window& window) m_buttons.append(move(button)); } - if (window.is_minimizable()) { + if (m_window.is_minimizable()) { auto button = make<Button>(*this, [this](auto&) { m_window.handle_window_menu_action(WindowMenuAction::MinimizeOrUnminimize); }); @@ -93,6 +101,8 @@ WindowFrame::WindowFrame(Window& window) } set_button_icons(); + + m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); } WindowFrame::~WindowFrame() @@ -101,7 +111,7 @@ WindowFrame::~WindowFrame() void WindowFrame::set_button_icons() { - m_dirty = true; + set_dirty(); if (m_window.is_frameless()) return; @@ -115,7 +125,7 @@ void WindowFrame::set_button_icons() void WindowFrame::reload_config() { String icons_path = WindowManager::the().palette().title_button_icons_path(); - int icons_scale = WindowManager::the().compositor_icon_scale(); + int icons_scale = WindowManager::the().compositor_icon_scale(); // TODO: We'll need to load icons for all scales in use! StringBuilder full_path; if (!s_minimize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { @@ -301,27 +311,30 @@ void WindowFrame::paint_normal_frame(Gfx::Painter& painter) paint_menubar(painter); } -void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect) +void WindowFrame::paint(Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) { - render_to_cache(); - - auto frame_rect = render_rect(); - auto window_rect = m_window.rect(); + if (auto* cached = render_to_cache(screen)) + cached->paint(*this, painter, rect); +} +void WindowFrame::RenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter, const Gfx::IntRect& rect) +{ + auto frame_rect = frame.render_rect(); + auto window_rect = frame.window().rect(); if (m_top_bottom) { auto top_bottom_height = frame_rect.height() - window_rect.height(); if (m_bottom_y > 0) { // We have a top piece auto src_rect = rect.intersected({ frame_rect.location(), { frame_rect.width(), m_bottom_y } }); if (!src_rect.is_empty()) - painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), m_opacity); + painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), frame.opacity()); } if (m_bottom_y < top_bottom_height) { // We have a bottom piece Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.bottom() + 1, frame_rect.width(), top_bottom_height - m_bottom_y }; auto src_rect = rect.intersected(rect_in_frame); if (!src_rect.is_empty()) - painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), m_opacity); + painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), frame.opacity()); } } @@ -332,19 +345,19 @@ void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect) Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.y(), m_right_x, window_rect.height() }; auto src_rect = rect.intersected(rect_in_frame); if (!src_rect.is_empty()) - painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), m_opacity); + painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), frame.opacity()); } if (m_right_x < left_right_width) { // We have a right piece Gfx::IntRect rect_in_frame { window_rect.right() + 1, window_rect.y(), left_right_width - m_right_x, window_rect.height() }; auto src_rect = rect.intersected(rect_in_frame); if (!src_rect.is_empty()) - painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), m_opacity); + painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), frame.opacity()); } } } -void WindowFrame::render(Gfx::Painter& painter) +void WindowFrame::render(Screen&, Gfx::Painter& painter) { if (m_window.is_frameless()) return; @@ -365,10 +378,7 @@ void WindowFrame::render(Gfx::Painter& painter) void WindowFrame::theme_changed() { - m_dirty = m_shadow_dirty = true; - m_top_bottom = nullptr; - m_left_right = nullptr; - m_bottom_y = m_right_x = 0; + m_rendered_cache = {}; layout_buttons(); set_button_icons(); @@ -376,19 +386,34 @@ void WindowFrame::theme_changed() m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); } -void WindowFrame::render_to_cache() +auto WindowFrame::render_to_cache(Screen& screen) -> RenderedCache* +{ + auto scale = screen.scale_factor(); + RenderedCache* rendered_cache; + auto cached_it = m_rendered_cache.find(scale); + if (cached_it == m_rendered_cache.end()) { + auto new_rendered_cache = make<RenderedCache>(); + rendered_cache = new_rendered_cache.ptr(); + m_rendered_cache.set(scale, move(new_rendered_cache)); + } else { + rendered_cache = cached_it->value.ptr(); + } + rendered_cache->render(*this, screen); + return rendered_cache; +} + +void WindowFrame::RenderedCache::render(WindowFrame& frame, Screen& screen) { if (!m_dirty) return; m_dirty = false; - m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); + auto scale = screen.scale_factor(); - static RefPtr<Gfx::Bitmap> s_tmp_bitmap; - auto frame_rect = rect(); + auto frame_rect = frame.rect(); auto frame_rect_including_shadow = frame_rect; - auto* shadow_bitmap = this->shadow_bitmap(); + auto* shadow_bitmap = frame.shadow_bitmap(); Gfx::IntPoint shadow_offset; if (shadow_bitmap) { @@ -398,19 +423,35 @@ void WindowFrame::render_to_cache() shadow_offset = { offset, offset }; } - auto window_rect = m_window.rect(); - auto scale = Screen::the().scale_factor(); - if (!s_tmp_bitmap || !s_tmp_bitmap->size().contains(frame_rect_including_shadow.size()) || s_tmp_bitmap->scale() != scale) { - // Explicitly clear the old bitmap first so this works on machines with very little memory - s_tmp_bitmap = nullptr; - s_tmp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, frame_rect_including_shadow.size(), scale); - if (!s_tmp_bitmap) { - dbgln("Could not create bitmap of size {}", frame_rect_including_shadow.size()); - return; + auto window_rect = frame.window().rect(); + + // TODO: if we stop using a scaling factor we should clear cached bitmaps from this map + static HashMap<int, RefPtr<Gfx::Bitmap>> s_tmp_bitmap_cache; + Gfx::Bitmap* tmp_bitmap; + { + auto tmp_it = s_tmp_bitmap_cache.find(scale); + if (tmp_it == s_tmp_bitmap_cache.end() || !tmp_it->value->size().contains(frame_rect_including_shadow.size())) { + // Explicitly clear the old bitmap first so this works on machines with very little memory + if (tmp_it != s_tmp_bitmap_cache.end()) + tmp_it->value = nullptr; + + auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, frame_rect_including_shadow.size(), scale); + if (!bitmap) { + s_tmp_bitmap_cache.remove(scale); + dbgln("Could not create bitmap of size {}", frame_rect_including_shadow.size()); + return; + } + tmp_bitmap = bitmap.ptr(); + if (tmp_it != s_tmp_bitmap_cache.end()) + tmp_it->value = bitmap.release_nonnull(); + else + s_tmp_bitmap_cache.set(scale, bitmap.release_nonnull()); + } else { + tmp_bitmap = tmp_it->value.ptr(); } } - VERIFY(s_tmp_bitmap); + VERIFY(tmp_bitmap); auto top_bottom_height = frame_rect_including_shadow.height() - window_rect.height(); auto left_right_width = frame_rect_including_shadow.width() - window_rect.width(); @@ -433,19 +474,19 @@ void WindowFrame::render_to_cache() auto& frame_rect_to_update = m_shadow_dirty ? frame_rect_including_shadow : frame_rect; Gfx::IntPoint update_location(m_shadow_dirty ? Gfx::IntPoint { 0, 0 } : shadow_offset); - Gfx::Painter painter(*s_tmp_bitmap); + Gfx::Painter painter(*tmp_bitmap); // Clear the frame area, not including the window content area, which we don't care about for (auto& rect : frame_rect_to_update.shatter(window_rect)) painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 }); if (m_shadow_dirty && shadow_bitmap) - paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, *shadow_bitmap); + frame.paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, *shadow_bitmap); { Gfx::PainterStateSaver save(painter); painter.translate(shadow_offset); - render(painter); + frame.render(screen, painter); } if (m_top_bottom && top_bottom_height > 0) { @@ -455,9 +496,9 @@ void WindowFrame::render_to_cache() Gfx::Painter top_bottom_painter(*m_top_bottom); top_bottom_painter.add_clip_rect({ update_location, { frame_rect_to_update.width(), top_bottom_height - update_location.y() - (frame_rect_including_shadow.bottom() - frame_rect_to_update.bottom()) } }); if (m_bottom_y > 0) - top_bottom_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, 0, frame_rect_including_shadow.width(), m_bottom_y }, 1.0, false); + top_bottom_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, 0, frame_rect_including_shadow.width(), m_bottom_y }, 1.0, false); if (m_bottom_y < top_bottom_height) - top_bottom_painter.blit({ 0, m_bottom_y }, *s_tmp_bitmap, { 0, frame_rect_including_shadow.height() - (frame_rect_including_shadow.bottom() - window_rect.bottom()), frame_rect_including_shadow.width(), top_bottom_height - m_bottom_y }, 1.0, false); + top_bottom_painter.blit({ 0, m_bottom_y }, *tmp_bitmap, { 0, frame_rect_including_shadow.height() - (frame_rect_including_shadow.bottom() - window_rect.bottom()), frame_rect_including_shadow.width(), top_bottom_height - m_bottom_y }, 1.0, false); } else { m_bottom_y = 0; } @@ -469,9 +510,9 @@ void WindowFrame::render_to_cache() Gfx::Painter left_right_painter(*m_left_right); left_right_painter.add_clip_rect({ update_location, { left_right_width - update_location.x() - (frame_rect_including_shadow.right() - frame_rect_to_update.right()), window_rect.height() } }); if (m_right_x > 0) - left_right_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false); + left_right_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false); if (m_right_x < left_right_width) - left_right_painter.blit({ m_right_x, 0 }, *s_tmp_bitmap, { (window_rect.right() - frame_rect_including_shadow.x()) + 1, m_bottom_y, frame_rect_including_shadow.width() - (frame_rect_including_shadow.right() - window_rect.right()), window_rect.height() }, 1.0, false); + left_right_painter.blit({ m_right_x, 0 }, *tmp_bitmap, { (window_rect.right() - frame_rect_including_shadow.x()) + 1, m_bottom_y, frame_rect_including_shadow.width() - (frame_rect_including_shadow.right() - window_rect.right()), window_rect.height() }, 1.0, false); } else { m_right_x = 0; } @@ -545,7 +586,7 @@ Gfx::DisjointRectSet WindowFrame::transparent_render_rects() const void WindowFrame::invalidate_titlebar() { - m_dirty = true; + set_dirty(); invalidate(titlebar_rect()); } @@ -561,7 +602,7 @@ void WindowFrame::invalidate(Gfx::IntRect relative_rect) auto frame_rect = rect(); auto window_rect = m_window.rect(); relative_rect.translate_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y()); - m_dirty = true; + set_dirty(); m_window.invalidate(relative_rect, true); } @@ -572,7 +613,7 @@ void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const auto old_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, old_rect)); auto new_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, new_rect)); if (old_frame_rect.size() != new_frame_rect.size()) - m_dirty = m_shadow_dirty = true; + set_dirty(true); auto& compositor = Compositor::the(); for (auto& dirty : old_frame_rect.shatter(new_frame_rect)) compositor.invalidate_screen(dirty); @@ -591,7 +632,7 @@ void WindowFrame::layout_buttons() m_buttons[i].set_relative_rect(button_rects[i]); } -Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) const +Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) { if (m_window.is_frameless()) return {}; @@ -602,19 +643,32 @@ Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) con if (window_rect.contains(position)) return {}; + auto* screen = Screen::find_by_location(position); + if (!screen) + return {}; + auto* cached = render_to_cache(*screen); + if (!cached) + return {}; + auto window_relative_position = position.translated(-render_rect().location()); + return cached->hit_test(*this, position, window_relative_position); +} + +Optional<HitTestResult> WindowFrame::RenderedCache::hit_test(WindowFrame& frame, Gfx::IntPoint const& position, Gfx::IntPoint const& window_relative_position) +{ HitTestResult result { - .window = m_window, + .window = frame.window(), .screen_position = position, .window_relative_position = window_relative_position, .is_frame_hit = true, }; - u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(window_state_for_theme()) * 255; + u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(frame.window_state_for_theme()) * 255; if (alpha_threshold == 0) return result; u8 alpha = 0xff; + auto window_rect = frame.window().rect(); if (position.y() < window_rect.y()) { if (m_top_bottom) { auto scaled_relative_point = window_relative_position * m_top_bottom->scale(); diff --git a/Userland/Services/WindowServer/WindowFrame.h b/Userland/Services/WindowServer/WindowFrame.h index dba13751c2..ee7f3f55a5 100644 --- a/Userland/Services/WindowServer/WindowFrame.h +++ b/Userland/Services/WindowServer/WindowFrame.h @@ -20,21 +20,46 @@ class Button; class Menu; class MouseEvent; class Window; +class Screen; class WindowFrame { public: + class RenderedCache { + friend class WindowFrame; + + public: + void paint(WindowFrame&, Gfx::Painter&, const Gfx::IntRect&); + void render(WindowFrame&, Screen&); + Optional<HitTestResult> hit_test(WindowFrame&, Gfx::IntPoint const&, Gfx::IntPoint const&); + + private: + RefPtr<Gfx::Bitmap> m_top_bottom; + RefPtr<Gfx::Bitmap> m_left_right; + int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half + int m_right_x { 0 }; // x-offset in m_left_right for the right half + bool m_shadow_dirty { true }; + bool m_dirty { true }; + }; + friend class RenderedCache; + static void reload_config(); explicit WindowFrame(Window&); ~WindowFrame(); + void window_was_constructed(Badge<Window>); + + Window& window() { return m_window; } + const Window& window() const { return m_window; } + Gfx::IntRect rect() const; Gfx::IntRect render_rect() const; Gfx::DisjointRectSet opaque_render_rects() const; Gfx::DisjointRectSet transparent_render_rects() const; - void paint(Gfx::Painter&, const Gfx::IntRect&); - void render(Gfx::Painter&); - void render_to_cache(); + + void paint(Screen&, Gfx::Painter&, const Gfx::IntRect&); + void render(Screen&, Gfx::Painter&); + RenderedCache* render_to_cache(Screen&); void handle_mouse_event(MouseEvent const&); void handle_titlebar_mouse_event(MouseEvent const&); @@ -78,13 +103,16 @@ public: void set_dirty(bool re_render_shadow = false) { - m_dirty = true; - m_shadow_dirty |= re_render_shadow; + for (auto& it : m_rendered_cache) { + auto& cached = *it.value; + cached.m_dirty = true; + cached.m_shadow_dirty |= re_render_shadow; + } } void theme_changed(); - Optional<HitTestResult> hit_test(Gfx::IntPoint const&) const; + Optional<HitTestResult> hit_test(Gfx::IntPoint const&); void open_menubar_menu(Menu&); @@ -109,17 +137,12 @@ private: Button* m_maximize_button { nullptr }; Button* m_minimize_button { nullptr }; - RefPtr<Gfx::Bitmap> m_top_bottom; - RefPtr<Gfx::Bitmap> m_left_right; - int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half - int m_right_x { 0 }; // x-offset in m_left_right for the right half + HashMap<int, NonnullOwnPtr<RenderedCache>> m_rendered_cache; RefPtr<Core::Timer> m_flash_timer; size_t m_flash_counter { 0 }; float m_opacity { 1 }; bool m_has_alpha_channel { false }; - bool m_shadow_dirty { false }; - bool m_dirty { false }; }; } diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 8764985310..df9cd6c478 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -93,11 +93,27 @@ Gfx::Font const& WindowManager::window_title_font() const return Gfx::FontDatabase::default_font().bold_variant(); } -bool WindowManager::set_resolution(int width, int height, int scale) +bool WindowManager::set_resolution(Screen& screen, int width, int height, int scale) { - bool success = Compositor::the().set_resolution(width, height, scale); + auto screen_rect = screen.rect(); + if (screen_rect.width() == width && screen_rect.height() == height && screen.scale_factor() == scale) + return true; + + // Make sure it's impossible to set an invalid resolution + if (!(width >= 640 && height >= 480 && scale >= 1)) { + dbgln("Compositor: Tried to set invalid resolution: {}x{}", width, height); + return false; + } + + auto old_scale_factor = screen.scale_factor(); + bool success = screen.set_resolution(width, height, scale); + if (success && old_scale_factor != scale) + reload_icon_bitmaps_after_scale_change(); + + Compositor::the().screen_resolution_changed(); + ClientConnection::for_each_client([&](ClientConnection& client) { - client.notify_about_new_screen_rect(Screen::the().rect()); + client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index()); }); if (success) { m_window_stack.for_each_window([](Window& window) { @@ -113,9 +129,9 @@ bool WindowManager::set_resolution(int width, int height, int scale) m_config->write_num_entry("Screen", "ScaleFactor", scale); m_config->sync(); } else { - dbgln("Saving fallback resolution: {} @1x to config file at {}", resolution(), m_config->filename()); - m_config->write_num_entry("Screen", "Width", resolution().width()); - m_config->write_num_entry("Screen", "Height", resolution().height()); + dbgln("Saving fallback resolution: {} @1x to config file at {}", screen.size(), m_config->filename()); + m_config->write_num_entry("Screen", "Width", screen.size().width()); + m_config->write_num_entry("Screen", "Height", screen.size().height()); m_config->write_num_entry("Screen", "ScaleFactor", 1); m_config->sync(); } @@ -123,14 +139,9 @@ bool WindowManager::set_resolution(int width, int height, int scale) return success; } -Gfx::IntSize WindowManager::resolution() const -{ - return Screen::the().size(); -} - void WindowManager::set_acceleration_factor(double factor) { - Screen::the().set_acceleration_factor(factor); + ScreenInput::the().set_acceleration_factor(factor); dbgln("Saving acceleration factor {} to config file at {}", factor, m_config->filename()); m_config->write_entry("Mouse", "AccelerationFactor", String::formatted("{}", factor)); m_config->sync(); @@ -138,7 +149,7 @@ void WindowManager::set_acceleration_factor(double factor) void WindowManager::set_scroll_step_size(unsigned step_size) { - Screen::the().set_scroll_step_size(step_size); + ScreenInput::the().set_scroll_step_size(step_size); dbgln("Saving scroll step size {} to config file at {}", step_size, m_config->filename()); m_config->write_entry("Mouse", "ScrollStepSize", String::number(step_size)); m_config->sync(); @@ -158,11 +169,6 @@ int WindowManager::double_click_speed() const return m_double_click_speed; } -int WindowManager::scale_factor() const -{ - return Screen::the().scale_factor(); -} - void WindowManager::add_window(Window& window) { bool is_first_window = m_window_stack.is_empty(); @@ -170,8 +176,9 @@ void WindowManager::add_window(Window& window) m_window_stack.add(window); if (window.is_fullscreen()) { - Core::EventLoop::current().post_event(window, make<ResizeEvent>(Screen::the().rect())); - window.set_rect(Screen::the().rect()); + auto& screen = Screen::main(); // TODO: support fullscreen windows on other screens! + Core::EventLoop::current().post_event(window, make<ResizeEvent>(screen.rect())); + window.set_rect(screen.rect()); } if (window.type() != WindowType::Desktop || is_first_window) @@ -558,8 +565,9 @@ bool WindowManager::process_ongoing_window_move(MouseEvent& event) const int tiling_deadzone = 10; const int secondary_deadzone = 2; - auto desktop = desktop_rect(); - + auto& cursor_screen = Screen::closest_to_location(event.position()); + auto desktop = desktop_rect(cursor_screen); + auto desktop_relative_to_screen = desktop.translated(-cursor_screen.rect().location()); if (m_move_window->is_maximized()) { auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); if (pixels_moved_from_start > 5) { @@ -574,31 +582,32 @@ bool WindowManager::process_ongoing_window_move(MouseEvent& event) bool is_resizable = m_move_window->is_resizable(); auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); - if (is_resizable && event.x() <= tiling_deadzone) { - if (event.y() <= tiling_deadzone + desktop.top()) - m_move_window->set_tiled(WindowTileType::TopLeft); - else if (event.y() >= desktop.height() - tiling_deadzone) - m_move_window->set_tiled(WindowTileType::BottomLeft); + auto event_location_relative_to_screen = event.position().translated(-cursor_screen.rect().location()); + if (is_resizable && event_location_relative_to_screen.x() <= tiling_deadzone) { + if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop_relative_to_screen.top()) + m_move_window->set_tiled(&cursor_screen, WindowTileType::TopLeft); + else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone) + m_move_window->set_tiled(&cursor_screen, WindowTileType::BottomLeft); else - m_move_window->set_tiled(WindowTileType::Left); - } else if (is_resizable && event.x() >= Screen::the().width() - tiling_deadzone) { - if (event.y() <= tiling_deadzone + desktop.top()) - m_move_window->set_tiled(WindowTileType::TopRight); - else if (event.y() >= desktop.height() - tiling_deadzone) - m_move_window->set_tiled(WindowTileType::BottomRight); + m_move_window->set_tiled(&cursor_screen, WindowTileType::Left); + } else if (is_resizable && event_location_relative_to_screen.x() >= cursor_screen.width() - tiling_deadzone) { + if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop.top()) + m_move_window->set_tiled(&cursor_screen, WindowTileType::TopRight); + else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone) + m_move_window->set_tiled(&cursor_screen, WindowTileType::BottomRight); else - m_move_window->set_tiled(WindowTileType::Right); - } else if (is_resizable && event.y() <= secondary_deadzone + desktop.top()) { - m_move_window->set_tiled(WindowTileType::Top); - } else if (is_resizable && event.y() >= desktop.bottom() - secondary_deadzone) { - m_move_window->set_tiled(WindowTileType::Bottom); + m_move_window->set_tiled(&cursor_screen, WindowTileType::Right); + } else if (is_resizable && event_location_relative_to_screen.y() <= secondary_deadzone + desktop_relative_to_screen.top()) { + m_move_window->set_tiled(&cursor_screen, WindowTileType::Top); + } else if (is_resizable && event_location_relative_to_screen.y() >= desktop_relative_to_screen.bottom() - secondary_deadzone) { + m_move_window->set_tiled(&cursor_screen, WindowTileType::Bottom); } else if (m_move_window->tiled() == WindowTileType::None) { Gfx::IntPoint pos = m_move_window_origin.translated(event.position() - m_move_origin); m_move_window->set_position_without_repaint(pos); // "Bounce back" the window if it would end up too far outside the screen. // If the user has let go of Mod_Super, maybe they didn't intentionally press it to begin with. Therefore, refuse to go into a state where knowledge about super-drags is necessary. bool force_titlebar_visible = !(m_keyboard_modifiers & Mod_Super); - m_move_window->nudge_into_desktop(force_titlebar_visible); + m_move_window->nudge_into_desktop(&cursor_screen, force_titlebar_visible); } else if (pixels_moved_from_start > 5) { m_move_window->set_untiled(event.position()); m_move_origin = event.position(); @@ -1067,7 +1076,7 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window) if (m_dnd_client || m_resize_window || m_move_window || m_cursor_tracking_button || MenuManager::the().has_open_menu()) return; - auto cursor_location = Screen::the().cursor_location(); + auto cursor_location = ScreenInput::the().cursor_location(); auto* currently_hovered = hovered_window(); if (updated_window) { if (!(updated_window == currently_hovered || updated_window->frame().rect().contains(cursor_location) || (currently_hovered && currently_hovered->frame().rect().contains(cursor_location)))) @@ -1110,32 +1119,31 @@ void WindowManager::clear_resize_candidate() m_resize_candidate = nullptr; } -Gfx::IntRect WindowManager::desktop_rect() const +Gfx::IntRect WindowManager::desktop_rect(Screen& screen) const { if (active_fullscreen_window()) - return Screen::the().rect(); - return { - 0, - 0, - Screen::the().width(), - Screen::the().height() - 28 - }; + return Screen::main().rect(); // TODO: we should support fullscreen windows on any screen + auto screen_rect = screen.rect(); + if (screen.is_main_screen()) + screen_rect.set_height(screen.height() - 28); + return screen_rect; } -Gfx::IntRect WindowManager::arena_rect_for_type(WindowType type) const +Gfx::IntRect WindowManager::arena_rect_for_type(Screen& screen, WindowType type) const { switch (type) { case WindowType::Desktop: + return Screen::bounding_rect(); case WindowType::Normal: case WindowType::ToolWindow: - return desktop_rect(); + return desktop_rect(screen); case WindowType::Menu: case WindowType::WindowSwitcher: case WindowType::Taskbar: case WindowType::Tooltip: case WindowType::Applet: case WindowType::Notification: - return Screen::the().rect(); + return screen.rect(); default: VERIFY_NOT_REACHED(); } @@ -1233,7 +1241,7 @@ void WindowManager::process_key_event(KeyEvent& event) } if (m_active_input_window->is_maximized()) maximize_windows(*m_active_input_window, false); - m_active_input_window->set_tiled(WindowTileType::Left); + m_active_input_window->set_tiled(nullptr, WindowTileType::Left); return; } if (event.key() == Key_Right) { @@ -1245,7 +1253,7 @@ void WindowManager::process_key_event(KeyEvent& event) } if (m_active_input_window->is_maximized()) maximize_windows(*m_active_input_window, false); - m_active_input_window->set_tiled(WindowTileType::Right); + m_active_input_window->set_tiled(nullptr, WindowTileType::Right); return; } } @@ -1448,24 +1456,29 @@ ResizeDirection WindowManager::resize_direction_of_window(Window const& window) return m_resize_direction; } -Gfx::IntRect WindowManager::maximized_window_rect(Window const& window) const +Gfx::IntRect WindowManager::maximized_window_rect(Window const& window, bool relative_to_window_screen) const { - Gfx::IntRect rect = Screen::the().rect(); + auto& screen = Screen::closest_to_rect(window.frame().rect()); + Gfx::IntRect rect = screen.rect(); // Subtract window title bar (leaving the border) rect.set_y(rect.y() + window.frame().titlebar_rect().height() + window.frame().menubar_rect().height()); rect.set_height(rect.height() - window.frame().titlebar_rect().height() - window.frame().menubar_rect().height()); - // Subtract taskbar window height if present - const_cast<WindowManager*>(this)->m_window_stack.for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) { - rect.set_height(rect.height() - taskbar_window.height()); - return IterationDecision::Break; - }); + if (screen.is_main_screen()) { + // Subtract taskbar window height if present + const_cast<WindowManager*>(this)->m_window_stack.for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) { + rect.set_height(rect.height() - taskbar_window.height()); + return IterationDecision::Break; + }); + } constexpr int tasteful_space_above_maximized_window = 1; rect.set_y(rect.y() + tasteful_space_above_maximized_window); rect.set_height(rect.height() - tasteful_space_above_maximized_window); + if (relative_to_window_screen) + rect.translate_by(-screen.rect().location()); return rect; } @@ -1577,9 +1590,10 @@ Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint const Gfx::IntPoint point; if (overlap_window) { + auto& screen = Screen::closest_to_location(desired); point = overlap_window->position() + shift; - point = { point.x() % Screen::the().width(), - (point.y() >= (Screen::the().height() - taskbar_height)) + point = { point.x() % screen.width(), + (point.y() >= (screen.height() - (screen.is_main_screen() ? taskbar_height : 0))) ? Gfx::WindowTheme::current().titlebar_height(Gfx::WindowTheme::WindowType::Normal, palette()) : point.y() }; } else { @@ -1593,7 +1607,7 @@ int WindowManager::compositor_icon_scale() const { if (!m_allow_hidpi_icons) return 1; - return scale_factor(); + return Screen::main().scale_factor(); // TODO: There is no *one* scale factor... } void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons) diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index ae968ad04f..a4452ecf57 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -79,7 +79,7 @@ public: void notify_progress_changed(Window&); void notify_modified_changed(Window&); - Gfx::IntRect maximized_window_rect(Window const&) const; + Gfx::IntRect maximized_window_rect(Window const&, bool relative_to_window_screen = false) const; ClientConnection const* dnd_client() const { return m_dnd_client.ptr(); } String const& dnd_text() const { return m_dnd_text; } @@ -105,8 +105,8 @@ public: void move_to_front_and_make_active(Window&); - Gfx::IntRect desktop_rect() const; - Gfx::IntRect arena_rect_for_type(WindowType) const; + Gfx::IntRect desktop_rect(Screen&) const; + Gfx::IntRect arena_rect_for_type(Screen&, WindowType) const; Cursor const& active_cursor() const; Cursor const& hidden_cursor() const { return *m_hidden_cursor; } @@ -129,9 +129,7 @@ public: Gfx::Font const& font() const; Gfx::Font const& window_title_font() const; - bool set_resolution(int width, int height, int scale); - Gfx::IntSize resolution() const; - int scale_factor() const; + bool set_resolution(Screen&, int width, int height, int scale); void set_acceleration_factor(double); void set_scroll_step_size(unsigned); diff --git a/Userland/Services/WindowServer/WindowServer.ipc b/Userland/Services/WindowServer/WindowServer.ipc index 09f8a27852..b31fb557a1 100644 --- a/Userland/Services/WindowServer/WindowServer.ipc +++ b/Userland/Services/WindowServer/WindowServer.ipc @@ -93,7 +93,7 @@ endpoint WindowServer set_background_color(String background_color) =| set_wallpaper_mode(String mode) =| - set_resolution(Gfx::IntSize resolution, int scale_factor) => (bool success, Gfx::IntSize resolution, int scale_factor) + set_resolution(u32 screen_index, Gfx::IntSize resolution, int scale_factor) => (bool success, Gfx::IntSize resolution, int scale_factor) set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =| get_wallpaper() => (String path) @@ -130,5 +130,5 @@ endpoint WindowServer set_double_click_speed(int speed) => () get_double_click_speed() => (int speed) - get_desktop_display_scale() => (int desktop_display_scale) + get_desktop_display_scale(u32 screen_index) => (int desktop_display_scale) } diff --git a/Userland/Services/WindowServer/WindowSwitcher.cpp b/Userland/Services/WindowServer/WindowSwitcher.cpp index 36d7418db6..ecc75a9bce 100644 --- a/Userland/Services/WindowServer/WindowSwitcher.cpp +++ b/Userland/Services/WindowServer/WindowSwitcher.cpp @@ -221,7 +221,7 @@ void WindowSwitcher::refresh() int space_for_window_rect = 180; m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2); m_rect.set_height(window_count * item_height() + padding() * 2); - m_rect.center_within(Screen::the().rect()); + m_rect.center_within(Screen::main().rect()); if (!m_switcher_window) m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher); m_switcher_window->set_rect(m_rect); diff --git a/Userland/Services/WindowServer/main.cpp b/Userland/Services/WindowServer/main.cpp index df2e8dabc2..69afde1b9c 100644 --- a/Userland/Services/WindowServer/main.cpp +++ b/Userland/Services/WindowServer/main.cpp @@ -75,10 +75,47 @@ int main(int, char**) return 1; } - int scale = wm_config->read_num_entry("Screen", "ScaleFactor", 1); - WindowServer::Screen screen(wm_config->read_num_entry("Screen", "Width", 1024 / scale), wm_config->read_num_entry("Screen", "Height", 768 / scale), scale); - screen.set_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters())); - screen.set_scroll_step_size(wm_config->read_num_entry("Mouse", "ScrollStepSize", 4)); + // First check which screens are explicitly configured + AK::HashTable<String> fb_devices_configured; + int main_screen_index = wm_config->read_num_entry("Screens", "MainScreen", 0); + for (int screen_index = 0;; screen_index++) { + auto group_name = String::formatted("Screen{}", screen_index); + if (!wm_config->has_group(group_name)) + break; + + int scale = wm_config->read_num_entry(group_name, "ScaleFactor", 1); + auto device_path = wm_config->read_entry(group_name, "Device", {}); + if (device_path.is_null() || device_path.is_empty()) { + dbgln("Screen {} misses Device setting", screen_index); + break; + } + + Gfx::IntRect virtual_rect { + wm_config->read_num_entry(group_name, "Left", 0 / scale), + wm_config->read_num_entry(group_name, "Top", 0 / scale), + wm_config->read_num_entry(group_name, "Width", 1024 / scale), + wm_config->read_num_entry("Screen", "Height", 768 / scale) + }; + auto* screen = WindowServer::Screen::create(device_path, virtual_rect, scale); + if (!screen) { + dbgln("Screen {} failed to be created", screen_index); + break; + } + + if (main_screen_index == screen_index) + screen->make_main_screen(); + + // Remember that we used this device for a screen already + fb_devices_configured.set(device_path); + } + + // TODO: Enumerate the /dev/fbX devices and set up any ones we find that we haven't already used + + auto& screen_input = WindowServer::ScreenInput::the(); + screen_input.set_cursor_location(WindowServer::Screen::main().rect().center()); + screen_input.set_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters())); + screen_input.set_scroll_step_size(wm_config->read_num_entry("Mouse", "ScrollStepSize", 4)); + WindowServer::Compositor::the(); auto wm = WindowServer::WindowManager::construct(*palette); auto am = WindowServer::AppletManager::construct(); |