1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 20:57:35 +00:00

WindowServer: Add initial support for rendering on multiple screens

This allows WindowServer to use multiple framebuffer devices and
compose the desktop with any arbitrary layout. Currently, it is assumed
that it is configured contiguous and non-overlapping, but this should
eventually be enforced.

To make rendering efficient, each window now also tracks on which
screens it needs to be rendered. This way we don't have to iterate all
the windows for each screen but instead use the same rendering loop and
then only render to the screen (or screens) that the window actually
uses.
This commit is contained in:
Tom 2021-06-13 06:16:06 -06:00 committed by Andreas Kling
parent 499c33ae0c
commit 4392da970a
42 changed files with 1127 additions and 563 deletions

View file

@ -1,4 +1,10 @@
[Screen] [Screens]
MainScreen=0
[Screen0]
Device=/dev/fb0
Left=0
Top=0
Width=1024 Width=1024
Height=768 Height=768
ScaleFactor=1 ScaleFactor=1

View file

@ -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()) { 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()) { if (!result.success()) {
GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), 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); "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 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) { 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()) { if (!result.success()) {
GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), 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); "Unable to set resolution", GUI::MessageBox::Type::Error);

View file

@ -25,11 +25,17 @@ Desktop::Desktop()
{ {
} }
void Desktop::did_receive_screen_rect(Badge<WindowServerConnection>, const Gfx::IntRect& rect) void Desktop::did_receive_screen_rects(Badge<WindowServerConnection>, const Vector<Gfx::IntRect, 4>& rects, size_t main_screen_index)
{ {
if (m_rect == rect) m_main_screen_index = main_screen_index;
return; m_rects = rects;
m_rect = rect; 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) void Desktop::set_background_color(const StringView& background_color)

View file

@ -26,14 +26,18 @@ public:
String wallpaper() const; String wallpaper() const;
bool set_wallpaper(const StringView& path, bool save_config = true); 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<Gfx::IntRect, 4>& rects() const { return m_rects; }
size_t main_screen_index() const { return m_main_screen_index; }
int taskbar_height() const { return TaskbarWindow::taskbar_height(); } int taskbar_height() const { return TaskbarWindow::taskbar_height(); }
void did_receive_screen_rect(Badge<WindowServerConnection>, const Gfx::IntRect&); void did_receive_screen_rects(Badge<WindowServerConnection>, const Vector<Gfx::IntRect, 4>&, size_t);
private: private:
Gfx::IntRect m_rect; Vector<Gfx::IntRect, 4> m_rects;
size_t m_main_screen_index { 0 };
Gfx::IntRect m_bounding_rect;
}; };
} }

View file

@ -52,7 +52,7 @@ public:
DragMove, DragMove,
Drop, Drop,
ThemeChange, ThemeChange,
ScreenRectChange, ScreenRectsChange,
ActionEnter, ActionEnter,
ActionLeave, ActionLeave,
@ -392,18 +392,21 @@ public:
} }
}; };
class ScreenRectChangeEvent final : public Event { class ScreenRectsChangeEvent final : public Event {
public: public:
explicit ScreenRectChangeEvent(const Gfx::IntRect& rect) explicit ScreenRectsChangeEvent(const Vector<Gfx::IntRect, 4>& rects, size_t main_screen_index)
: Event(Type::ScreenRectChange) : Event(Type::ScreenRectsChange)
, m_rect(rect) , m_rects(rects)
, m_main_screen_index(main_screen_index)
{ {
} }
const Gfx::IntRect& rect() const { return m_rect; } const Vector<Gfx::IntRect, 4>& rects() const { return m_rects; }
size_t main_screen_index() const { return m_main_screen_index; }
private: private:
Gfx::IntRect m_rect; Vector<Gfx::IntRect, 4> m_rects;
size_t m_main_screen_index;
}; };
class FocusEvent final : public Event { class FocusEvent final : public Event {

View file

@ -52,7 +52,7 @@ class Painter;
class RadioButton; class RadioButton;
class ResizeCorner; class ResizeCorner;
class ResizeEvent; class ResizeEvent;
class ScreenRectChangeEvent; class ScreenRectsChangeEvent;
class Scrollbar; class Scrollbar;
class AbstractScrollableWidget; class AbstractScrollableWidget;
class Slider; class Slider;

View file

@ -506,7 +506,7 @@ void Widget::theme_change_event(ThemeChangeEvent&)
{ {
} }
void Widget::screen_rect_change_event(ScreenRectChangeEvent&) void Widget::screen_rects_change_event(ScreenRectsChangeEvent&)
{ {
} }

View file

@ -312,7 +312,7 @@ protected:
virtual void drag_leave_event(Event&); virtual void drag_leave_event(Event&);
virtual void drop_event(DropEvent&); virtual void drop_event(DropEvent&);
virtual void theme_change_event(ThemeChangeEvent&); 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_begin_inspection() override;
virtual void did_end_inspection() override; virtual void did_end_inspection() override;

View file

@ -495,11 +495,11 @@ void Window::handle_theme_change_event(ThemeChangeEvent& event)
dispatch_theme_change(*m_main_widget.ptr(), dispatch_theme_change); 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) if (!m_main_widget)
return; 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.dispatch_event(event, this);
widget.for_each_child_widget([&](auto& widget) -> IterationDecision { widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
widget.dispatch_event(event, this); widget.dispatch_event(event, this);
@ -507,8 +507,8 @@ void Window::handle_screen_rect_change_event(ScreenRectChangeEvent& event)
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
}; };
dispatch_screen_rect_change(*m_main_widget.ptr(), dispatch_screen_rect_change); dispatch_screen_rects_change(*m_main_widget.ptr(), dispatch_screen_rects_change);
screen_rect_change_event(event); screen_rects_change_event(event);
} }
void Window::handle_drag_move_event(DragEvent& event) void Window::handle_drag_move_event(DragEvent& event)
@ -578,8 +578,8 @@ void Window::event(Core::Event& event)
if (event.type() == Event::ThemeChange) if (event.type() == Event::ThemeChange)
return handle_theme_change_event(static_cast<ThemeChangeEvent&>(event)); return handle_theme_change_event(static_cast<ThemeChangeEvent&>(event));
if (event.type() == Event::ScreenRectChange) if (event.type() == Event::ScreenRectsChange)
return handle_screen_rect_change_event(static_cast<ScreenRectChangeEvent&>(event)); return handle_screen_rects_change_event(static_cast<ScreenRectsChangeEvent&>(event));
Core::Object::event(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&)
{ {
} }

View file

@ -203,7 +203,7 @@ public:
protected: protected:
Window(Core::Object* parent = nullptr); Window(Core::Object* parent = nullptr);
virtual void wm_event(WMEvent&); virtual void wm_event(WMEvent&);
virtual void screen_rect_change_event(ScreenRectChangeEvent&); virtual void screen_rects_change_event(ScreenRectsChangeEvent&);
private: private:
void update_cursor(); void update_cursor();
@ -218,7 +218,7 @@ private:
void handle_became_active_or_inactive_event(Core::Event&); void handle_became_active_or_inactive_event(Core::Event&);
void handle_close_request(); void handle_close_request();
void handle_theme_change_event(ThemeChangeEvent&); 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_drag_move_event(DragEvent&);
void handle_left_event(); void handle_left_event();

View file

@ -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. // 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<Messages::WindowClient::FastGreet>(); auto message = wait_for_specific_message<Messages::WindowClient::FastGreet>();
set_system_theme_from_anonymous_buffer(message->theme_buffer()); 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_default_font_query(message->default_font_query());
Gfx::FontDatabase::set_fixed_width_font_query(message->fixed_width_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<Gfx::IntRect> const&, u32, Core::AnonymousBuffer const&, String const&, String const&)
{ {
// NOTE: This message is handled in the constructor. // 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<ActionEvent>(GUI::Event::ActionLeave, *action)); Core::EventLoop::current().post_event(*app, make<ActionEvent>(GUI::Event::ActionLeave, *action));
} }
void WindowServerConnection::screen_rect_changed(Gfx::IntRect const& rect) void WindowServerConnection::screen_rects_changed(Vector<Gfx::IntRect> const& rects, u32 main_screen_index)
{ {
Desktop::the().did_receive_screen_rect({}, rect); Desktop::the().did_receive_screen_rects({}, rects, main_screen_index);
Window::for_each_window({}, [rect](auto& window) { Window::for_each_window({}, [&](auto& window) {
Core::EventLoop::current().post_event(window, make<ScreenRectChangeEvent>(rect)); Core::EventLoop::current().post_event(window, make<ScreenRectsChangeEvent>(rects, main_screen_index));
}); });
} }

View file

@ -22,7 +22,7 @@ public:
private: private:
WindowServerConnection(); WindowServerConnection();
virtual void fast_greet(Gfx::IntRect const&, Core::AnonymousBuffer const&, String const&, String const&) override; virtual void fast_greet(Vector<Gfx::IntRect> const&, u32, Core::AnonymousBuffer const&, String const&, String const&) override;
virtual void paint(i32, Gfx::IntSize const&, Vector<Gfx::IntRect> const&) override; virtual void paint(i32, Gfx::IntSize const&, Vector<Gfx::IntRect> const&) override;
virtual void mouse_move(i32, Gfx::IntPoint const&, u32, u32, u32, i32, bool, Vector<String> const&) override; virtual void mouse_move(i32, Gfx::IntPoint const&, u32, u32, u32, i32, bool, Vector<String> const&) override;
virtual void mouse_down(i32, Gfx::IntPoint const&, u32, u32, u32, i32) 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_entered(i32, u32) override;
virtual void menu_item_left(i32, u32) override; virtual void menu_item_left(i32, u32) override;
virtual void menu_visibility_did_change(i32, bool) 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<Gfx::IntRect> const&, u32) override;
virtual void set_wallpaper_finished(bool) override; virtual void set_wallpaper_finished(bool) override;
virtual void drag_dropped(i32, Gfx::IntPoint const&, String const&, HashMap<String, ByteBuffer> const&) override; virtual void drag_dropped(i32, Gfx::IntPoint const&, String const&, HashMap<String, ByteBuffer> const&) override;
virtual void drag_accepted() override; virtual void drag_accepted() override;

View file

@ -71,7 +71,7 @@ void OutOfProcessWebView::create_client()
client().async_update_system_theme(Gfx::current_system_theme_buffer()); 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_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) void OutOfProcessWebView::load(const URL& url)
@ -192,9 +192,9 @@ void OutOfProcessWebView::theme_change_event(GUI::ThemeChangeEvent& event)
request_repaint(); 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<WebContentClient>, i32 bitmap_id) void OutOfProcessWebView::notify_server_did_paint(Badge<WebContentClient>, i32 bitmap_id)

View file

@ -74,7 +74,7 @@ private:
virtual void mousewheel_event(GUI::MouseEvent&) override; virtual void mousewheel_event(GUI::MouseEvent&) override;
virtual void keydown_event(GUI::KeyEvent&) override; virtual void keydown_event(GUI::KeyEvent&) override;
virtual void theme_change_event(GUI::ThemeChangeEvent&) 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 // ^AbstractScrollableWidget
virtual void did_scroll() override; virtual void did_scroll() override;

View file

@ -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()]);
} }
} }

View file

@ -27,7 +27,7 @@ public:
private: private:
NotificationWindow(i32 client_id, const String& text, const String& title, const Gfx::ShareableBitmap&); 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; Gfx::IntRect m_original_rect;
i32 m_id; i32 m_id;

View file

@ -58,7 +58,7 @@ TaskbarWindow::TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu)
set_window_type(GUI::WindowType::Taskbar); set_window_type(GUI::WindowType::Taskbar);
set_title("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<TaskbarWidget>(); auto& main_widget = set_main_widget<TaskbarWidget>();
main_widget.set_layout<GUI::HorizontalBoxLayout>(); main_widget.set_layout<GUI::HorizontalBoxLayout>();
@ -148,8 +148,9 @@ void TaskbarWindow::create_quick_launch_bar()
quick_launch_bar.set_fixed_size(total_width, 24); 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<Gfx::IntRect, 4>& 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() }; Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
set_rect(new_rect); set_rect(new_rect);
update_applet_area(); 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());
} }

View file

@ -21,7 +21,7 @@ public:
private: private:
explicit TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu); explicit TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu);
void create_quick_launch_bar(); void create_quick_launch_bar();
void on_screen_rect_change(const Gfx::IntRect&); void on_screen_rects_change(const Vector<Gfx::IntRect, 4>&, size_t);
NonnullRefPtr<GUI::Button> create_button(const WindowIdentifier&); NonnullRefPtr<GUI::Button> create_button(const WindowIdentifier&);
void add_window_button(::Window&, const WindowIdentifier&); void add_window_button(::Window&, const WindowIdentifier&);
void remove_window_button(::Window&, bool); void remove_window_button(::Window&, bool);
@ -29,7 +29,7 @@ private:
::Window* find_window_owner(::Window&) const; ::Window* find_window_owner(::Window&) const;
virtual void wm_event(GUI::WMEvent&) override; 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(); void update_applet_area();

View file

@ -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); 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<Gfx::IntRect>& 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) void ClientConnection::load_url(const URL& url)

View file

@ -34,7 +34,7 @@ private:
virtual void update_system_theme(Core::AnonymousBuffer const&) override; virtual void update_system_theme(Core::AnonymousBuffer const&) override;
virtual void update_system_fonts(String const&, String 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<Gfx::IntRect> const&, u32) override;
virtual void load_url(URL const&) override; virtual void load_url(URL const&) override;
virtual void load_html(String const&, URL const&) override; virtual void load_html(String const&, URL const&) override;
virtual void paint(Gfx::IntRect const&, i32) override; virtual void paint(Gfx::IntRect const&, i32) override;

View file

@ -28,7 +28,7 @@ public:
void set_palette_impl(const Gfx::PaletteImpl&); void set_palette_impl(const Gfx::PaletteImpl&);
void set_viewport_rect(const Gfx::IntRect&); 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<Gfx::IntRect, 4>& 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; } void set_should_show_line_box_borders(bool b) { m_should_show_line_box_borders = b; }

View file

