/* * Copyright (c) 2019-2020, Jesse Buhagiar * Copyright (c) 2020-2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include "MonitorSettingsWidget.h" #include #include #include #include #include #include #include #include #include #include namespace DisplaySettings { ErrorOr> 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 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 MonitorSettingsWidget::create_frame() { TRY(load_from_gml(monitor_settings_window_gml)); m_monitor_widget = *find_descendant_of_type_named("monitor_widget"); m_screen_combo = *find_descendant_of_type_named("screen_combo"); m_screen_combo->set_only_allow_values_from_model(true); m_screen_combo->set_model(*GUI::ItemListModel::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("resolution_combo"); m_resolution_combo->set_only_allow_values_from_model(true); m_resolution_combo->set_model(*GUI::ItemListModel::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("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("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("display_dpi"); return {}; } static ErrorOr 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 { 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 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 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 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)); } } } auto dpi_label_value = String::formatted("{} dpi", screen_dpi.value()); if (screen_dpi.has_value() && !dpi_label_value.is_error()) { m_dpi_label->set_tooltip(screen_dpi_tooltip.to_deprecated_string()); m_dpi_label->set_text(dpi_label_value.release_value().to_deprecated_string()); 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 { auto output = String::formatted("Do you want to keep the new settings? They will be reverted after {} {}.", 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, "Apply new screen layout"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); } }