Serenity Operating System
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}