@ -2,7 +2,7 @@ endpoint WebContentServer
{ {
update_system_theme(Core::AnonymousBuffer theme_buffer) =| update_system_theme(Core::AnonymousBuffer theme_buffer) =|
update_system_fonts(String default_font_query, String fixed_width_font_query) =| update_system_fonts(String default_font_query, String fixed_width_font_query) =|
update_screen_rect(Gfx::IntRect rect) =| update_screen_rects(Vector<Gfx::IntRect> rects, u32 main_screen_index) =|
load_url(URL url) =| load_url(URL url) =|
load_html(String html, URL url) =| load_html(String html, URL url) =|

View file

@ -53,7 +53,7 @@ ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socke
s_connections = new HashMap<int, NonnullRefPtr<ClientConnection>>; s_connections = new HashMap<int, NonnullRefPtr<ClientConnection>>;
s_connections->set(client_id, *this); 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() ClientConnection::~ClientConnection()
@ -77,9 +77,9 @@ void ClientConnection::die()
}); });
} }
void ClientConnection::notify_about_new_screen_rect(Gfx::IntRect const& rect) void ClientConnection::notify_about_new_screen_rects(Vector<Gfx::IntRect, 4> const& rects, size_t main_screen_index)
{ {
async_screen_rect_changed(rect); async_screen_rects_changed(rects, main_screen_index);
} }
void ClientConnection::create_menubar(i32 menubar_id) void ClientConnection::create_menubar(i32 menubar_id)
@ -297,9 +297,14 @@ Messages::WindowServer::GetWallpaperResponse ClientConnection::get_wallpaper()
return Compositor::the().wallpaper_path(); 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) 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; auto new_rect = rect;
window.apply_minimum_size(new_rect); window.apply_minimum_size(new_rect);
window.set_rect(new_rect); window.set_rect(new_rect);
window.nudge_into_desktop(); window.nudge_into_desktop(nullptr);
window.request_update(window.rect()); window.request_update(window.rect());
return 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(); auto new_rect = window.rect();
bool did_size_clamp = window.apply_minimum_size(new_rect); bool did_size_clamp = window.apply_minimum_size(new_rect);
window.set_rect(new_rect); window.set_rect(new_rect);
window.nudge_into_desktop(); window.nudge_into_desktop(nullptr);
window.request_update(window.rect()); window.request_update(window.rect());
if (did_size_clamp) 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); window->set_minimum_size(minimum_size);
bool did_size_clamp = window->apply_minimum_size(new_rect); bool did_size_clamp = window->apply_minimum_size(new_rect);
window->set_rect(new_rect); window->set_rect(new_rect);
window->nudge_into_desktop(); window->nudge_into_desktop(nullptr);
if (did_size_clamp) if (did_size_clamp)
window->refresh_client_size(); window->refresh_client_size();
} }
if (window->type() == WindowType::Desktop) { if (window->type() == WindowType::Desktop) {
window->set_rect(WindowManager::the().desktop_rect()); window->set_rect(Screen::bounding_rect());
window->recalculate_rect(); window->recalculate_rect();
} }
window->set_opacity(opacity); 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. // 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? // Maybe the client should be allowed to specify what initiated this request?
WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left); WindowManager::the().start_window_resize(window, ScreenInput::the().cursor_location(), MouseButton::Left);
} }
Messages::WindowServer::StartDragResponse ClientConnection::start_drag(String const& text, HashMap<String, ByteBuffer> const& mime_data, Gfx::ShareableBitmap const& drag_bitmap) Messages::WindowServer::StartDragResponse ClientConnection::start_drag(String const& text, HashMap<String, ByteBuffer> const& mime_data, Gfx::ShareableBitmap const& drag_bitmap)
@ -830,7 +835,7 @@ void ClientConnection::pong()
Messages::WindowServer::GetGlobalCursorPositionResponse ClientConnection::get_global_cursor_position() 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) 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() 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) 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() 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) void ClientConnection::set_double_click_speed(i32 speed)
@ -909,25 +914,27 @@ void ClientConnection::did_become_responsive()
Messages::WindowServer::GetScreenBitmapResponse ClientConnection::get_screen_bitmap(Optional<Gfx::IntRect> const& rect) Messages::WindowServer::GetScreenBitmapResponse ClientConnection::get_screen_bitmap(Optional<Gfx::IntRect> const& rect)
{ {
auto& screen = Screen::main(); // TODO: implement screenshots from other screens or areas spanning multiple screens
if (rect.has_value()) { 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(); 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(); return bitmap.to_shareable_bitmap();
} }
Messages::WindowServer::GetScreenBitmapAroundCursorResponse ClientConnection::get_screen_bitmap_around_cursor(Gfx::IntSize const& size) Messages::WindowServer::GetScreenBitmapAroundCursorResponse ClientConnection::get_screen_bitmap_around_cursor(Gfx::IntSize const& size)
{ {
auto scale_factor = WindowManager::the().scale_factor(); auto& screen = Screen::main(); // TODO: implement getting screen bitmaps from other screens or areas spanning multiple screens
auto cursor_location = Screen::the().cursor_location(); 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() }; 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. // 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. // FIXME: This is rather wasteful. We can probably think of a way to avoid this.
Compositor::the().compose(); 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(); return bitmap->to_shareable_bitmap();
} }
@ -942,9 +949,12 @@ Messages::WindowServer::IsWindowModifiedResponse ClientConnection::is_window_mod
return window.is_modified(); 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) void ClientConnection::set_window_modified(i32 window_id, bool modified)

View file

@ -40,7 +40,7 @@ public:
static ClientConnection* from_client_id(int client_id); static ClientConnection* from_client_id(int client_id);
static void for_each_client(Function<void(ClientConnection&)>); static void for_each_client(Function<void(ClientConnection&)>);
void notify_about_new_screen_rect(const Gfx::IntRect&); void notify_about_new_screen_rects(const Vector<Gfx::IntRect, 4>&, size_t);
void post_paint_message(Window&, bool ignore_occlusion = false); void post_paint_message(Window&, bool ignore_occlusion = false);
Menu* find_menu_by_id(int menu_id) Menu* find_menu_by_id(int menu_id)
@ -125,7 +125,7 @@ private:
virtual void set_background_color(String const&) override; virtual void set_background_color(String const&) override;
virtual void set_wallpaper_mode(String const&) override; virtual void set_wallpaper_mode(String const&) override;
virtual Messages::WindowServer::GetWallpaperResponse get_wallpaper() 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_cursor(i32, i32) override;
virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override; virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override;
virtual void popup_menu(i32, Gfx::IntPoint 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 Messages::WindowServer::GetDoubleClickSpeedResponse get_double_click_speed() override;
virtual void set_window_modified(i32, bool) override; virtual void set_window_modified(i32, bool) override;
virtual Messages::WindowServer::IsWindowModifiedResponse is_window_modified(i32) 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); Window* window_from_id(i32 window_id);

View file

