diff --git a/Base/etc/WindowServer.ini b/Base/etc/WindowServer.ini index 4c83ec6273..177edeef48 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 2cab396734..b99e6b16a8 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 9c0e05296a..6bc688c6ba 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 de53efeb0c..58b7504611 100644 --- a/Userland/Libraries/LibGUI/Desktop.h +++ b/Userland/Libraries/LibGUI/Desktop.h @@ -26,14 +26,18 @@ public: 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 34b8b65759..edbf4ad2c9 100644 --- a/Userland/Libraries/LibGUI/Event.h +++ b/Userland/Libraries/LibGUI/Event.h @@ -52,7 +52,7 @@ public: DragMove, Drop, ThemeChange, - ScreenRectChange, + ScreenRectsChange, ActionEnter, ActionLeave, @@ -392,18 +392,21 @@ public: } }; -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 00e5e7725b..156fd60b8f 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 d3d00b9b1a..30654a4cef 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 d22eacc1e4..38a3db98d4 100644 --- a/Userland/Libraries/LibGUI/Widget.h +++ b/Userland/Libraries/LibGUI/Widget.h @@ -312,7 +312,7 @@ protected: 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 d9a9e92456..f972e38ab7 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 987dac4e8b..e06b1033fc 100644 --- a/Userland/Libraries/LibGUI/Window.h +++ b/Userland/Libraries/LibGUI/Window.h @@ -203,7 +203,7 @@ public: 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 @@ private: 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 b819b3e97c..a0ddbf0e98 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 ee1a032752..94ef32f1ed 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowServerConnection.h @@ -22,7 +22,7 @@ public: 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 @@ private: 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 3208398fea..e84be38764 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 8338291a1e..c82508f58c 100644 --- a/Userland/Libraries/LibWeb/OutOfProcessWebView.h +++ b/Userland/Libraries/LibWeb/OutOfProcessWebView.h @@ -74,7 +74,7 @@ private: 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 b05b25398b..99c03e23e2 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 c850266c52..9007496e31 100644 --- a/Userland/Services/NotificationServer/NotificationWindow.h +++ b/Userland/Services/NotificationServer/NotificationWindow.h @@ -27,7 +27,7 @@ public: 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 c700cbf756..f2498e63ed 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 9eaa727595..d6c170718c 100644 --- a/Userland/Services/Taskbar/TaskbarWindow.h +++ b/Userland/Services/Taskbar/TaskbarWindow.h @@ -21,7 +21,7 @@ public: 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 @@ private: ::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 093a5267d5..5cf714a0b3 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 596b3ade28..cd8f3577d9 100644 --- a/Userland/Services/WebContent/ClientConnection.h +++ b/Userland/Services/WebContent/ClientConnection.h @@ -34,7 +34,7 @@ private: 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 09e8725ecd..fcf624feb0 100644 --- a/Userland/Services/WebContent/PageHost.h +++ b/Userland/Services/WebContent/PageHost.h @@ -28,7 +28,7 @@ public: 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 375cbfd078..40b97157fd 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 3ae815316b..9aa68c523e 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 2564370a69..27ed7fc8f4 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -40,7 +40,7 @@ public: static ClientConnection* from_client_id(int client_id); static void for_each_client(Function); - void 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 @@ private: virtual void set_background_color(String const&) override; virtual void set_wallpaper_mode(String const&) override; virtual Messages::WindowServer::GetWallpaperResponse get_wallpaper() override; - virtual Messages::WindowServer::SetResolutionResponse set_resolution(Gfx::IntSize const&, int) override; + virtual Messages::WindowServer::SetResolutionResponse set_resolution(u32, Gfx::IntSize const&, int) override; virtual void set_window_cursor(i32, i32) override; virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override; virtual void popup_menu(i32, Gfx::IntPoint const&) override; @@ -153,7 +153,7 @@ private: virtual Messages::WindowServer::GetDoubleClickSpeedResponse get_double_click_speed() override; virtual void set_window_modified(i32, bool) override; virtual Messages::WindowServer::IsWindowModifiedResponse is_window_modified(i32) override; - virtual Messages::WindowServer::GetDesktopDisplayScaleResponse get_desktop_display_scale() override; + virtual Messages::WindowServer::GetDesktopDisplayScaleResponse get_desktop_display_scale(u32) override; Window* window_from_id(i32 window_id); diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 8793e295af..1d329c8767 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -61,28 +61,44 @@ Compositor::Compositor() }, this); - m_screen_can_set_buffer = Screen::the().can_set_buffer(); init_bitmaps(); } -void Compositor::init_bitmaps() +const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge, 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_screen = ScreenInput::the().cursor_location_screen(); + + 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 cursor_rect = current_cursor_rect(); + bool need_to_draw_cursor = false; - - auto back_painter = *m_back_painter; - auto temp_painter = *m_temp_painter; - - auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) { - if (!need_to_draw_cursor && rect.intersects(cursor_rect)) { + 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); + 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(render_rect); - Gfx::PainterStateSaver saver(back_painter); - back_painter.add_clip_rect(render_rect); - compose_window_rect(back_painter, render_rect); + 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); + 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()); - prepare_transparency_rect(render_rect); - paint_wallpaper(temp_painter, render_rect); + 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); + 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(render_rect); - Gfx::PainterStateSaver saver(temp_painter); - temp_painter.add_clip_rect(render_rect); - compose_window_rect(temp_painter, render_rect); + 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; - 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(); + // 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) { + 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; }); - 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(); - 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); - } + Screen::for_each([&](auto& screen) { + flush(screen); + return IterationDecision::Continue; + }); - if (m_screen_can_set_buffer) - flip_buffers(); - - 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 c22cfdccf9..ea245dfdb8 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 @@ public: void invalidate_screen(); void invalidate_screen(const Gfx::IntRect&); - bool set_resolution(int desired_width, int desired_height, int scale_factor); + void screen_resolution_changed(); bool set_background_color(const String& background_color); @@ -55,46 +56,58 @@ public: void did_construct_window_manager(Badge); - 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 @@ private: 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 5bc5b4a062..f2cb81f7ea 100644 --- a/Userland/Services/WindowServer/EventLoop.cpp +++ b/Userland/Services/WindowServer/EventLoop.cpp @@ -81,9 +81,9 @@ EventLoop::~EventLoop() void EventLoop::drain_mouse() { - auto& screen = Screen::the(); + auto& screen_input = ScreenInput::the(); MousePacket state; - state.buttons = screen.mouse_button_state(); + state.buttons = screen_input.mouse_button_state(); unsigned buttons = state.buttons; MousePacket packets[32]; @@ -114,7 +114,7 @@ void EventLoop::drain_mouse() if (buttons != state.buttons) { state.buttons = buttons; dbgln_if(WSMESSAGELOOP_DEBUG, "EventLoop: Mouse Button Event"); - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); if (state.is_relative) { state.x = 0; state.y = 0; @@ -123,21 +123,21 @@ void EventLoop::drain_mouse() } } if (state.is_relative && (state.x || state.y || state.z)) - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); if (!state.is_relative) - screen.on_receive_mouse_data(state); + screen_input.on_receive_mouse_data(state); } void EventLoop::drain_keyboard() { - auto& screen = Screen::the(); + auto& screen_input = ScreenInput::the(); for (;;) { ::KeyEvent event; ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent)); if (nread == 0) break; VERIFY(nread == sizeof(::KeyEvent)); - screen.on_receive_keyboard_data(event); + screen_input.on_receive_keyboard_data(event); } } diff --git a/Userland/Services/WindowServer/Menu.cpp b/Userland/Services/WindowServer/Menu.cpp index f878ab1035..942df82120 100644 --- a/Userland/Services/WindowServer/Menu.cpp +++ b/Userland/Services/WindowServer/Menu.cpp @@ -136,7 +136,7 @@ Window& Menu::ensure_menu_window() next_item_location.translate_by(0, height); } - int window_height_available = Screen::the().height() - frame_thickness() * 2; + int window_height_available = Screen::main().height() - frame_thickness() * 2; // TODO: we don't know yet on what screen! int max_window_height = (window_height_available / item_height()) * item_height() + frame_thickness() * 2; int content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness(); int window_height = min(max_window_height, content_height); @@ -584,14 +584,15 @@ void Menu::do_popup(const Gfx::IntPoint& position, bool make_input, bool as_subm redraw_if_theme_changed(); const int margin = 30; + auto& screen = Screen::closest_to_location(position); Gfx::IntPoint adjusted_pos = position; - if (adjusted_pos.x() + window.width() >= Screen::the().width() - margin) { + if (adjusted_pos.x() + window.width() >= screen.width() - margin) { adjusted_pos = adjusted_pos.translated(-window.width(), 0); } else { adjusted_pos.set_x(adjusted_pos.x() + 1); } - if (adjusted_pos.y() + window.height() >= Screen::the().height() - margin) { + if (adjusted_pos.y() + window.height() >= screen.height() - margin) { adjusted_pos = adjusted_pos.translated(0, -min(window.height(), adjusted_pos.y())); if (as_submenu) adjusted_pos = adjusted_pos.translated(0, item_height()); diff --git a/Userland/Services/WindowServer/Screen.cpp b/Userland/Services/WindowServer/Screen.cpp index bc94fa5a7f..e6b7716e9e 100644 --- a/Userland/Services/WindowServer/Screen.cpp +++ b/Userland/Services/WindowServer/Screen.cpp @@ -19,21 +19,37 @@ namespace WindowServer { -static Screen* s_the; +NonnullOwnPtrVector Screen::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 e4885f0238..4e094ee795 100644 --- a/Userland/Services/WindowServer/Screen.h +++ b/Userland/Services/WindowServer/Screen.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -19,34 +20,17 @@ constexpr double mouse_accel_max = 3.5; constexpr double mouse_accel_min = 0.5; constexpr unsigned scroll_step_size_min = 1; -class Screen { +// Most people will probably have 4 screens or less +constexpr size_t default_screen_count = 4; + +class Screen; + +class ScreenInput { public: - Screen(unsigned width, unsigned height, int scale_factor); - ~Screen(); + static ScreenInput& the(); - bool set_resolution(int width, int height, int scale_factor); - bool can_set_buffer() { return m_can_set_buffer; } - void set_buffer(int index); - - int physical_width() const { return width() * scale_factor(); } - 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 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::IntPoint cursor_location() const { return m_physical_cursor_location / m_scale_factor; } + 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; } @@ -58,8 +42,130 @@ public: 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: - void on_change_resolution(int pitch, int physical_width, int physical_height, int scale_factor); + 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: + 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(); + + 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); + + int physical_width() const { return width() * scale_factor(); } + int physical_height() const { return height() * scale_factor(); } + size_t pitch() const { return m_pitch; } + + 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); + + Gfx::IntSize physical_size() const { return { physical_width(), physical_height() }; } + + Gfx::IntSize size() const { return { m_virtual_rect.width(), m_virtual_rect.height() }; } + Gfx::IntRect rect() const { return m_virtual_rect; } + +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(); + + bool is_opened() const { return m_framebuffer_fd >= 0; } + + void on_change_resolution(bool initial, int pitch, int physical_width, int physical_height, int scale_factor, Screen& screen_with_cursor); + + 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 @@ private: bool m_can_set_buffer { false }; int m_pitch { 0 }; - int m_width { 0 }; - int m_height { 0 }; + Gfx::IntRect m_virtual_rect; int m_framebuffer_fd { -1 }; int m_scale_factor { 1 }; - - Gfx::IntPoint m_physical_cursor_location; - unsigned m_mouse_button_state { 0 }; - unsigned m_modifiers { 0 }; - double m_acceleration_factor { 1.0 }; - unsigned m_scroll_step_size { 1 }; }; inline Gfx::RGBA32* Screen::scanline(int y) diff --git a/Userland/Services/WindowServer/WMClientConnection.cpp b/Userland/Services/WindowServer/WMClientConnection.cpp index d1174d0c37..e1ca5d2b17 100644 --- a/Userland/Services/WindowServer/WMClientConnection.cpp +++ b/Userland/Services/WindowServer/WMClientConnection.cpp @@ -96,7 +96,7 @@ void WMClientConnection::start_window_resize(i32 client_id, i32 window_id) auto& window = *(*it).value; // FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button. // Maybe the client should be allowed to specify what initiated this request? - WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left); + WindowManager::the().start_window_resize(window, ScreenInput::the().cursor_location(), MouseButton::Left); } void WMClientConnection::set_window_minimized(i32 client_id, i32 window_id, bool minimized) diff --git a/Userland/Services/WindowServer/Window.cpp b/Userland/Services/WindowServer/Window.cpp index 0b89368725..3ec39650ad 100644 --- a/Userland/Services/WindowServer/Window.cpp +++ b/Userland/Services/WindowServer/Window.cpp @@ -174,12 +174,25 @@ bool Window::apply_minimum_size(Gfx::IntRect& rect) return did_size_clamp; } -void Window::nudge_into_desktop(bool force_titlebar_visible) +void Window::nudge_into_desktop(Screen* target_screen, bool force_titlebar_visible) { - Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(type()); + if (!target_screen) { + // If no explicit target screen was supplied, + // guess based on the current frame rectangle + target_screen = &Screen::closest_to_rect(rect()); + } + Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(*target_screen, type()); auto min_visible = 1; - if (type() == WindowType::Normal) + switch (type()) { + case WindowType::Normal: min_visible = 30; + break; + case WindowType::Desktop: + set_rect(arena); + return; + default: + break; + } // Push the frame around such that at least `min_visible` pixels of the *frame* are in the desktop rect. auto old_frame_rect = frame().rect(); @@ -203,6 +216,7 @@ void Window::nudge_into_desktop(bool force_titlebar_visible) width(), height(), }; + set_rect(new_window_rect); } @@ -684,7 +698,7 @@ void Window::handle_window_menu_action(WindowMenuAction action) WindowManager::the().move_to_front_and_make_active(*this); break; case WindowMenuAction::Move: - WindowManager::the().start_window_move(*this, Screen::the().cursor_location()); + WindowManager::the().start_window_move(*this, ScreenInput::the().cursor_location()); break; case WindowMenuAction::Close: request_close(); @@ -746,7 +760,7 @@ void Window::set_fullscreen(bool fullscreen) Gfx::IntRect new_window_rect = m_rect; if (m_fullscreen) { m_saved_nonfullscreen_rect = m_rect; - new_window_rect = Screen::the().rect(); + new_window_rect = Screen::main().rect(); // TODO: We should support fullscreen on any screen } else if (!m_saved_nonfullscreen_rect.is_empty()) { new_window_rect = m_saved_nonfullscreen_rect; } @@ -755,56 +769,73 @@ void Window::set_fullscreen(bool fullscreen) set_rect(new_window_rect); } -Gfx::IntRect Window::tiled_rect(WindowTileType tiled) const +Gfx::IntRect Window::tiled_rect(Screen* target_screen, WindowTileType tiled) const { + if (!target_screen) { + // If no explicit target screen was supplied, + // guess based on the current frame rectangle + target_screen = &Screen::closest_to_rect(frame().rect()); + } + VERIFY(tiled != WindowTileType::None); int frame_width = (m_frame.rect().width() - m_rect.width()) / 2; int titlebar_height = m_frame.titlebar_rect().height(); - int menu_height = WindowManager::the().maximized_window_rect(*this).y(); - int max_height = WindowManager::the().maximized_window_rect(*this).height(); + auto maximized_rect_relative_to_window_screen = WindowManager::the().maximized_window_rect(*this, true); + int menu_height = maximized_rect_relative_to_window_screen.y(); + int max_height = maximized_rect_relative_to_window_screen.height(); + auto& screen = *target_screen; + auto screen_location = screen.rect().location(); switch (tiled) { case WindowTileType::Left: return Gfx::IntRect(0, menu_height, - Screen::the().width() / 2 - frame_width, - max_height); + screen.width() / 2 - frame_width, + max_height) + .translated(screen_location); case WindowTileType::Right: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height, - Screen::the().width() / 2 - frame_width, - max_height); + screen.width() / 2 - frame_width, + max_height) + .translated(screen_location); case WindowTileType::Top: return Gfx::IntRect(0, menu_height, - Screen::the().width(), - (max_height - titlebar_height) / 2 - frame_width); + screen.width(), + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::Bottom: return Gfx::IntRect(0, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width(), - (max_height - titlebar_height) / 2 - frame_width); + screen.width(), + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::TopLeft: return Gfx::IntRect(0, menu_height, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::TopRight: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::BottomLeft: return Gfx::IntRect(0, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); case WindowTileType::BottomRight: - return Gfx::IntRect(Screen::the().width() / 2 + frame_width, + return Gfx::IntRect(screen.width() / 2 + frame_width, menu_height + (titlebar_height + max_height) / 2 + frame_width, - Screen::the().width() / 2 - frame_width, - (max_height - titlebar_height) / 2 - frame_width); + screen.width() / 2 - frame_width, + (max_height - titlebar_height) / 2 - frame_width) + .translated(screen_location); default: VERIFY_NOT_REACHED(); } @@ -831,7 +862,7 @@ bool Window::set_untiled(Optional 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 bf1403ebac..129f7d7281 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 @@ public: 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 @@ public: { 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 @@ public: void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); } void set_rect_without_repaint(const Gfx::IntRect&); bool apply_minimum_size(Gfx::IntRect&); - void nudge_into_desktop(bool force_titlebar_visible = true); + void nudge_into_desktop(Screen*, bool force_titlebar_visible = true); Gfx::IntSize minimum_size() const { return m_minimum_size; } void set_minimum_size(const Gfx::IntSize&); @@ -260,7 +262,7 @@ public: void start_minimize_animation(); void end_minimize_animation() { m_minimize_animation_step = -1; } - Gfx::IntRect tiled_rect(WindowTileType) const; + Gfx::IntRect tiled_rect(Screen*, WindowTileType) const; void recalculate_rect(); IntrusiveListNode m_list_node; @@ -319,6 +321,14 @@ public: 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 @@ private: 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 f54b0fccd3..a3219415f0 100644 --- a/Userland/Services/WindowServer/WindowClient.ipc +++ b/Userland/Services/WindowServer/WindowClient.ipc @@ -1,6 +1,6 @@ endpoint WindowClient { - fast_greet(Gfx::IntRect screen_rect, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| + fast_greet(Vector 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 fe799039d5..add33622ef 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