Serenity Operating System
at master 437 lines 17 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/ScopeGuard.h> 8#include <Kernel/API/Graphics.h> 9#include <LibIPC/Decoder.h> 10#include <LibIPC/Encoder.h> 11#include <Services/WindowServer/ScreenLayout.h> 12#include <errno.h> 13#include <fcntl.h> 14#include <string.h> 15 16namespace WindowServer { 17 18bool ScreenLayout::is_valid(DeprecatedString* error_msg) const 19{ 20 if (screens.is_empty()) { 21 if (error_msg) 22 *error_msg = "Must have at least one screen"; 23 return false; 24 } 25 if (main_screen_index >= screens.size()) { 26 if (error_msg) 27 *error_msg = DeprecatedString::formatted("Invalid main screen index: {}", main_screen_index); 28 return false; 29 } 30 int smallest_x = 0; 31 int smallest_y = 0; 32 for (size_t i = 0; i < screens.size(); i++) { 33 auto& screen = screens[i]; 34 if (screen.mode == Screen::Mode::Device && (screen.device->is_empty() || screen.device->is_null())) { 35 if (error_msg) 36 *error_msg = DeprecatedString::formatted("Screen #{} has no path", i); 37 return false; 38 } 39 for (size_t j = 0; j < screens.size(); j++) { 40 auto& other_screen = screens[j]; 41 if (&other_screen == &screen) 42 continue; 43 if (screen.device == other_screen.device) { 44 if (error_msg) 45 *error_msg = DeprecatedString::formatted("Screen #{} is using same device as screen #{}", i, j); 46 return false; 47 } 48 if (screen.virtual_rect().intersects(other_screen.virtual_rect())) { 49 if (error_msg) 50 *error_msg = DeprecatedString::formatted("Screen #{} overlaps with screen #{}", i, j); 51 return false; 52 } 53 } 54 if (screen.location.x() < 0 || screen.location.y() < 0) { 55 if (error_msg) 56 *error_msg = DeprecatedString::formatted("Screen #{} has invalid location: {}", i, screen.location); 57 return false; 58 } 59 if (screen.resolution.width() <= 0 || screen.resolution.height() <= 0) { 60 if (error_msg) 61 *error_msg = DeprecatedString::formatted("Screen #{} has invalid resolution: {}", i, screen.resolution); 62 return false; 63 } 64 if (screen.scale_factor < 1) { 65 if (error_msg) 66 *error_msg = DeprecatedString::formatted("Screen #{} has invalid scale factor: {}", i, screen.scale_factor); 67 return false; 68 } 69 if (i == 0 || screen.location.x() < smallest_x) 70 smallest_x = screen.location.x(); 71 if (i == 0 || screen.location.y() < smallest_y) 72 smallest_y = screen.location.y(); 73 } 74 if (smallest_x != 0 || smallest_y != 0) { 75 if (error_msg) 76 *error_msg = "Screen layout has not been normalized"; 77 return false; 78 } 79 Vector<Screen const*, 16> reachable_screens { &screens[main_screen_index] }; 80 bool did_reach_another_screen; 81 do { 82 did_reach_another_screen = false; 83 auto* latest_reachable_screen = reachable_screens[reachable_screens.size() - 1]; 84 for (auto& screen : screens) { 85 if (&screen == latest_reachable_screen || reachable_screens.contains_slow(&screen)) 86 continue; 87 if (screen.virtual_rect().is_adjacent(latest_reachable_screen->virtual_rect())) { 88 reachable_screens.append(&screen); 89 did_reach_another_screen = true; 90 break; 91 } 92 } 93 } while (did_reach_another_screen); 94 if (reachable_screens.size() != screens.size()) { 95 for (size_t i = 0; i < screens.size(); i++) { 96 auto& screen = screens[i]; 97 if (!reachable_screens.contains_slow(&screen)) { 98 if (error_msg) 99 *error_msg = DeprecatedString::formatted("Screen #{} {} cannot be reached from main screen #{} {}", i, screen.virtual_rect(), main_screen_index, screens[main_screen_index].virtual_rect()); 100 break; 101 } 102 } 103 return false; 104 } 105 return true; 106} 107 108bool ScreenLayout::normalize() 109{ 110 // Check for any overlaps and try to move screens 111 Vector<Gfx::IntRect, 8> screen_virtual_rects; 112 for (auto& screen : screens) 113 screen_virtual_rects.append(screen.virtual_rect()); 114 115 bool did_change = false; 116 for (;;) { 117 // Separate any overlapping screens 118 if (Gfx::IntRect::disperse(screen_virtual_rects)) { 119 did_change = true; 120 continue; 121 } 122 123 // Check if all screens are still reachable 124 Vector<Gfx::IntRect*, 8> reachable_rects; 125 126 auto recalculate_reachable = [&]() { 127 reachable_rects = { &screen_virtual_rects[main_screen_index] }; 128 bool did_reach_another; 129 do { 130 did_reach_another = false; 131 auto& latest_reachable_rect = *reachable_rects[reachable_rects.size() - 1]; 132 for (auto& rect : screen_virtual_rects) { 133 if (&rect == &latest_reachable_rect || reachable_rects.contains_slow(&rect)) 134 continue; 135 if (rect.is_adjacent(latest_reachable_rect)) { 136 reachable_rects.append(&rect); 137 did_reach_another = true; 138 break; 139 } 140 } 141 } while (did_reach_another); 142 }; 143 144 recalculate_reachable(); 145 if (reachable_rects.size() != screen_virtual_rects.size()) { 146 // Some screens were not reachable, try to move one somewhere closer 147 for (auto& screen_rect : screen_virtual_rects) { 148 if (reachable_rects.contains_slow(&screen_rect)) 149 continue; 150 151 float closest_distance = 0; 152 Gfx::IntRect* closest_rect = nullptr; 153 for (auto& screen_rect2 : screen_virtual_rects) { 154 if (&screen_rect2 == &screen_rect) 155 continue; 156 if (!reachable_rects.contains_slow(&screen_rect2)) 157 continue; 158 auto distance = screen_rect.outside_center_point_distance_to(screen_rect2); 159 if (!closest_rect || distance < closest_distance) { 160 closest_distance = distance; 161 closest_rect = &screen_rect2; 162 } 163 } 164 VERIFY(closest_rect); // We should always have one! 165 VERIFY(closest_rect != &screen_rect); 166 167 // Move the screen_rect closer to closest_rect 168 auto is_adjacent_to_reachable = [&]() { 169 for (auto* rect : reachable_rects) { 170 if (rect == &screen_rect) 171 continue; 172 if (screen_rect.is_adjacent(*rect)) 173 return true; 174 } 175 return false; 176 }; 177 178 // Move it until we're touching a reachable screen 179 do { 180 auto outside_center_points = screen_rect.closest_outside_center_points(*closest_rect); 181 int delta_x = 0; 182 if (outside_center_points[0].x() < outside_center_points[1].x()) 183 delta_x = 1; 184 else if (outside_center_points[0].x() > outside_center_points[1].x()) 185 delta_x = -1; 186 int delta_y = 0; 187 if (outside_center_points[0].y() < outside_center_points[1].y()) 188 delta_y = 1; 189 else if (outside_center_points[0].y() > outside_center_points[1].y()) 190 delta_y = -1; 191 VERIFY(delta_x != 0 || delta_y != 0); 192 screen_rect.translate_by(delta_x, delta_y); 193 } while (!is_adjacent_to_reachable()); 194 195 recalculate_reachable(); 196 did_change = true; 197 break; // We only try to move one at at time 198 } 199 200 // Moved the screen, re-evaluate 201 continue; 202 } 203 break; 204 } 205 206 int smallest_x = 0; 207 int smallest_y = 0; 208 for (size_t i = 0; i < screen_virtual_rects.size(); i++) { 209 auto& rect = screen_virtual_rects[i]; 210 if (i == 0 || rect.x() < smallest_x) 211 smallest_x = rect.x(); 212 if (i == 0 || rect.y() < smallest_y) 213 smallest_y = rect.y(); 214 } 215 if (smallest_x != 0 || smallest_y != 0) { 216 for (auto& rect : screen_virtual_rects) 217 rect.translate_by(-smallest_x, -smallest_y); 218 did_change = true; 219 } 220 221 for (size_t i = 0; i < screens.size(); i++) 222 screens[i].location = screen_virtual_rects[i].location(); 223 224 VERIFY(is_valid()); 225 return did_change; 226} 227 228bool ScreenLayout::load_config(Core::ConfigFile const& config_file, DeprecatedString* error_msg) 229{ 230 screens.clear_with_capacity(); 231 main_screen_index = config_file.read_num_entry("Screens", "MainScreen", 0); 232 for (size_t index = 0;; index++) { 233 auto group_name = DeprecatedString::formatted("Screen{}", index); 234 if (!config_file.has_group(group_name)) 235 break; 236 auto str_mode = config_file.read_entry(group_name, "Mode"); 237 Screen::Mode mode { Screen::Mode::Invalid }; 238 if (str_mode == "Device") { 239 mode = Screen::Mode::Device; 240 } else if (str_mode == "Virtual") { 241 mode = Screen::Mode::Virtual; 242 } 243 244 if (mode == Screen::Mode::Invalid) { 245 *error_msg = DeprecatedString::formatted("Invalid screen mode '{}'", str_mode); 246 *this = {}; 247 return false; 248 } 249 auto device = (mode == Screen::Mode::Device) ? config_file.read_entry(group_name, "Device") : Optional<DeprecatedString> {}; 250 screens.append({ mode, device, 251 { config_file.read_num_entry(group_name, "Left"), config_file.read_num_entry(group_name, "Top") }, 252 { config_file.read_num_entry(group_name, "Width"), config_file.read_num_entry(group_name, "Height") }, 253 config_file.read_num_entry(group_name, "ScaleFactor", 1) }); 254 } 255 if (!is_valid(error_msg)) { 256 *this = {}; 257 return false; 258 } 259 return true; 260} 261 262bool ScreenLayout::save_config(Core::ConfigFile& config_file, bool sync) const 263{ 264 config_file.write_num_entry("Screens", "MainScreen", main_screen_index); 265 266 size_t index = 0; 267 while (index < screens.size()) { 268 auto& screen = screens[index]; 269 auto group_name = DeprecatedString::formatted("Screen{}", index); 270 config_file.write_entry(group_name, "Mode", Screen::mode_to_string(screen.mode)); 271 if (screen.mode == Screen::Mode::Device) 272 config_file.write_entry(group_name, "Device", screen.device.value()); 273 config_file.write_num_entry(group_name, "Left", screen.location.x()); 274 config_file.write_num_entry(group_name, "Top", screen.location.y()); 275 config_file.write_num_entry(group_name, "Width", screen.resolution.width()); 276 config_file.write_num_entry(group_name, "Height", screen.resolution.height()); 277 config_file.write_num_entry(group_name, "ScaleFactor", screen.scale_factor); 278 index++; 279 } 280 // Prune screens no longer in the layout 281 for (;;) { 282 auto group_name = DeprecatedString::formatted("Screen{}", index++); 283 if (!config_file.has_group(group_name)) 284 break; 285 config_file.remove_group(group_name); 286 } 287 288 if (sync && config_file.sync().is_error()) 289 return false; 290 return true; 291} 292 293bool ScreenLayout::operator!=(ScreenLayout const& other) const 294{ 295 if (this == &other) 296 return false; 297 if (main_screen_index != other.main_screen_index) 298 return true; 299 if (screens.size() != other.screens.size()) 300 return true; 301 for (size_t i = 0; i < screens.size(); i++) { 302 if (screens[i] != other.screens[i]) 303 return true; 304 } 305 return false; 306} 307 308bool ScreenLayout::try_auto_add_display_connector(DeprecatedString const& device_path) 309{ 310 int display_connector_fd = open(device_path.characters(), O_RDWR | O_CLOEXEC); 311 if (display_connector_fd < 0) { 312 int err = errno; 313 dbgln("Error ({}) opening display connector device {}", err, device_path); 314 return false; 315 } 316 ScopeGuard fd_guard([&] { 317 close(display_connector_fd); 318 }); 319 320 GraphicsHeadModeSetting mode_setting {}; 321 memset(&mode_setting, 0, sizeof(GraphicsHeadModeSetting)); 322 if (graphics_connector_get_head_mode_setting(display_connector_fd, &mode_setting) < 0) { 323 int err = errno; 324 dbgln("Error ({}) querying resolution from display connector device {}", err, device_path); 325 return false; 326 } 327 if (mode_setting.horizontal_active == 0 || mode_setting.vertical_active == 0) { 328 // Looks like the display is not turned on. Since we don't know what the desired 329 // resolution should be, use the main display as reference. 330 if (screens.is_empty()) 331 return false; 332 auto& main_screen = screens[main_screen_index]; 333 mode_setting.horizontal_active = main_screen.resolution.width(); 334 mode_setting.vertical_active = main_screen.resolution.height(); 335 } 336 337 auto append_screen = [&](Gfx::IntRect const& new_screen_rect) { 338 screens.append({ .mode = Screen::Mode::Device, 339 .device = device_path, 340 .location = new_screen_rect.location(), 341 .resolution = new_screen_rect.size(), 342 .scale_factor = 1 }); 343 }; 344 345 if (screens.is_empty()) { 346 append_screen({ 0, 0, mode_setting.horizontal_active, mode_setting.vertical_active }); 347 return true; 348 } 349 350 auto original_screens = move(screens); 351 screens = original_screens; 352 ArmedScopeGuard screens_guard([&] { 353 screens = move(original_screens); 354 }); 355 356 // Now that we know the current resolution, try to find a location that we can add onto 357 // TODO: make this a little more sophisticated in case a more complex layout is already configured 358 for (auto& screen : screens) { 359 auto screen_rect = screen.virtual_rect(); 360 Gfx::IntRect new_screen_rect { 361 screen_rect.right() + 1, 362 screen_rect.top(), 363 (int)mode_setting.horizontal_active, 364 (int)mode_setting.vertical_active 365 }; 366 367 bool collision = false; 368 for (auto& other_screen : screens) { 369 if (&screen == &other_screen) 370 continue; 371 if (other_screen.virtual_rect().intersects(new_screen_rect)) { 372 collision = true; 373 break; 374 } 375 } 376 377 if (!collision) { 378 append_screen(new_screen_rect); 379 if (is_valid()) { 380 // We got lucky! 381 screens_guard.disarm(); 382 return true; 383 } 384 } 385 } 386 387 dbgln("Failed to add display connector device {} with resolution {}x{} to screen layout", device_path, mode_setting.horizontal_active, mode_setting.vertical_active); 388 return false; 389} 390 391} 392 393namespace IPC { 394 395template<> 396ErrorOr<void> encode(Encoder& encoder, WindowServer::ScreenLayout::Screen const& screen) 397{ 398 TRY(encoder.encode(screen.mode)); 399 TRY(encoder.encode(screen.device)); 400 TRY(encoder.encode(screen.location)); 401 TRY(encoder.encode(screen.resolution)); 402 TRY(encoder.encode(screen.scale_factor)); 403 404 return {}; 405} 406 407template<> 408ErrorOr<WindowServer::ScreenLayout::Screen> decode(Decoder& decoder) 409{ 410 auto mode = TRY(decoder.decode<WindowServer::ScreenLayout::Screen::Mode>()); 411 auto device = TRY(decoder.decode<Optional<DeprecatedString>>()); 412 auto location = TRY(decoder.decode<Gfx::IntPoint>()); 413 auto resolution = TRY(decoder.decode<Gfx::IntSize>()); 414 auto scale_factor = TRY(decoder.decode<int>()); 415 416 return WindowServer::ScreenLayout::Screen { mode, device, location, resolution, scale_factor }; 417} 418 419template<> 420ErrorOr<void> encode(Encoder& encoder, WindowServer::ScreenLayout const& screen_layout) 421{ 422 TRY(encoder.encode(screen_layout.screens)); 423 TRY(encoder.encode(screen_layout.main_screen_index)); 424 425 return {}; 426} 427 428template<> 429ErrorOr<WindowServer::ScreenLayout> decode(Decoder& decoder) 430{ 431 auto screens = TRY(decoder.decode<Vector<WindowServer::ScreenLayout::Screen>>()); 432 auto main_screen_index = TRY(decoder.decode<unsigned>()); 433 434 return WindowServer::ScreenLayout { move(screens), main_screen_index }; 435} 436 437}