@ -61,28 +61,44 @@ Compositor::Compositor()
}, },
this); this);
m_screen_can_set_buffer = Screen::the().can_set_buffer();
init_bitmaps(); init_bitmaps();
} }
void Compositor::init_bitmaps() const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
{
return *m_screen_data[screen.index()].m_front_bitmap;
}
void Compositor::ScreenData::init_bitmaps(Screen& screen)
{ {
auto& screen = Screen::the();
auto size = screen.size(); 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_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0));
m_front_painter = make<Gfx::Painter>(*m_front_bitmap); m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
m_front_painter->translate(-screen.rect().location());
if (m_screen_can_set_buffer) if (screen.can_set_buffer())
m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(screen.physical_height())); m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(screen.physical_height()));
else else
m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor());
m_back_painter = make<Gfx::Painter>(*m_back_bitmap); m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
m_back_painter->translate(-screen.rect().location());
m_temp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor()); m_temp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor());
m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap); m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap);
m_temp_painter->translate(-screen.rect().location());
m_buffers_are_flipped = false; 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(); invalidate_screen();
} }
@ -101,7 +117,6 @@ void Compositor::did_construct_window_manager(Badge<WindowManager>)
void Compositor::compose() void Compositor::compose()
{ {
auto& wm = WindowManager::the(); auto& wm = WindowManager::the();
auto& ws = Screen::the();
{ {
auto& current_cursor = wm.active_cursor(); auto& current_cursor = wm.active_cursor();
@ -122,11 +137,26 @@ void Compositor::compose()
} }
auto dirty_screen_rects = move(m_dirty_screen_rects); auto dirty_screen_rects = move(m_dirty_screen_rects);
dirty_screen_rects.add(m_last_geometry_label_damage_rect.intersected(ws.rect())); auto* dnd_client = wm.dnd_client();
dirty_screen_rects.add(m_last_dnd_rect.intersected(ws.rect())); if (!m_last_geometry_label_damage_rect.is_empty() || !m_last_dnd_rect.is_empty() || (m_invalidated_cursor && dnd_client)) {
if (m_invalidated_cursor) { Screen::for_each([&](auto& screen) {
if (wm.dnd_client()) if (!m_last_geometry_label_damage_rect.is_empty()) {
dirty_screen_rects.add(wm.dnd_rect().intersected(ws.rect())); 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 // Mark window regions as dirty that need to be re-rendered
@ -180,61 +210,81 @@ void Compositor::compose()
dbgln("dirty screen: {}", r); dbgln("dirty screen: {}", r);
} }
Gfx::DisjointRectSet flush_rects; auto& cursor_screen = ScreenInput::the().cursor_location_screen();
Gfx::DisjointRectSet flush_transparent_rects;
Gfx::DisjointRectSet flush_special_rects; 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(); auto cursor_rect = current_cursor_rect();
bool need_to_draw_cursor = false; bool need_to_draw_cursor = false;
Gfx::IntRect previous_cursor_rect;
auto back_painter = *m_back_painter; Screen* previous_cursor_screen = nullptr;
auto temp_painter = *m_temp_painter; 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)) {
auto check_restore_cursor_back = [&](const Gfx::IntRect& rect) {
if (!need_to_draw_cursor && rect.intersects(cursor_rect)) {
// Restore what's behind the cursor if anything touches the area of the cursor // Restore what's behind the cursor if anything touches the area of the cursor
need_to_draw_cursor = true; 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); dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect);
VERIFY(!flush_rects.intersects(rect)); VERIFY(!screen_data.m_flush_rects.intersects(rect));
VERIFY(!flush_transparent_rects.intersects(rect)); VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect));
flush_rects.add(rect); screen_data.m_flush_rects.add(rect);
check_restore_cursor_back(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); dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect);
VERIFY(!flush_rects.intersects(rect)); VERIFY(!screen_data.m_flush_rects.intersects(rect));
for (auto& r : flush_transparent_rects.rects()) { for (auto& r : screen_data.m_flush_transparent_rects.rects()) {
if (r == rect) if (r == rect)
return; return;
} }
flush_transparent_rects.add(rect); screen_data.m_flush_transparent_rects.add(rect);
check_restore_cursor_back(rect); check_restore_cursor_back(screen, rect);
}; };
if (!m_cursor_back_bitmap || m_invalidated_cursor) if (!m_screen_data[cursor_screen.index()].m_cursor_back_bitmap || m_invalidated_cursor)
check_restore_cursor_back(cursor_rect); 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! // FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color!
painter.fill_rect(rect, background_color); painter.fill_rect(rect, background_color);
if (m_wallpaper) { if (m_wallpaper) {
if (m_wallpaper_mode == WallpaperMode::Center) { if (m_wallpaper_mode == WallpaperMode::Center) {
Gfx::IntPoint offset { (ws.width() - m_wallpaper->width()) / 2, (ws.height() - m_wallpaper->height()) / 2 }; Gfx::IntPoint offset { (screen.width() - m_wallpaper->width()) / 2, (screen.height() - m_wallpaper->height()) / 2 };
painter.blit_offset(rect.location(), *m_wallpaper, rect, offset); painter.blit_offset(rect.location(), *m_wallpaper, rect.translated(-screen_rect.location()), offset);
} else if (m_wallpaper_mode == WallpaperMode::Tile) { } else if (m_wallpaper_mode == WallpaperMode::Tile) {
painter.draw_tiled_bitmap(rect, *m_wallpaper); painter.draw_tiled_bitmap(rect, *m_wallpaper);
} else if (m_wallpaper_mode == WallpaperMode::Stretch) { } else if (m_wallpaper_mode == WallpaperMode::Stretch) {
float hscale = (float)m_wallpaper->width() / (float)ws.width(); float hscale = (float)m_wallpaper->width() / (float)screen.width();
float vscale = (float)m_wallpaper->height() / (float)ws.height(); float vscale = (float)m_wallpaper->height() / (float)screen.height();
// TODO: this may look ugly, we should scale to a backing bitmap and then blit // 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); painter.draw_scaled_bitmap(rect, *m_wallpaper, src_rect);
} else { } else {
VERIFY_NOT_REACHED(); 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) { m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](const Gfx::IntRect& render_rect) {
dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {}", render_rect); Screen::for_each([&](auto& screen) {
prepare_rect(render_rect); auto screen_rect = screen.rect();
paint_wallpaper(back_painter, render_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; return IterationDecision::Continue;
}); });
auto compose_window = [&](Window& window) -> IterationDecision { auto compose_window = [&](Window& window) -> IterationDecision {
auto frame_rect = window.frame().render_rect(); if (window.screens().is_empty()) {
if (!frame_rect.intersects(ws.rect())) // This window doesn't intersect with any screens, so there's nothing to render
return IterationDecision::Continue; return IterationDecision::Continue;
}
auto frame_rect = window.frame().render_rect();
auto window_rect = window.rect(); auto window_rect = window.rect();
auto frame_rects = frame_rect.shatter(window_rect); auto frame_rects = frame_rect.shatter(window_rect);
dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect); dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect);
RefPtr<Gfx::Bitmap> backing_store = window.backing_store(); RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
auto compose_window_rect = [&](Gfx::Painter& painter, const Gfx::IntRect& rect) { auto compose_window_rect = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) {
if (!window.is_fullscreen()) { if (!window.is_fullscreen()) {
rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) { rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) {
Gfx::PainterStateSaver saver(painter); Gfx::PainterStateSaver saver(painter);
painter.add_clip_rect(intersected_rect); painter.add_clip_rect(intersected_rect);
dbgln_if(COMPOSE_DEBUG, " render frame: {}", 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; return IterationDecision::Continue;
}); });
} }
@ -359,12 +419,18 @@ void Compositor::compose()
auto& opaque_rects = window.opaque_rects(); auto& opaque_rects = window.opaque_rects();
if (!opaque_rects.is_empty()) { if (!opaque_rects.is_empty()) {
opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { 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); prepare_rect(*screen, screen_render_rect);
Gfx::PainterStateSaver saver(back_painter); auto& back_painter = *m_screen_data[screen->index()].m_back_painter;
back_painter.add_clip_rect(render_rect); Gfx::PainterStateSaver saver(back_painter);
compose_window_rect(back_painter, render_rect); back_painter.add_clip_rect(screen_render_rect);
compose_window_rect(*screen, back_painter, screen_render_rect);
}
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
} }
@ -374,22 +440,36 @@ void Compositor::compose()
auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects(); auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects();
if (!transparency_wallpaper_rects.is_empty()) { if (!transparency_wallpaper_rects.is_empty()) {
transparency_wallpaper_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { 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); auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter;
paint_wallpaper(temp_painter, render_rect); prepare_transparency_rect(*screen, screen_render_rect);
paint_wallpaper(*screen, temp_painter, screen_render_rect, screen_rect);
}
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
} }
auto& transparency_rects = window.transparency_rects(); auto& transparency_rects = window.transparency_rects();
if (!transparency_rects.is_empty()) { if (!transparency_rects.is_empty()) {
transparency_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) { 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); prepare_transparency_rect(*screen, screen_render_rect);
Gfx::PainterStateSaver saver(temp_painter); auto& temp_painter = *m_screen_data[screen->index()].m_temp_painter;
temp_painter.add_clip_rect(render_rect); Gfx::PainterStateSaver saver(temp_painter);
compose_window_rect(temp_painter, render_rect); temp_painter.add_clip_rect(screen_render_rect);
compose_window_rect(*screen, temp_painter, screen_render_rect);
}
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
} }
@ -411,24 +491,35 @@ void Compositor::compose()
// Check that there are no overlapping transparent and opaque flush rectangles // Check that there are no overlapping transparent and opaque flush rectangles
VERIFY(![&]() { VERIFY(![&]() {
for (auto& rect_transparent : flush_transparent_rects.rects()) { bool is_overlapping = false;
for (auto& rect_opaque : flush_rects.rects()) { Screen::for_each([&](auto& screen) {
if (rect_opaque.intersects(rect_transparent)) { auto& screen_data = m_screen_data[screen.index()];
dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent)); auto& flush_transparent_rects = screen_data.m_flush_transparent_rects;
return true; 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 IterationDecision::Continue;
return false; });
return is_overlapping;
}()); }());
// Copy anything rendered to the temporary buffer to the back buffer // Copy anything rendered to the temporary buffer to the back buffer
for (auto& rect : flush_transparent_rects.rects()) Screen::for_each([&](auto& screen) {
back_painter.blit(rect.location(), *m_temp_bitmap, rect); 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; draw_geometry_label(cursor_screen);
if (draw_geometry_label(geometry_label_damage_rect))
flush_special_rects.add(geometry_label_damage_rect);
} }
m_invalidated_any = false; m_invalidated_any = false;
@ -438,34 +529,50 @@ void Compositor::compose()
if (wm.dnd_client()) { if (wm.dnd_client()) {
auto dnd_rect = wm.dnd_rect(); auto dnd_rect = wm.dnd_rect();
// TODO: render once into a backing bitmap, then just blit... Screen::for_each([&](auto& screen) {
auto render_dnd = [&]() { auto screen_rect = screen.rect();
back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200)); auto render_dnd_rect = screen_rect.intersected(dnd_rect);
back_painter.draw_rect(dnd_rect, wm.palette().selection()); if (render_dnd_rect.is_empty())
if (!wm.dnd_text().is_empty()) { return IterationDecision::Continue;
auto text_rect = dnd_rect; auto& screen_data = m_screen_data[screen.index()];
if (wm.dnd_bitmap()) auto& back_painter = *screen_data.m_back_painter;
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) { // TODO: render once into a backing bitmap, then just blit...
Gfx::PainterStateSaver saver(back_painter); auto render_dnd = [&]() {
back_painter.add_clip_rect(render_rect); back_painter.fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
render_dnd(); 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; 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 { } else {
if (!m_last_dnd_rect.is_empty()) { if (!m_last_dnd_rect.is_empty()) {
invalidate_screen(m_last_dnd_rect); 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) { if (need_to_draw_cursor) {
flush_rects.add(cursor_rect); auto& screen_data = m_screen_data[cursor_screen.index()];
if (cursor_rect != m_last_cursor_rect) screen_data.draw_cursor(cursor_screen, cursor_rect);
flush_rects.add(m_last_cursor_rect); screen_data.m_flush_rects.add(cursor_rect);
draw_cursor(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) { Screen::for_each([&](auto& screen) {
for (auto& rect : flush_rects.rects()) flush(screen);
m_front_painter->fill_rect(rect, Color::Yellow); return IterationDecision::Continue;
} });
if (m_screen_can_set_buffer) if (did_render_animation)
flip_buffers(); step_animations();
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);
} }
void Compositor::flush(const Gfx::IntRect& a_rect) void Compositor::flush(Screen& screen)
{ {
auto rect = Gfx::IntRect::intersection(a_rect, Screen::the().rect()); auto& screen_data = m_screen_data[screen.index()];
if (m_flash_flush) {
// Almost everything in Compositor is in logical coordinates, with the painters having for (auto& rect : screen_data.m_flush_rects.rects())
// a scale applied. But this routine accesses the backbuffer pixels directly, so it screen_data.m_front_painter->fill_rect(rect, Color::Yellow);
// 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;
} }
for (int y = 0; y < rect.height(); ++y) { if (screen_data.m_screen_can_set_buffer)
fast_u32_copy(to_ptr, from_ptr, rect.width()); screen_data.flip_buffers(screen);
from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch); 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() void Compositor::invalidate_screen()
{ {
invalidate_screen(Screen::the().rect()); invalidate_screen(Screen::bounding_rect());
} }
void Compositor::invalidate_screen(const Gfx::IntRect& screen_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) if (m_invalidated_any)
return; return;
@ -623,19 +750,21 @@ bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callba
return true; return true;
} }
void Compositor::flip_buffers() void Compositor::ScreenData::flip_buffers(Screen& screen)
{ {
VERIFY(m_screen_can_set_buffer); VERIFY(m_screen_can_set_buffer);
swap(m_front_bitmap, m_back_bitmap); swap(m_front_bitmap, m_back_bitmap);
swap(m_front_painter, m_back_painter); 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; 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; bool did_render_any = false;
auto& painter = *m_back_painter; auto& painter = *m_screen_data[screen.index()].m_back_painter;
Gfx::PainterStateSaver saver(painter); Gfx::PainterStateSaver saver(painter);
painter.set_draw_op(Gfx::Painter::DrawOp::Invert); 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) 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 painter.draw_rect(rect, Color::Transparent); // Color doesn't matter, we draw inverted
flush_rects.add(rect); flush_rects.add(rect);
invalidate_screen(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(); window.step_minimize_animation();
if (window.minimize_animation_index() >= minimize_animation_steps) if (window.minimize_animation_index() >= minimize_animation_steps)
window.end_minimize_animation(); 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(); init_bitmaps();
invalidate_occlusions(); invalidate_occlusions();
compose(); compose();
return success;
} }
Gfx::IntRect Compositor::current_cursor_rect() const Gfx::IntRect Compositor::current_cursor_rect() const
{ {
auto& wm = WindowManager::the(); auto& wm = WindowManager::the();
auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); 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) void Compositor::invalidate_cursor(bool compose_immediately)
@ -714,13 +840,13 @@ void Compositor::invalidate_cursor(bool compose_immediately)
start_compose_async_timer(); 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& 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); 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) { if (!window_being_moved_or_resized) {
m_last_geometry_label_damage_rect = {}; m_last_geometry_label_damage_rect = {};
return false; return;
} }
auto geometry_string = window_being_moved_or_resized->rect().to_string(); auto geometry_string = window_being_moved_or_resized->rect().to_string();
if (!window_being_moved_or_resized->size_increment().is_null()) { 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 }; 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()); 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()) if (geometry_label_rect.left() < desktop_rect.left())
geometry_label_rect.set_left(desktop_rect.left()); geometry_label_rect.set_left(desktop_rect.left());
if (geometry_label_rect.top() < desktop_rect.top()) 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()) if (geometry_label_rect.bottom() > desktop_rect.bottom())
geometry_label_rect.set_bottom_without_resize(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)); 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); 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()); 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; m_last_geometry_label_damage_rect = geometry_label_damage_rect;
return true;
} }
void Compositor::change_cursor(const Cursor* cursor) 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(); 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()) { 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::the().scale_factor()); m_cursor_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor());
m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap); m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap);
} }
auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor(); auto& compositor = Compositor::the();
m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(Screen::the().rect())); auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor();
m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(m_current_cursor_frame)); 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; 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()) if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale())
return; 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_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() void Compositor::notify_display_links()
@ -863,14 +998,17 @@ void Compositor::recompute_occlusions()
dbgln_if(OCCLUSIONS_DEBUG, "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()) { 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) { WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) {
auto& visible_opaque = w.opaque_rects(); auto& visible_opaque = w.opaque_rects();
auto& transparency_rects = w.transparency_rects(); auto& transparency_rects = w.transparency_rects();
auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects(); auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
w.screens().clear_with_capacity();
if (&w == fullscreen_window) { if (&w == fullscreen_window) {
w.screens().append(&main_screen);
if (w.is_opaque()) { if (w.is_opaque()) {
visible_opaque = screen_rect; visible_opaque = screen_rect;
transparency_rects.clear(); transparency_rects.clear();
@ -890,28 +1028,39 @@ void Compositor::recompute_occlusions()
m_opaque_wallpaper_rects.clear(); m_opaque_wallpaper_rects.clear();
} else { } else {
Gfx::DisjointRectSet visible_rects(screen_rect); Gfx::DisjointRectSet visible_rects;
visible_rects.add_many(Screen::rects());
bool have_transparent = false; bool have_transparent = false;
WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) { WindowManager::the().window_stack().for_each_visible_window_from_front_to_back([&](Window& w) {
w.transparency_wallpaper_rects().clear(); w.transparency_wallpaper_rects().clear();
auto& visible_opaque = w.opaque_rects(); auto& visible_opaque = w.opaque_rects();
visible_opaque.clear();
auto& transparency_rects = w.transparency_rects(); auto& transparency_rects = w.transparency_rects();
if (w.is_minimized()) { transparency_rects.clear();
visible_opaque.clear(); w.screens().clear_with_capacity();
transparency_rects.clear(); if (w.is_minimized())
return IterationDecision::Continue; return IterationDecision::Continue;
}
auto transparent_render_rects = w.frame().transparent_render_rects().intersected(screen_rect); auto transparent_frame_render_rects = w.frame().transparent_render_rects();
auto opaque_render_rects = w.frame().opaque_render_rects().intersected(screen_rect); auto opaque_frame_render_rects = w.frame().opaque_render_rects();
if (transparent_render_rects.is_empty() && opaque_render_rects.is_empty()) { Gfx::DisjointRectSet visible_opaque_rects;
visible_opaque.clear(); Screen::for_each([&](auto& screen) {
transparency_rects.clear(); 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; return IterationDecision::Continue;
} });
visible_opaque = visible_rects.intersected(visible_opaque_rects);
visible_opaque = visible_rects.intersected(opaque_render_rects);
transparency_rects = move(transparent_render_rects);
auto render_rect = w.frame().render_rect(); auto render_rect = w.frame().render_rect();
@ -967,8 +1116,31 @@ void Compositor::recompute_occlusions()
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
bool have_opaque = !visible_opaque.is_empty();
if (!transparency_rects.is_empty()) if (!transparency_rects.is_empty())
have_transparent = true; 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)); 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) { wm.window_stack().for_each_visible_window_from_back_to_front([&](Window& w) {
auto window_frame_rect = w.frame().render_rect().intersected(screen_rect); auto window_frame_rect = w.frame().render_rect();
if (w.is_minimized() || window_frame_rect.is_empty()) if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty())
return IterationDecision::Continue; return IterationDecision::Continue;
if constexpr (OCCLUSIONS_DEBUG) { 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()) for (auto& r : w.opaque_rects().rects())
dbgln(" opaque: {}", r); dbgln(" opaque: {}", r);
for (auto& r : w.transparency_wallpaper_rects().rects()) for (auto& r : w.transparency_wallpaper_rects().rects())

View file

@ -11,6 +11,7 @@
#include <LibCore/Object.h> #include <LibCore/Object.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/DisjointRectSet.h> #include <LibGfx/DisjointRectSet.h>
#include <WindowServer/Screen.h>
namespace WindowServer { namespace WindowServer {
@ -36,7 +37,7 @@ public:
void invalidate_screen(); void invalidate_screen();
void invalidate_screen(const Gfx::IntRect&); 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); bool set_background_color(const String& background_color);
@ -55,46 +56,58 @@ public:
void did_construct_window_manager(Badge<WindowManager>); void did_construct_window_manager(Badge<WindowManager>);
const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>) const { return *m_front_bitmap; } const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
private: private:
Compositor(); Compositor();
void init_bitmaps(); void init_bitmaps();
void flip_buffers(); bool render_animation_frame(Screen&, Gfx::DisjointRectSet&);
void flush(const Gfx::IntRect&); void step_animations();
void run_animations(Gfx::DisjointRectSet&);
void notify_display_links(); void notify_display_links();
void start_compose_async_timer(); void start_compose_async_timer();
void recompute_occlusions(); void recompute_occlusions();
bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&); bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&);
void change_cursor(const Cursor*); void change_cursor(const Cursor*);
void draw_cursor(const Gfx::IntRect&); void draw_geometry_label(Screen&);
void restore_cursor_back(); void flush(Screen&);
bool draw_geometry_label(Gfx::IntRect&);
RefPtr<Core::Timer> m_compose_timer; RefPtr<Core::Timer> m_compose_timer;
RefPtr<Core::Timer> m_immediate_compose_timer; RefPtr<Core::Timer> m_immediate_compose_timer;
bool m_flash_flush { false }; 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_occlusions_dirty { true };
bool m_invalidated_any { true }; bool m_invalidated_any { true };
bool m_invalidated_window { false }; bool m_invalidated_window { false };
bool m_invalidated_cursor { false }; bool m_invalidated_cursor { false };
RefPtr<Gfx::Bitmap> m_front_bitmap; struct ScreenData {
RefPtr<Gfx::Bitmap> m_back_bitmap; RefPtr<Gfx::Bitmap> m_front_bitmap;
RefPtr<Gfx::Bitmap> m_temp_bitmap; RefPtr<Gfx::Bitmap> m_back_bitmap;
OwnPtr<Gfx::Painter> m_back_painter; RefPtr<Gfx::Bitmap> m_temp_bitmap;
OwnPtr<Gfx::Painter> m_front_painter; OwnPtr<Gfx::Painter> m_back_painter;
OwnPtr<Gfx::Painter> m_temp_painter; OwnPtr<Gfx::Painter> m_front_painter;
OwnPtr<Gfx::Painter> m_temp_painter;
RefPtr<Gfx::Bitmap> m_cursor_back_bitmap;
OwnPtr<Gfx::Painter> m_cursor_back_painter;
Gfx::IntRect m_last_cursor_rect;
bool m_buffers_are_flipped { false };
bool m_screen_can_set_buffer { false };
bool m_cursor_back_is_valid { false };
Gfx::DisjointRectSet m_flush_rects;
Gfx::DisjointRectSet m_flush_transparent_rects;
Gfx::DisjointRectSet m_flush_special_rects;
void init_bitmaps(Screen&);
void flip_buffers(Screen&);
void draw_cursor(Screen&, const Gfx::IntRect&);
bool restore_cursor_back(Screen&, Gfx::IntRect&);
};
friend class ScreenData;
Vector<ScreenData, default_screen_count> m_screen_data;
Gfx::DisjointRectSet m_dirty_screen_rects; Gfx::DisjointRectSet m_dirty_screen_rects;
Gfx::DisjointRectSet m_opaque_wallpaper_rects; Gfx::DisjointRectSet m_opaque_wallpaper_rects;
RefPtr<Gfx::Bitmap> m_cursor_back_bitmap;
OwnPtr<Gfx::Painter> m_cursor_back_painter;
Gfx::IntRect m_last_cursor_rect;
Gfx::IntRect m_last_dnd_rect; Gfx::IntRect m_last_dnd_rect;
Gfx::IntRect m_last_geometry_label_damage_rect; Gfx::IntRect m_last_geometry_label_damage_rect;
@ -103,6 +116,7 @@ private:
RefPtr<Gfx::Bitmap> m_wallpaper; RefPtr<Gfx::Bitmap> m_wallpaper;
const Cursor* m_current_cursor { nullptr }; const Cursor* m_current_cursor { nullptr };
Screen* m_current_cursor_screen { nullptr };
unsigned m_current_cursor_frame { 0 }; unsigned m_current_cursor_frame { 0 };
RefPtr<Core::Timer> m_cursor_timer; RefPtr<Core::Timer> m_cursor_timer;

View file

@ -81,9 +81,9 @@ EventLoop::~EventLoop()
void EventLoop::drain_mouse() void EventLoop::drain_mouse()
{ {
auto& screen = Screen::the(); auto& screen_input = ScreenInput::the();
MousePacket state; MousePacket state;
state.buttons = screen.mouse_button_state(); state.buttons = screen_input.mouse_button_state();
unsigned buttons = state.buttons; unsigned buttons = state.buttons;
MousePacket packets[32]; MousePacket packets[32];
@ -114,7 +114,7 @@ void EventLoop::drain_mouse()
if (buttons != state.buttons) { if (buttons != state.buttons) {
state.buttons = buttons; state.buttons = buttons;
dbgln_if(WSMESSAGELOOP_DEBUG, "EventLoop: Mouse Button Event"); 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) { if (state.is_relative) {
state.x = 0; state.x = 0;
state.y = 0; state.y = 0;
@ -123,21 +123,21 @@ void EventLoop::drain_mouse()
} }
} }
if (state.is_relative && (state.x || state.y || state.z)) 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) if (!state.is_relative)
screen.on_receive_mouse_data(state); screen_input.on_receive_mouse_data(state);
} }
void EventLoop::drain_keyboard() void EventLoop::drain_keyboard()
{ {
auto& screen = Screen::the(); auto& screen_input = ScreenInput::the();
for (;;) { for (;;) {
::KeyEvent event; ::KeyEvent event;
ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent)); ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent));
if (nread == 0) if (nread == 0)
break; break;
VERIFY(nread == sizeof(::KeyEvent)); VERIFY(nread == sizeof(::KeyEvent));
screen.on_receive_keyboard_data(event); screen_input.on_receive_keyboard_data(event);
} }
} }

