diff --git a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp index b99e6b16a86..abff8e00e1b 100644 --- a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp +++ b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp @@ -80,23 +80,19 @@ void MonitorSettingsWidget::create_frame() void MonitorSettingsWidget::load_current_settings() { - auto ws_config = Core::ConfigFile::open("/etc/WindowServer.ini"); - - int scale_factor = ws_config->read_num_entry("Screen", "ScaleFactor", 1); - if (scale_factor != 1 && scale_factor != 2) { - dbgln("unexpected ScaleFactor {}, setting to 1", scale_factor); - scale_factor = 1; + m_screen_layout = GUI::WindowServerConnection::the().get_screen_layout(); + auto& screen = m_screen_layout.screens[m_screen_layout.main_screen_index]; + if (screen.scale_factor != 1 && screen.scale_factor != 2) { + dbgln("unexpected ScaleFactor {}, setting to 1", screen.scale_factor); + screen.scale_factor = 1; } - (scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true); - m_monitor_widget->set_desktop_scale_factor(scale_factor); + (screen.scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true); + m_monitor_widget->set_desktop_scale_factor(screen.scale_factor); // Let's attempt to find the current resolution and select it! - Gfx::IntSize find_size; - find_size.set_width(ws_config->read_num_entry("Screen", "Width", 1024)); - find_size.set_height(ws_config->read_num_entry("Screen", "Height", 768)); - auto index = m_resolutions.find_first_index(find_size).value_or(0); - Gfx::IntSize m_current_resolution = m_resolutions.at(index); - m_monitor_widget->set_desktop_resolution(m_current_resolution); + auto index = m_resolutions.find_first_index(screen.resolution).value_or(0); + Gfx::IntSize current_resolution = m_resolutions.at(index); + m_monitor_widget->set_desktop_resolution(current_resolution); m_resolution_combo->set_selected_index(index); m_monitor_widget->update(); @@ -104,28 +100,19 @@ void MonitorSettingsWidget::load_current_settings() void MonitorSettingsWidget::apply_settings() { - // Store the current screen resolution and scale factor in case the user wants to revert to it. - auto ws_config(Core::ConfigFile::open("/etc/WindowServer.ini")); - Gfx::IntSize current_resolution; - current_resolution.set_width(ws_config->read_num_entry("Screen", "Width", 1024)); - current_resolution.set_height(ws_config->read_num_entry("Screen", "Height", 768)); - int current_scale_factor = ws_config->read_num_entry("Screen", "ScaleFactor", 1); - if (current_scale_factor != 1 && current_scale_factor != 2) { - dbgln("unexpected ScaleFactor {}, setting to 1", current_scale_factor); - current_scale_factor = 1; - } + // TODO: implement multi-screen support + auto& main_screen = m_screen_layout.screens[m_screen_layout.main_screen_index]; + main_screen.resolution = m_monitor_widget->desktop_resolution(); + main_screen.scale_factor = (m_display_scale_radio_2x->is_checked() ? 2 : 1); - if (current_resolution != m_monitor_widget->desktop_resolution() || current_scale_factor != m_monitor_widget->desktop_scale_factor()) { - u32 display_index = 0; // TODO: implement multiple display support - auto result = GUI::WindowServerConnection::the().set_resolution(display_index, m_monitor_widget->desktop_resolution(), m_monitor_widget->desktop_scale_factor()); - if (!result.success()) { - GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), - "Unable to set resolution", GUI::MessageBox::Type::Error); - } else { + // Fetch the latest configuration again, in case it has been changed by someone else. + // This isn't technically race free, but if the user automates changing settings we can't help... + auto current_layout = GUI::WindowServerConnection::the().get_screen_layout(); + if (m_screen_layout != current_layout) { + auto result = GUI::WindowServerConnection::the().set_screen_layout(m_screen_layout, false); + if (result.success()) { auto box = GUI::MessageBox::construct(window(), String::formatted("Do you want to keep the new settings? They will be reverted after 10 seconds."), - String::formatted("New screen resolution: {}x{} @ {}x", m_monitor_widget->desktop_resolution().width(), m_monitor_widget->desktop_resolution().height(), - m_monitor_widget->desktop_scale_factor()), - GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); + "Apply new screen layout", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); box->set_icon(window()->icon()); // If after 10 seconds the user doesn't close the message box, just close it. @@ -135,12 +122,15 @@ void MonitorSettingsWidget::apply_settings() // If the user selects "No", closes the window or the window gets closed by the 10 seconds timer, revert the changes. if (box->exec() != GUI::MessageBox::ExecYes) { - result = GUI::WindowServerConnection::the().set_resolution(display_index, current_resolution, current_scale_factor); - if (!result.success()) { - GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), - "Unable to set resolution", GUI::MessageBox::Type::Error); + auto save_result = GUI::WindowServerConnection::the().save_screen_layout(); + if (!save_result.success()) { + GUI::MessageBox::show(window(), String::formatted("Error saving settings: {}", save_result.error_msg()), + "Unable to save setting", GUI::MessageBox::Type::Error); } } + } else { + GUI::MessageBox::show(window(), String::formatted("Error setting screen layout: {}", result.error_msg()), + "Unable to apply changes", GUI::MessageBox::Type::Error); } } } diff --git a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.h b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.h index 129a3ef185b..6503d0d6356 100644 --- a/Userland/Applications/DisplaySettings/MonitorSettingsWidget.h +++ b/Userland/Applications/DisplaySettings/MonitorSettingsWidget.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace DisplaySettings { @@ -27,6 +28,7 @@ private: void create_resolution_list(); void load_current_settings(); + WindowServer::ScreenLayout m_screen_layout; Vector m_resolutions; RefPtr m_monitor_widget; diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index 434f81cddef..7f6cf35750f 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -74,6 +74,7 @@ set(SOURCES RegularEditingEngine.cpp ResizeCorner.cpp RunningProcessesModel.cpp + ScreenLayout.cpp ScrollableContainerWidget.cpp Scrollbar.cpp SeparatorWidget.cpp diff --git a/Userland/Libraries/LibGUI/Desktop.h b/Userland/Libraries/LibGUI/Desktop.h index 58b7504611d..a915ea4bac6 100644 --- a/Userland/Libraries/LibGUI/Desktop.h +++ b/Userland/Libraries/LibGUI/Desktop.h @@ -11,11 +11,17 @@ #include #include #include +#include namespace GUI { +using ScreenLayout = WindowServer::ScreenLayout; + class Desktop { public: + // Most people will probably have 4 screens or less + static constexpr size_t default_screen_rect_count = 4; + static Desktop& the(); Desktop(); @@ -35,7 +41,7 @@ public: void did_receive_screen_rects(Badge, const Vector&, size_t); private: - Vector m_rects; + Vector m_rects; size_t m_main_screen_index { 0 }; Gfx::IntRect m_bounding_rect; }; diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h index 156fd60b8ff..a12e0e9f68b 100644 --- a/Userland/Libraries/LibGUI/Forward.h +++ b/Userland/Libraries/LibGUI/Forward.h @@ -85,3 +85,7 @@ enum class ModelRole; enum class SortOrder; } + +namespace WindowServer { +class ScreenLayout; +} diff --git a/Userland/Libraries/LibGUI/ScreenLayout.cpp b/Userland/Libraries/LibGUI/ScreenLayout.cpp new file mode 100644 index 00000000000..dbd8fc093a1 --- /dev/null +++ b/Userland/Libraries/LibGUI/ScreenLayout.cpp @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include diff --git a/Userland/Libraries/LibGUI/WindowManagerServerConnection.h b/Userland/Libraries/LibGUI/WindowManagerServerConnection.h index 8898448c1c3..eddeaf25e50 100644 --- a/Userland/Libraries/LibGUI/WindowManagerServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowManagerServerConnection.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.h b/Userland/Libraries/LibGUI/WindowServerConnection.h index 94ef32f1ed6..f8df378dd84 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowServerConnection.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include diff --git a/Userland/Services/NotificationServer/ClientConnection.h b/Userland/Services/NotificationServer/ClientConnection.h index 309267864f8..d35a53c99ba 100644 --- a/Userland/Services/NotificationServer/ClientConnection.h +++ b/Userland/Services/NotificationServer/ClientConnection.h @@ -7,6 +7,9 @@ #pragma once #include +#include + +// Must be included after WindowServer/ScreenLayout.h #include #include diff --git a/Userland/Services/WindowServer/CMakeLists.txt b/Userland/Services/WindowServer/CMakeLists.txt index 210c11c906b..ce1149deed2 100644 --- a/Userland/Services/WindowServer/CMakeLists.txt +++ b/Userland/Services/WindowServer/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES MenuItem.cpp MenuManager.cpp Screen.cpp + ScreenLayout.cpp Window.cpp WindowFrame.cpp WindowManager.cpp diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index 9aa68c523e7..f72176cefc4 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -297,14 +297,23 @@ Messages::WindowServer::GetWallpaperResponse ClientConnection::get_wallpaper() return Compositor::the().wallpaper_path(); } -Messages::WindowServer::SetResolutionResponse ClientConnection::set_resolution(u32 screen_index, Gfx::IntSize const& resolution, int scale_factor) +Messages::WindowServer::SetScreenLayoutResponse ClientConnection::set_screen_layout(ScreenLayout const& screen_layout, bool save) { - 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 }; + String error_msg; + bool success = WindowManager::the().set_screen_layout(ScreenLayout(screen_layout), save, error_msg); + return { success, move(error_msg) }; +} + +Messages::WindowServer::GetScreenLayoutResponse ClientConnection::get_screen_layout() +{ + return { WindowManager::the().get_screen_layout() }; +} + +Messages::WindowServer::SaveScreenLayoutResponse ClientConnection::save_screen_layout() +{ + String error_msg; + bool success = WindowManager::the().save_screen_layout(error_msg); + return { success, move(error_msg) }; } void ClientConnection::set_window_title(i32 window_id, String const& title) diff --git a/Userland/Services/WindowServer/ClientConnection.h b/Userland/Services/WindowServer/ClientConnection.h index 27ed7fc8f4c..4f171712def 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ class Compositor; class Window; class Menu; class Menubar; +class ScreenLayout; class WMClientConnection; class ClientConnection final @@ -125,7 +127,9 @@ private: virtual void set_background_color(String const&) override; virtual void set_wallpaper_mode(String const&) override; virtual Messages::WindowServer::GetWallpaperResponse get_wallpaper() override; - virtual Messages::WindowServer::SetResolutionResponse set_resolution(u32, Gfx::IntSize const&, int) override; + virtual Messages::WindowServer::SetScreenLayoutResponse set_screen_layout(ScreenLayout const&, bool) override; + virtual Messages::WindowServer::GetScreenLayoutResponse get_screen_layout() override; + virtual Messages::WindowServer::SaveScreenLayoutResponse save_screen_layout() override; virtual void set_window_cursor(i32, i32) override; virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override; virtual void popup_menu(i32, Gfx::IntPoint const&) override; diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 1d329c87678..84391c9ca75 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -815,6 +815,9 @@ void Compositor::step_animations() void Compositor::screen_resolution_changed() { + // Screens may be gone now, invalidate any references to them + m_current_cursor_screen = nullptr; + init_bitmaps(); invalidate_occlusions(); compose(); diff --git a/Userland/Services/WindowServer/Screen.cpp b/Userland/Services/WindowServer/Screen.cpp index e6b7716e9ed..fb105577179 100644 --- a/Userland/Services/WindowServer/Screen.cpp +++ b/Userland/Services/WindowServer/Screen.cpp @@ -22,6 +22,7 @@ namespace WindowServer { NonnullOwnPtrVector Screen::s_screens; Screen* Screen::s_main_screen { nullptr }; Gfx::IntRect Screen::s_bounding_screens_rect {}; +ScreenLayout Screen::s_layout; ScreenInput& ScreenInput::the() { @@ -43,19 +44,53 @@ const Screen& ScreenInput::cursor_location_screen() const 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) +bool Screen::apply_layout(ScreenLayout&& screen_layout, String& error_msg) { - m_framebuffer_fd = open(device.characters(), O_RDWR | O_CLOEXEC); - if (m_framebuffer_fd < 0) { - perror(String::formatted("failed to open {}", device).characters()); - VERIFY_NOT_REACHED(); + if (!screen_layout.is_valid(&error_msg)) + return false; + + auto screens_backup = move(s_screens); + auto layout_backup = move(s_layout); + for (auto& old_screen : screens_backup) + old_screen.close_device(); + + AK::ArmedScopeGuard rollback([&] { + for (auto& screen : s_screens) + screen.close_device(); + s_screens = move(screens_backup); + s_layout = move(layout_backup); + for (auto& old_screen : screens_backup) { + if (!old_screen.open_device()) { + // Don't set error_msg here, it should already be set + dbgln("Rolling back screen layout failed: could not open device"); + } + } + update_bounding_rect(); + }); + s_layout = move(screen_layout); + for (size_t index = 0; index < s_layout.screens.size(); index++) { + auto* screen = WindowServer::Screen::create(s_layout.screens[index]); + if (!screen) { + error_msg = String::formatted("Error creating screen #{}", index); + return false; + } + + if (s_layout.main_screen_index == index) + screen->make_main_screen(); + + screen->open_device(); } - if (fb_set_buffer(m_framebuffer_fd, 0) == 0) { - m_can_set_buffer = true; - } + rollback.disarm(); + update_bounding_rect(); + return true; +} + +Screen::Screen(ScreenLayout::Screen& screen_info) + : m_virtual_rect(screen_info.location, { screen_info.resolution.width() / screen_info.scale_factor, screen_info.resolution.height() / screen_info.scale_factor }) + , m_info(screen_info) +{ + open_device(); // If the cursor is not in a valid screen (yet), force it into one dbgln("Screen() current physical cursor location: {} rect: {}", ScreenInput::the().cursor_location(), rect()); @@ -65,12 +100,41 @@ Screen::Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale Screen::~Screen() { - close(m_framebuffer_fd); + close_device(); +} + +bool Screen::open_device() +{ + close_device(); + m_framebuffer_fd = open(m_info.device.characters(), O_RDWR | O_CLOEXEC); + if (m_framebuffer_fd < 0) { + perror(String::formatted("failed to open {}", m_info.device).characters()); + return false; + } + + m_can_set_buffer = (fb_set_buffer(m_framebuffer_fd, 0) == 0); + set_resolution(true); + return true; +} + +void Screen::close_device() +{ + if (m_framebuffer_fd >= 0) { + close(m_framebuffer_fd); + m_framebuffer_fd = -1; + } + if (m_framebuffer) { + int rc = munmap(m_framebuffer, m_size_in_bytes); + VERIFY(rc == 0); + + m_framebuffer = nullptr; + m_size_in_bytes = 0; + } } void Screen::init() { - do_set_resolution(true, m_virtual_rect.width(), m_virtual_rect.height(), m_scale_factor); + set_resolution(true); } Screen& Screen::closest_to_rect(const Gfx::IntRect& rect) @@ -113,64 +177,53 @@ void Screen::update_bounding_rect() } } -bool Screen::do_set_resolution(bool initial, int width, int height, int new_scale_factor) +bool Screen::set_resolution(bool initial) { // 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(); + Screen* screen_with_cursor = nullptr; + if (!initial) + screen_with_cursor = &ScreenInput::the().cursor_location_screen(); - int new_physical_width = width * new_scale_factor; - int new_physical_height = height * new_scale_factor; - if (!initial && physical_width() == new_physical_width && physical_height() == new_physical_height) { - VERIFY(initial || scale_factor() != new_scale_factor); - on_change_resolution(initial, m_pitch, physical_width(), physical_height(), new_scale_factor, screen_with_cursor); - return true; - } - - FBResolution physical_resolution { 0, (unsigned)new_physical_width, (unsigned)new_physical_height }; + FBResolution physical_resolution { 0, (unsigned)m_info.resolution.width(), (unsigned)m_info.resolution.height() }; int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc); + auto on_change_resolution = [&]() { + if (initial || physical_resolution.width != (unsigned)m_info.resolution.width() || physical_resolution.height != (unsigned)m_info.resolution.height()) { + if (m_framebuffer) { + size_t previous_size_in_bytes = m_size_in_bytes; + int rc = munmap(m_framebuffer, previous_size_in_bytes); + VERIFY(rc == 0); + } + + int rc = fb_get_size_in_bytes(m_framebuffer_fd, &m_size_in_bytes); + VERIFY(rc == 0); + + m_framebuffer = (Gfx::RGBA32*)mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0); + VERIFY(m_framebuffer && m_framebuffer != (void*)-1); + } + + m_info.resolution = { physical_resolution.width, physical_resolution.height }; + m_pitch = physical_resolution.pitch; + if (this == screen_with_cursor) { + auto& screen_input = ScreenInput::the(); + screen_input.set_cursor_location(screen_input.cursor_location().constrained(rect())); + } + }; + if (rc == 0) { - on_change_resolution(initial, physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor, screen_with_cursor); + on_change_resolution(); return true; } if (rc == -1) { int err = errno; - dbgln("Screen #{}: Failed to set resolution {}x{}: {}", index(), width, height, strerror(err)); - on_change_resolution(initial, physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor, screen_with_cursor); + dbgln("Screen #{}: Failed to set resolution {}: {}", index(), m_info.resolution, strerror(err)); + on_change_resolution(); return false; } VERIFY_NOT_REACHED(); } -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 (initial || physical_width() != new_physical_width || physical_height() != new_physical_height) { - if (m_framebuffer) { - size_t previous_size_in_bytes = m_size_in_bytes; - int rc = munmap(m_framebuffer, previous_size_in_bytes); - VERIFY(rc == 0); - } - - int rc = fb_get_size_in_bytes(m_framebuffer_fd, &m_size_in_bytes); - VERIFY(rc == 0); - - m_framebuffer = (Gfx::RGBA32*)mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0); - VERIFY(m_framebuffer && m_framebuffer != (void*)-1); - } - - m_pitch = pitch; - m_virtual_rect.set_width(new_physical_width / new_scale_factor); - m_virtual_rect.set_height(new_physical_height / new_scale_factor); - m_scale_factor = new_scale_factor; - update_bounding_rect(); - - 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) { VERIFY(m_can_set_buffer); diff --git a/Userland/Services/WindowServer/Screen.h b/Userland/Services/WindowServer/Screen.h index 4e094ee7959..68b204372d9 100644 --- a/Userland/Services/WindowServer/Screen.h +++ b/Userland/Services/WindowServer/Screen.h @@ -6,6 +6,7 @@ #pragma once +#include "ScreenLayout.h" #include #include #include @@ -72,6 +73,9 @@ public: } ~Screen(); + static bool apply_layout(ScreenLayout&&, String&); + static const ScreenLayout& layout() { return s_layout; } + static Screen& main() { VERIFY(s_main_screen); @@ -124,11 +128,6 @@ public: void make_main_screen() { s_main_screen = this; } bool is_main_screen() const { return s_main_screen == this; } - template - bool set_resolution(Args&&... args) - { - return do_set_resolution(false, forward(args)...); - } bool can_set_buffer() { return m_can_set_buffer; } void set_buffer(int index); @@ -138,7 +137,7 @@ public: int width() const { return m_virtual_rect.width(); } int height() const { return m_virtual_rect.height(); } - int scale_factor() const { return m_scale_factor; } + int scale_factor() const { return m_info.scale_factor; } Gfx::RGBA32* scanline(int y); @@ -148,9 +147,11 @@ public: Gfx::IntRect rect() const { return m_virtual_rect; } private: - Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale_factor); + Screen(ScreenLayout::Screen&); + bool open_device(); + void close_device(); void init(); - bool do_set_resolution(bool initial, int width, int height, int scale_factor); + bool set_resolution(bool initial); static void update_indices() { for (size_t i = 0; i < s_screens.size(); i++) @@ -160,11 +161,10 @@ private: bool is_opened() const { return m_framebuffer_fd >= 0; } - void on_change_resolution(bool initial, int pitch, int physical_width, int physical_height, int scale_factor, Screen& screen_with_cursor); - static NonnullOwnPtrVector s_screens; static Screen* s_main_screen; static Gfx::IntRect s_bounding_screens_rect; + static ScreenLayout s_layout; size_t m_index { 0 }; size_t m_size_in_bytes; @@ -175,7 +175,8 @@ private: int m_pitch { 0 }; Gfx::IntRect m_virtual_rect; int m_framebuffer_fd { -1 }; - int m_scale_factor { 1 }; + + ScreenLayout::Screen& m_info; }; inline Gfx::RGBA32* Screen::scanline(int y) diff --git a/Userland/Services/WindowServer/ScreenLayout.cpp b/Userland/Services/WindowServer/ScreenLayout.cpp new file mode 100644 index 00000000000..77245bd309b --- /dev/null +++ b/Userland/Services/WindowServer/ScreenLayout.cpp @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "ScreenLayout.ipp" diff --git a/Userland/Services/WindowServer/ScreenLayout.h b/Userland/Services/WindowServer/ScreenLayout.h new file mode 100644 index 00000000000..e9d52bc43b2 --- /dev/null +++ b/Userland/Services/WindowServer/ScreenLayout.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WindowServer { + +class ScreenLayout { +public: + struct Screen { + String device; + Gfx::IntPoint location; + Gfx::IntSize resolution; + int scale_factor; + + Gfx::IntRect virtual_rect() const + { + return { location, { resolution.width() / scale_factor, resolution.height() / scale_factor } }; + } + + auto operator<=>(const Screen&) const = default; + }; + + Vector screens; + unsigned main_screen_index { 0 }; + + bool is_valid(String* error_msg = nullptr) const; + void normalize(); + bool load_config(const Core::ConfigFile& config_file, String* error_msg = nullptr); + bool save_config(Core::ConfigFile& config_file, bool sync = true) const; + + // TODO: spaceship operator + bool operator!=(const ScreenLayout& other) const; + bool operator==(const ScreenLayout& other) const + { + return !(*this != other); + } +}; + +} + +namespace IPC { + +bool encode(Encoder&, const WindowServer::ScreenLayout::Screen&); +bool decode(Decoder&, WindowServer::ScreenLayout::Screen&); +bool encode(Encoder&, const WindowServer::ScreenLayout&); +bool decode(Decoder&, WindowServer::ScreenLayout&); + +} diff --git a/Userland/Services/WindowServer/ScreenLayout.ipp b/Userland/Services/WindowServer/ScreenLayout.ipp new file mode 100644 index 00000000000..b2c55a3a590 --- /dev/null +++ b/Userland/Services/WindowServer/ScreenLayout.ipp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +// Must be included after LibIPC/Forward.h +#include +#include + +namespace WindowServer { + +bool ScreenLayout::is_valid(String* error_msg) const +{ + if (screens.is_empty()) { + if (error_msg) + *error_msg = "Must have at least one screen"; + return false; + } + if (main_screen_index >= screens.size()) { + if (error_msg) + *error_msg = String::formatted("Invalid main screen index: {}", main_screen_index); + return false; + } + int smallest_x = 0; + int smallest_y = 0; + for (size_t i = 0; i < screens.size(); i++) { + auto& screen = screens[i]; + if (screen.device.is_null() || screen.device.is_empty()) { + if (error_msg) + *error_msg = String::formatted("Screen #{} has no path", i); + return false; + } + for (size_t j = 0; j < screens.size(); j++) { + auto& other_screen = screens[j]; + if (&other_screen == &screen) + continue; + if (screen.device == other_screen.device) { + if (error_msg) + *error_msg = String::formatted("Screen #{} is using same device as screen #{}", i, j); + return false; + } + if (screen.virtual_rect().intersects(other_screen.virtual_rect())) { + if (error_msg) + *error_msg = String::formatted("Screen #{} overlaps with screen #{}", i, j); + return false; + } + } + if (screen.location.x() < 0 || screen.location.y() < 0) { + if (error_msg) + *error_msg = String::formatted("Screen #{} has invalid location: {}", i, screen.location); + return false; + } + if (screen.resolution.width() <= 0 || screen.resolution.height() <= 0) { + if (error_msg) + *error_msg = String::formatted("Screen #{} has invalid resolution: {}", i, screen.resolution); + return false; + } + if (screen.scale_factor < 1) { + if (error_msg) + *error_msg = String::formatted("Screen #{} has invalid scale factor: {}", i, screen.scale_factor); + return false; + } + if (i == 0 || screen.location.x() < smallest_x) + smallest_x = screen.location.x(); + if (i == 0 || screen.location.y() < smallest_y) + smallest_y = screen.location.y(); + } + if (smallest_x != 0 || smallest_y != 0) { + if (error_msg) + *error_msg = "Screen layout has not been normalized"; + return false; + } + Vector reachable_screens { &screens[main_screen_index] }; + bool did_reach_another_screen; + do { + did_reach_another_screen = false; + auto* latest_reachable_screen = reachable_screens[reachable_screens.size() - 1]; + for (auto& screen : screens) { + if (&screen == latest_reachable_screen || reachable_screens.contains_slow(&screen)) + continue; + if (screen.virtual_rect().is_adjacent(latest_reachable_screen->virtual_rect())) { + reachable_screens.append(&screen); + did_reach_another_screen = true; + break; + } + } + } while (did_reach_another_screen); + if (reachable_screens.size() != screens.size()) { + for (size_t i = 0; i < screens.size(); i++) { + auto& screen = screens[i]; + if (!reachable_screens.contains_slow(&screen)) { + if (error_msg) + *error_msg = String::formatted("Screen #{} {} cannot be reached from main screen #{} {}", i, screen.virtual_rect(), main_screen_index, screens[main_screen_index].virtual_rect()); + break; + } + } + return false; + } + return true; +} + +void ScreenLayout::normalize() +{ + int smallest_x = 0; + int smallest_y = 0; + for (size_t i = 0; i < screens.size(); i++) { + auto& screen = screens[i]; + if (i == 0 || screen.location.x() < smallest_x) + smallest_x = screen.location.x(); + if (i == 0 || screen.location.y() < smallest_y) + smallest_y = screen.location.y(); + } + if (smallest_x != 0 || smallest_y != 0) { + for (auto& screen : screens) + screen.location.translate_by(-smallest_x, -smallest_y); + } +} + +bool ScreenLayout::load_config(const Core::ConfigFile& config_file, String* error_msg) +{ + screens.clear_with_capacity(); + main_screen_index = config_file.read_num_entry("Screens", "DefaultScreen", 0); + for (size_t index = 0;; index++) { + auto group_name = String::formatted("Screen{}", index); + if (!config_file.has_group(group_name)) + break; + screens.append({ config_file.read_entry(group_name, "Device"), + { config_file.read_num_entry(group_name, "Left"), config_file.read_num_entry(group_name, "Top") }, + { config_file.read_num_entry(group_name, "Width"), config_file.read_num_entry(group_name, "Height") }, + config_file.read_num_entry(group_name, "ScaleFactor", 1) }); + } + if (!is_valid(error_msg)) { + *this = {}; + return false; + } + return true; +} + +bool ScreenLayout::save_config(Core::ConfigFile& config_file, bool sync) const +{ + config_file.write_num_entry("Screens", "DefaultScreen", main_screen_index); + + size_t index = 0; + while (index < screens.size()) { + auto& screen = screens[index]; + auto group_name = String::formatted("Screen{}", index); + config_file.write_entry(group_name, "Device", screen.device); + config_file.write_num_entry(group_name, "Left", screen.location.x()); + config_file.write_num_entry(group_name, "Top", screen.location.y()); + config_file.write_num_entry(group_name, "Width", screen.resolution.width()); + config_file.write_num_entry(group_name, "Height", screen.resolution.height()); + config_file.write_num_entry(group_name, "ScaleFactor", screen.scale_factor); + index++; + } + // Prune screens no longer in the layout + for (;;) { + auto group_name = String::formatted("Screen{}", index++); + if (!config_file.has_group(group_name)) + break; + config_file.remove_group(group_name); + } + + if (sync && !config_file.sync()) + return false; + return true; +} + +bool ScreenLayout::operator!=(const ScreenLayout& other) const +{ + if (this == &other) + return false; + if (main_screen_index != other.main_screen_index) + return true; + if (screens.size() != other.screens.size()) + return true; + for (size_t i = 0; i < screens.size(); i++) { + if (screens[i] != other.screens[i]) + return true; + } + return false; +} + +} + +namespace IPC { + +bool encode(Encoder& encoder, const WindowServer::ScreenLayout::Screen& screen) +{ + encoder << screen.device << screen.location << screen.resolution << screen.scale_factor; + return true; +} + +bool decode(Decoder& decoder, WindowServer::ScreenLayout::Screen& screen) +{ + String device; + if (!decoder.decode(device)) + return false; + Gfx::IntPoint location; + if (!decoder.decode(location)) + return false; + Gfx::IntSize resolution; + if (!decoder.decode(resolution)) + return false; + int scale_factor = 0; + if (!decoder.decode(scale_factor)) + return false; + screen = { device, location, resolution, scale_factor }; + return true; +} + +bool encode(Encoder& encoder, const WindowServer::ScreenLayout& screen_layout) +{ + encoder << screen_layout.screens << screen_layout.main_screen_index; + return true; +} + +bool decode(Decoder& decoder, WindowServer::ScreenLayout& screen_layout) +{ + Vector screens; + if (!decoder.decode(screens)) + return false; + unsigned main_screen_index = 0; + if (!decoder.decode(main_screen_index)) + return false; + screen_layout = { move(screens), main_screen_index }; + return true; +} + +} diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index df9cd6c478d..3fa3ce6d98c 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -93,50 +93,42 @@ Gfx::Font const& WindowManager::window_title_font() const return Gfx::FontDatabase::default_font().bold_variant(); } -bool WindowManager::set_resolution(Screen& screen, int width, int height, int scale) +bool WindowManager::set_screen_layout(ScreenLayout&& screen_layout, bool save, String& error_msg) { - 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); + if (!Screen::apply_layout(move(screen_layout), error_msg)) 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(); + reload_icon_bitmaps_after_scale_change(); Compositor::the().screen_resolution_changed(); ClientConnection::for_each_client([&](ClientConnection& client) { client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index()); }); - if (success) { - m_window_stack.for_each_window([](Window& window) { - window.recalculate_rect(); - return IterationDecision::Continue; - }); + + m_window_stack.for_each_window([](Window& window) { + window.screens().clear_with_capacity(); + window.recalculate_rect(); + return IterationDecision::Continue; + }); + + if (save) + Screen::layout().save_config(*m_config); + return true; +} + +ScreenLayout WindowManager::get_screen_layout() const +{ + return Screen::layout(); +} + +bool WindowManager::save_screen_layout(String& error_msg) +{ + if (!Screen::layout().save_config(*m_config)) { + error_msg = "Could not save"; + return false; } - if (m_config) { - if (success) { - dbgln("Saving resolution: {} @ {}x to config file at {}", Gfx::IntSize(width, height), scale, m_config->filename()); - m_config->write_num_entry("Screen", "Width", width); - m_config->write_num_entry("Screen", "Height", height); - m_config->write_num_entry("Screen", "ScaleFactor", scale); - m_config->sync(); - } else { - dbgln("Saving fallback resolution: {} @1x to config file at {}", screen.size(), m_config->filename()); - m_config->write_num_entry("Screen", "Width", screen.size().width()); - m_config->write_num_entry("Screen", "Height", screen.size().height()); - m_config->write_num_entry("Screen", "ScaleFactor", 1); - m_config->sync(); - } - } - return success; + return true; } void WindowManager::set_acceleration_factor(double factor) diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index a4452ecf574..98e32a5a437 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,9 @@ public: Gfx::Font const& font() const; Gfx::Font const& window_title_font() const; - bool set_resolution(Screen&, int width, int height, int scale); + bool set_screen_layout(ScreenLayout&&, bool, String&); + ScreenLayout get_screen_layout() const; + bool save_screen_layout(String&); void set_acceleration_factor(double); void set_scroll_step_size(unsigned); diff --git a/Userland/Services/WindowServer/WindowServer.ipc b/Userland/Services/WindowServer/WindowServer.ipc index b31fb557a1a..7ea2b7580cd 100644 --- a/Userland/Services/WindowServer/WindowServer.ipc +++ b/Userland/Services/WindowServer/WindowServer.ipc @@ -93,7 +93,10 @@ endpoint WindowServer set_background_color(String background_color) =| set_wallpaper_mode(String mode) =| - set_resolution(u32 screen_index, Gfx::IntSize resolution, int scale_factor) => (bool success, Gfx::IntSize resolution, int scale_factor) + set_screen_layout(::WindowServer::ScreenLayout screen_layout, bool save) => (bool success, String error_msg) + get_screen_layout() => (::WindowServer::ScreenLayout screen_layout) + save_screen_layout() => (bool success, String error_msg) + set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =| get_wallpaper() => (String path) diff --git a/Userland/Services/WindowServer/main.cpp b/Userland/Services/WindowServer/main.cpp index 284688614e5..bad6afb5715 100644 --- a/Userland/Services/WindowServer/main.cpp +++ b/Userland/Services/WindowServer/main.cpp @@ -76,84 +76,26 @@ int main(int, char**) } // First check which screens are explicitly configured - AK::HashTable fb_devices_configured; - int main_screen_index = wm_config->read_num_entry("Screens", "MainScreen", 0); - for (int screen_index = 0;; screen_index++) { - auto group_name = String::formatted("Screen{}", screen_index); - if (!wm_config->has_group(group_name)) - break; - - int scale = wm_config->read_num_entry(group_name, "ScaleFactor", 1); - auto device_path = wm_config->read_entry(group_name, "Device", {}); - if (device_path.is_null() || device_path.is_empty()) { - dbgln("Screen {} misses Device setting", screen_index); - break; - } - - Gfx::IntRect virtual_rect { - wm_config->read_num_entry(group_name, "Left", 0 / scale), - wm_config->read_num_entry(group_name, "Top", 0 / scale), - wm_config->read_num_entry(group_name, "Width", 1024 / scale), - wm_config->read_num_entry("Screen", "Height", 768 / scale) - }; - - // Check if the screen would be overlapping with another one - if (WindowServer::Screen::for_each([&](auto& screen) { - if (screen.rect().intersects(virtual_rect)) { - dbgln("Screen {} rect {} overlaps with screen {} rect {}: Ignoring configuration", screen.index(), screen.rect(), screen_index, virtual_rect); - return IterationDecision::Break; - } - return IterationDecision::Continue; - }) - == IterationDecision::Break) { - // Ignore the configuration - break; - } - - 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); - } - - // Check that all screens are contiguous and can be "reached" from the main screen { - Vector reachable_screens { &WindowServer::Screen::main() }; - bool did_reach_another_screen; - do { - did_reach_another_screen = false; - auto* latest_reachable_screen = reachable_screens[reachable_screens.size() - 1]; - WindowServer::Screen::for_each([&](auto& screen) { - if (&screen == latest_reachable_screen || reachable_screens.contains_slow(&screen)) - return IterationDecision::Continue; - if (screen.rect().is_adjacent(latest_reachable_screen->rect())) { - reachable_screens.append(&screen); - did_reach_another_screen = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - } while (did_reach_another_screen); - if (reachable_screens.size() != WindowServer::Screen::count()) { - WindowServer::Screen::for_each([&](auto& screen) { - if (!reachable_screens.contains_slow(&screen)) - dbgln("Screen {} cannot be reached from main screen {}: Bad configuration!", screen.index(), WindowServer::Screen::main().index()); - return IterationDecision::Continue; - }); - dbgln("At least one screen is not adjacent to another screen, exiting!"); + AK::HashTable fb_devices_configured; + WindowServer::ScreenLayout screen_layout; + String error_msg; + if (!screen_layout.load_config(*wm_config, &error_msg)) { + dbgln("Error loading screen configuration: {}", error_msg); + return 1; + } + + for (auto& screen_info : screen_layout.screens) + fb_devices_configured.set(screen_info.device); + + // TODO: Enumerate the /dev/fbX devices and set up any ones we find that we haven't already used + + if (!WindowServer::Screen::apply_layout(move(screen_layout), error_msg)) { + dbgln("Error applying screen layout: {}", error_msg); return 1; } } - // 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())); @@ -169,10 +111,9 @@ int main(int, char**) return 1; } - if (unveil("/dev", "") < 0) { - perror("unveil /dev"); - return 1; - } + // NOTE: Because we dynamically need to be able to open new /dev/fb* + // devices we can't really unveil all of /dev unless we have some + // other mechanism that can hand us file descriptors for these. if (unveil(nullptr, nullptr) < 0) { perror("unveil"); diff --git a/Userland/Utilities/chres.cpp b/Userland/Utilities/chres.cpp index d36b4c8d3b7..7e2506d0d20 100644 --- a/Userland/Utilities/chres.cpp +++ b/Userland/Utilities/chres.cpp @@ -26,9 +26,16 @@ int main(int argc, char** argv) // A Core::EventLoop is all we need, but WindowServerConnection needs a full Application object. char* dummy_argv[] = { argv[0] }; auto app = GUI::Application::construct(1, dummy_argv); - auto result = GUI::WindowServerConnection::the().set_resolution(screen, Gfx::IntSize { width, height }, scale); - if (!result.success()) { - warnln("failed to set resolution"); + auto screen_layout = GUI::WindowServerConnection::the().get_screen_layout(); + if (screen < 0 || (size_t)screen >= screen_layout.screens.size()) { + warnln("invalid screen index: {}", screen); + return 1; + } + auto& main_screen = screen_layout.screens[screen]; + main_screen.resolution = { width, height }; + auto set_result = GUI::WindowServerConnection::the().set_screen_layout(screen_layout, true); + if (!set_result.success()) { + warnln("failed to set resolution: {}", set_result.error_msg()); return 1; } }