1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 12:35:00 +00:00

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.
This commit is contained in:
Tom 2021-06-14 21:19:56 -06:00 committed by Andreas Kling
parent 34394044b3
commit aa15bf81e4
23 changed files with 558 additions and 228 deletions

View file

@ -80,23 +80,19 @@ void MonitorSettingsWidget::create_frame()
void MonitorSettingsWidget::load_current_settings() void MonitorSettingsWidget::load_current_settings()
{ {
auto ws_config = Core::ConfigFile::open("/etc/WindowServer.ini"); m_screen_layout = GUI::WindowServerConnection::the().get_screen_layout();
auto& screen = m_screen_layout.screens[m_screen_layout.main_screen_index];
int scale_factor = ws_config->read_num_entry("Screen", "ScaleFactor", 1); if (screen.scale_factor != 1 && screen.scale_factor != 2) {
if (scale_factor != 1 && scale_factor != 2) { dbgln("unexpected ScaleFactor {}, setting to 1", screen.scale_factor);
dbgln("unexpected ScaleFactor {}, setting to 1", scale_factor); screen.scale_factor = 1;
scale_factor = 1;
} }
(scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true); (screen.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); m_monitor_widget->set_desktop_scale_factor(screen.scale_factor);
// Let's attempt to find the current resolution and select it! // Let's attempt to find the current resolution and select it!
Gfx::IntSize find_size; auto index = m_resolutions.find_first_index(screen.resolution).value_or(0);
find_size.set_width(ws_config->read_num_entry("Screen", "Width", 1024)); Gfx::IntSize current_resolution = m_resolutions.at(index);
find_size.set_height(ws_config->read_num_entry("Screen", "Height", 768)); m_monitor_widget->set_desktop_resolution(current_resolution);
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);
m_resolution_combo->set_selected_index(index); m_resolution_combo->set_selected_index(index);
m_monitor_widget->update(); m_monitor_widget->update();
@ -104,28 +100,19 @@ void MonitorSettingsWidget::load_current_settings()
void MonitorSettingsWidget::apply_settings() void MonitorSettingsWidget::apply_settings()
{ {
// Store the current screen resolution and scale factor in case the user wants to revert to it. // TODO: implement multi-screen support
auto ws_config(Core::ConfigFile::open("/etc/WindowServer.ini")); auto& main_screen = m_screen_layout.screens[m_screen_layout.main_screen_index];
Gfx::IntSize current_resolution; main_screen.resolution = m_monitor_widget->desktop_resolution();
current_resolution.set_width(ws_config->read_num_entry("Screen", "Width", 1024)); main_screen.scale_factor = (m_display_scale_radio_2x->is_checked() ? 2 : 1);
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;
}
if (current_resolution != m_monitor_widget->desktop_resolution() || current_scale_factor != m_monitor_widget->desktop_scale_factor()) { // Fetch the latest configuration again, in case it has been changed by someone else.
u32 display_index = 0; // TODO: implement multiple display support // This isn't technically race free, but if the user automates changing settings we can't help...
auto result = GUI::WindowServerConnection::the().set_resolution(display_index, m_monitor_widget->desktop_resolution(), m_monitor_widget->desktop_scale_factor()); auto current_layout = GUI::WindowServerConnection::the().get_screen_layout();
if (!result.success()) { if (m_screen_layout != current_layout) {
GUI::MessageBox::show(nullptr, String::formatted("Reverting to resolution {}x{} @ {}x", result.resolution().width(), result.resolution().height(), result.scale_factor()), auto result = GUI::WindowServerConnection::the().set_screen_layout(m_screen_layout, false);
"Unable to set resolution", GUI::MessageBox::Type::Error); if (result.success()) {
} else {
auto box = GUI::MessageBox::construct(window(), String::formatted("Do you want to keep the new settings? They will be reverted after 10 seconds."), 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(), "Apply new screen layout", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
m_monitor_widget->desktop_scale_factor()),
GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
box->set_icon(window()->icon()); box->set_icon(window()->icon());
// If after 10 seconds the user doesn't close the message box, just close it. // 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 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(display_index, current_resolution, current_scale_factor); auto save_result = GUI::WindowServerConnection::the().save_screen_layout();
if (!result.success()) { if (!save_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(window(), String::formatted("Error saving settings: {}", save_result.error_msg()),
"Unable to set resolution", GUI::MessageBox::Type::Error); "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);
} }
} }
} }

View file

@ -11,6 +11,7 @@
#include <LibGUI/ColorInput.h> #include <LibGUI/ColorInput.h>
#include <LibGUI/ComboBox.h> #include <LibGUI/ComboBox.h>
#include <LibGUI/RadioButton.h> #include <LibGUI/RadioButton.h>
#include <WindowServer/ScreenLayout.h>
namespace DisplaySettings { namespace DisplaySettings {
@ -27,6 +28,7 @@ private:
void create_resolution_list(); void create_resolution_list();
void load_current_settings(); void load_current_settings();
WindowServer::ScreenLayout m_screen_layout;
Vector<Gfx::IntSize> m_resolutions; Vector<Gfx::IntSize> m_resolutions;
RefPtr<DisplaySettings::MonitorWidget> m_monitor_widget; RefPtr<DisplaySettings::MonitorWidget> m_monitor_widget;

View file

@ -74,6 +74,7 @@ set(SOURCES
RegularEditingEngine.cpp RegularEditingEngine.cpp
ResizeCorner.cpp ResizeCorner.cpp
RunningProcessesModel.cpp RunningProcessesModel.cpp
ScreenLayout.cpp
ScrollableContainerWidget.cpp ScrollableContainerWidget.cpp
Scrollbar.cpp Scrollbar.cpp
SeparatorWidget.cpp SeparatorWidget.cpp

View file

@ -11,11 +11,17 @@
#include <LibGUI/Forward.h> #include <LibGUI/Forward.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <Services/Taskbar/TaskbarWindow.h> #include <Services/Taskbar/TaskbarWindow.h>
#include <Services/WindowServer/ScreenLayout.h>
namespace GUI { namespace GUI {
using ScreenLayout = WindowServer::ScreenLayout;
class Desktop { class Desktop {
public: public:
// Most people will probably have 4 screens or less
static constexpr size_t default_screen_rect_count = 4;
static Desktop& the(); static Desktop& the();
Desktop(); Desktop();
@ -35,7 +41,7 @@ public:
void did_receive_screen_rects(Badge<WindowServerConnection>, const Vector<Gfx::IntRect, 4>&, size_t); void did_receive_screen_rects(Badge<WindowServerConnection>, const Vector<Gfx::IntRect, 4>&, size_t);
private: private:
Vector<Gfx::IntRect, 4> m_rects; Vector<Gfx::IntRect, default_screen_rect_count> m_rects;
size_t m_main_screen_index { 0 }; size_t m_main_screen_index { 0 };
Gfx::IntRect m_bounding_rect; Gfx::IntRect m_bounding_rect;
}; };

View file

@ -85,3 +85,7 @@ enum class ModelRole;
enum class SortOrder; enum class SortOrder;
} }
namespace WindowServer {
class ScreenLayout;
}

View file

@ -0,0 +1,7 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Services/WindowServer/ScreenLayout.ipp>

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <LibIPC/ServerConnection.h> #include <LibIPC/ServerConnection.h>
#include <WindowServer/ScreenLayout.h>
#include <WindowServer/WindowManagerClientEndpoint.h> #include <WindowServer/WindowManagerClientEndpoint.h>
#include <WindowServer/WindowManagerServerEndpoint.h> #include <WindowServer/WindowManagerServerEndpoint.h>

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <LibIPC/ServerConnection.h> #include <LibIPC/ServerConnection.h>
#include <WindowServer/ScreenLayout.h>
#include <WindowServer/WindowClientEndpoint.h> #include <WindowServer/WindowClientEndpoint.h>
#include <WindowServer/WindowServerEndpoint.h> #include <WindowServer/WindowServerEndpoint.h>

View file

@ -7,6 +7,9 @@
#pragma once #pragma once
#include <LibIPC/ClientConnection.h> #include <LibIPC/ClientConnection.h>
#include <WindowServer/ScreenLayout.h>
// Must be included after WindowServer/ScreenLayout.h
#include <NotificationServer/NotificationClientEndpoint.h> #include <NotificationServer/NotificationClientEndpoint.h>
#include <NotificationServer/NotificationServerEndpoint.h> #include <NotificationServer/NotificationServerEndpoint.h>

View file

@ -22,6 +22,7 @@ set(SOURCES
MenuItem.cpp MenuItem.cpp
MenuManager.cpp MenuManager.cpp
Screen.cpp Screen.cpp
ScreenLayout.cpp
Window.cpp Window.cpp
WindowFrame.cpp WindowFrame.cpp
WindowManager.cpp WindowManager.cpp

View file

@ -297,14 +297,23 @@ Messages::WindowServer::GetWallpaperResponse ClientConnection::get_wallpaper()
return Compositor::the().wallpaper_path(); 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)) { String error_msg;
bool success = WindowManager::the().set_resolution(*screen, resolution.width(), resolution.height(), scale_factor); bool success = WindowManager::the().set_screen_layout(ScreenLayout(screen_layout), save, error_msg);
return { success, screen->size(), screen->scale_factor() }; return { success, move(error_msg) };
} }
dbgln("Setting resolution: Invalid screen index {}", screen_index);
return { false, {}, 0 }; 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) void ClientConnection::set_window_title(i32 window_id, String const& title)

View file

@ -18,6 +18,7 @@
#include <LibIPC/ClientConnection.h> #include <LibIPC/ClientConnection.h>
#include <WindowServer/Event.h> #include <WindowServer/Event.h>
#include <WindowServer/Menu.h> #include <WindowServer/Menu.h>
#include <WindowServer/ScreenLayout.h>
#include <WindowServer/WindowClientEndpoint.h> #include <WindowServer/WindowClientEndpoint.h>
#include <WindowServer/WindowServerEndpoint.h> #include <WindowServer/WindowServerEndpoint.h>
@ -27,6 +28,7 @@ class Compositor;
class Window; class Window;
class Menu; class Menu;
class Menubar; class Menubar;
class ScreenLayout;
class WMClientConnection; class WMClientConnection;
class ClientConnection final class ClientConnection final
@ -125,7 +127,9 @@ 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(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_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;

View file

@ -815,6 +815,9 @@ void Compositor::step_animations()
void Compositor::screen_resolution_changed() void Compositor::screen_resolution_changed()
{ {
// Screens may be gone now, invalidate any references to them
m_current_cursor_screen = nullptr;
init_bitmaps(); init_bitmaps();
invalidate_occlusions(); invalidate_occlusions();
compose(); compose();

View file

@ -22,6 +22,7 @@ namespace WindowServer {
NonnullOwnPtrVector<Screen, default_screen_count> Screen::s_screens; NonnullOwnPtrVector<Screen, default_screen_count> Screen::s_screens;
Screen* Screen::s_main_screen { nullptr }; Screen* Screen::s_main_screen { nullptr };
Gfx::IntRect Screen::s_bounding_screens_rect {}; Gfx::IntRect Screen::s_bounding_screens_rect {};
ScreenLayout Screen::s_layout;
ScreenInput& ScreenInput::the() ScreenInput& ScreenInput::the()
{ {
@ -43,20 +44,54 @@ const Screen& ScreenInput::cursor_location_screen() const
return *screen; return *screen;
} }
Screen::Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale_factor) bool Screen::apply_layout(ScreenLayout&& screen_layout, String& error_msg)
: m_virtual_rect(virtual_rect)
, m_scale_factor(scale_factor)
{ {
m_framebuffer_fd = open(device.characters(), O_RDWR | O_CLOEXEC); if (!screen_layout.is_valid(&error_msg))
if (m_framebuffer_fd < 0) { return false;
perror(String::formatted("failed to open {}", device).characters());
VERIFY_NOT_REACHED(); 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 (fb_set_buffer(m_framebuffer_fd, 0) == 0) { if (s_layout.main_screen_index == index)
m_can_set_buffer = true; screen->make_main_screen();
screen->open_device();
} }
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 // 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()); dbgln("Screen() current physical cursor location: {} rect: {}", ScreenInput::the().cursor_location(), rect());
if (!find_by_location(ScreenInput::the().cursor_location())) if (!find_by_location(ScreenInput::the().cursor_location()))
@ -65,12 +100,41 @@ Screen::Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale
Screen::~Screen() Screen::~Screen()
{ {
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); 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() 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) Screen& Screen::closest_to_rect(const Gfx::IntRect& rect)
@ -113,39 +177,19 @@ 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... // 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; FBResolution physical_resolution { 0, (unsigned)m_info.resolution.width(), (unsigned)m_info.resolution.height() };
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 };
int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution); int rc = fb_set_resolution(m_framebuffer_fd, &physical_resolution);
dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc); dbgln_if(WSSCREEN_DEBUG, "Screen #{}: fb_set_resolution() - return code {}", index(), rc);
if (rc == 0) { auto on_change_resolution = [&]() {
on_change_resolution(initial, physical_resolution.pitch, physical_resolution.width, physical_resolution.height, new_scale_factor, screen_with_cursor); if (initial || physical_resolution.width != (unsigned)m_info.resolution.width() || physical_resolution.height != (unsigned)m_info.resolution.height()) {
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);
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) { 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);
@ -159,16 +203,25 @@ void Screen::on_change_resolution(bool initial, int pitch, int new_physical_widt
VERIFY(m_framebuffer && m_framebuffer != (void*)-1); VERIFY(m_framebuffer && m_framebuffer != (void*)-1);
} }
m_pitch = pitch; m_info.resolution = { physical_resolution.width, physical_resolution.height };
m_virtual_rect.set_width(new_physical_width / new_scale_factor); m_pitch = physical_resolution.pitch;
m_virtual_rect.set_height(new_physical_height / new_scale_factor); if (this == screen_with_cursor) {
m_scale_factor = new_scale_factor;
update_bounding_rect();
if (this == &screen_with_cursor) {
auto& screen_input = ScreenInput::the(); auto& screen_input = ScreenInput::the();
screen_input.set_cursor_location(screen_input.cursor_location().constrained(rect())); screen_input.set_cursor_location(screen_input.cursor_location().constrained(rect()));
} }
};
if (rc == 0) {
on_change_resolution();
return true;
}
if (rc == -1) {
int err = errno;
dbgln("Screen #{}: Failed to set resolution {}: {}", index(), m_info.resolution, strerror(err));
on_change_resolution();
return false;
}
VERIFY_NOT_REACHED();
} }
void Screen::set_buffer(int index) void Screen::set_buffer(int index)

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include "ScreenLayout.h"
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <Kernel/API/KeyCode.h> #include <Kernel/API/KeyCode.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
@ -72,6 +73,9 @@ public:
} }
~Screen(); ~Screen();
static bool apply_layout(ScreenLayout&&, String&);
static const ScreenLayout& layout() { return s_layout; }
static Screen& main() static Screen& main()
{ {
VERIFY(s_main_screen); VERIFY(s_main_screen);
@ -124,11 +128,6 @@ public:
void make_main_screen() { s_main_screen = this; } void make_main_screen() { s_main_screen = this; }
bool is_main_screen() const { return 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; } bool can_set_buffer() { return m_can_set_buffer; }
void set_buffer(int index); void set_buffer(int index);
@ -138,7 +137,7 @@ public:
int width() const { return m_virtual_rect.width(); } int width() const { return m_virtual_rect.width(); }
int height() const { return m_virtual_rect.height(); } 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); Gfx::RGBA32* scanline(int y);
@ -148,9 +147,11 @@ public:
Gfx::IntRect rect() const { return m_virtual_rect; } Gfx::IntRect rect() const { return m_virtual_rect; }
private: private:
Screen(const String& device, const Gfx::IntRect& virtual_rect, int scale_factor); Screen(ScreenLayout::Screen&);
bool open_device();
void close_device();
void init(); void init();
bool do_set_resolution(bool initial, int width, int height, int scale_factor); bool set_resolution(bool initial);
static void update_indices() static void update_indices()
{ {
for (size_t i = 0; i < s_screens.size(); i++) for (size_t i = 0; i < s_screens.size(); i++)
@ -160,11 +161,10 @@ private:
bool is_opened() const { return m_framebuffer_fd >= 0; } 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 NonnullOwnPtrVector<Screen, default_screen_count> s_screens;
static Screen* s_main_screen; static Screen* s_main_screen;
static Gfx::IntRect s_bounding_screens_rect; static Gfx::IntRect s_bounding_screens_rect;
static ScreenLayout s_layout;
size_t m_index { 0 }; size_t m_index { 0 };
size_t m_size_in_bytes; size_t m_size_in_bytes;
@ -175,7 +175,8 @@ private:
int m_pitch { 0 }; int m_pitch { 0 };
Gfx::IntRect m_virtual_rect; Gfx::IntRect m_virtual_rect;
int m_framebuffer_fd { -1 }; int m_framebuffer_fd { -1 };
int m_scale_factor { 1 };
ScreenLayout::Screen& m_info;
}; };
inline Gfx::RGBA32* Screen::scanline(int y) inline Gfx::RGBA32* Screen::scanline(int y)

View file

@ -0,0 +1,7 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ScreenLayout.ipp"

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibCore/ConfigFile.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
#include <LibIPC/Forward.h>
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<Screen> 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&);
}