View file

@ -136,7 +136,7 @@ Window& Menu::ensure_menu_window()
next_item_location.translate_by(0, height); 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 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 content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness();
int window_height = min(max_window_height, content_height); 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(); redraw_if_theme_changed();
const int margin = 30; const int margin = 30;
auto& screen = Screen::closest_to_location(position);
Gfx::IntPoint adjusted_pos = 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); adjusted_pos = adjusted_pos.translated(-window.width(), 0);
} else { } else {
adjusted_pos.set_x(adjusted_pos.x() + 1); 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())); adjusted_pos = adjusted_pos.translated(0, -min(window.height(), adjusted_pos.y()));
if (as_submenu) if (as_submenu)
adjusted_pos = adjusted_pos.translated(0, item_height()); adjusted_pos = adjusted_pos.translated(0, item_height());

View file

@ -19,21 +19,37 @@
namespace WindowServer { namespace WindowServer {
static Screen* s_the; NonnullOwnPtrVector<Screen, default_screen_count> Screen::s_screens;
Screen* Screen::s_main_screen { nullptr };
Gfx::IntRect Screen::s_bounding_screens_rect {};
Screen& Screen::the() ScreenInput& ScreenInput::the()
{ {
VERIFY(s_the); static ScreenInput s_the;
return *s_the; return s_the;
} }
Screen::Screen(unsigned desired_width, unsigned desired_height, int scale_factor) Screen& ScreenInput::cursor_location_screen()
{ {
VERIFY(!s_the); auto* screen = Screen::find_by_location(m_cursor_location);
s_the = this; VERIFY(screen);
m_framebuffer_fd = open("/dev/fb0", O_RDWR | O_CLOEXEC); 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) { if (m_framebuffer_fd < 0) {
perror("failed to open /dev/fb0"); perror(String::formatted("failed to open {}", device).characters());
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
@ -41,8 +57,10 @@ Screen::Screen(unsigned desired_width, unsigned desired_height, int scale_factor
m_can_set_buffer = true; m_can_set_buffer = true;
} }
set_resolution(desired_width, desired_height, scale_factor); // If the cursor is not in a valid screen (yet), force it into one
m_physical_cursor_location = physical_rect().center(); 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() Screen::~Screen()
@ -50,35 +68,84 @@ Screen::~Screen()
close(m_framebuffer_fd); 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_width = width * new_scale_factor;
int new_physical_height = height * new_scale_factor; int new_physical_height = height * new_scale_factor;
if (physical_width() == new_physical_width && physical_height() == new_physical_height) { if (!initial && physical_width() == new_physical_width && physical_height() == new_physical_height) {
VERIFY(scale_factor() != new_scale_factor); VERIFY(initial || scale_factor() != new_scale_factor);
on_change_resolution(m_pitch, physical_width(), physical_height(), new_scale_factor); on_change_resolution(initial, m_pitch, physical_width(), physical_height(), new_scale_factor, screen_with_cursor);
return true; return true;
} }
FBResolution physical_resolution { 0, (unsigned)new_physical_width, (unsigned)new_physical_height }; FBResolution physical_resolution { 0, (unsigned)new_physical_width, (unsigned)new_physical_height };
int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); 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) { 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; return true;
} }
if (rc == -1) { if (rc == -1) {
dbgln("Invalid resolution {}x{}", width, height); int err = errno;
on_change_resolution(physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor); 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; return false;
} }
VERIFY_NOT_REACHED(); 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) { if (m_framebuffer) {
size_t previous_size_in_bytes = m_size_in_bytes; size_t previous_size_in_bytes = m_size_in_bytes;
int rc = munmap(m_framebuffer, previous_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_pitch = pitch;
m_width = new_physical_width / new_scale_factor; m_virtual_rect.set_width(new_physical_width / new_scale_factor);
m_height = new_physical_height / new_scale_factor; m_virtual_rect.set_height(new_physical_height / new_scale_factor);
m_scale_factor = 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) void Screen::set_buffer(int index)
@ -107,31 +178,35 @@ void Screen::set_buffer(int index)
VERIFY(rc == 0); 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); VERIFY(factor >= mouse_accel_min && factor <= mouse_accel_max);
m_acceleration_factor = factor; 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); VERIFY(step_size >= scroll_step_size_min);
m_scroll_step_size = step_size; 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) { if (packet.is_relative) {
m_physical_cursor_location.translate_by(packet.x * m_acceleration_factor, packet.y * m_acceleration_factor); 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_physical_cursor_location); dbgln_if(WSSCREEN_DEBUG, "Screen: New Relative mouse point @ {}", m_cursor_location);
} else { } else {
m_physical_cursor_location = { packet.x * physical_width() / 0xffff, packet.y * physical_height() / 0xffff }; 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_physical_cursor_location); dbgln_if(WSSCREEN_DEBUG, "Screen: New Absolute mouse point @ {}", m_cursor_location);
} }
m_physical_cursor_location.constrain(physical_rect()); auto* moved_to_screen = Screen::find_by_location(m_cursor_location);
auto new_location = m_physical_cursor_location / m_scale_factor; if (!moved_to_screen) {
m_cursor_location = m_cursor_location.constrained(current_screen.rect());
moved_to_screen = &current_screen;
}
unsigned buttons = packet.buttons; unsigned buttons = packet.buttons;
unsigned prev_buttons = m_mouse_button_state; 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) { auto post_mousedown_or_mouseup_if_needed = [&](MouseButton button) {
if (!(changed_buttons & (unsigned)button)) if (!(changed_buttons & (unsigned)button))
return; return;
auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, new_location, buttons, button, m_modifiers); auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, m_cursor_location, buttons, button, m_modifiers);
Core::EventLoop::current().post_event(WindowManager::the(), move(message)); Core::EventLoop::current().post_event(WindowManager::the(), move(message));
}; };
post_mousedown_or_mouseup_if_needed(MouseButton::Left); 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::Middle);
post_mousedown_or_mouseup_if_needed(MouseButton::Back); post_mousedown_or_mouseup_if_needed(MouseButton::Back);
post_mousedown_or_mouseup_if_needed(MouseButton::Forward); post_mousedown_or_mouseup_if_needed(MouseButton::Forward);
if (new_location != prev_location) { if (m_cursor_location != prev_location) {
auto message = make<MouseEvent>(Event::MouseMove, new_location, buttons, MouseButton::None, m_modifiers); auto message = make<MouseEvent>(Event::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers);
if (WindowManager::the().dnd_client()) if (WindowManager::the().dnd_client())
message->set_mime_data(WindowManager::the().dnd_mime_data()); message->set_mime_data(WindowManager::the().dnd_mime_data());
Core::EventLoop::current().post_event(WindowManager::the(), move(message)); Core::EventLoop::current().post_event(WindowManager::the(), move(message));
} }
if (packet.z) { if (packet.z) {
auto message = make<MouseEvent>(Event::MouseWheel, new_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size); auto message = make<MouseEvent>(Event::MouseWheel, m_cursor_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size);
Core::EventLoop::current().post_event(WindowManager::the(), move(message)); Core::EventLoop::current().post_event(WindowManager::the(), move(message));
} }
if (new_location != prev_location) if (m_cursor_location != prev_location)
Compositor::the().invalidate_cursor(); 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(); m_modifiers = kernel_event.modifiers();
auto message = make<KeyEvent>(kernel_event.is_press() ? Event::KeyDown : Event::KeyUp, kernel_event.key, kernel_event.code_point, kernel_event.modifiers(), kernel_event.scancode); auto message = make<KeyEvent>(kernel_event.is_press() ? Event::KeyDown : Event::KeyUp, kernel_event.key, kernel_event.code_point, kernel_event.modifiers(), kernel_event.scancode);

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <Kernel/API/KeyCode.h> #include <Kernel/API/KeyCode.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
@ -19,34 +20,17 @@ constexpr double mouse_accel_max = 3.5;
constexpr double mouse_accel_min = 0.5; constexpr double mouse_accel_min = 0.5;
constexpr unsigned scroll_step_size_min = 1; 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: public:
Screen(unsigned width, unsigned height, int scale_factor); static ScreenInput& the();
~Screen();
bool set_resolution(int width, int height, int scale_factor); Screen& cursor_location_screen();
bool can_set_buffer() { return m_can_set_buffer; } const Screen& cursor_location_screen() const;
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; }
unsigned mouse_button_state() const { return m_mouse_button_state; } unsigned mouse_button_state() const { return m_mouse_button_state; }
double acceleration_factor() const { return m_acceleration_factor; } double acceleration_factor() const { return m_acceleration_factor; }
@ -58,8 +42,130 @@ public:
void on_receive_mouse_data(const MousePacket&); void on_receive_mouse_data(const MousePacket&);
void on_receive_keyboard_data(::KeyEvent); 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: 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<typename... Args>
static Screen* create(Args&&... args)
{
auto screen = adopt_own(*new Screen(forward<Args>(args)...));
if (!screen->is_opened())
return nullptr;
auto* screen_ptr = screen.ptr();
s_screens.append(move(screen));
update_indices();
update_bounding_rect();
if (!s_main_screen)
s_main_screen = screen_ptr;
screen_ptr->init();
return screen_ptr;
}
~Screen();
static Screen& main()
{
VERIFY(s_main_screen);
return *s_main_screen;
}
static Screen& closest_to_rect(const Gfx::IntRect&);
static Screen& closest_to_location(const Gfx::IntPoint&);
static Screen* find_by_index(size_t index)
{
if (index >= s_screens.size())
return nullptr;
return &s_screens[index];
}
static Vector<Gfx::IntRect, 4> rects()
{
Vector<Gfx::IntRect, 4> rects;
for (auto& screen : s_screens)
rects.append(screen.rect());
return rects;
}
static Screen* find_by_location(const Gfx::IntPoint& point)
{
for (auto& screen : s_screens) {
if (screen.rect().contains(point))
return &screen;
}
return nullptr;
}
static const Gfx::IntRect& bounding_rect() { return s_bounding_screens_rect; }
static size_t count() { return s_screens.size(); }
size_t index() const { return m_index; }
template<typename F>
static IterationDecision for_each(F f)
{
for (auto& screen : s_screens) {
IterationDecision decision = f(screen);
if (decision != IterationDecision::Continue)
return decision;
}
return IterationDecision::Continue;
}
void make_main_screen() { s_main_screen = this; }
bool is_main_screen() const { return s_main_screen == this; }
template<typename... Args>
bool set_resolution(Args&&... args)
{
return do_set_resolution(false, forward<Args>(args)...);
}
bool can_set_buffer() { return m_can_set_buffer; }
void set_buffer(int index);
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<Screen, default_screen_count> s_screens;
static Screen* s_main_screen;
static Gfx::IntRect s_bounding_screens_rect;
size_t m_index { 0 };
size_t m_size_in_bytes; size_t m_size_in_bytes;
@ -67,16 +173,9 @@ private:
bool m_can_set_buffer { false }; bool m_can_set_buffer { false };
int m_pitch { 0 }; int m_pitch { 0 };
int m_width { 0 }; Gfx::IntRect m_virtual_rect;
int m_height { 0 };
int m_framebuffer_fd { -1 }; int m_framebuffer_fd { -1 };
int m_scale_factor { 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) inline Gfx::RGBA32* Screen::scanline(int y)

