MonitorSettingsWidget.cpp 15 KB

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