From 4392da970ac0fc433530579b13030a41ff4a8f59 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 13 Jun 2021 06:16:06 -0600 Subject: [PATCH] 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. --- Base/etc/WindowServer.ini | 8 +- .../DisplaySettings/MonitorSettingsWidget.cpp | 5 +- Userland/Libraries/LibGUI/Desktop.cpp | 14 +- Userland/Libraries/LibGUI/Desktop.h | 10 +- Userland/Libraries/LibGUI/Event.h | 17 +- Userland/Libraries/LibGUI/Forward.h | 2 +- Userland/Libraries/LibGUI/Widget.cpp | 2 +- Userland/Libraries/LibGUI/Widget.h | 2 +- Userland/Libraries/LibGUI/Window.cpp | 14 +- Userland/Libraries/LibGUI/Window.h | 4 +- .../LibGUI/WindowServerConnection.cpp | 12 +- .../Libraries/LibGUI/WindowServerConnection.h | 4 +- .../Libraries/LibWeb/OutOfProcessWebView.cpp | 6 +- .../Libraries/LibWeb/OutOfProcessWebView.h | 2 +- .../NotificationServer/NotificationWindow.cpp | 4 +- .../NotificationServer/NotificationWindow.h | 2 +- Userland/Services/Taskbar/TaskbarWindow.cpp | 9 +- Userland/Services/Taskbar/TaskbarWindow.h | 4 +- .../Services/WebContent/ClientConnection.cpp | 4 +- .../Services/WebContent/ClientConnection.h | 2 +- Userland/Services/WebContent/PageHost.h | 2 +- .../Services/WebContent/WebContentServer.ipc | 2 +- .../WindowServer/ClientConnection.cpp | 50 +- .../Services/WindowServer/ClientConnection.h | 6 +- Userland/Services/WindowServer/Compositor.cpp | 594 +++++++++++------- Userland/Services/WindowServer/Compositor.h | 52 +- Userland/Services/WindowServer/EventLoop.cpp | 14 +- Userland/Services/WindowServer/Menu.cpp | 7 +- Userland/Services/WindowServer/Screen.cpp | 155 +++-- Userland/Services/WindowServer/Screen.h | 155 ++++- .../WindowServer/WMClientConnection.cpp | 2 +- Userland/Services/WindowServer/Window.cpp | 95 ++- Userland/Services/WindowServer/Window.h | 19 +- .../Services/WindowServer/WindowClient.ipc | 4 +- .../Services/WindowServer/WindowFrame.cpp | 146 +++-- Userland/Services/WindowServer/WindowFrame.h | 47 +- .../Services/WindowServer/WindowManager.cpp | 140 +++-- .../Services/WindowServer/WindowManager.h | 10 +- .../Services/WindowServer/WindowServer.ipc | 4 +- .../Services/WindowServer/WindowSwitcher.cpp | 2 +- Userland/Services/WindowServer/main.cpp | 45 +- Userland/Utilities/chres.cpp | 4 +- 42 files changed, 1123 insertions(+), 559 deletions(-) diff --git a/Base/etc/WindowServer.ini b/Base/etc/WindowServer.ini index 4c83ec62731885..177edeef489d52 100644 --- a/Base/etc/WindowServer.ini +++ b/Base/etc/WindowServer.ini @@ -1,4 +1,10 @@ -[Screen] +[Screens] +MainScreen=0 + +[Screen0] +Device=/dev/fb0 +Left=0 +Top=0 Width=1024 Height=768 ScaleFactor=1 diff --git a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp index 2cab39673484f6..b99e6b16a86118 100644 --- a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp +++ b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp @@ -116,7 +116,8 @@ void MonitorSettingsWidget::apply_settings() } if (current_resolution != m_monitor_widget->desktop_resolution() || current_scale_factor != m_monitor_widget->desktop_scale_factor()) { - auto result = GUI::WindowServerConnection::the().set_resolution(m_monitor_widget->desktop_resolution(), m_monitor_widget->desktop_scale_factor()); + u32 display_index = 0; // TODO: implement multiple display support + auto result = GUI::WindowServerConnection::the().set_resolution(display_index, m_monitor_widget->desktop_resolution(), m_monitor_widget->desktop_scale_factor()); if (!result.success()) { GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), "Unable to set resolution", GUI::MessageBox::Type::Error); @@ -134,7 +135,7 @@ void MonitorSettingsWidget::apply_settings() // If the user selects "No", closes the window or the window gets closed by the 10 seconds timer, revert the changes. if (box->exec() != GUI::MessageBox::ExecYes) { - result = GUI::WindowServerConnection::the().set_resolution(current_resolution, current_scale_factor); + result = GUI::WindowServerConnection::the().set_resolution(display_index, current_resolution, current_scale_factor); if (!result.success()) { GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), "Unable to set resolution", GUI::MessageBox::Type::Error); diff --git a/Userland/Libraries/LibGUI/Desktop.cpp b/Userland/Libraries/LibGUI/Desktop.cpp index 9c0e05296ae8ff..6bc688c6bab29f 100644 --- a/Userland/Libraries/LibGUI/Desktop.cpp +++ b/Userland/Libraries/LibGUI/Desktop.cpp @@ -25,11 +25,17 @@ Desktop::Desktop() { } -void Desktop::did_receive_screen_rect(Badge, const Gfx::IntRect& rect) +void Desktop::did_receive_screen_rects(Badge, const Vector& rects, size_t main_screen_index) { - if (m_rect == rect) - return; - m_rect = rect; + m_main_screen_index = main_screen_index; + m_rects = rects; + if (!m_rects.is_empty()) { + m_bounding_rect = m_rects[0]; + for (size_t i = 1; i < m_rects.size(); i++) + m_bounding_rect = m_bounding_rect.united(m_rects[i]); + } else { + m_bounding_rect = {}; + } } void Desktop::set_background_color(const StringView& background_color) diff --git a/Userland/Libraries/LibGUI/Desktop.h b/Userland/Libraries/LibGUI/Desktop.h index de53efeb0cb82d..58b7504611d5a0 100644 --- a/Userland/Libraries/LibGUI/Desktop.h +++ b/Userland/Libraries/LibGUI/Desktop.h @@ -26,14 +26,18 @@ class Desktop { String wallpaper() const; bool set_wallpaper(const StringView& path, bool save_config = true); - Gfx::IntRect rect() const { return m_rect; } + Gfx::IntRect rect() const { return m_bounding_rect; } + const Vector& rects() const { return m_rects; } + size_t main_screen_index() const { return m_main_screen_index; } int taskbar_height() const { return TaskbarWindow::taskbar_height(); } - void did_receive_screen_rect(Badge, const Gfx::IntRect&); + void did_receive_screen_rects(Badge, const Vector&, size_t); private: - Gfx::IntRect m_rect; + Vector m_rects; + size_t m_main_screen_index { 0 }; + Gfx::IntRect m_bounding_rect; }; } diff --git a/Userland/Libraries/LibGUI/Event.h b/Userland/Libraries/LibGUI/Event.h index 34b8b65759b510..edbf4ad2c94b86 100644 --- a/Userland/Libraries/LibGUI/Event.h +++ b/Userland/Libraries/LibGUI/Event.h @@ -52,7 +52,7 @@ class Event : public Core::Event { DragMove, Drop, ThemeChange, - ScreenRectChange, + ScreenRectsChange, ActionEnter, ActionLeave, @@ -392,18 +392,21 @@ class ThemeChangeEvent final : public Event { } }; -class ScreenRectChangeEvent final : public Event { +class ScreenRectsChangeEvent final : public Event { public: - explicit ScreenRectChangeEvent(const Gfx::IntRect& rect) - : Event(Type::ScreenRectChange) - , m_rect(rect) + explicit ScreenRectsChangeEvent(const Vector& rects, size_t main_screen_index) + : Event(Type::ScreenRectsChange) + , m_rects(rects) + , m_main_screen_index(main_screen_index) { } - const Gfx::IntRect& rect() const { return m_rect; } + const Vector& rects() const { return m_rects; } + size_t main_screen_index() const { return m_main_screen_index; } private: - Gfx::IntRect m_rect; + Vector m_rects; + size_t m_main_screen_index; }; class FocusEvent final : public Event { diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h index 00e5e7725b9277..156fd60b8ffde7 100644 --- a/Userland/Libraries/LibGUI/Forward.h +++ b/Userland/Libraries/LibGUI/Forward.h @@ -52,7 +52,7 @@ class Painter; class RadioButton; class ResizeCorner; class ResizeEvent; -class ScreenRectChangeEvent; +class ScreenRectsChangeEvent; class Scrollbar; class AbstractScrollableWidget; class Slider; diff --git a/Userland/Libraries/LibGUI/Widget.cpp b/Userland/Libraries/LibGUI/Widget.cpp index d3d00b9b1ac614..30654a4cef1e3b 100644 --- a/Userland/Libraries/LibGUI/Widget.cpp +++ b/Userland/Libraries/LibGUI/Widget.cpp @@ -506,7 +506,7 @@ void Widget::theme_change_event(ThemeChangeEvent&) { } -void Widget::screen_rect_change_event(ScreenRectChangeEvent&) +void Widget::screen_rects_change_event(ScreenRectsChangeEvent&) { } diff --git a/Userland/Libraries/LibGUI/Widget.h b/Userland/Libraries/LibGUI/Widget.h index d22eacc1e454bf..38a3db98d435a7 100644 --- a/Userland/Libraries/LibGUI/Widget.h +++ b/Userland/Libraries/LibGUI/Widget.h @@ -312,7 +312,7 @@ class Widget : public Core::Object { virtual void drag_leave_event(Event&); virtual void drop_event(DropEvent&); virtual void theme_change_event(ThemeChangeEvent&); - virtual void screen_rect_change_event(ScreenRectChangeEvent&); + virtual void screen_rects_change_event(ScreenRectsChangeEvent&); virtual void did_begin_inspection() override; virtual void did_end_inspection() override; diff --git a/Userland/Libraries/LibGUI/Window.cpp b/Userland/Libraries/LibGUI/Window.cpp index d9a9e92456105a..f972e38ab79cc3 100644 --- a/Userland/Libraries/LibGUI/Window.cpp +++ b/Userland/Libraries/LibGUI/Window.cpp @@ -495,11 +495,11 @@ void Window::handle_theme_change_event(ThemeChangeEvent& event) dispatch_theme_change(*m_main_widget.ptr(), dispatch_theme_change); } -void Window::handle_screen_rect_change_event(ScreenRectChangeEvent& event) +void Window::handle_screen_rects_change_event(ScreenRectsChangeEvent& event) { if (!m_main_widget) return; - auto dispatch_screen_rect_change = [&](auto& widget, auto recursive) { + auto dispatch_screen_rects_change = [&](auto& widget, auto recursive) { widget.dispatch_event(event, this); widget.for_each_child_widget([&](auto& widget) -> IterationDecision { widget.dispatch_event(event, this); @@ -507,8 +507,8 @@ void Window::handle_screen_rect_change_event(ScreenRectChangeEvent& event) return IterationDecision::Continue; }); }; - dispatch_screen_rect_change(*m_main_widget.ptr(), dispatch_screen_rect_change); - screen_rect_change_event(event); + dispatch_screen_rects_change(*m_main_widget.ptr(), dispatch_screen_rects_change); + screen_rects_change_event(event); } void Window::handle_drag_move_event(DragEvent& event) @@ -578,8 +578,8 @@ void Window::event(Core::Event& event) if (event.type() == Event::ThemeChange) return handle_theme_change_event(static_cast(event)); - if (event.type() == Event::ScreenRectChange) - return handle_screen_rect_change_event(static_cast(event)); + if (event.type() == Event::ScreenRectsChange) + return handle_screen_rects_change_event(static_cast(event)); Core::Object::event(event); } @@ -811,7 +811,7 @@ void Window::wm_event(WMEvent&) { } -void Window::screen_rect_change_event(ScreenRectChangeEvent&) +void Window::screen_rects_change_event(ScreenRectsChangeEvent&) { } diff --git a/Userland/Libraries/LibGUI/Window.h b/Userland/Libraries/LibGUI/Window.h index 987dac4e8b0e7d..e06b1033fcb00c 100644 --- a/Userland/Libraries/LibGUI/Window.h +++ b/Userland/Libraries/LibGUI/Window.h @@ -203,7 +203,7 @@ class Window : public Core::Object { protected: Window(Core::Object* parent = nullptr); virtual void wm_event(WMEvent&); - virtual void screen_rect_change_event(ScreenRectChangeEvent&); + virtual void screen_rects_change_event(ScreenRectsChangeEvent&); private: void update_cursor(); @@ -218,7 +218,7 @@ class Window : public Core::Object { void handle_became_active_or_inactive_event(Core::Event&); void handle_close_request(); void handle_theme_change_event(ThemeChangeEvent&); - void handle_screen_rect_change_event(ScreenRectChangeEvent&); + void handle_screen_rects_change_event(ScreenRectsChangeEvent&); void handle_drag_move_event(DragEvent&); void handle_left_event(); diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.cpp b/Userland/Libraries/LibGUI/WindowServerConnection.cpp index b819b3e97c3ed0..a0ddbf0e98ba6a 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.cpp +++ b/Userland/Libraries/LibGUI/WindowServerConnection.cpp @@ -48,12 +48,12 @@ WindowServerConnection::WindowServerConnection() // All we have to do is wait for it to arrive. This avoids a round-trip during application startup. auto message = wait_for_specific_message(); set_system_theme_from_anonymous_buffer(message->theme_buffer()); - Desktop::the().did_receive_screen_rect({}, message->screen_rect()); + Desktop::the().did_receive_screen_rects({}, message->screen_rects(), message->main_screen_index()); Gfx::FontDatabase::set_default_font_query(message->default_font_query()); Gfx::FontDatabase::set_fixed_width_font_query(message->fixed_width_font_query()); } -void WindowServerConnection::fast_greet(Gfx::IntRect const&, Core::AnonymousBuffer const&, String const&, String const&) +void WindowServerConnection::fast_greet(Vector const&, u32, Core::AnonymousBuffer const&, String const&, String const&) { // NOTE: This message is handled in the constructor. } @@ -311,11 +311,11 @@ void WindowServerConnection::menu_item_left(i32 menu_id, u32 identifier) Core::EventLoop::current().post_event(*app, make(GUI::Event::ActionLeave, *action)); } -void WindowServerConnection::screen_rect_changed(Gfx::IntRect const& rect) +void WindowServerConnection::screen_rects_changed(Vector const& rects, u32 main_screen_index) { - Desktop::the().did_receive_screen_rect({}, rect); - Window::for_each_window({}, [rect](auto& window) { - Core::EventLoop::current().post_event(window, make(rect)); + Desktop::the().did_receive_screen_rects({}, rects, main_screen_index); + Window::for_each_window({}, [&](auto& window) { + Core::EventLoop::current().post_event(window, make(rects, main_screen_index)); }); } diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.h b/Userland/Libraries/LibGUI/WindowServerConnection.h index ee1a032752e5d2..94ef32f1ed6566 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowServerConnection.h @@ -22,7 +22,7 @@ class WindowServerConnection final private: WindowServerConnection(); - virtual void fast_greet(Gfx::IntRect const&, Core::AnonymousBuffer const&, String const&, String const&) override; + virtual void fast_greet(Vector const&, u32, Core::AnonymousBuffer const&, String const&, String const&) override; virtual void paint(i32, Gfx::IntSize const&, Vector const&) override; virtual void mouse_move(i32, Gfx::IntPoint const&, u32, u32, u32, i32, bool, Vector const&) override; virtual void mouse_down(i32, Gfx::IntPoint const&, u32, u32, u32, i32) override; @@ -43,7 +43,7 @@ class WindowServerConnection final virtual void menu_item_entered(i32, u32) override; virtual void menu_item_left(i32, u32) override; virtual void menu_visibility_did_change(i32, bool) override; - virtual void screen_rect_changed(Gfx::IntRect const&) override; + virtual void screen_rects_changed(Vector const&, u32) override; virtual void set_wallpaper_finished(bool) override; virtual void drag_dropped(i32, Gfx::IntPoint const&, String const&, HashMap const&) override; virtual void drag_accepted() override; diff --git a/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp b/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp index 3208398fea28ed..e84be38764073f 100644 --- a/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp +++ b/Userland/Libraries/LibWeb/OutOfProcessWebView.cpp @@ -71,7 +71,7 @@ void OutOfProcessWebView::create_client() client().async_update_system_theme(Gfx::current_system_theme_buffer()); client().async_update_system_fonts(Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query()); - client().async_update_screen_rect(GUI::Desktop::the().rect()); + client().async_update_screen_rects(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index()); } void OutOfProcessWebView::load(const URL& url) @@ -192,9 +192,9 @@ void OutOfProcessWebView::theme_change_event(GUI::ThemeChangeEvent& event) request_repaint(); } -void OutOfProcessWebView::screen_rect_change_event(GUI::ScreenRectChangeEvent& event) +void OutOfProcessWebView::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event) { - client().async_update_screen_rect(event.rect()); + client().async_update_screen_rects(event.rects(), event.main_screen_index()); } void OutOfProcessWebView::notify_server_did_paint(Badge, i32 bitmap_id) diff --git a/Userland/Libraries/LibWeb/OutOfProcessWebView.h b/Userland/Libraries/LibWeb/OutOfProcessWebView.h index 8338291a1ecbe3..c82508f58ca90f 100644 --- a/Userland/Libraries/LibWeb/OutOfProcessWebView.h +++ b/Userland/Libraries/LibWeb/OutOfProcessWebView.h @@ -74,7 +74,7 @@ class OutOfProcessWebView final virtual void mousewheel_event(GUI::MouseEvent&) override; virtual void keydown_event(GUI::KeyEvent&) override; virtual void theme_change_event(GUI::ThemeChangeEvent&) override; - virtual void screen_rect_change_event(GUI::ScreenRectChangeEvent&) override; + virtual void screen_rects_change_event(GUI::ScreenRectsChangeEvent&) override; // ^AbstractScrollableWidget virtual void did_scroll() override; diff --git a/Userland/Services/NotificationServer/NotificationWindow.cpp b/Userland/Services/NotificationServer/NotificationWindow.cpp index b05b25398b9be7..99c03e23e2f184 100644 --- a/Userland/Services/NotificationServer/NotificationWindow.cpp +++ b/Userland/Services/NotificationServer/NotificationWindow.cpp @@ -129,9 +129,9 @@ void NotificationWindow::set_image(const Gfx::ShareableBitmap& image) } } -void NotificationWindow::screen_rect_change_event(GUI::ScreenRectChangeEvent& event) +void NotificationWindow::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event) { - update_notification_window_locations(event.rect()); + update_notification_window_locations(event.rects()[event.main_screen_index()]); } } diff --git a/Userland/Services/NotificationServer/NotificationWindow.h b/Userland/Services/NotificationServer/NotificationWindow.h index c850266c52537f..9007496e318064 100644 --- a/Userland/Services/NotificationServer/NotificationWindow.h +++ b/Userland/Services/NotificationServer/NotificationWindow.h @@ -27,7 +27,7 @@ class NotificationWindow final : public GUI::Window { private: NotificationWindow(i32 client_id, const String& text, const String& title, const Gfx::ShareableBitmap&); - virtual void screen_rect_change_event(GUI::ScreenRectChangeEvent&) override; + virtual void screen_rects_change_event(GUI::ScreenRectsChangeEvent&) override; Gfx::IntRect m_original_rect; i32 m_id; diff --git a/Userland/Services/Taskbar/TaskbarWindow.cpp b/Userland/Services/Taskbar/TaskbarWindow.cpp index c700cbf756960e..f2498e63ed3c49 100644 --- a/Userland/Services/Taskbar/TaskbarWindow.cpp +++ b/Userland/Services/Taskbar/TaskbarWindow.cpp @@ -58,7 +58,7 @@ TaskbarWindow::TaskbarWindow(NonnullRefPtr start_menu) set_window_type(GUI::WindowType::Taskbar); set_title("Taskbar"); - on_screen_rect_change(GUI::Desktop::the().rect()); + on_screen_rects_change(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index()); auto& main_widget = set_main_widget(); main_widget.set_layout(); @@ -148,8 +148,9 @@ void TaskbarWindow::create_quick_launch_bar() quick_launch_bar.set_fixed_size(total_width, 24); } -void TaskbarWindow::on_screen_rect_change(const Gfx::IntRect& rect) +void TaskbarWindow::on_screen_rects_change(const Vector& rects, size_t main_screen_index) { + const auto& rect = rects[main_screen_index]; Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() }; set_rect(new_rect); update_applet_area(); @@ -332,7 +333,7 @@ void TaskbarWindow::wm_event(GUI::WMEvent& event) } } -void TaskbarWindow::screen_rect_change_event(GUI::ScreenRectChangeEvent& event) +void TaskbarWindow::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event) { - on_screen_rect_change(event.rect()); + on_screen_rects_change(event.rects(), event.main_screen_index()); } diff --git a/Userland/Services/Taskbar/TaskbarWindow.h b/Userland/Services/Taskbar/TaskbarWindow.h index 9eaa72759535d3..d6c170718ce05b 100644 --- a/Userland/Services/Taskbar/TaskbarWindow.h +++ b/Userland/Services/Taskbar/TaskbarWindow.h @@ -21,7 +21,7 @@ class TaskbarWindow final : public GUI::Window { private: explicit TaskbarWindow(NonnullRefPtr start_menu); void create_quick_launch_bar(); - void on_screen_rect_change(const Gfx::IntRect&); + void on_screen_rects_change(const Vector&, size_t); NonnullRefPtr create_button(const WindowIdentifier&); void add_window_button(::Window&, const WindowIdentifier&); void remove_window_button(::Window&, bool); @@ -29,7 +29,7 @@ class TaskbarWindow final : public GUI::Window { ::Window* find_window_owner(::Window&) const; virtual void wm_event(GUI::WMEvent&) override; - virtual void screen_rect_change_event(GUI::ScreenRectChangeEvent&) override; + virtual void screen_rects_change_event(GUI::ScreenRectsChangeEvent&) override; void update_applet_area(); diff --git a/Userland/Services/WebContent/ClientConnection.cpp b/Userland/Services/WebContent/ClientConnection.cpp index 093a5267d53a5c..5cf714a0b3ef20 100644 --- a/Userland/Services/WebContent/ClientConnection.cpp +++ b/Userland/Services/WebContent/ClientConnection.cpp @@ -72,9 +72,9 @@ void ClientConnection::update_system_fonts(String const& default_font_query, Str Gfx::FontDatabase::set_fixed_width_font_query(fixed_width_font_query); } -void ClientConnection::update_screen_rect(const Gfx::IntRect& rect) +void ClientConnection::update_screen_rects(const Vector& rects, u32 main_screen) { - m_page_host->set_screen_rect(rect); + m_page_host->set_screen_rects(rects, main_screen); } void ClientConnection::load_url(const URL& url) diff --git a/Userland/Services/WebContent/ClientConnection.h b/Userland/Services/WebContent/ClientConnection.h index 596b3ade284b52..cd8f3577d9961a 100644 --- a/Userland/Services/WebContent/ClientConnection.h +++ b/Userland/Services/WebContent/ClientConnection.h @@ -34,7 +34,7 @@ class ClientConnection final virtual void update_system_theme(Core::AnonymousBuffer const&) override; virtual void update_system_fonts(String const&, String const&) override; - virtual void update_screen_rect(Gfx::IntRect const&) override; + virtual void update_screen_rects(Vector const&, u32) override; virtual void load_url(URL const&) override; virtual void load_html(String const&, URL const&) override; virtual void paint(Gfx::IntRect const&, i32) override; diff --git a/Userland/Services/WebContent/PageHost.h b/Userland/Services/WebContent/PageHost.h index 09e8725ecdb4e8..fcf624feb0d0a4 100644 --- a/Userland/Services/WebContent/PageHost.h +++ b/Userland/Services/WebContent/PageHost.h @@ -28,7 +28,7 @@ class PageHost final : public Web::PageClient { void set_palette_impl(const Gfx::PaletteImpl&); void set_viewport_rect(const Gfx::IntRect&); - void set_screen_rect(const Gfx::IntRect& rect) { m_screen_rect = rect; }; + void set_screen_rects(const Vector& rects, size_t main_screen_index) { m_screen_rect = rects[main_screen_index]; }; void set_should_show_line_box_borders(bool b) { m_should_show_line_box_borders = b; } diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index 375cbfd078cf09..40b97157fd7640 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -2,7 +2,7 @@ endpoint WebContentServer { update_system_theme(Core::AnonymousBuffer theme_buffer) =| update_system_fonts(String default_font_query, String fixed_width_font_query) =| - update_screen_rect(Gfx::IntRect rect) =| + update_screen_rects(Vector rects, u32 main_screen_index) =| load_url(URL url) =| load_html(String html, URL url) =| diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index 3ae815316b5036..9aa68c523e78df 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -53,7 +53,7 @@ ClientConnection::ClientConnection(NonnullRefPtr client_socke s_connections = new HashMap>; 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 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 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 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 2564370a69649f..27ed7fc8f4cd11 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -40,7 +40,7 @@ class ClientConnection final static ClientConnection* from_client_id(int client_id); static void for_each_client(Function); - void notify_about_new_screen_rect(const Gfx::IntRect&); + void notify_about_new_screen_rects(const Vector&, size_t); void post_paint_message(Window&, bool ignore_occlusion = false); Menu* find_menu_by_id(int menu_id) @@ -125,7 +125,7 @@ class ClientConnection final 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 @@ class ClientConnection final 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 8793e295af097a..1d329c876781fa 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, 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(*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(*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(*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) 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 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&& 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(*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 c22cfdccf9dc08..ea245dfdb898c3 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace WindowServer { @@ -36,7 +37,7 @@ class Compositor final : public Core::Object { 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 @@ class Compositor final : public Core::Object { void did_construct_window_manager(Badge); - const Gfx::Bitmap& front_bitmap_for_screenshot(Badge) const { return *m_front_bitmap; } + const Gfx::Bitmap& front_bitmap_for_screenshot(Badge, 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 m_compose_timer; RefPtr 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 m_front_bitmap; - RefPtr m_back_bitmap; - RefPtr m_temp_bitmap; - OwnPtr m_back_painter; - OwnPtr m_front_painter; - OwnPtr m_temp_painter; + struct ScreenData { + RefPtr m_front_bitmap; + RefPtr m_back_bitmap; + RefPtr m_temp_bitmap; + OwnPtr m_back_painter; + OwnPtr m_front_painter; + OwnPtr m_temp_painter; + RefPtr m_cursor_back_bitmap; + OwnPtr 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 m_screen_data; Gfx::DisjointRectSet m_dirty_screen_rects; Gfx::DisjointRectSet m_opaque_wallpaper_rects; - RefPtr m_cursor_back_bitmap; - OwnPtr 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 @@ class Compositor final : public Core::Object { RefPtr m_wallpaper; const Cursor* m_current_cursor { nullptr }; + Screen* m_current_cursor_screen { nullptr }; unsigned m_current_cursor_frame { 0 }; RefPtr m_cursor_timer; diff --git a/Userland/Services/WindowServer/EventLoop.cpp b/Userland/Services/WindowServer/EventLoop.cpp index 5bc5b4a0624e77..f2cb81f7ea8126 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 f878ab1035a336..942df821207e7c 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 bc94fa5a7f1581..e6b7716e9ed561 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::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(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, new_location, buttons, button, m_modifiers); + auto message = make(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(Event::MouseMove, new_location, buttons, MouseButton::None, m_modifiers); + if (m_cursor_location != prev_location) { + auto message = make(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(Event::MouseWheel, new_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size); + auto message = make(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(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 e4885f0238df29..4e094ee7959204 100644 --- a/Userland/Services/WindowServer/Screen.h +++ b/Userland/Services/WindowServer/Screen.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -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 + static Screen* create(Args&&... args) + { + auto screen = adopt_own(*new Screen(forward(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 rects() + { + Vector 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 + 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 + bool set_resolution(Args&&... args) + { + return do_set_resolution(false, forward(args)...); + } bool can_set_buffer() { return m_can_set_buffer; } void set_buffer(int index); @@ -32,34 +136,36 @@ class Screen { 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 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 @@ class Screen { 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 d1174d0c37ab87..e1ca5d2b172174 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 0b89368725ea51..3ec39650adcaf1 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 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(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 Window::hit_test(Gfx::IntPoint const& position, bool include_frame) const +Optional 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 bf1403ebacbdcd..129f7d7281d14e 100644 --- a/Userland/Services/WindowServer/Window.h +++ b/Userland/Services/WindowServer/Window.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -97,7 +98,7 @@ class Window final : public Core::Object { void set_fullscreen(bool); WindowTileType tiled() const { return m_tiled; } - void set_tiled(WindowTileType); + void set_tiled(Screen*, WindowTileType); bool set_untiled(Optional fixed_point = {}); bool is_occluded() const { return m_occluded; } @@ -139,7 +140,8 @@ class Window final : public Core::Object { { m_alpha_hit_threshold = threshold; } - Optional hit_test(const Gfx::IntPoint&, bool include_frame = true) const; + + Optional 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 @@ class Window final : public Core::Object { 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 @@ class Window final : public Core::Object { 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 m_list_node; @@ -319,6 +321,14 @@ class Window final : public Core::Object { WindowStack const* outer_stack() const { return m_outer_stack; } void set_outer_stack(Badge, WindowStack* stack) { m_outer_stack = stack; } + const Vector& screens() const { return m_screens; } + Vector& 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 @@ class Window final : public Core::Object { Gfx::IntRect m_rect; Gfx::IntRect m_saved_nonfullscreen_rect; Gfx::IntRect m_taskbar_rect; + Vector 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 f54b0fccd3844a..a3219415f03d6b 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 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 rects) =| mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector 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 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 fe799039d5515d..add33622ef7e09 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -64,6 +64,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) { { auto button = make