View file

@ -96,7 +96,7 @@ void WMClientConnection::start_window_resize(i32 client_id, i32 window_id)
auto& window = *(*it).value; auto& window = *(*it).value;
// FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button. // 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? // 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) void WMClientConnection::set_window_minimized(i32 client_id, i32 window_id, bool minimized)

View file

@ -174,12 +174,25 @@ bool Window::apply_minimum_size(Gfx::IntRect& rect)
return did_size_clamp; 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; auto min_visible = 1;
if (type() == WindowType::Normal) switch (type()) {
case WindowType::Normal:
min_visible = 30; 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. // 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(); auto old_frame_rect = frame().rect();
@ -203,6 +216,7 @@ void Window::nudge_into_desktop(bool force_titlebar_visible)
width(), width(),
height(), height(),
}; };
set_rect(new_window_rect); 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); WindowManager::the().move_to_front_and_make_active(*this);
break; break;
case WindowMenuAction::Move: case WindowMenuAction::Move:
WindowManager::the().start_window_move(*this, Screen::the().cursor_location()); WindowManager::the().start_window_move(*this, ScreenInput::the().cursor_location());
break; break;
case WindowMenuAction::Close: case WindowMenuAction::Close:
request_close(); request_close();
@ -746,7 +760,7 @@ void Window::set_fullscreen(bool fullscreen)
Gfx::IntRect new_window_rect = m_rect; Gfx::IntRect new_window_rect = m_rect;
if (m_fullscreen) { if (m_fullscreen) {
m_saved_nonfullscreen_rect = m_rect; 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()) { } else if (!m_saved_nonfullscreen_rect.is_empty()) {
new_window_rect = m_saved_nonfullscreen_rect; new_window_rect = m_saved_nonfullscreen_rect;
} }
@ -755,56 +769,73 @@ void Window::set_fullscreen(bool fullscreen)
set_rect(new_window_rect); 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); VERIFY(tiled != WindowTileType::None);
int frame_width = (m_frame.rect().width() - m_rect.width()) / 2; int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
int titlebar_height = m_frame.titlebar_rect().height(); int titlebar_height = m_frame.titlebar_rect().height();
int menu_height = WindowManager::the().maximized_window_rect(*this).y(); auto maximized_rect_relative_to_window_screen = WindowManager::the().maximized_window_rect(*this, true);
int max_height = WindowManager::the().maximized_window_rect(*this).height(); 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) { switch (tiled) {
case WindowTileType::Left: case WindowTileType::Left:
return Gfx::IntRect(0, return Gfx::IntRect(0,
menu_height, menu_height,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
max_height); max_height)
.translated(screen_location);
case WindowTileType::Right: case WindowTileType::Right:
return Gfx::IntRect(Screen::the().width() / 2 + frame_width, return Gfx::IntRect(screen.width() / 2 + frame_width,
menu_height, menu_height,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
max_height); max_height)
.translated(screen_location);
case WindowTileType::Top: case WindowTileType::Top:
return Gfx::IntRect(0, return Gfx::IntRect(0,
menu_height, menu_height,
Screen::the().width(), screen.width(),
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
case WindowTileType::Bottom: case WindowTileType::Bottom:
return Gfx::IntRect(0, return Gfx::IntRect(0,
menu_height + (titlebar_height + max_height) / 2 + frame_width, menu_height + (titlebar_height + max_height) / 2 + frame_width,
Screen::the().width(), screen.width(),
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
case WindowTileType::TopLeft: case WindowTileType::TopLeft:
return Gfx::IntRect(0, return Gfx::IntRect(0,
menu_height, menu_height,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
case WindowTileType::TopRight: case WindowTileType::TopRight:
return Gfx::IntRect(Screen::the().width() / 2 + frame_width, return Gfx::IntRect(screen.width() / 2 + frame_width,
menu_height, menu_height,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
case WindowTileType::BottomLeft: case WindowTileType::BottomLeft:
return Gfx::IntRect(0, return Gfx::IntRect(0,
menu_height + (titlebar_height + max_height) / 2 + frame_width, menu_height + (titlebar_height + max_height) / 2 + frame_width,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
case WindowTileType::BottomRight: 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, menu_height + (titlebar_height + max_height) / 2 + frame_width,
Screen::the().width() / 2 - frame_width, screen.width() / 2 - frame_width,
(max_height - titlebar_height) / 2 - frame_width); (max_height - titlebar_height) / 2 - frame_width)
.translated(screen_location);
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
@ -831,7 +862,7 @@ bool Window::set_untiled(Optional<Gfx::IntPoint> fixed_point)
return true; return true;
} }
void Window::set_tiled(WindowTileType tiled) void Window::set_tiled(Screen* screen, WindowTileType tiled)
{ {
VERIFY(tiled != WindowTileType::None); VERIFY(tiled != WindowTileType::None);
@ -845,7 +876,7 @@ void Window::set_tiled(WindowTileType tiled)
m_untiled_rect = m_rect; m_untiled_rect = m_rect;
m_tiled = tiled; m_tiled = tiled;
set_rect(tiled_rect(tiled)); set_rect(tiled_rect(screen, tiled));
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect)); Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
} }
@ -861,11 +892,11 @@ void Window::recalculate_rect()
bool send_event = true; bool send_event = true;
if (m_tiled != WindowTileType::None) { if (m_tiled != WindowTileType::None) {
set_rect(tiled_rect(m_tiled)); set_rect(tiled_rect(nullptr, m_tiled));
} else if (is_maximized()) { } else if (is_maximized()) {
set_rect(WindowManager::the().maximized_window_rect(*this)); set_rect(WindowManager::the().maximized_window_rect(*this));
} else if (type() == WindowType::Desktop) { } else if (type() == WindowType::Desktop) {
set_rect(WindowManager::the().desktop_rect()); set_rect(WindowManager::the().arena_rect_for_type(Screen::main(), WindowType::Desktop));
} else { } else {
send_event = false; send_event = false;
} }
@ -953,7 +984,7 @@ bool Window::is_descendant_of(Window& window) const
return false; return false;
} }
Optional<HitTestResult> Window::hit_test(Gfx::IntPoint const& position, bool include_frame) const Optional<HitTestResult> Window::hit_test(Gfx::IntPoint const& position, bool include_frame)
{ {
if (!m_hit_testing_enabled) if (!m_hit_testing_enabled)
return {}; return {};

View file

@ -14,6 +14,7 @@
#include <LibGfx/DisjointRectSet.h> #include <LibGfx/DisjointRectSet.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <WindowServer/Cursor.h> #include <WindowServer/Cursor.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowFrame.h> #include <WindowServer/WindowFrame.h>
#include <WindowServer/WindowType.h> #include <WindowServer/WindowType.h>
@ -97,7 +98,7 @@ public:
void set_fullscreen(bool); void set_fullscreen(bool);
WindowTileType tiled() const { return m_tiled; } WindowTileType tiled() const { return m_tiled; }
void set_tiled(WindowTileType); void set_tiled(Screen*, WindowTileType);
bool set_untiled(Optional<Gfx::IntPoint> fixed_point = {}); bool set_untiled(Optional<Gfx::IntPoint> fixed_point = {});
bool is_occluded() const { return m_occluded; } bool is_occluded() const { return m_occluded; }
@ -139,7 +140,8 @@ public:
{ {
m_alpha_hit_threshold = threshold; m_alpha_hit_threshold = threshold;
} }
Optional<HitTestResult> hit_test(const Gfx::IntPoint&, bool include_frame = true) const;
Optional<HitTestResult> hit_test(const Gfx::IntPoint&, bool include_frame = true);
int x() const { return m_rect.x(); } int x() const { return m_rect.x(); }
int y() const { return m_rect.y(); } 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(int x, int y, int width, int height) { set_rect({ x, y, width, height }); }
void set_rect_without_repaint(const Gfx::IntRect&); void set_rect_without_repaint(const Gfx::IntRect&);
bool apply_minimum_size(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; } Gfx::IntSize minimum_size() const { return m_minimum_size; }
void set_minimum_size(const Gfx::IntSize&); void set_minimum_size(const Gfx::IntSize&);
@ -260,7 +262,7 @@ public:
void start_minimize_animation(); void start_minimize_animation();
void end_minimize_animation() { m_minimize_animation_step = -1; } 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(); void recalculate_rect();
IntrusiveListNode<Window> m_list_node; IntrusiveListNode<Window> m_list_node;
@ -319,6 +321,14 @@ public:
WindowStack const* outer_stack() const { return m_outer_stack; } WindowStack const* outer_stack() const { return m_outer_stack; }
void set_outer_stack(Badge<WindowStack>, WindowStack* stack) { m_outer_stack = stack; } void set_outer_stack(Badge<WindowStack>, WindowStack* stack) { m_outer_stack = stack; }
const Vector<Screen*, default_screen_count>& screens() const { return m_screens; }
Vector<Screen*, default_screen_count>& screens() { return m_screens; }
void did_construct()
{
frame().window_was_constructed({});
}
private: private:
Window(ClientConnection&, WindowType, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window = nullptr); 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); Window(Core::Object&, WindowType);
@ -344,6 +354,7 @@ private:
Gfx::IntRect m_rect; Gfx::IntRect m_rect;
Gfx::IntRect m_saved_nonfullscreen_rect; Gfx::IntRect m_saved_nonfullscreen_rect;
Gfx::IntRect m_taskbar_rect; Gfx::IntRect m_taskbar_rect;
Vector<Screen*, default_screen_count> m_screens;
Gfx::DisjointRectSet m_dirty_rects; Gfx::DisjointRectSet m_dirty_rects;
Gfx::DisjointRectSet m_opaque_rects; Gfx::DisjointRectSet m_opaque_rects;
Gfx::DisjointRectSet m_transparency_rects; Gfx::DisjointRectSet m_transparency_rects;

View file

@ -1,6 +1,6 @@
endpoint WindowClient endpoint WindowClient
{ {
fast_greet(Gfx::IntRect screen_rect, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| fast_greet(Vector<Gfx::IntRect> screen_rects, u32 main_screen_index, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =|
paint(i32 window_id, Gfx::IntSize window_size, Vector<Gfx::IntRect> rects) =| paint(i32 window_id, Gfx::IntSize window_size, Vector<Gfx::IntRect> rects) =|
mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector<String> mime_types) =| mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector<String> mime_types) =|
@ -25,7 +25,7 @@ endpoint WindowClient
menu_item_left(i32 menu_id, u32 identifier) =| menu_item_left(i32 menu_id, u32 identifier) =|
menu_visibility_did_change(i32 menu_id, bool visible) =| menu_visibility_did_change(i32 menu_id, bool visible) =|
screen_rect_changed(Gfx::IntRect rect) =| screen_rects_changed(Vector<Gfx::IntRect> rects, u32 main_screen_index) =|
set_wallpaper_finished(bool success) =| set_wallpaper_finished(bool success) =|

View file

@ -64,6 +64,14 @@ static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& re
WindowFrame::WindowFrame(Window& window) WindowFrame::WindowFrame(Window& window)
: m_window(window) : m_window(window)
{
// Because Window constructs a WindowFrame during its construction, we need
// to be careful and defer doing initialization that assumes a fully
// constructed Window. It is fully constructed when Window notifies us with
// a call to WindowFrame::window_was_constructed.
}
void WindowFrame::window_was_constructed(Badge<Window>)
{ {
{ {
auto button = make<Button>(*this, [this](auto&) { auto button = make<Button>(*this, [this](auto&) {
@ -73,7 +81,7 @@ WindowFrame::WindowFrame(Window& window)
m_buttons.append(move(button)); m_buttons.append(move(button));
} }
if (window.is_resizable()) { if (m_window.is_resizable()) {
auto button = make<Button>(*this, [this](auto&) { auto button = make<Button>(*this, [this](auto&) {
m_window.handle_window_menu_action(WindowMenuAction::MaximizeOrRestore); m_window.handle_window_menu_action(WindowMenuAction::MaximizeOrRestore);
}); });
@ -84,7 +92,7 @@ WindowFrame::WindowFrame(Window& window)
m_buttons.append(move(button)); m_buttons.append(move(button));
} }
if (window.is_minimizable()) { if (m_window.is_minimizable()) {
auto button = make<Button>(*this, [this](auto&) { auto button = make<Button>(*this, [this](auto&) {
m_window.handle_window_menu_action(WindowMenuAction::MinimizeOrUnminimize); m_window.handle_window_menu_action(WindowMenuAction::MinimizeOrUnminimize);
}); });
@ -93,6 +101,8 @@ WindowFrame::WindowFrame(Window& window)
} }
set_button_icons(); set_button_icons();
m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette());
} }
WindowFrame::~WindowFrame() WindowFrame::~WindowFrame()
@ -101,7 +111,7 @@ WindowFrame::~WindowFrame()
void WindowFrame::set_button_icons() void WindowFrame::set_button_icons()
{ {
m_dirty = true; set_dirty();
if (m_window.is_frameless()) if (m_window.is_frameless())
return; return;
@ -115,7 +125,7 @@ void WindowFrame::set_button_icons()
void WindowFrame::reload_config() void WindowFrame::reload_config()
{ {
String icons_path = WindowManager::the().palette().title_button_icons_path(); String icons_path = WindowManager::the().palette().title_button_icons_path();
int icons_scale = WindowManager::the().compositor_icon_scale(); int icons_scale = WindowManager::the().compositor_icon_scale(); // TODO: We'll need to load icons for all scales in use!
StringBuilder full_path; StringBuilder full_path;
if (!s_minimize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_minimize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) {
@ -301,27 +311,30 @@ void WindowFrame::paint_normal_frame(Gfx::Painter& painter)
paint_menubar(painter); paint_menubar(painter);
} }
void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect) void WindowFrame::paint(Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect)
{ {
render_to_cache(); if (auto* cached = render_to_cache(screen))
cached->paint(*this, painter, rect);
auto frame_rect = render_rect(); }
auto window_rect = m_window.rect();
void WindowFrame::RenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter, const Gfx::IntRect& rect)
{
auto frame_rect = frame.render_rect();
auto window_rect = frame.window().rect();
if (m_top_bottom) { if (m_top_bottom) {
auto top_bottom_height = frame_rect.height() - window_rect.height(); auto top_bottom_height = frame_rect.height() - window_rect.height();
if (m_bottom_y > 0) { if (m_bottom_y > 0) {
// We have a top piece // We have a top piece
auto src_rect = rect.intersected({ frame_rect.location(), { frame_rect.width(), m_bottom_y } }); auto src_rect = rect.intersected({ frame_rect.location(), { frame_rect.width(), m_bottom_y } });
if (!src_rect.is_empty()) if (!src_rect.is_empty())
painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), m_opacity); painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), frame.opacity());
} }
if (m_bottom_y < top_bottom_height) { if (m_bottom_y < top_bottom_height) {
// We have a bottom piece // We have a bottom piece
Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.bottom() + 1, frame_rect.width(), top_bottom_height - m_bottom_y }; Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.bottom() + 1, frame_rect.width(), top_bottom_height - m_bottom_y };
auto src_rect = rect.intersected(rect_in_frame); auto src_rect = rect.intersected(rect_in_frame);
if (!src_rect.is_empty()) if (!src_rect.is_empty())
painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), m_opacity); painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-rect_in_frame.x(), -rect_in_frame.y() + m_bottom_y), frame.opacity());
} }
} }
@ -332,19 +345,19 @@ void WindowFrame::paint(Gfx::Painter& painter, const Gfx::IntRect& rect)
Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.y(), m_right_x, window_rect.height() }; Gfx::IntRect rect_in_frame { frame_rect.x(), window_rect.y(), m_right_x, window_rect.height() };
auto src_rect = rect.intersected(rect_in_frame); auto src_rect = rect.intersected(rect_in_frame);
if (!src_rect.is_empty()) if (!src_rect.is_empty())
painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), m_opacity); painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.location()), frame.opacity());
} }
if (m_right_x < left_right_width) { if (m_right_x < left_right_width) {
// We have a right piece // We have a right piece
Gfx::IntRect rect_in_frame { window_rect.right() + 1, window_rect.y(), left_right_width - m_right_x, window_rect.height() }; Gfx::IntRect rect_in_frame { window_rect.right() + 1, window_rect.y(), left_right_width - m_right_x, window_rect.height() };
auto src_rect = rect.intersected(rect_in_frame); auto src_rect = rect.intersected(rect_in_frame);
if (!src_rect.is_empty()) if (!src_rect.is_empty())
painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), m_opacity); painter.blit(src_rect.location(), *m_left_right, src_rect.translated(-rect_in_frame.x() + m_right_x, -rect_in_frame.y()), frame.opacity());
} }
} }
} }
void WindowFrame::render(Gfx::Painter& painter) void WindowFrame::render(Screen&, Gfx::Painter& painter)
{ {
if (m_window.is_frameless()) if (m_window.is_frameless())
return; return;
@ -365,10 +378,7 @@ void WindowFrame::render(Gfx::Painter& painter)
void WindowFrame::theme_changed() void WindowFrame::theme_changed()
{ {
m_dirty = m_shadow_dirty = true; m_rendered_cache = {};
m_top_bottom = nullptr;
m_left_right = nullptr;
m_bottom_y = m_right_x = 0;
layout_buttons(); layout_buttons();
set_button_icons(); set_button_icons();
@ -376,19 +386,34 @@ void WindowFrame::theme_changed()
m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette());
} }
void WindowFrame::render_to_cache() auto WindowFrame::render_to_cache(Screen& screen) -> RenderedCache*
{
auto scale = screen.scale_factor();
RenderedCache* rendered_cache;
auto cached_it = m_rendered_cache.find(scale);
if (cached_it == m_rendered_cache.end()) {
auto new_rendered_cache = make<RenderedCache>();
rendered_cache = new_rendered_cache.ptr();
m_rendered_cache.set(scale, move(new_rendered_cache));
} else {
rendered_cache = cached_it->value.ptr();
}
rendered_cache->render(*this, screen);
return rendered_cache;
}
void WindowFrame::RenderedCache::render(WindowFrame& frame, Screen& screen)
{ {
if (!m_dirty) if (!m_dirty)
return; return;
m_dirty = false; m_dirty = false;
m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); auto scale = screen.scale_factor();
static RefPtr<Gfx::Bitmap> s_tmp_bitmap; auto frame_rect = frame.rect();
auto frame_rect = rect();
auto frame_rect_including_shadow = frame_rect; auto frame_rect_including_shadow = frame_rect;
auto* shadow_bitmap = this->shadow_bitmap(); auto* shadow_bitmap = frame.shadow_bitmap();
Gfx::IntPoint shadow_offset; Gfx::IntPoint shadow_offset;
if (shadow_bitmap) { if (shadow_bitmap) {
@ -398,19 +423,35 @@ void WindowFrame::render_to_cache()
shadow_offset = { offset, offset }; shadow_offset = { offset, offset };
} }
auto window_rect = m_window.rect(); auto window_rect = frame.window().rect();
auto scale = Screen::the().scale_factor();
if (!s_tmp_bitmap || !s_tmp_bitmap->size().contains(frame_rect_including_shadow.size()) || s_tmp_bitmap->scale() != scale) { // TODO: if we stop using a scaling factor we should clear cached bitmaps from this map
// Explicitly clear the old bitmap first so this works on machines with very little memory static HashMap<int, RefPtr<Gfx::Bitmap>> s_tmp_bitmap_cache;
s_tmp_bitmap = nullptr; Gfx::Bitmap* tmp_bitmap;
s_tmp_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, frame_rect_including_shadow.size(), scale); {
if (!s_tmp_bitmap) { auto tmp_it = s_tmp_bitmap_cache.find(scale);
dbgln("Could not create bitmap of size {}", frame_rect_including_shadow.size()); if (tmp_it == s_tmp_bitmap_cache.end() || !tmp_it->value->size().contains(frame_rect_including_shadow.size())) {
return; // Explicitly clear the old bitmap first so this works on machines with very little memory
if (tmp_it != s_tmp_bitmap_cache.end())
tmp_it->value = nullptr;
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, frame_rect_including_shadow.size(), scale);
if (!bitmap) {
s_tmp_bitmap_cache.remove(scale);
dbgln("Could not create bitmap of size {}", frame_rect_including_shadow.size());
return;
}
tmp_bitmap = bitmap.ptr();
if (tmp_it != s_tmp_bitmap_cache.end())
tmp_it->value = bitmap.release_nonnull();
else
s_tmp_bitmap_cache.set(scale, bitmap.release_nonnull());
} else {
tmp_bitmap = tmp_it->value.ptr();
} }
} }
VERIFY(s_tmp_bitmap); VERIFY(tmp_bitmap);
auto top_bottom_height = frame_rect_including_shadow.height() - window_rect.height(); auto top_bottom_height = frame_rect_including_shadow.height() - window_rect.height();
auto left_right_width = frame_rect_including_shadow.width() - window_rect.width(); auto left_right_width = frame_rect_including_shadow.width() - window_rect.width();
@ -433,19 +474,19 @@ void WindowFrame::render_to_cache()
auto& frame_rect_to_update = m_shadow_dirty ? frame_rect_including_shadow : frame_rect; auto& frame_rect_to_update = m_shadow_dirty ? frame_rect_including_shadow : frame_rect;
Gfx::IntPoint update_location(m_shadow_dirty ? Gfx::IntPoint { 0, 0 } : shadow_offset); Gfx::IntPoint update_location(m_shadow_dirty ? Gfx::IntPoint { 0, 0 } : shadow_offset);
Gfx::Painter painter(*s_tmp_bitmap); Gfx::Painter painter(*tmp_bitmap);
// Clear the frame area, not including the window content area, which we don't care about // Clear the frame area, not including the window content area, which we don't care about
for (auto& rect : frame_rect_to_update.shatter(window_rect)) for (auto& rect : frame_rect_to_update.shatter(window_rect))
painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 }); painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 });
if (m_shadow_dirty && shadow_bitmap) if (m_shadow_dirty && shadow_bitmap)
paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, *shadow_bitmap); frame.paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, *shadow_bitmap);
{ {
Gfx::PainterStateSaver save(painter); Gfx::PainterStateSaver save(painter);
painter.translate(shadow_offset); painter.translate(shadow_offset);
render(painter); frame.render(screen, painter);
} }
if (m_top_bottom && top_bottom_height > 0) { if (m_top_bottom && top_bottom_height > 0) {
@ -455,9 +496,9 @@ void WindowFrame::render_to_cache()
Gfx::Painter top_bottom_painter(*m_top_bottom); Gfx::Painter top_bottom_painter(*m_top_bottom);
top_bottom_painter.add_clip_rect({ update_location, { frame_rect_to_update.width(), top_bottom_height - update_location.y() - (frame_rect_including_shadow.bottom() - frame_rect_to_update.bottom()) } }); top_bottom_painter.add_clip_rect({ update_location, { frame_rect_to_update.width(), top_bottom_height - update_location.y() - (frame_rect_including_shadow.bottom() - frame_rect_to_update.bottom()) } });
if (m_bottom_y > 0) if (m_bottom_y > 0)
top_bottom_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, 0, frame_rect_including_shadow.width(), m_bottom_y }, 1.0, false); top_bottom_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, 0, frame_rect_including_shadow.width(), m_bottom_y }, 1.0, false);
if (m_bottom_y < top_bottom_height) if (m_bottom_y < top_bottom_height)
top_bottom_painter.blit({ 0, m_bottom_y }, *s_tmp_bitmap, { 0, frame_rect_including_shadow.height() - (frame_rect_including_shadow.bottom() - window_rect.bottom()), frame_rect_including_shadow.width(), top_bottom_height - m_bottom_y }, 1.0, false); top_bottom_painter.blit({ 0, m_bottom_y }, *tmp_bitmap, { 0, frame_rect_including_shadow.height() - (frame_rect_including_shadow.bottom() - window_rect.bottom()), frame_rect_including_shadow.width(), top_bottom_height - m_bottom_y }, 1.0, false);
} else { } else {
m_bottom_y = 0; m_bottom_y = 0;
} }
@ -469,9 +510,9 @@ void WindowFrame::render_to_cache()
Gfx::Painter left_right_painter(*m_left_right); Gfx::Painter left_right_painter(*m_left_right);
left_right_painter.add_clip_rect({ update_location, { left_right_width - update_location.x() - (frame_rect_including_shadow.right() - frame_rect_to_update.right()), window_rect.height() } }); left_right_painter.add_clip_rect({ update_location, { left_right_width - update_location.x() - (frame_rect_including_shadow.right() - frame_rect_to_update.right()), window_rect.height() } });
if (m_right_x > 0) if (m_right_x > 0)
left_right_painter.blit({ 0, 0 }, *s_tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false); left_right_painter.blit({ 0, 0 }, *tmp_bitmap, { 0, m_bottom_y, m_right_x, window_rect.height() }, 1.0, false);
if (m_right_x < left_right_width) if (m_right_x < left_right_width)
left_right_painter.blit({ m_right_x, 0 }, *s_tmp_bitmap, { (window_rect.right() - frame_rect_including_shadow.x()) + 1, m_bottom_y, frame_rect_including_shadow.width() - (frame_rect_including_shadow.right() - window_rect.right()), window_rect.height() }, 1.0, false); left_right_painter.blit({ m_right_x, 0 }, *tmp_bitmap, { (window_rect.right() - frame_rect_including_shadow.x()) + 1, m_bottom_y, frame_rect_including_shadow.width() - (frame_rect_including_shadow.right() - window_rect.right()), window_rect.height() }, 1.0, false);
} else { } else {
m_right_x = 0; m_right_x = 0;
} }
@ -545,7 +586,7 @@ Gfx::DisjointRectSet WindowFrame::transparent_render_rects() const
void WindowFrame::invalidate_titlebar() void WindowFrame::invalidate_titlebar()
{ {
m_dirty = true; set_dirty();
invalidate(titlebar_rect()); invalidate(titlebar_rect());
} }
@ -561,7 +602,7 @@ void WindowFrame::invalidate(Gfx::IntRect relative_rect)
auto frame_rect = rect(); auto frame_rect = rect();
auto window_rect = m_window.rect(); auto window_rect = m_window.rect();
relative_rect.translate_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y()); relative_rect.translate_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y());
m_dirty = true; set_dirty();
m_window.invalidate(relative_rect, true); m_window.invalidate(relative_rect, true);
} }
@ -572,7 +613,7 @@ void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const
auto old_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, old_rect)); auto old_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, old_rect));
auto new_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, new_rect)); auto new_frame_rect = inflated_for_shadow(frame_rect_for_window(m_window, new_rect));
if (old_frame_rect.size() != new_frame_rect.size()) if (old_frame_rect.size() != new_frame_rect.size())
m_dirty = m_shadow_dirty = true; set_dirty(true);
auto& compositor = Compositor::the(); auto& compositor = Compositor::the();
for (auto& dirty : old_frame_rect.shatter(new_frame_rect)) for (auto& dirty : old_frame_rect.shatter(new_frame_rect))
compositor.invalidate_screen(dirty); compositor.invalidate_screen(dirty);
@ -591,7 +632,7 @@ void WindowFrame::layout_buttons()
m_buttons[i].set_relative_rect(button_rects[i]); m_buttons[i].set_relative_rect(button_rects[i]);
} }
Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) const Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position)
{ {
if (m_window.is_frameless()) if (m_window.is_frameless())
return {}; return {};
@ -602,19 +643,32 @@ Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) con
if (window_rect.contains(position)) if (window_rect.contains(position))
return {}; return {};
auto* screen = Screen::find_by_location(position);
if (!screen)
return {};
auto* cached = render_to_cache(*screen);
if (!cached)
return {};
auto window_relative_position = position.translated(-render_rect().location()); auto window_relative_position = position.translated(-render_rect().location());
return cached->hit_test(*this, position, window_relative_position);
}
Optional<HitTestResult> WindowFrame::RenderedCache::hit_test(WindowFrame& frame, Gfx::IntPoint const& position, Gfx::IntPoint const& window_relative_position)
{
HitTestResult result { HitTestResult result {
.window = m_window, .window = frame.window(),
.screen_position = position, .screen_position = position,
.window_relative_position = window_relative_position, .window_relative_position = window_relative_position,
.is_frame_hit = true, .is_frame_hit = true,
}; };
u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(window_state_for_theme()) * 255; u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(frame.window_state_for_theme()) * 255;
if (alpha_threshold == 0) if (alpha_threshold == 0)
return result; return result;
u8 alpha = 0xff; u8 alpha = 0xff;
auto window_rect = frame.window().rect();
if (position.y() < window_rect.y()) { if (position.y() < window_rect.y()) {
if (m_top_bottom) { if (m_top_bottom) {
auto scaled_relative_point = window_relative_position * m_top_bottom->scale(); auto scaled_relative_point = window_relative_position * m_top_bottom->scale();

View file

@ -20,21 +20,46 @@ class Button;
class Menu; class Menu;
class MouseEvent; class MouseEvent;
class Window; class Window;
class Screen;
class WindowFrame { class WindowFrame {
public: public:
class RenderedCache {
friend class WindowFrame;
public:
void paint(WindowFrame&, Gfx::Painter&, const Gfx::IntRect&);
void render(WindowFrame&, Screen&);
Optional<HitTestResult> hit_test(WindowFrame&, Gfx::IntPoint const&, Gfx::IntPoint const&);
private:
RefPtr<Gfx::Bitmap> m_top_bottom;
RefPtr<Gfx::Bitmap> m_left_right;
int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half
int m_right_x { 0 }; // x-offset in m_left_right for the right half
bool m_shadow_dirty { true };
bool m_dirty { true };
};
friend class RenderedCache;
static void reload_config(); static void reload_config();
explicit WindowFrame(Window&); explicit WindowFrame(Window&);
~WindowFrame(); ~WindowFrame();
void window_was_constructed(Badge<Window>);
Window& window() { return m_window; }
const Window& window() const { return m_window; }
Gfx::IntRect rect() const; Gfx::IntRect rect() const;
Gfx::IntRect render_rect() const; Gfx::IntRect render_rect() const;
Gfx::DisjointRectSet opaque_render_rects() const; Gfx::DisjointRectSet opaque_render_rects() const;
Gfx::DisjointRectSet transparent_render_rects() const; Gfx::DisjointRectSet transparent_render_rects() const;
void paint(Gfx::Painter&, const Gfx::IntRect&);
void render(Gfx::Painter&); void paint(Screen&, Gfx::Painter&, const Gfx::IntRect&);
void render_to_cache(); void render(Screen&, Gfx::Painter&);
RenderedCache* render_to_cache(Screen&);
void handle_mouse_event(MouseEvent const&); void handle_mouse_event(MouseEvent const&);
void handle_titlebar_mouse_event(MouseEvent const&); void handle_titlebar_mouse_event(MouseEvent const&);
@ -78,13 +103,16 @@ public:
void set_dirty(bool re_render_shadow = false) void set_dirty(bool re_render_shadow = false)
{ {
m_dirty = true; for (auto& it : m_rendered_cache) {
m_shadow_dirty |= re_render_shadow; auto& cached = *it.value;
cached.m_dirty = true;
cached.m_shadow_dirty |= re_render_shadow;
}
} }
void theme_changed(); void theme_changed();
Optional<HitTestResult> hit_test(Gfx::IntPoint const&) const; Optional<HitTestResult> hit_test(Gfx::IntPoint const&);
void open_menubar_menu(Menu&); void open_menubar_menu(Menu&);
@ -109,17 +137,12 @@ private:
Button* m_maximize_button { nullptr }; Button* m_maximize_button { nullptr };
Button* m_minimize_button { nullptr }; Button* m_minimize_button { nullptr };
RefPtr<Gfx::Bitmap> m_top_bottom; HashMap<int, NonnullOwnPtr<RenderedCache>> m_rendered_cache;
RefPtr<Gfx::Bitmap> m_left_right;
int m_bottom_y { 0 }; // y-offset in m_top_bottom for the bottom half
int m_right_x { 0 }; // x-offset in m_left_right for the right half
RefPtr<Core::Timer> m_flash_timer; RefPtr<Core::Timer> m_flash_timer;
size_t m_flash_counter { 0 }; size_t m_flash_counter { 0 };
float m_opacity { 1 }; float m_opacity { 1 };
bool m_has_alpha_channel { false }; bool m_has_alpha_channel { false };
bool m_shadow_dirty { false };
bool m_dirty { false };
}; };
} }

View file

@ -93,11 +93,27 @@ Gfx::Font const& WindowManager::window_title_font() const
return Gfx::FontDatabase::default_font().bold_variant(); return Gfx::FontDatabase::default_font().bold_variant();
} }
bool WindowManager::set_resolution(int width, int height, int scale) bool WindowManager::set_resolution(Screen& screen, int width, int height, int scale)
{ {
bool success = Compositor::the().set_resolution(width, height, scale); auto screen_rect = screen.rect();
if (screen_rect.width() == width && screen_rect.height() == height && screen.scale_factor() == scale)
return true;
// Make sure it's impossible to set an invalid resolution
if (!(width >= 640 && height >= 480 && scale >= 1)) {
dbgln("Compositor: Tried to set invalid resolution: {}x{}", width, height);
return false;
}
auto old_scale_factor = screen.scale_factor();
bool success = screen.set_resolution(width, height, scale);
if (success && old_scale_factor != scale)
reload_icon_bitmaps_after_scale_change();
Compositor::the().screen_resolution_changed();
ClientConnection::for_each_client([&](ClientConnection& client) { ClientConnection::for_each_client([&](ClientConnection& client) {
client.notify_about_new_screen_rect(Screen::the().rect()); client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index());
}); });
if (success) { if (success) {
m_window_stack.for_each_window([](Window& window) { m_window_stack.for_each_window([](Window& window) {
@ -113,9 +129,9 @@ bool WindowManager::set_resolution(int width, int height, int scale)
m_config->write_num_entry("Screen", "ScaleFactor", scale); m_config->write_num_entry("Screen", "ScaleFactor", scale);
m_config->sync(); m_config->sync();
} else { } else {
dbgln("Saving fallback resolution: {} @1x to config file at {}", resolution(), m_config->filename()); dbgln("Saving fallback resolution: {} @1x to config file at {}", screen.size(), m_config->filename());
m_config->write_num_entry("Screen", "Width", resolution().width()); m_config->write_num_entry("Screen", "Width", screen.size().width());
m_config->write_num_entry("Screen", "Height", resolution().height()); m_config->write_num_entry("Screen", "Height", screen.size().height());
m_config->write_num_entry("Screen", "ScaleFactor", 1); m_config->write_num_entry("Screen", "ScaleFactor", 1);
m_config->sync(); m_config->sync();
} }
@ -123,14 +139,9 @@ bool WindowManager::set_resolution(int width, int height, int scale)
return success; return success;
} }
Gfx::IntSize WindowManager::resolution() const
{
return Screen::the().size();
}
void WindowManager::set_acceleration_factor(double factor) void WindowManager::set_acceleration_factor(double factor)
{ {
Screen::the().set_acceleration_factor(factor); ScreenInput::the().set_acceleration_factor(factor);
dbgln("Saving acceleration factor {} to config file at {}", factor, m_config->filename()); dbgln("Saving acceleration factor {} to config file at {}", factor, m_config->filename());
m_config->write_entry("Mouse", "AccelerationFactor", String::formatted("{}", factor)); m_config->write_entry("Mouse", "AccelerationFactor", String::formatted("{}", factor));
m_config->sync(); m_config->sync();
@ -138,7 +149,7 @@ void WindowManager::set_acceleration_factor(double factor)
void WindowManager::set_scroll_step_size(unsigned step_size) void WindowManager::set_scroll_step_size(unsigned step_size)
{ {
Screen::the().set_scroll_step_size(step_size); ScreenInput::the().set_scroll_step_size(step_size);
dbgln("Saving scroll step size {} to config file at {}", step_size, m_config->filename()); dbgln("Saving scroll step size {} to config file at {}", step_size, m_config->filename());
m_config->write_entry("Mouse", "ScrollStepSize", String::number(step_size)); m_config->write_entry("Mouse", "ScrollStepSize", String::number(step_size));
m_config->sync(); m_config->sync();
@ -158,11 +169,6 @@ int WindowManager::double_click_speed() const
return m_double_click_speed; return m_double_click_speed;
} }
int WindowManager::scale_factor() const
{
return Screen::the().scale_factor();
}
void WindowManager::add_window(Window& window) void WindowManager::add_window(Window& window)
{ {
bool is_first_window = m_window_stack.is_empty(); bool is_first_window = m_window_stack.is_empty();
@ -170,8 +176,9 @@ void WindowManager::add_window(Window& window)
m_window_stack.add(window); m_window_stack.add(window);
if (window.is_fullscreen()) { if (window.is_fullscreen()) {
Core::EventLoop::current().post_event(window, make<ResizeEvent>(Screen::the().rect())); auto& screen = Screen::main(); // TODO: support fullscreen windows on other screens!
window.set_rect(Screen::the().rect()); Core::EventLoop::current().post_event(window, make<ResizeEvent>(screen.rect()));
window.set_rect(screen.rect());
} }
if (window.type() != WindowType::Desktop || is_first_window) if (window.type() != WindowType::Desktop || is_first_window)
@ -558,8 +565,9 @@ bool WindowManager::process_ongoing_window_move(MouseEvent& event)
const int tiling_deadzone = 10; const int tiling_deadzone = 10;
const int secondary_deadzone = 2; const int secondary_deadzone = 2;
auto desktop = desktop_rect(); auto& cursor_screen = Screen::closest_to_location(event.position());
auto desktop = desktop_rect(cursor_screen);
auto desktop_relative_to_screen = desktop.translated(-cursor_screen.rect().location());
if (m_move_window->is_maximized()) { if (m_move_window->is_maximized()) {
auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin);
if (pixels_moved_from_start > 5) { if (pixels_moved_from_start > 5) {
@ -574,31 +582,32 @@ bool WindowManager::process_ongoing_window_move(MouseEvent& event)
bool is_resizable = m_move_window->is_resizable(); bool is_resizable = m_move_window->is_resizable();
auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin);
if (is_resizable && event.x() <= tiling_deadzone) { auto event_location_relative_to_screen = event.position().translated(-cursor_screen.rect().location());
if (event.y() <= tiling_deadzone + desktop.top()) if (is_resizable && event_location_relative_to_screen.x() <= tiling_deadzone) {
m_move_window->set_tiled(WindowTileType::TopLeft); if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop_relative_to_screen.top())
else if (event.y() >= desktop.height() - tiling_deadzone) m_move_window->set_tiled(&cursor_screen, WindowTileType::TopLeft);
m_move_window->set_tiled(WindowTileType::BottomLeft); else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone)
m_move_window->set_tiled(&cursor_screen, WindowTileType::BottomLeft);
else else
m_move_window->set_tiled(WindowTileType::Left); m_move_window->set_tiled(&cursor_screen, WindowTileType::Left);
} else if (is_resizable && event.x() >= Screen::the().width() - tiling_deadzone) { } else if (is_resizable && event_location_relative_to_screen.x() >= cursor_screen.width() - tiling_deadzone) {
if (event.y() <= tiling_deadzone + desktop.top()) if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop.top())
m_move_window->set_tiled(WindowTileType::TopRight); m_move_window->set_tiled(&cursor_screen, WindowTileType::TopRight);
else if (event.y() >= desktop.height() - tiling_deadzone) else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone)
m_move_window->set_tiled(WindowTileType::BottomRight); m_move_window->set_tiled(&cursor_screen, WindowTileType::BottomRight);
else else
m_move_window->set_tiled(WindowTileType::Right); m_move_window->set_tiled(&cursor_screen, WindowTileType::Right);
} else if (is_resizable && event.y() <= secondary_deadzone + desktop.top()) { } else if (is_resizable && event_location_relative_to_screen.y() <= secondary_deadzone + desktop_relative_to_screen.top()) {
m_move_window->set_tiled(WindowTileType::Top); m_move_window->set_tiled(&cursor_screen, WindowTileType::Top);
} else if (is_resizable && event.y() >= desktop.bottom() - secondary_deadzone) { } else if (is_resizable && event_location_relative_to_screen.y() >= desktop_relative_to_screen.bottom() - secondary_deadzone) {
m_move_window->set_tiled(WindowTileType::Bottom); m_move_window->set_tiled(&cursor_screen, WindowTileType::Bottom);
} else if (m_move_window->tiled() == WindowTileType::None) { } else if (m_move_window->tiled() == WindowTileType::None) {
Gfx::IntPoint pos = m_move_window_origin.translated(event.position() - m_move_origin); Gfx::IntPoint pos = m_move_window_origin.translated(event.position() - m_move_origin);
m_move_window->set_position_without_repaint(pos); m_move_window->set_position_without_repaint(pos);
// "Bounce back" the window if it would end up too far outside the screen. // "Bounce back" the window if it would end up too far outside the screen.
// If the user has let go of Mod_Super, maybe they didn't intentionally press it to begin with. Therefore, refuse to go into a state where knowledge about super-drags is necessary. // If the user has let go of Mod_Super, maybe they didn't intentionally press it to begin with. Therefore, refuse to go into a state where knowledge about super-drags is necessary.
bool force_titlebar_visible = !(m_keyboard_modifiers & Mod_Super); bool force_titlebar_visible = !(m_keyboard_modifiers & Mod_Super);
m_move_window->nudge_into_desktop(force_titlebar_visible); m_move_window->nudge_into_desktop(&cursor_screen, force_titlebar_visible);
} else if (pixels_moved_from_start > 5) { } else if (pixels_moved_from_start > 5) {
m_move_window->set_untiled(event.position()); m_move_window->set_untiled(event.position());
m_move_origin = event.position(); m_move_origin = event.position();
@ -1067,7 +1076,7 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window)
if (m_dnd_client || m_resize_window || m_move_window || m_cursor_tracking_button || MenuManager::the().has_open_menu()) if (m_dnd_client || m_resize_window || m_move_window || m_cursor_tracking_button || MenuManager::the().has_open_menu())
return; return;
auto cursor_location = Screen::the().cursor_location(); auto cursor_location = ScreenInput::the().cursor_location();
auto* currently_hovered = hovered_window(); auto* currently_hovered = hovered_window();
if (updated_window) { if (updated_window) {
if (!(updated_window == currently_hovered || updated_window->frame().rect().contains(cursor_location) || (currently_hovered && currently_hovered->frame().rect().contains(cursor_location)))) if (!(updated_window == currently_hovered || updated_window->frame().rect().contains(cursor_location) || (currently_hovered && currently_hovered->frame().rect().contains(cursor_location))))
@ -1110,32 +1119,31 @@ void WindowManager::clear_resize_candidate()
m_resize_candidate = nullptr; m_resize_candidate = nullptr;
} }
Gfx::IntRect WindowManager::desktop_rect() const Gfx::IntRect WindowManager::desktop_rect(Screen& screen) const
{ {
if (active_fullscreen_window()) if (active_fullscreen_window())
return Screen::the().rect(); return Screen::main().rect(); // TODO: we should support fullscreen windows on any screen
return { auto screen_rect = screen.rect();
0, if (screen.is_main_screen())
0, screen_rect.set_height(screen.height() - 28);
Screen::the().width(), return screen_rect;
Screen::the().height() - 28
};
} }
Gfx::IntRect WindowManager::arena_rect_for_type(WindowType type) const Gfx::IntRect WindowManager::arena_rect_for_type(Screen& screen, WindowType type) const
{ {
switch (type) { switch (type) {
case WindowType::Desktop: case WindowType::Desktop:
return Screen::bounding_rect();
case WindowType::Normal: case WindowType::Normal:
case WindowType::ToolWindow: case WindowType::ToolWindow:
return desktop_rect(); return desktop_rect(screen);
case WindowType::Menu: case WindowType::Menu:
case WindowType::WindowSwitcher: case WindowType::WindowSwitcher:
case WindowType::Taskbar: case WindowType::Taskbar:
case WindowType::Tooltip: case WindowType::Tooltip:
case WindowType::Applet: case WindowType::Applet:
case WindowType::Notification: case WindowType::Notification:
return Screen::the().rect(); return screen.rect();
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
@ -1233,7 +1241,7 @@ void WindowManager::process_key_event(KeyEvent& event)
} }
if (m_active_input_window->is_maximized()) if (m_active_input_window->is_maximized())
maximize_windows(*m_active_input_window, false); maximize_windows(*m_active_input_window, false);
m_active_input_window->set_tiled(WindowTileType::Left); m_active_input_window->set_tiled(nullptr, WindowTileType::Left);
return; return;
} }
if (event.key() == Key_Right) { if (event.key() == Key_Right) {
@ -1245,7 +1253,7 @@ void WindowManager::process_key_event(KeyEvent& event)
} }
if (m_active_input_window->is_maximized()) if (m_active_input_window->is_maximized())
maximize_windows(*m_active_input_window, false); maximize_windows(*m_active_input_window, false);
m_active_input_window->set_tiled(WindowTileType::Right); m_active_input_window->set_tiled(nullptr, WindowTileType::Right);
return; return;
} }
} }
@ -1448,24 +1456,29 @@ ResizeDirection WindowManager::resize_direction_of_window(Window const& window)
return m_resize_direction; return m_resize_direction;
} }
Gfx::IntRect WindowManager::maximized_window_rect(Window const& window) const Gfx::IntRect WindowManager::maximized_window_rect(Window const& window, bool relative_to_window_screen) const
{ {
Gfx::IntRect rect = Screen::the().rect(); auto& screen = Screen::closest_to_rect(window.frame().rect());
Gfx::IntRect rect = screen.rect();
// Subtract window title bar (leaving the border) // Subtract window title bar (leaving the border)
rect.set_y(rect.y() + window.frame().titlebar_rect().height() + window.frame().menubar_rect().height()); rect.set_y(rect.y() + window.frame().titlebar_rect().height() + window.frame().menubar_rect().height());
rect.set_height(rect.height() - window.frame().titlebar_rect().height() - window.frame().menubar_rect().height()); rect.set_height(rect.height() - window.frame().titlebar_rect().height() - window.frame().menubar_rect().height());
// Subtract taskbar window height if present if (screen.is_main_screen()) {
const_cast<WindowManager*>(this)->m_window_stack.for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) { // Subtract taskbar window height if present
rect.set_height(rect.height() - taskbar_window.height()); const_cast<WindowManager*>(this)->m_window_stack.for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) {
return IterationDecision::Break; rect.set_height(rect.height() - taskbar_window.height());
}); return IterationDecision::Break;
});
}
constexpr int tasteful_space_above_maximized_window = 1; constexpr int tasteful_space_above_maximized_window = 1;
rect.set_y(rect.y() + tasteful_space_above_maximized_window); rect.set_y(rect.y() + tasteful_space_above_maximized_window);
rect.set_height(rect.height() - tasteful_space_above_maximized_window); rect.set_height(rect.height() - tasteful_space_above_maximized_window);
if (relative_to_window_screen)
rect.translate_by(-screen.rect().location());
return rect; return rect;
} }
@ -1577,9 +1590,10 @@ Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint const
Gfx::IntPoint point; Gfx::IntPoint point;
if (overlap_window) { if (overlap_window) {
auto& screen = Screen::closest_to_location(desired);
point = overlap_window->position() + shift; point = overlap_window->position() + shift;
point = { point.x() % Screen::the().width(), point = { point.x() % screen.width(),
(point.y() >= (Screen::the().height() - taskbar_height)) (point.y() >= (screen.height() - (screen.is_main_screen() ? taskbar_height : 0)))
? Gfx::WindowTheme::current().titlebar_height(Gfx::WindowTheme::WindowType::Normal, palette()) ? Gfx::WindowTheme::current().titlebar_height(Gfx::WindowTheme::WindowType::Normal, palette())
: point.y() }; : point.y() };
} else { } else {
@ -1593,7 +1607,7 @@ int WindowManager::compositor_icon_scale() const
{ {
if (!m_allow_hidpi_icons) if (!m_allow_hidpi_icons)
return 1; return 1;
return scale_factor(); return Screen::main().scale_factor(); // TODO: There is no *one* scale factor...
} }
void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons) void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons)

