MonitorSettingsWidget.cpp 12 KB

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