Serenity Operating System
at master 297 lines 13 kB view raw
1/* 2 * Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com> 3 * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "MonitorSettingsWidget.h" 9#include <Applications/DisplaySettings/MonitorSettingsGML.h> 10#include <LibGUI/BoxLayout.h> 11#include <LibGUI/Button.h> 12#include <LibGUI/ComboBox.h> 13#include <LibGUI/ConnectionToWindowServer.h> 14#include <LibGUI/ItemListModel.h> 15#include <LibGUI/Label.h> 16#include <LibGUI/MessageBox.h> 17#include <LibGUI/RadioButton.h> 18#include <LibGfx/SystemTheme.h> 19 20namespace DisplaySettings { 21 22MonitorSettingsWidget::MonitorSettingsWidget() 23{ 24 create_resolution_list(); 25 create_frame(); 26 load_current_settings(); 27} 28 29void MonitorSettingsWidget::create_resolution_list() 30{ 31 // TODO: Find a better way to get the default resolution 32 m_resolutions.append({ 640, 480 }); 33 m_resolutions.append({ 800, 600 }); 34 m_resolutions.append({ 1024, 768 }); 35 m_resolutions.append({ 1280, 720 }); 36 m_resolutions.append({ 1280, 768 }); 37 m_resolutions.append({ 1280, 960 }); 38 m_resolutions.append({ 1280, 1024 }); 39 m_resolutions.append({ 1360, 768 }); 40 m_resolutions.append({ 1368, 768 }); 41 m_resolutions.append({ 1440, 900 }); 42 m_resolutions.append({ 1600, 900 }); 43 m_resolutions.append({ 1600, 1200 }); 44 m_resolutions.append({ 1920, 1080 }); 45 m_resolutions.append({ 2048, 1152 }); 46 m_resolutions.append({ 2256, 1504 }); 47 m_resolutions.append({ 2560, 1080 }); 48 m_resolutions.append({ 2560, 1440 }); 49 m_resolutions.append({ 3440, 1440 }); 50 51 for (auto resolution : m_resolutions) { 52 // Use Euclid's Algorithm to calculate greatest common factor 53 i32 a = resolution.width(); 54 i32 b = resolution.height(); 55 i32 gcf = 0; 56 for (;;) { 57 i32 r = a % b; 58 if (r == 0) { 59 gcf = b; 60 break; 61 } 62 a = b; 63 b = r; 64 } 65 66 i32 aspect_width = resolution.width() / gcf; 67 i32 aspect_height = resolution.height() / gcf; 68 m_resolution_strings.append(DeprecatedString::formatted("{}x{} ({}:{})", resolution.width(), resolution.height(), aspect_width, aspect_height)); 69 } 70} 71 72void MonitorSettingsWidget::create_frame() 73{ 74 load_from_gml(monitor_settings_window_gml).release_value_but_fixme_should_propagate_errors(); 75 76 m_monitor_widget = *find_descendant_of_type_named<DisplaySettings::MonitorWidget>("monitor_widget"); 77 78 m_screen_combo = *find_descendant_of_type_named<GUI::ComboBox>("screen_combo"); 79 m_screen_combo->set_only_allow_values_from_model(true); 80 m_screen_combo->set_model(*GUI::ItemListModel<DeprecatedString>::create(m_screens)); 81 m_screen_combo->on_change = [this](auto&, const GUI::ModelIndex& index) { 82 m_selected_screen_index = index.row(); 83 selected_screen_index_or_resolution_changed(); 84 }; 85 86 m_resolution_combo = *find_descendant_of_type_named<GUI::ComboBox>("resolution_combo"); 87 m_resolution_combo->set_only_allow_values_from_model(true); 88 m_resolution_combo->set_model(*GUI::ItemListModel<DeprecatedString>::create(m_resolution_strings)); 89 m_resolution_combo->on_change = [this](auto&, const GUI::ModelIndex& index) { 90 auto& selected_screen = m_screen_layout.screens[m_selected_screen_index]; 91 selected_screen.resolution = m_resolutions.at(index.row()); 92 // Try to auto re-arrange things if there are overlaps or disconnected screens 93 m_screen_layout.normalize(); 94 selected_screen_index_or_resolution_changed(); 95 set_modified(true); 96 }; 97 98 m_display_scale_radio_1x = *find_descendant_of_type_named<GUI::RadioButton>("scale_1x"); 99 m_display_scale_radio_1x->on_checked = [this](bool checked) { 100 if (checked) { 101 auto& selected_screen = m_screen_layout.screens[m_selected_screen_index]; 102 selected_screen.scale_factor = 1; 103 // Try to auto re-arrange things if there are overlaps or disconnected screens 104 m_screen_layout.normalize(); 105 m_monitor_widget->set_desktop_scale_factor(1); 106 m_monitor_widget->update(); 107 set_modified(true); 108 } 109 }; 110 m_display_scale_radio_2x = *find_descendant_of_type_named<GUI::RadioButton>("scale_2x"); 111 m_display_scale_radio_2x->on_checked = [this](bool checked) { 112 if (checked) { 113 auto& selected_screen = m_screen_layout.screens[m_selected_screen_index]; 114 selected_screen.scale_factor = 2; 115 // Try to auto re-arrange things if there are overlaps or disconnected screens 116 m_screen_layout.normalize(); 117 m_monitor_widget->set_desktop_scale_factor(2); 118 m_monitor_widget->update(); 119 set_modified(true); 120 } 121 }; 122 123 m_dpi_label = *find_descendant_of_type_named<GUI::Label>("display_dpi"); 124} 125 126static DeprecatedString display_name_from_edid(EDID::Parser const& edid) 127{ 128 auto manufacturer_name = edid.manufacturer_name(); 129 auto product_name = edid.display_product_name(); 130 131 auto build_manufacturer_product_name = [&]() { 132 if (product_name.is_null() || product_name.is_empty()) 133 return manufacturer_name; 134 return DeprecatedString::formatted("{} {}", manufacturer_name, product_name); 135 }; 136 137 if (auto screen_size = edid.screen_size(); screen_size.has_value()) { 138 auto diagonal_inch = hypot(screen_size.value().horizontal_cm(), screen_size.value().vertical_cm()) / 2.54; 139 return DeprecatedString::formatted("{} {}\"", build_manufacturer_product_name(), roundf(diagonal_inch)); 140 } 141 142 return build_manufacturer_product_name(); 143} 144 145void MonitorSettingsWidget::load_current_settings() 146{ 147 m_screen_layout = GUI::ConnectionToWindowServer::the().get_screen_layout(); 148 149 m_screens.clear(); 150 m_screen_edids.clear(); 151 152 size_t virtual_screen_count = 0; 153 for (size_t i = 0; i < m_screen_layout.screens.size(); i++) { 154 DeprecatedString screen_display_name; 155 if (m_screen_layout.screens[i].mode == WindowServer::ScreenLayout::Screen::Mode::Device) { 156 if (auto edid = EDID::Parser::from_display_connector_device(m_screen_layout.screens[i].device.value()); !edid.is_error()) { // TODO: multihead 157 screen_display_name = display_name_from_edid(edid.value()); 158 m_screen_edids.append(edid.release_value()); 159 } else { 160 dbgln("Error getting EDID from device {}: {}", m_screen_layout.screens[i].device.value(), edid.error()); 161 screen_display_name = m_screen_layout.screens[i].device.value(); 162 m_screen_edids.append({}); 163 } 164 } else { 165 dbgln("Frame buffer {} is virtual.", i); 166 screen_display_name = DeprecatedString::formatted("Virtual screen {}", virtual_screen_count++); 167 m_screen_edids.append({}); 168 } 169 if (i == m_screen_layout.main_screen_index) 170 m_screens.append(DeprecatedString::formatted("{}: {} (main screen)", i + 1, screen_display_name)); 171 else 172 m_screens.append(DeprecatedString::formatted("{}: {}", i + 1, screen_display_name)); 173 } 174 m_selected_screen_index = m_screen_layout.main_screen_index; 175 m_screen_combo->set_selected_index(m_selected_screen_index); 176 selected_screen_index_or_resolution_changed(); 177} 178 179void MonitorSettingsWidget::selected_screen_index_or_resolution_changed() 180{ 181 auto& screen = m_screen_layout.screens[m_selected_screen_index]; 182 183 // Let's attempt to find the current resolution based on the screen layout settings 184 auto index = m_resolutions.find_first_index(screen.resolution).value_or(0); 185 Gfx::IntSize current_resolution = m_resolutions.at(index); 186 187 Optional<unsigned> screen_dpi; 188 DeprecatedString screen_dpi_tooltip; 189 if (m_screen_edids[m_selected_screen_index].has_value()) { 190 auto& edid = m_screen_edids[m_selected_screen_index]; 191 if (auto screen_size = edid.value().screen_size(); screen_size.has_value()) { 192 auto x_cm = screen_size.value().horizontal_cm(); 193 auto y_cm = screen_size.value().vertical_cm(); 194 auto diagonal_inch = hypot(x_cm, y_cm) / 2.54; 195 auto diagonal_pixels = hypot(current_resolution.width(), current_resolution.height()); 196 if (diagonal_pixels != 0.0) { 197 screen_dpi = diagonal_pixels / diagonal_inch; 198 screen_dpi_tooltip = DeprecatedString::formatted("{} inch display ({}cm x {}cm)", roundf(diagonal_inch), x_cm, y_cm); 199 } 200 } 201 } 202 203 if (screen_dpi.has_value()) { 204 m_dpi_label->set_tooltip(screen_dpi_tooltip); 205 m_dpi_label->set_text(DeprecatedString::formatted("{} dpi", screen_dpi.value())); 206 m_dpi_label->set_visible(true); 207 } else { 208 m_dpi_label->set_visible(false); 209 } 210 211 if (screen.scale_factor != 1 && screen.scale_factor != 2) { 212 dbgln("unexpected ScaleFactor {}, setting to 1", screen.scale_factor); 213 screen.scale_factor = 1; 214 } 215 (screen.scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true, GUI::AllowCallback::No); 216 m_monitor_widget->set_desktop_scale_factor(screen.scale_factor); 217 218 // Select the current selected resolution as it may differ 219 m_monitor_widget->set_desktop_resolution(current_resolution); 220 m_resolution_combo->set_selected_index(index, GUI::AllowCallback::No); 221 222 m_monitor_widget->update(); 223} 224 225void MonitorSettingsWidget::apply_settings() 226{ 227 // Fetch the latest configuration again, in case it has been changed by someone else. 228 // This isn't technically race free, but if the user automates changing settings we can't help... 229 auto current_layout = GUI::ConnectionToWindowServer::the().get_screen_layout(); 230 if (m_screen_layout != current_layout) { 231 auto result = GUI::ConnectionToWindowServer::the().set_screen_layout(m_screen_layout, false); 232 if (result.success()) { 233 load_current_settings(); // Refresh 234 235 auto seconds_until_revert = 10; 236 237 auto box_text = [&seconds_until_revert] { 238 return DeprecatedString::formatted("Do you want to keep the new settings? They will be reverted after {} {}.", 239 seconds_until_revert, seconds_until_revert == 1 ? "second" : "seconds"); 240 }; 241 242 auto box = GUI::MessageBox::construct(window(), box_text(), "Apply new screen layout"sv, 243 GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); 244 box->set_icon(window()->icon()); 245 246 // If after 10 seconds the user doesn't close the message box, just close it. 247 auto revert_timer = Core::Timer::create_repeating(1000, [&] { 248 seconds_until_revert -= 1; 249 box->set_text(box_text()); 250 if (seconds_until_revert <= 0) { 251 box->close(); 252 } 253 }).release_value_but_fixme_should_propagate_errors(); 254 revert_timer->start(); 255 256 // If the user selects "No", closes the window or the window gets closed by the 10 seconds timer, revert the changes. 257 if (box->exec() == GUI::MessageBox::ExecResult::Yes) { 258 auto save_result = GUI::ConnectionToWindowServer::the().save_screen_layout(); 259 if (!save_result.success()) { 260 GUI::MessageBox::show(window(), DeprecatedString::formatted("Error saving settings: {}", save_result.error_msg()), 261 "Unable to save setting"sv, GUI::MessageBox::Type::Error); 262 } 263 } else { 264 auto restore_result = GUI::ConnectionToWindowServer::the().set_screen_layout(current_layout, false); 265 if (!restore_result.success()) { 266 GUI::MessageBox::show(window(), DeprecatedString::formatted("Error restoring settings: {}", restore_result.error_msg()), 267 "Unable to restore setting"sv, GUI::MessageBox::Type::Error); 268 } else { 269 load_current_settings(); 270 } 271 } 272 } else { 273 GUI::MessageBox::show(window(), DeprecatedString::formatted("Error setting screen layout: {}", result.error_msg()), 274 "Unable to apply changes"sv, GUI::MessageBox::Type::Error); 275 } 276 } 277} 278 279void MonitorSettingsWidget::show_screen_numbers(bool show) 280{ 281 if (m_showing_screen_numbers == show) 282 return; 283 m_showing_screen_numbers = show; 284 GUI::ConnectionToWindowServer::the().async_show_screen_numbers(show); 285} 286 287void MonitorSettingsWidget::show_event(GUI::ShowEvent&) 288{ 289 show_screen_numbers(true); 290} 291 292void MonitorSettingsWidget::hide_event(GUI::HideEvent&) 293{ 294 show_screen_numbers(false); 295} 296 297}