View file

@ -79,7 +79,7 @@ public:
void notify_progress_changed(Window&); void notify_progress_changed(Window&);
void notify_modified_changed(Window&); void notify_modified_changed(Window&);
Gfx::IntRect maximized_window_rect(Window const&) const; Gfx::IntRect maximized_window_rect(Window const&, bool relative_to_window_screen = false) const;
ClientConnection const* dnd_client() const { return m_dnd_client.ptr(); } ClientConnection const* dnd_client() const { return m_dnd_client.ptr(); }
String const& dnd_text() const { return m_dnd_text; } String const& dnd_text() const { return m_dnd_text; }
@ -105,8 +105,8 @@ public:
void move_to_front_and_make_active(Window&); void move_to_front_and_make_active(Window&);
Gfx::IntRect desktop_rect() const; Gfx::IntRect desktop_rect(Screen&) const;
Gfx::IntRect arena_rect_for_type(WindowType) const; Gfx::IntRect arena_rect_for_type(Screen&, WindowType) const;
Cursor const& active_cursor() const; Cursor const& active_cursor() const;
Cursor const& hidden_cursor() const { return *m_hidden_cursor; } Cursor const& hidden_cursor() const { return *m_hidden_cursor; }
@ -129,9 +129,7 @@ public:
Gfx::Font const& font() const; Gfx::Font const& font() const;
Gfx::Font const& window_title_font() const; Gfx::Font const& window_title_font() const;
bool set_resolution(int width, int height, int scale); bool set_resolution(Screen&, int width, int height, int scale);
Gfx::IntSize resolution() const;
int scale_factor() const;
void set_acceleration_factor(double); void set_acceleration_factor(double);
void set_scroll_step_size(unsigned); void set_scroll_step_size(unsigned);

