From aa15bf81e48637cf6365f15f4eaada645eae4040 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 14 Jun 2021 21:19:56 -0600 Subject: [PATCH] WindowServer: Add API to set/get screen layouts This sets the stage so that DisplaySettings can configure the screen layout and set various screen resolutions in one go. It also allows for an easy "atomic" revert of the previous settings. --- .../DisplaySettings/MonitorSettingsWidget.cpp | 66 +++-- .../DisplaySettings/MonitorSettingsWidget.h | 2 + Userland/Libraries/LibGUI/CMakeLists.txt | 1 + Userland/Libraries/LibGUI/Desktop.h | 8 +- Userland/Libraries/LibGUI/Forward.h | 4 + Userland/Libraries/LibGUI/ScreenLayout.cpp | 7 + .../LibGUI/WindowManagerServerConnection.h | 1 + .../Libraries/LibGUI/WindowServerConnection.h | 1 + .../NotificationServer/ClientConnection.h | 3 + Userland/Services/WindowServer/CMakeLists.txt | 1 + .../WindowServer/ClientConnection.cpp | 23 +- .../Services/WindowServer/ClientConnection.h | 6 +- Userland/Services/WindowServer/Compositor.cpp | 3 + Userland/Services/WindowServer/Screen.cpp | 161 ++++++++---- Userland/Services/WindowServer/Screen.h | 23 +- .../Services/WindowServer/ScreenLayout.cpp | 7 + Userland/Services/WindowServer/ScreenLayout.h | 59 +++++ .../Services/WindowServer/ScreenLayout.ipp | 232 ++++++++++++++++++ .../Services/WindowServer/WindowManager.cpp | 60 ++--- .../Services/WindowServer/WindowManager.h | 5 +- .../Services/WindowServer/WindowServer.ipc | 5 +- Userland/Services/WindowServer/main.cpp | 95 ++----- Userland/Utilities/chres.cpp | 13 +- 23 files changed, 558 insertions(+), 228 deletions(-) create mode 100644 Userland/Libraries/LibGUI/ScreenLayout.cpp create mode 100644 Userland/Services/WindowServer/ScreenLayout.cpp create mode 100644 Userland/Services/WindowServer/ScreenLayout.h create mode 100644 Userland/Services/WindowServer/ScreenLayout.ipp 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; } }