View file

@ -0,0 +1,232 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Services/WindowServer/ScreenLayout.h>
// Must be included after LibIPC/Forward.h
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
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<const Screen*, 16> 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<WindowServer::ScreenLayout::Screen> 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;
}
}

View file

@ -93,21 +93,11 @@ 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(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::apply_layout(move(screen_layout), error_msg))
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; 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(); Compositor::the().screen_resolution_changed();
@ -115,28 +105,30 @@ bool WindowManager::set_resolution(Screen& screen, int width, int height, int sc
ClientConnection::for_each_client([&](ClientConnection& client) { ClientConnection::for_each_client([&](ClientConnection& client) {
client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index()); client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index());
}); });
if (success) {
m_window_stack.for_each_window([](Window& window) { m_window_stack.for_each_window([](Window& window) {
window.screens().clear_with_capacity();
window.recalculate_rect(); window.recalculate_rect();
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
if (save)
Screen::layout().save_config(*m_config);
return true;
} }
if (m_config) {
if (success) { ScreenLayout WindowManager::get_screen_layout() const
dbgln("Saving resolution: {} @ {}x to config file at {}", Gfx::IntSize(width, height), scale, m_config->filename()); {
m_config->write_num_entry("Screen", "Width", width); return Screen::layout();
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();
} }
bool WindowManager::save_screen_layout(String& error_msg)
{
if (!Screen::layout().save_config(*m_config)) {
error_msg = "Could not save";
return false;
} }
return success; return true;
} }
void WindowManager::set_acceleration_factor(double factor) void WindowManager::set_acceleration_factor(double factor)

View file

@ -21,6 +21,7 @@
#include <WindowServer/Event.h> #include <WindowServer/Event.h>
#include <WindowServer/MenuManager.h> #include <WindowServer/MenuManager.h>
#include <WindowServer/Menubar.h> #include <WindowServer/Menubar.h>
#include <WindowServer/ScreenLayout.h>
#include <WindowServer/WMClientConnection.h> #include <WindowServer/WMClientConnection.h>
#include <WindowServer/WindowSwitcher.h> #include <WindowServer/WindowSwitcher.h>
#include <WindowServer/WindowType.h> #include <WindowServer/WindowType.h>
@ -129,7 +130,9 @@ 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(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_acceleration_factor(double);
void set_scroll_step_size(unsigned); void set_scroll_step_size(unsigned);

View file

@ -93,7 +93,10 @@ 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(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) =| set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =|
get_wallpaper() => (String path) get_wallpaper() => (String path)

View file

@ -76,84 +76,26 @@ int main(int, char**)
} }
// First check which screens are explicitly configured // First check which screens are explicitly configured
AK::HashTable<String> 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<WindowServer::Screen*, 16> reachable_screens { &WindowServer::Screen::main() }; AK::HashTable<String> fb_devices_configured;
bool did_reach_another_screen; WindowServer::ScreenLayout screen_layout;
do { String error_msg;
did_reach_another_screen = false; if (!screen_layout.load_config(*wm_config, &error_msg)) {
auto* latest_reachable_screen = reachable_screens[reachable_screens.size() - 1]; dbgln("Error loading screen configuration: {}", error_msg);
WindowServer::Screen::for_each([&](auto& screen) { return 1;
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;
}); for (auto& screen_info : screen_layout.screens)
} while (did_reach_another_screen); fb_devices_configured.set(screen_info.device);
if (reachable_screens.size() != WindowServer::Screen::count()) {
WindowServer::Screen::for_each([&](auto& screen) { // TODO: Enumerate the /dev/fbX devices and set up any ones we find that we haven't already used
if (!reachable_screens.contains_slow(&screen))
dbgln("Screen {} cannot be reached from main screen {}: Bad configuration!", screen.index(), WindowServer::Screen::main().index()); if (!WindowServer::Screen::apply_layout(move(screen_layout), error_msg)) {
return IterationDecision::Continue; dbgln("Error applying screen layout: {}", error_msg);
});
dbgln("At least one screen is not adjacent to another screen, exiting!");
return 1; 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(); auto& screen_input = WindowServer::ScreenInput::the();
screen_input.set_cursor_location(WindowServer::Screen::main().rect().center()); 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_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters()));
@ -169,10 +111,9 @@ int main(int, char**)
return 1; return 1;
} }
if (unveil("/dev", "") < 0) { // NOTE: Because we dynamically need to be able to open new /dev/fb*
perror("unveil /dev"); // devices we can't really unveil all of /dev unless we have some
return 1; // other mechanism that can hand us file descriptors for these.
}
if (unveil(nullptr, nullptr) < 0) { if (unveil(nullptr, nullptr) < 0) {
perror("unveil"); perror("unveil");

View file

@ -26,9 +26,16 @@ 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(screen, Gfx::IntSize { width, height }, scale); auto screen_layout = GUI::WindowServerConnection::the().get_screen_layout();
if (!result.success()) { if (screen < 0 || (size_t)screen >= screen_layout.screens.size()) {
warnln("failed to set resolution"); 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; return 1;
} }
} }