View file

@ -93,7 +93,7 @@ endpoint WindowServer
set_background_color(String background_color) =| set_background_color(String background_color) =|
set_wallpaper_mode(String mode) =| set_wallpaper_mode(String mode) =|
set_resolution(Gfx::IntSize resolution, int scale_factor) => (bool success, Gfx::IntSize resolution, int scale_factor) set_resolution(u32 screen_index, Gfx::IntSize resolution, int scale_factor) => (bool success, Gfx::IntSize resolution, int scale_factor)
set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =| set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =|
get_wallpaper() => (String path) get_wallpaper() => (String path)
@ -130,5 +130,5 @@ endpoint WindowServer
set_double_click_speed(int speed) => () set_double_click_speed(int speed) => ()
get_double_click_speed() => (int speed) get_double_click_speed() => (int speed)
get_desktop_display_scale() => (int desktop_display_scale) get_desktop_display_scale(u32 screen_index) => (int desktop_display_scale)
} }

View file

@ -221,7 +221,7 @@ void WindowSwitcher::refresh()
int space_for_window_rect = 180; int space_for_window_rect = 180;
m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2); m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2);
m_rect.set_height(window_count * item_height() + padding() * 2); m_rect.set_height(window_count * item_height() + padding() * 2);
m_rect.center_within(Screen::the().rect()); m_rect.center_within(Screen::main().rect());
if (!m_switcher_window) if (!m_switcher_window)
m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher); m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher);
m_switcher_window->set_rect(m_rect); m_switcher_window->set_rect(m_rect);

