1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 04:58:13 +00:00
serenity/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp
Edwin Rijkee dfbc2839b4 DisplaySettings: Handle case where there is no DPI value
DisplaySettings uses the optional `screen_dpi` value before checking
if it is set, causing an assertion failure. This change moves the
usage into the block where it is known to be set.

One situation where this is known to occur is on real hardware when
using the MULTIBOOT_VIDEO_MODE multiboot flag to enable graphical
display output.
2023-07-27 12:53:25 -04:00

343 lines
15 KiB
C++

/*
* Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com>
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "MonitorSettingsWidget.h"
#include <Applications/DisplaySettings/MonitorSettingsGML.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/ComboBox.h>
#include <LibGUI/ConnectionToWindowServer.h>
#include <LibGUI/ItemListModel.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/RadioButton.h>
#include <LibGfx/SystemTheme.h>
namespace DisplaySettings {
ErrorOr<NonnullRefPtr<MonitorSettingsWidget>> MonitorSettingsWidget::try_create()
{
auto monitor_settings_widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) MonitorSettingsWidget()));
TRY(monitor_settings_widget->create_resolution_list());
TRY(monitor_settings_widget->create_frame());
TRY(monitor_settings_widget->load_current_settings());
return monitor_settings_widget;
}
ErrorOr<void> MonitorSettingsWidget::create_resolution_list()
{
// TODO: Find a better way to get the default resolution
TRY(m_resolutions.try_append({ 640, 480 }));
TRY(m_resolutions.try_append({ 800, 600 }));
TRY(m_resolutions.try_append({ 1024, 768 }));
TRY(m_resolutions.try_append({ 1280, 720 }));
TRY(m_resolutions.try_append({ 1280, 768 }));
TRY(m_resolutions.try_append({ 1280, 960 }));
TRY(m_resolutions.try_append({ 1280, 1024 }));
TRY(m_resolutions.try_append({ 1360, 768 }));
TRY(m_resolutions.try_append({ 1368, 768 }));
TRY(m_resolutions.try_append({ 1440, 900 }));
TRY(m_resolutions.try_append({ 1600, 900 }));
TRY(m_resolutions.try_append({ 1600, 1200 }));
TRY(m_resolutions.try_append({ 1920, 1080 }));
TRY(m_resolutions.try_append({ 2048, 1152 }));
TRY(m_resolutions.try_append({ 2256, 1504 }));
TRY(m_resolutions.try_append({ 2560, 1080 }));
TRY(m_resolutions.try_append({ 2560, 1440 }));
TRY(m_resolutions.try_append({ 3440, 1440 }));
for (auto resolution : m_resolutions) {
// Use Euclid's Algorithm to calculate greatest common factor
i32 a = resolution.width();
i32 b = resolution.height();
i32 gcf = 0;
for (;;) {
i32 r = a % b;
if (r == 0) {
gcf = b;
break;
}
a = b;
b = r;
}
i32 aspect_width = resolution.width() / gcf;
i32 aspect_height = resolution.height() / gcf;
TRY(m_resolution_strings.try_append(TRY(String::formatted("{}x{} ({}:{})", resolution.width(), resolution.height(), aspect_width, aspect_height))));
}
return {};
}
ErrorOr<void> MonitorSettingsWidget::create_frame()
{
TRY(load_from_gml(monitor_settings_window_gml));
m_monitor_widget = *find_descendant_of_type_named<DisplaySettings::MonitorWidget>("monitor_widget");
m_screen_combo = *find_descendant_of_type_named<GUI::ComboBox>("screen_combo");
m_screen_combo->set_only_allow_values_from_model(true);
m_screen_combo->set_model(*GUI::ItemListModel<String>::create(m_screens));
m_screen_combo->on_change = [this](auto&, const GUI::ModelIndex& index) {
m_selected_screen_index = index.row();
auto result = selected_screen_index_or_resolution_changed();
if (result.is_error())
GUI::MessageBox::show_error(window(), "Screen info could not be updated"sv);
};
m_resolution_combo = *find_descendant_of_type_named<GUI::ComboBox>("resolution_combo");
m_resolution_combo->set_only_allow_values_from_model(true);
m_resolution_combo->set_model(*GUI::ItemListModel<String>::create(m_resolution_strings));
m_resolution_combo->on_change = [this](auto&, const GUI::ModelIndex& index) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.resolution = m_resolutions.at(index.row());
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
auto result = selected_screen_index_or_resolution_changed();
if (result.is_error()) {
GUI::MessageBox::show_error(window(), "Screen info could not be updated"sv);
return;
}
set_modified(true);
};
m_display_scale_radio_1x = *find_descendant_of_type_named<GUI::RadioButton>("scale_1x");
m_display_scale_radio_1x->on_checked = [this](bool checked) {
if (checked) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.scale_factor = 1;
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
m_monitor_widget->set_desktop_scale_factor(1);
m_monitor_widget->update();
set_modified(true);
}
};
m_display_scale_radio_2x = *find_descendant_of_type_named<GUI::RadioButton>("scale_2x");
m_display_scale_radio_2x->on_checked = [this](bool checked) {
if (checked) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.scale_factor = 2;
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
m_monitor_widget->set_desktop_scale_factor(2);
m_monitor_widget->update();
set_modified(true);
}
};
m_dpi_label = *find_descendant_of_type_named<GUI::Label>("display_dpi");
return {};
}
static ErrorOr<String> display_name_from_edid(EDID::Parser const& edid)
{
auto manufacturer_name = edid.manufacturer_name();
auto product_name = edid.display_product_name();
auto build_manufacturer_product_name = [&]() -> ErrorOr<String> {
if (product_name.is_null() || product_name.is_empty())
return TRY(String::from_deprecated_string(manufacturer_name));
return String::formatted("{} {}", manufacturer_name, product_name);
};
if (auto screen_size = edid.screen_size(); screen_size.has_value()) {
auto diagonal_inch = hypot(screen_size.value().horizontal_cm(), screen_size.value().vertical_cm()) / 2.54;
return String::formatted("{} {}\"", TRY(build_manufacturer_product_name()), roundf(diagonal_inch));
}
return build_manufacturer_product_name();
}
ErrorOr<void> MonitorSettingsWidget::load_current_settings()
{
m_screen_layout = GUI::ConnectionToWindowServer::the().get_screen_layout();
m_screens.clear();
m_screen_edids.clear();
size_t virtual_screen_count = 0;
for (size_t i = 0; i < m_screen_layout.screens.size(); i++) {
String screen_display_name;
if (m_screen_layout.screens[i].mode == WindowServer::ScreenLayout::Screen::Mode::Device) {
if (auto edid = EDID::Parser::from_display_connector_device(m_screen_layout.screens[i].device.value()); !edid.is_error()) { // TODO: multihead
screen_display_name = TRY(display_name_from_edid(edid.value()));
TRY(m_screen_edids.try_append(edid.release_value()));
} else {
dbgln("Error getting EDID from device {}: {}", m_screen_layout.screens[i].device.value(), edid.error());
screen_display_name = TRY(String::from_deprecated_string(m_screen_layout.screens[i].device.value()));
TRY(m_screen_edids.try_append({}));
}
} else {
dbgln("Frame buffer {} is virtual.", i);
screen_display_name = TRY(String::formatted("Virtual screen {}", virtual_screen_count++));
TRY(m_screen_edids.try_append({}));
}
if (i == m_screen_layout.main_screen_index)
TRY(m_screens.try_append(TRY(String::formatted("{}: {} (main screen)", i + 1, screen_display_name))));
else
TRY(m_screens.try_append(TRY(String::formatted("{}: {}", i + 1, screen_display_name))));
}
m_selected_screen_index = m_screen_layout.main_screen_index;
m_screen_combo->set_selected_index(m_selected_screen_index);
TRY(selected_screen_index_or_resolution_changed());
return {};
}
ErrorOr<void> MonitorSettingsWidget::selected_screen_index_or_resolution_changed()
{
auto& screen = m_screen_layout.screens[m_selected_screen_index];
// Let's attempt to find the current resolution based on the screen layout settings
auto index = m_resolutions.find_first_index(screen.resolution).value_or(0);
Gfx::IntSize current_resolution = m_resolutions.at(index);
Optional<unsigned> screen_dpi;
String screen_dpi_tooltip;
if (m_screen_edids[m_selected_screen_index].has_value()) {
auto& edid = m_screen_edids[m_selected_screen_index];
if (auto screen_size = edid.value().screen_size(); screen_size.has_value()) {
auto x_cm = screen_size.value().horizontal_cm();
auto y_cm = screen_size.value().vertical_cm();
auto diagonal_inch = hypot(x_cm, y_cm) / 2.54;
auto diagonal_pixels = hypot(current_resolution.width(), current_resolution.height());
if (diagonal_pixels != 0.0) {
screen_dpi = diagonal_pixels / diagonal_inch;
screen_dpi_tooltip = TRY(String::formatted("{} inch display ({}cm x {}cm)", roundf(diagonal_inch), x_cm, y_cm));
}
}
}
if (screen_dpi.has_value()) {
auto dpi_label_value = TRY(String::formatted("{} dpi", screen_dpi.value()));
m_dpi_label->set_tooltip(screen_dpi_tooltip.to_deprecated_string());
m_dpi_label->set_text(move(dpi_label_value));
m_dpi_label->set_visible(true);
} else {
m_dpi_label->set_visible(false);
}
if (screen.scale_factor != 1 && screen.scale_factor != 2) {
dbgln("unexpected ScaleFactor {}, setting to 1", screen.scale_factor);
screen.scale_factor = 1;
}
(screen.scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true, GUI::AllowCallback::No);
m_monitor_widget->set_desktop_scale_factor(screen.scale_factor);
// Select the current selected resolution as it may differ
m_monitor_widget->set_desktop_resolution(current_resolution);
m_resolution_combo->set_selected_index(index, GUI::AllowCallback::No);
m_monitor_widget->update();
return {};
}
void MonitorSettingsWidget::apply_settings()
{
// 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::ConnectionToWindowServer::the().get_screen_layout();
if (m_screen_layout != current_layout) {
auto result = GUI::ConnectionToWindowServer::the().set_screen_layout(m_screen_layout, false);
// Run load_current_settings to refresh screen info.
if (result.success() && !load_current_settings().is_error()) {
auto seconds_until_revert = 10;
auto box_text = [this, &seconds_until_revert]() -> ErrorOr<String> {
auto output = String::formatted("Do you want to keep the new screen layout?\nReverting in {} {}.",
seconds_until_revert, seconds_until_revert == 1 ? "second" : "seconds");
if (output.is_error()) {
GUI::MessageBox::show_error(window(), "Unable to apply changes"sv);
return Error::from_string_literal("Unable to create a formatted string");
}
return output.release_value();
};
auto current_box_text_or_error = box_text();
if (current_box_text_or_error.is_error())
return;
auto current_box_text = current_box_text_or_error.release_value();
auto box = GUI::MessageBox::create(window(), current_box_text, "Confirm Settings"sv,
GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo)
.release_value_but_fixme_should_propagate_errors();
box->set_icon(window()->icon());
// If after 10 seconds the user doesn't close the message box, just close it.
auto revert_timer_or_error = Core::Timer::create_repeating(1000, [&] {
seconds_until_revert -= 1;
current_box_text_or_error = box_text();
if (current_box_text_or_error.is_error()) {
seconds_until_revert = 0;
box->close();
return;
}
auto current_box_text = current_box_text_or_error.release_value();
box->set_text(current_box_text);
if (seconds_until_revert <= 0) {
box->close();
}
});
if (revert_timer_or_error.is_error()) {
GUI::MessageBox::show_error(window(), "Unable to apply changes"sv);
return;
}
auto revert_timer = revert_timer_or_error.release_value();
revert_timer->start();
// 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::ExecResult::Yes) {
auto save_result = GUI::ConnectionToWindowServer::the().save_screen_layout();
if (!save_result.success()) {
auto detailed_error_message = String::formatted("Error saving settings: {}", save_result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to save settings"sv);
}
} else {
auto restore_result = GUI::ConnectionToWindowServer::the().set_screen_layout(current_layout, false);
if (!restore_result.success() || load_current_settings().is_error()) {
auto detailed_error_message = String::formatted("Error restoring settings: {}", restore_result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to restore settings"sv);
}
}
} else {
auto detailed_error_message = String::formatted("Error setting screen layout: {}", result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to set screen layout"sv);
}
}
}
void MonitorSettingsWidget::show_screen_numbers(bool show)
{
if (m_showing_screen_numbers == show)
return;
m_showing_screen_numbers = show;
GUI::ConnectionToWindowServer::the().async_show_screen_numbers(show);
}
void MonitorSettingsWidget::show_event(GUI::ShowEvent&)
{
show_screen_numbers(true);
}
void MonitorSettingsWidget::hide_event(GUI::HideEvent&)
{
show_screen_numbers(false);
}
}