View file

@ -75,10 +75,47 @@ int main(int, char**)
return 1; return 1;
} }
int scale = wm_config->read_num_entry("Screen", "ScaleFactor", 1); // First check which screens are explicitly configured
WindowServer::Screen screen(wm_config->read_num_entry("Screen", "Width", 1024 / scale), wm_config->read_num_entry("Screen", "Height", 768 / scale), scale); AK::HashTable<String> fb_devices_configured;
screen.set_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters())); int main_screen_index = wm_config->read_num_entry("Screens", "MainScreen", 0);
screen.set_scroll_step_size(wm_config->read_num_entry("Mouse", "ScrollStepSize", 4)); for (int screen_index = 0;; screen_index++) {
auto group_name = String::formatted("Screen{}", screen_index);
if (!wm_config->has_group(group_name))
break;
int scale = wm_config->read_num_entry(group_name, "ScaleFactor", 1);
auto device_path = wm_config->read_entry(group_name, "Device", {});
if (device_path.is_null() || device_path.is_empty()) {
dbgln("Screen {} misses Device setting", screen_index);
break;
}
Gfx::IntRect virtual_rect {
wm_config->read_num_entry(group_name, "Left", 0 / scale),
wm_config->read_num_entry(group_name, "Top", 0 / scale),
wm_config->read_num_entry(group_name, "Width", 1024 / scale),
wm_config->read_num_entry("Screen", "Height", 768 / scale)
};
auto* screen = WindowServer::Screen::create(device_path, virtual_rect, scale);
if (!screen) {
dbgln("Screen {} failed to be created", screen_index);
break;
}
if (main_screen_index == screen_index)
screen->make_main_screen();
// Remember that we used this device for a screen already
fb_devices_configured.set(device_path);
}
// TODO: Enumerate the /dev/fbX devices and set up any ones we find that we haven't already used
auto& screen_input = WindowServer::ScreenInput::the();
screen_input.set_cursor_location(WindowServer::Screen::main().rect().center());
screen_input.set_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters()));
screen_input.set_scroll_step_size(wm_config->read_num_entry("Mouse", "ScrollStepSize", 4));
WindowServer::Compositor::the(); WindowServer::Compositor::the();
auto wm = WindowServer::WindowManager::construct(*palette); auto wm = WindowServer::WindowManager::construct(*palette);
auto am = WindowServer::AppletManager::construct(); auto am = WindowServer::AppletManager::construct();

View file

@ -10,12 +10,14 @@
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int screen = -1;
int width = -1; int width = -1;
int height = -1; int height = -1;
int scale = 1; int scale = 1;
Core::ArgsParser args_parser; Core::ArgsParser args_parser;
args_parser.set_general_help("Change the screen resolution."); args_parser.set_general_help("Change the screen resolution.");
args_parser.add_positional_argument(screen, "Screen", "screen");
args_parser.add_positional_argument(width, "Width", "width"); args_parser.add_positional_argument(width, "Width", "width");
args_parser.add_positional_argument(height, "Height", "height"); args_parser.add_positional_argument(height, "Height", "height");
args_parser.add_positional_argument(scale, "Scale Factor", "scale", Core::ArgsParser::Required::No); args_parser.add_positional_argument(scale, "Scale Factor", "scale", Core::ArgsParser::Required::No);
@ -24,7 +26,7 @@ int main(int argc, char** argv)
// A Core::EventLoop is all we need, but WindowServerConnection needs a full Application object. // A Core::EventLoop is all we need, but WindowServerConnection needs a full Application object.
char* dummy_argv[] = { argv[0] }; char* dummy_argv[] = { argv[0] };
auto app = GUI::Application::construct(1, dummy_argv); auto app = GUI::Application::construct(1, dummy_argv);
auto result = GUI::WindowServerConnection::the().set_resolution(Gfx::IntSize { width, height }, scale); auto result = GUI::WindowServerConnection::the().set_resolution(screen, Gfx::IntSize { width, height }, scale);
if (!result.success()) { if (!result.success()) {
warnln("failed to set resolution"); warnln("failed to set resolution");
return 1; return 1;