1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/display/cros_display_config.h"
6
7 #include <utility>
8
9 #include "ash/display/display_alignment_controller.h"
10 #include "ash/display/display_configuration_controller.h"
11 #include "ash/display/display_highlight_controller.h"
12 #include "ash/display/display_prefs.h"
13 #include "ash/display/overscan_calibrator.h"
14 #include "ash/display/resolution_notification_controller.h"
15 #include "ash/display/screen_orientation_controller.h"
16 #include "ash/display/touch_calibrator_controller.h"
17 #include "ash/display/window_tree_host_manager.h"
18 #include "ash/public/cpp/ash_features.h"
19 #include "ash/public/cpp/tablet_mode_observer.h"
20 #include "ash/public/mojom/cros_display_config.mojom.h"
21 #include "ash/shell.h"
22 #include "ash/touch/ash_touch_transform_controller.h"
23 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
24 #include "base/bind.h"
25 #include "base/optional.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "mojo/public/cpp/bindings/associated_remote.h"
28 #include "ui/display/display.h"
29 #include "ui/display/display_layout.h"
30 #include "ui/display/display_layout_builder.h"
31 #include "ui/display/display_observer.h"
32 #include "ui/display/manager/display_manager.h"
33 #include "ui/display/manager/display_util.h"
34 #include "ui/display/mojom/display_mojom_traits.h"
35 #include "ui/display/screen.h"
36
37 namespace ash {
38
39 namespace {
40
41 // Maximum allowed bounds origin absolute value.
42 constexpr int kMaxBoundsOrigin = 200 * 1000;
43
44 // This is the default range of display width in logical pixels allowed after
45 // applying display zoom.
46 constexpr int kDefaultMaxZoomWidth = 4096;
47 constexpr int kDefaultMinZoomWidth = 640;
48
GetDisplayManager()49 display::DisplayManager* GetDisplayManager() {
50 return Shell::Get()->display_manager();
51 }
52
GetDisplayId(const std::string & display_id_str)53 int64_t GetDisplayId(const std::string& display_id_str) {
54 int64_t display_id;
55 if (!base::StringToInt64(display_id_str, &display_id))
56 display_id = display::kInvalidDisplayId;
57 return display_id;
58 }
59
60 // Gets the display with the provided string id.
GetDisplay(const std::string & display_id_str)61 display::Display GetDisplay(const std::string& display_id_str) {
62 int64_t display_id = GetDisplayId(display_id_str);
63 if (display_id == display::kInvalidDisplayId)
64 return display::Display();
65 display::DisplayManager* display_manager = GetDisplayManager();
66 if (display_manager->IsInUnifiedMode() &&
67 display_id != display::kUnifiedDisplayId) {
68 // In unified desktop mode, the mirroring displays, which constitue the
69 // combined unified display, are contained in the software mirroring
70 // displays list in the display manager.
71 // The active list of displays only contains a single unified display entry
72 // with id |kUnifiedDisplayId|.
73 return display_manager->GetMirroringDisplayById(display_id);
74 }
75 return display_manager->GetDisplayForId(display_id);
76 }
77
GetMojomDisplayLayoutPosition(display::DisplayPlacement::Position position)78 mojom::DisplayLayoutPosition GetMojomDisplayLayoutPosition(
79 display::DisplayPlacement::Position position) {
80 switch (position) {
81 case display::DisplayPlacement::TOP:
82 return mojom::DisplayLayoutPosition::kTop;
83 case display::DisplayPlacement::RIGHT:
84 return mojom::DisplayLayoutPosition::kRight;
85 case display::DisplayPlacement::BOTTOM:
86 return mojom::DisplayLayoutPosition::kBottom;
87 case display::DisplayPlacement::LEFT:
88 return mojom::DisplayLayoutPosition::kLeft;
89 }
90 NOTREACHED();
91 return mojom::DisplayLayoutPosition::kLeft;
92 }
93
GetDisplayPlacementPosition(mojom::DisplayLayoutPosition position)94 display::DisplayPlacement::Position GetDisplayPlacementPosition(
95 mojom::DisplayLayoutPosition position) {
96 switch (position) {
97 case mojom::DisplayLayoutPosition::kTop:
98 return display::DisplayPlacement::TOP;
99 case mojom::DisplayLayoutPosition::kRight:
100 return display::DisplayPlacement::RIGHT;
101 case mojom::DisplayLayoutPosition::kBottom:
102 return display::DisplayPlacement::BOTTOM;
103 case mojom::DisplayLayoutPosition::kLeft:
104 return display::DisplayPlacement::LEFT;
105 }
106 NOTREACHED();
107 return display::DisplayPlacement::LEFT;
108 }
109
GetDisplayLayouts()110 std::vector<mojom::DisplayLayoutPtr> GetDisplayLayouts() {
111 auto layouts = std::vector<mojom::DisplayLayoutPtr>();
112 display::Screen* screen = display::Screen::GetScreen();
113 const std::vector<display::Display>& displays = screen->GetAllDisplays();
114 display::DisplayManager* display_manager = GetDisplayManager();
115 for (const display::Display& display : displays) {
116 const display::DisplayPlacement placement =
117 display_manager->GetCurrentResolvedDisplayLayout().FindPlacementById(
118 display.id());
119 if (placement.display_id == display::kInvalidDisplayId)
120 continue;
121 auto layout = mojom::DisplayLayout::New();
122 layout->id = base::NumberToString(placement.display_id);
123 layout->parent_id = base::NumberToString(placement.parent_display_id);
124 layout->position = GetMojomDisplayLayoutPosition(placement.position);
125 layout->offset = placement.offset;
126 layouts.emplace_back(std::move(layout));
127 }
128 return layouts;
129 }
130
GetDisplayUnifiedLayouts()131 std::vector<mojom::DisplayLayoutPtr> GetDisplayUnifiedLayouts() {
132 auto layouts = std::vector<mojom::DisplayLayoutPtr>();
133 display::DisplayManager* display_manager = GetDisplayManager();
134
135 const display::UnifiedDesktopLayoutMatrix& matrix =
136 display_manager->current_unified_desktop_matrix();
137 for (size_t row_index = 0; row_index < matrix.size(); ++row_index) {
138 const auto& row = matrix[row_index];
139 for (size_t column_index = 0; column_index < row.size(); ++column_index) {
140 if (column_index == 0 && row_index == 0) {
141 // No placement for the primary display.
142 continue;
143 }
144 auto layout = mojom::DisplayLayout::New();
145 const int64_t display_id = row[column_index];
146 // Parent display is either the one in the above row, or the one on the
147 // left in the same row.
148 const int64_t parent_id = column_index == 0
149 ? matrix[row_index - 1][column_index]
150 : row[column_index - 1];
151 layout->id = base::NumberToString(display_id);
152 layout->parent_id = base::NumberToString(parent_id);
153 layout->position = column_index == 0
154 ? mojom::DisplayLayoutPosition::kBottom
155 : mojom::DisplayLayoutPosition::kRight;
156 layout->offset = 0;
157 layouts.emplace_back(std::move(layout));
158 }
159 }
160 return layouts;
161 }
162
SetDisplayLayoutMode(const mojom::DisplayLayoutInfo & info)163 mojom::DisplayConfigResult SetDisplayLayoutMode(
164 const mojom::DisplayLayoutInfo& info) {
165 display::DisplayManager* display_manager = GetDisplayManager();
166 if (info.layout_mode == mojom::DisplayLayoutMode::kNormal) {
167 display_manager->SetDefaultMultiDisplayModeForCurrentDisplays(
168 display::DisplayManager::EXTENDED);
169 display_manager->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
170 return mojom::DisplayConfigResult::kSuccess;
171 }
172
173 if (info.layout_mode == mojom::DisplayLayoutMode::kUnified) {
174 if (!display_manager->unified_desktop_enabled())
175 return mojom::DisplayConfigResult::kUnifiedNotEnabledError;
176 display_manager->SetDefaultMultiDisplayModeForCurrentDisplays(
177 display::DisplayManager::UNIFIED);
178 display_manager->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
179 return mojom::DisplayConfigResult::kSuccess;
180 }
181
182 DCHECK(info.layout_mode == mojom::DisplayLayoutMode::kMirrored);
183
184 // 'Normal' mirror mode.
185 if (!info.mirror_source_id) {
186 display_manager->SetMirrorMode(display::MirrorMode::kNormal, base::nullopt);
187 return mojom::DisplayConfigResult::kSuccess;
188 }
189
190 // 'Mixed' mirror mode.
191 display::Display source = GetDisplay(*info.mirror_source_id);
192 if (source.id() == display::kInvalidDisplayId)
193 return mojom::DisplayConfigResult::kMirrorModeSourceIdError;
194 display::DisplayIdList destination_ids;
195 if (info.mirror_destination_ids) {
196 for (const std::string& id_str : *info.mirror_destination_ids) {
197 int64_t destination_id = GetDisplayId(id_str);
198 if (destination_id == display::kInvalidDisplayId)
199 return mojom::DisplayConfigResult::kMirrorModeDestIdError;
200 destination_ids.emplace_back(destination_id);
201 }
202 } else {
203 const std::vector<display::Display>& displays =
204 display::Screen::GetScreen()->GetAllDisplays();
205 for (const display::Display& display : displays)
206 destination_ids.emplace_back(display.id());
207 }
208 base::Optional<display::MixedMirrorModeParams> mixed_params(
209 base::in_place, source.id(), destination_ids);
210 const display::MixedMirrorModeParamsErrors error_type =
211 display::ValidateParamsForMixedMirrorMode(
212 display_manager->GetCurrentDisplayIdList(), *mixed_params);
213 switch (error_type) {
214 case display::MixedMirrorModeParamsErrors::kErrorSingleDisplay:
215 return mojom::DisplayConfigResult::kMirrorModeSingleDisplayError;
216 case display::MixedMirrorModeParamsErrors::kErrorSourceIdNotFound:
217 return mojom::DisplayConfigResult::kMirrorModeSourceIdError;
218 case display::MixedMirrorModeParamsErrors::kErrorDestinationIdsEmpty:
219 case display::MixedMirrorModeParamsErrors::kErrorDestinationIdNotFound:
220 case display::MixedMirrorModeParamsErrors::kErrorDuplicateId:
221 return mojom::DisplayConfigResult::kMirrorModeDestIdError;
222 case display::MixedMirrorModeParamsErrors::kSuccess:
223 break;
224 }
225 display_manager->SetMirrorMode(display::MirrorMode::kMixed, mixed_params);
226 return mojom::DisplayConfigResult::kSuccess;
227 }
228
GetDisplayMode(const display::ManagedDisplayInfo & display_info,const display::ManagedDisplayMode & display_mode)229 mojom::DisplayModePtr GetDisplayMode(
230 const display::ManagedDisplayInfo& display_info,
231 const display::ManagedDisplayMode& display_mode) {
232 auto result = mojom::DisplayMode::New();
233 gfx::Size size_dip = display_mode.GetSizeInDIP();
234 result->size = size_dip;
235 result->size_in_native_pixels = display_mode.size();
236 result->device_scale_factor = display_mode.device_scale_factor();
237 result->refresh_rate = display_mode.refresh_rate();
238 result->is_native = display_mode.native();
239 result->is_interlaced = display_mode.is_interlaced();
240 return result;
241 }
242
DisplayRotationFromRotationOptions(mojom::DisplayRotationOptions option)243 display::Display::Rotation DisplayRotationFromRotationOptions(
244 mojom::DisplayRotationOptions option) {
245 switch (option) {
246 case mojom::DisplayRotationOptions::kAutoRotate:
247 return display::Display::ROTATE_0;
248
249 case mojom::DisplayRotationOptions::kZeroDegrees:
250 return display::Display::ROTATE_0;
251
252 case mojom::DisplayRotationOptions::k90Degrees:
253 return display::Display::ROTATE_90;
254
255 case mojom::DisplayRotationOptions::k180Degrees:
256 return display::Display::ROTATE_180;
257
258 case mojom::DisplayRotationOptions::k270Degrees:
259 return display::Display::ROTATE_270;
260 }
261 }
262
RotationOptionsFromDisplayRotation(display::Display::Rotation rotation)263 mojom::DisplayRotationOptions RotationOptionsFromDisplayRotation(
264 display::Display::Rotation rotation) {
265 const bool is_in_tablet_physical_state =
266 Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
267 const bool is_auto_rotate_enabled =
268 !Shell::Get()->screen_orientation_controller()->user_rotation_locked();
269 if (is_in_tablet_physical_state && is_auto_rotate_enabled)
270 return mojom::DisplayRotationOptions::kAutoRotate;
271
272 switch (rotation) {
273 case display::Display::ROTATE_0:
274 return mojom::DisplayRotationOptions::kZeroDegrees;
275
276 case display::Display::ROTATE_90:
277 return mojom::DisplayRotationOptions::k90Degrees;
278
279 case display::Display::ROTATE_180:
280 return mojom::DisplayRotationOptions::k180Degrees;
281
282 case display::Display::ROTATE_270:
283 return mojom::DisplayRotationOptions::k270Degrees;
284 }
285 }
286
GetDisplayUnitInfo(const display::Display & display,int64_t primary_id)287 mojom::DisplayUnitInfoPtr GetDisplayUnitInfo(const display::Display& display,
288 int64_t primary_id) {
289 display::DisplayManager* display_manager = GetDisplayManager();
290 const display::ManagedDisplayInfo& display_info =
291 display_manager->GetDisplayInfo(display.id());
292
293 auto info = mojom::DisplayUnitInfo::New();
294 info->id = base::NumberToString(display.id());
295 info->name = display_manager->GetDisplayNameForId(display.id());
296
297 if (!display_info.manufacturer_id().empty() ||
298 !display_info.product_id().empty() ||
299 (display_info.year_of_manufacture() !=
300 display::kInvalidYearOfManufacture)) {
301 info->edid = mojom::Edid::New();
302 info->edid->manufacturer_id = display_info.manufacturer_id();
303 info->edid->product_id = display_info.product_id();
304 info->edid->year_of_manufacture = display_info.year_of_manufacture();
305 }
306
307 info->is_primary = display.id() == primary_id;
308 info->is_internal = display.IsInternal();
309 info->is_enabled = true;
310 info->is_in_tablet_physical_state =
311 Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
312 const bool has_accelerometer_support =
313 display.accelerometer_support() ==
314 display::Display::AccelerometerSupport::AVAILABLE;
315 info->has_touch_support =
316 display.touch_support() == display::Display::TouchSupport::AVAILABLE;
317 info->has_accelerometer_support = has_accelerometer_support;
318
319 const float device_dpi = display_info.device_dpi();
320 info->dpi_x = device_dpi * display.size().width() /
321 display_info.bounds_in_native().width();
322 info->dpi_y = device_dpi * display.size().height() /
323 display_info.bounds_in_native().height();
324
325 info->rotation_options =
326 RotationOptionsFromDisplayRotation(display.rotation());
327 info->bounds = display.bounds();
328 info->overscan = display_manager->GetOverscanInsets(display.id());
329 info->work_area = display.work_area();
330
331 int display_mode_index = 0;
332 display::ManagedDisplayMode active_mode;
333 bool has_active_mode = display_manager->GetActiveModeForDisplayId(
334 display_info.id(), &active_mode);
335 for (const display::ManagedDisplayMode& display_mode :
336 display_info.display_modes()) {
337 info->available_display_modes.emplace_back(
338 GetDisplayMode(display_info, display_mode));
339 if (has_active_mode && display_mode.IsEquivalent(active_mode))
340 info->selected_display_mode_index = display_mode_index;
341 ++display_mode_index;
342 }
343
344 info->display_zoom_factor = display_info.zoom_factor();
345 if (has_active_mode) {
346 auto zoom_levels = display::GetDisplayZoomFactors(active_mode);
347 // Ensure that the current zoom factor is in the list.
348 display::InsertDsfIntoList(&zoom_levels, display_info.zoom_factor());
349 info->available_display_zoom_factors.assign(zoom_levels.begin(),
350 zoom_levels.end());
351
352 } else {
353 info->available_display_zoom_factors.push_back(display_info.zoom_factor());
354 }
355
356 return info;
357 }
358
359 // Validates that DisplayProperties are valid with the current DisplayManager
360 // configuration. Returns an error on failure.
ValidateDisplayProperties(const mojom::DisplayConfigProperties & properties,const display::Display & display)361 mojom::DisplayConfigResult ValidateDisplayProperties(
362 const mojom::DisplayConfigProperties& properties,
363 const display::Display& display) {
364 display::DisplayManager* display_manager = GetDisplayManager();
365
366 int64_t id = display.id();
367 if (id == display::kInvalidDisplayId)
368 return mojom::DisplayConfigResult::kInvalidDisplayIdError;
369
370 // Overscan cannot be changed for the internal display, and should be at most
371 // half of the screen size.
372 if (properties.overscan) {
373 if (display.IsInternal())
374 return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError;
375
376 if (properties.overscan->left() < 0 || properties.overscan->top() < 0 ||
377 properties.overscan->right() < 0 || properties.overscan->bottom() < 0) {
378 DLOG(ERROR) << "Negative overscan";
379 return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
380 }
381
382 const gfx::Insets overscan = display_manager->GetOverscanInsets(id);
383 int screen_width = display.bounds().width() + overscan.width();
384 int screen_height = display.bounds().height() + overscan.height();
385
386 if ((properties.overscan->left() + properties.overscan->right()) * 2 >
387 screen_width ||
388 (properties.overscan->top() + properties.overscan->bottom()) * 2 >
389 screen_height) {
390 DLOG(ERROR) << "Overscan: " << properties.overscan->ToString()
391 << " exceeds bounds: " << screen_width << "x"
392 << screen_height;
393 return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
394 }
395 }
396
397 // The bounds cannot be changed for the primary display and should be inside
398 // a reasonable bounds.
399 if (properties.bounds_origin) {
400 const display::Display& primary =
401 display::Screen::GetScreen()->GetPrimaryDisplay();
402 if (id == primary.id() || properties.set_primary)
403 return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError;
404 if (properties.bounds_origin->x() > kMaxBoundsOrigin ||
405 properties.bounds_origin->x() < -kMaxBoundsOrigin ||
406 properties.bounds_origin->y() > kMaxBoundsOrigin ||
407 properties.bounds_origin->y() < -kMaxBoundsOrigin) {
408 DLOG(ERROR) << "Bounds origin out of range";
409 return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
410 }
411 }
412
413 if (properties.display_zoom_factor > 0) {
414 display::ManagedDisplayMode current_mode;
415 if (!display_manager->GetActiveModeForDisplayId(id, ¤t_mode)) {
416 DLOG(ERROR) << "No active mode for display: " << id;
417 return mojom::DisplayConfigResult::kInvalidDisplayIdError;
418 }
419 // This check is added to limit the range of display zoom that can be
420 // applied via the system display API. The said range is such that when a
421 // display zoom is applied, the final logical width in pixels should lie
422 // within the range of 640 pixels and 4096 pixels.
423 const int landscape_width =
424 std::max(current_mode.size().width(), current_mode.size().height());
425 const int max_allowed_width =
426 std::max(kDefaultMaxZoomWidth, landscape_width);
427 const int min_allowed_width =
428 std::min(kDefaultMinZoomWidth, landscape_width);
429 int current_width = static_cast<float>(landscape_width) /
430 current_mode.device_scale_factor();
431 if (current_width / properties.display_zoom_factor > max_allowed_width ||
432 current_width / properties.display_zoom_factor < min_allowed_width) {
433 DLOG(ERROR) << "Display zoom factor out of range";
434 return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError;
435 }
436 }
437
438 return mojom::DisplayConfigResult::kSuccess;
439 }
440
441 // Sets the display layout for the target display in reference to the primary
442 // display.
SetDisplayLayoutFromBounds(const gfx::Rect & primary_display_bounds,int64_t primary_display_id,const gfx::Rect & target_display_bounds,int64_t target_display_id)443 void SetDisplayLayoutFromBounds(const gfx::Rect& primary_display_bounds,
444 int64_t primary_display_id,
445 const gfx::Rect& target_display_bounds,
446 int64_t target_display_id) {
447 display::DisplayPlacement placement(
448 display::DisplayLayout::CreatePlacementForRectangles(
449 primary_display_bounds, target_display_bounds));
450 placement.display_id = target_display_id;
451 placement.parent_display_id = primary_display_id;
452
453 std::unique_ptr<display::DisplayLayout> layout(new display::DisplayLayout);
454 layout->placement_list.push_back(placement);
455 layout->primary_id = primary_display_id;
456
457 Shell::Get()->display_configuration_controller()->SetDisplayLayout(
458 std::move(layout));
459 }
460
461 // Attempts to set the display mode for display |id|.
SetDisplayMode(int64_t id,const mojom::DisplayMode & display_mode,mojom::DisplayConfigSource source)462 mojom::DisplayConfigResult SetDisplayMode(
463 int64_t id,
464 const mojom::DisplayMode& display_mode,
465 mojom::DisplayConfigSource source) {
466 display::DisplayManager* display_manager = GetDisplayManager();
467
468 display::ManagedDisplayMode current_mode;
469 if (!display_manager->GetActiveModeForDisplayId(id, ¤t_mode))
470 return mojom::DisplayConfigResult::kInvalidDisplayIdError;
471
472 display::ManagedDisplayMode new_mode(
473 display_mode.size_in_native_pixels, display_mode.refresh_rate,
474 display_mode.is_interlaced, display_mode.is_native,
475 display_mode.device_scale_factor);
476
477 if (!new_mode.IsEquivalent(current_mode)) {
478 // For the internal display, the display mode will be applied directly.
479 // Otherwise a confirm/revert notification will be prepared first, and the
480 // display mode will be applied. If the user accepts the mode change by
481 // dismissing the notification, MaybeStoreDisplayPrefs() will be called back
482 // to persist the new preferences.
483 if (!Shell::Get()
484 ->resolution_notification_controller()
485 ->PrepareNotificationAndSetDisplayMode(
486 id, current_mode, new_mode, source, base::BindOnce([]() {
487 Shell::Get()->display_prefs()->MaybeStoreDisplayPrefs();
488 }))) {
489 return mojom::DisplayConfigResult::kSetDisplayModeError;
490 }
491 }
492
493 return mojom::DisplayConfigResult::kSuccess;
494 }
495
GetCalibrationPair(const mojom::TouchCalibrationPair & pair)496 display::TouchCalibrationData::CalibrationPointPair GetCalibrationPair(
497 const mojom::TouchCalibrationPair& pair) {
498 return std::make_pair(pair.display_point, pair.touch_point);
499 }
500
501 } // namespace
502
503 // -----------------------------------------------------------------------------
504 // CrosDisplayConfig::ObserverImpl:
505
506 // Observes display and tablet mode events, and notifies the
507 // CrosDisplayConfigObservers with OnDisplayConfigChanged() in response to those
508 // events.
509 class CrosDisplayConfig::ObserverImpl
510 : public display::DisplayObserver,
511 public TabletModeObserver,
512 public ScreenOrientationController::Observer {
513 public:
ObserverImpl()514 explicit ObserverImpl() {
515 display::Screen::GetScreen()->AddObserver(this);
516 Shell::Get()->tablet_mode_controller()->AddObserver(this);
517 Shell::Get()->screen_orientation_controller()->AddObserver(this);
518 }
519
~ObserverImpl()520 ~ObserverImpl() override {
521 Shell::Get()->screen_orientation_controller()->RemoveObserver(this);
522 Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
523 display::Screen::GetScreen()->RemoveObserver(this);
524 }
525
AddObserver(mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer)526 void AddObserver(
527 mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver>
528 observer) {
529 observers_.Add(mojo::AssociatedRemote<mojom::CrosDisplayConfigObserver>(
530 std::move(observer)));
531 }
532
533 // display::DisplayObserver:
OnDisplayAdded(const display::Display & new_display)534 void OnDisplayAdded(const display::Display& new_display) override {
535 NotifyObserversDisplayConfigChanged();
536 }
537
OnDisplayRemoved(const display::Display & old_display)538 void OnDisplayRemoved(const display::Display& old_display) override {
539 NotifyObserversDisplayConfigChanged();
540 }
541
OnDisplayMetricsChanged(const display::Display & display,uint32_t metrics)542 void OnDisplayMetricsChanged(const display::Display& display,
543 uint32_t metrics) override {
544 NotifyObserversDisplayConfigChanged();
545 }
546
547 // TabletModeObserver:
OnTabletPhysicalStateChanged()548 void OnTabletPhysicalStateChanged() override {
549 NotifyObserversDisplayConfigChanged();
550 }
551
552 // ScreenOrientationController::Observer:
OnUserRotationLockChanged()553 void OnUserRotationLockChanged() override {
554 NotifyObserversDisplayConfigChanged();
555 }
556
557 private:
NotifyObserversDisplayConfigChanged()558 void NotifyObserversDisplayConfigChanged() {
559 for (auto& observer : observers_)
560 observer->OnDisplayConfigChanged();
561 }
562
563 mojo::AssociatedRemoteSet<mojom::CrosDisplayConfigObserver> observers_;
564
565 DISALLOW_COPY_AND_ASSIGN(ObserverImpl);
566 };
567
568 // -----------------------------------------------------------------------------
569 // CrosDisplayConfig:
570
CrosDisplayConfig()571 CrosDisplayConfig::CrosDisplayConfig()
572 : observer_impl_(std::make_unique<ObserverImpl>()) {}
573
574 CrosDisplayConfig::~CrosDisplayConfig() = default;
575
BindReceiver(mojo::PendingReceiver<mojom::CrosDisplayConfigController> receiver)576 void CrosDisplayConfig::BindReceiver(
577 mojo::PendingReceiver<mojom::CrosDisplayConfigController> receiver) {
578 receivers_.Add(this, std::move(receiver));
579 }
580
AddObserver(mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer)581 void CrosDisplayConfig::AddObserver(
582 mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer) {
583 observer_impl_->AddObserver(std::move(observer));
584 }
585
GetDisplayLayoutInfo(GetDisplayLayoutInfoCallback callback)586 void CrosDisplayConfig::GetDisplayLayoutInfo(
587 GetDisplayLayoutInfoCallback callback) {
588 display::DisplayManager* display_manager = GetDisplayManager();
589
590 auto info = mojom::DisplayLayoutInfo::New();
591 if (display_manager->IsInUnifiedMode()) {
592 info->layout_mode = mojom::DisplayLayoutMode::kUnified;
593 } else if (display_manager->IsInMirrorMode()) {
594 info->layout_mode = mojom::DisplayLayoutMode::kMirrored;
595 info->mirror_source_id =
596 base::NumberToString(display_manager->mirroring_source_id());
597 info->mirror_destination_ids = std::vector<std::string>();
598 for (int64_t id : display_manager->GetMirroringDestinationDisplayIdList())
599 info->mirror_destination_ids->emplace_back(base::NumberToString(id));
600 } else {
601 info->layout_mode = mojom::DisplayLayoutMode::kNormal;
602 }
603
604 if (display_manager->IsInUnifiedMode()) {
605 info->layouts = GetDisplayUnifiedLayouts();
606 } else if (display_manager->num_connected_displays() > 1) {
607 info->layouts = GetDisplayLayouts();
608 }
609
610 std::move(callback).Run(std::move(info));
611 }
612
SetDisplayLayouts(const std::vector<mojom::DisplayLayoutPtr> & layouts)613 mojom::DisplayConfigResult SetDisplayLayouts(
614 const std::vector<mojom::DisplayLayoutPtr>& layouts) {
615 display::DisplayManager* display_manager = GetDisplayManager();
616 display::DisplayLayoutBuilder builder(
617 display_manager->GetCurrentResolvedDisplayLayout());
618 int64_t root_id = display::kInvalidDisplayId;
619 std::set<int64_t> layout_ids;
620 builder.ClearPlacements();
621 for (const mojom::DisplayLayoutPtr& layout_ptr : layouts) {
622 const mojom::DisplayLayout& layout = *layout_ptr;
623 display::Display display = GetDisplay(layout.id);
624 if (display.id() == display::kInvalidDisplayId) {
625 LOG(ERROR) << "Display layout has invalid id: " << layout.id;
626 return mojom::DisplayConfigResult::kInvalidDisplayIdError;
627 }
628 display::Display parent = GetDisplay(layout.parent_id);
629 if (parent.id() == display::kInvalidDisplayId) {
630 if (root_id != display::kInvalidDisplayId) {
631 LOG(ERROR) << "Display layout has invalid parent: " << layout.parent_id;
632 return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
633 }
634 root_id = display.id();
635 continue; // No placement for root (primary) display.
636 }
637 layout_ids.insert(display.id());
638 display::DisplayPlacement::Position position =
639 GetDisplayPlacementPosition(layout.position);
640 builder.AddDisplayPlacement(display.id(), parent.id(), position,
641 layout.offset);
642 }
643
644 const display::DisplayIdList display_ids =
645 display_manager->GetCurrentDisplayIdList();
646 std::unique_ptr<display::DisplayLayout> layout = builder.Build();
647 if (display_manager->IsInUnifiedMode()) {
648 if (root_id == display::kInvalidDisplayId) {
649 // Look for a display with no layout info to use as the root.
650 for (int64_t id : display_ids) {
651 if (!base::Contains(layout_ids, id)) {
652 root_id = id;
653 break;
654 }
655 }
656 if (root_id == display::kInvalidDisplayId) {
657 LOG(ERROR) << "Invalid unified layout: No root id";
658 return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
659 }
660 }
661 layout->primary_id = root_id;
662 display::UnifiedDesktopLayoutMatrix matrix;
663 if (!display::BuildUnifiedDesktopMatrix(display_ids, *layout, &matrix)) {
664 LOG(ERROR) << "Invalid unified layout: No proper conversion to a matrix";
665 return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
666 }
667 Shell::Get()
668 ->display_configuration_controller()
669 ->SetUnifiedDesktopLayoutMatrix(matrix);
670 } else {
671 if (!display::DisplayLayout::Validate(display_ids, *layout)) {
672 return mojom::DisplayConfigResult::kInvalidDisplayLayoutError;
673 }
674 Shell::Get()->display_configuration_controller()->SetDisplayLayout(
675 std::move(layout));
676 }
677 return mojom::DisplayConfigResult::kSuccess;
678 }
679
SetDisplayLayoutInfo(mojom::DisplayLayoutInfoPtr info,SetDisplayLayoutInfoCallback callback)680 void CrosDisplayConfig::SetDisplayLayoutInfo(
681 mojom::DisplayLayoutInfoPtr info,
682 SetDisplayLayoutInfoCallback callback) {
683 mojom::DisplayConfigResult result = SetDisplayLayoutMode(*info);
684 if (result != mojom::DisplayConfigResult::kSuccess) {
685 std::move(callback).Run(result);
686 return;
687 }
688 if (info->layouts) {
689 result = SetDisplayLayouts(*info->layouts);
690 if (result != mojom::DisplayConfigResult::kSuccess) {
691 std::move(callback).Run(result);
692 return;
693 }
694 }
695 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
696 }
697
GetDisplayUnitInfoList(bool single_unified,GetDisplayUnitInfoListCallback callback)698 void CrosDisplayConfig::GetDisplayUnitInfoList(
699 bool single_unified,
700 GetDisplayUnitInfoListCallback callback) {
701 std::vector<mojom::DisplayUnitInfoPtr> info_list;
702 display::DisplayManager* display_manager = GetDisplayManager();
703
704 std::vector<display::Display> displays;
705 int64_t primary_id;
706 if (!display_manager->IsInUnifiedMode()) {
707 displays = display::Screen::GetScreen()->GetAllDisplays();
708 primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
709 } else if (single_unified) {
710 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i)
711 displays.push_back(display_manager->GetDisplayAt(i));
712 primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
713 } else {
714 displays = display_manager->software_mirroring_display_list();
715 primary_id = Shell::Get()
716 ->display_configuration_controller()
717 ->GetPrimaryMirroringDisplayForUnifiedDesktop()
718 .id();
719 }
720
721 for (const display::Display& display : displays)
722 info_list.emplace_back(GetDisplayUnitInfo(display, primary_id));
723 std::move(callback).Run(std::move(info_list));
724 }
725
SetDisplayProperties(const std::string & id,mojom::DisplayConfigPropertiesPtr properties,mojom::DisplayConfigSource source,SetDisplayPropertiesCallback callback)726 void CrosDisplayConfig::SetDisplayProperties(
727 const std::string& id,
728 mojom::DisplayConfigPropertiesPtr properties,
729 mojom::DisplayConfigSource source,
730 SetDisplayPropertiesCallback callback) {
731 const display::Display display = GetDisplay(id);
732 mojom::DisplayConfigResult result =
733 ValidateDisplayProperties(*properties, display);
734 if (result != mojom::DisplayConfigResult::kSuccess) {
735 std::move(callback).Run(result);
736 return;
737 }
738
739 display::DisplayManager* display_manager = GetDisplayManager();
740 DisplayConfigurationController* display_configuration_controller =
741 Shell::Get()->display_configuration_controller();
742 const display::Display& primary =
743 display::Screen::GetScreen()->GetPrimaryDisplay();
744
745 if (properties->set_primary && display.id() != primary.id()) {
746 display_configuration_controller->SetPrimaryDisplayId(
747 display.id(), false /* don't throttle */);
748 }
749
750 if (properties->overscan)
751 display_manager->SetOverscanInsets(display.id(), *properties->overscan);
752
753 if (properties->rotation) {
754 const mojom::DisplayRotationOptions rotation_options =
755 properties->rotation->rotation;
756 const bool is_in_tablet_physical_state =
757 Shell::Get()->tablet_mode_controller()->is_in_tablet_physical_state();
758 auto* screen_orientation_controller =
759 Shell::Get()->screen_orientation_controller();
760 const bool auto_rotate_requested =
761 rotation_options == mojom::DisplayRotationOptions::kAutoRotate;
762 if (auto_rotate_requested && !is_in_tablet_physical_state) {
763 LOG(ERROR) << "Auto-rotate is supported only when the device is in "
764 << "physical tablet state. This will be treated as a request "
765 << " to set the display rotation to 0 degrees.";
766 }
767
768 display::Display::Rotation rotation =
769 DisplayRotationFromRotationOptions(properties->rotation->rotation);
770 if (is_in_tablet_physical_state) {
771 if (auto_rotate_requested) {
772 if (screen_orientation_controller->user_rotation_locked())
773 screen_orientation_controller->ToggleUserRotationLock();
774 } else {
775 screen_orientation_controller->SetLockToRotation(rotation);
776 }
777 } else {
778 display_configuration_controller->SetDisplayRotation(
779 display.id(), rotation, display::Display::RotationSource::USER);
780 }
781 }
782
783 if (properties->bounds_origin &&
784 *properties->bounds_origin != display.bounds().origin()) {
785 gfx::Rect display_bounds = display.bounds();
786 display_bounds.Offset(
787 properties->bounds_origin->x() - display.bounds().x(),
788 properties->bounds_origin->y() - display.bounds().y());
789 SetDisplayLayoutFromBounds(primary.bounds(), primary.id(), display_bounds,
790 display.id());
791 }
792
793 if (properties->display_zoom_factor > 0) {
794 display_manager->UpdateZoomFactor(display.id(),
795 properties->display_zoom_factor);
796 }
797
798 // Set the display mode. Note: if this returns an error, other properties
799 // will have already been applied. TODO(stevenjb): Validate the display mode
800 // before applying any properties.
801 if (properties->display_mode) {
802 result = SetDisplayMode(display.id(), *properties->display_mode, source);
803 if (result != mojom::DisplayConfigResult::kSuccess) {
804 std::move(callback).Run(result);
805 return;
806 }
807 }
808
809 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
810 }
811
SetUnifiedDesktopEnabled(bool enabled)812 void CrosDisplayConfig::SetUnifiedDesktopEnabled(bool enabled) {
813 GetDisplayManager()->SetUnifiedDesktopEnabled(enabled);
814 }
815
OverscanCalibration(const std::string & display_id,mojom::DisplayConfigOperation op,const base::Optional<gfx::Insets> & delta,OverscanCalibrationCallback callback)816 void CrosDisplayConfig::OverscanCalibration(
817 const std::string& display_id,
818 mojom::DisplayConfigOperation op,
819 const base::Optional<gfx::Insets>& delta,
820 OverscanCalibrationCallback callback) {
821 display::Display display = GetDisplay(display_id);
822 if (display.id() == display::kInvalidDisplayId) {
823 std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError);
824 return;
825 }
826
827 OverscanCalibrator* calibrator = GetOverscanCalibrator(display_id);
828 if (!calibrator && op != mojom::DisplayConfigOperation::kStart) {
829 LOG(ERROR) << "Calibrator does not exist for op=" << op;
830 std::move(callback).Run(
831 mojom::DisplayConfigResult::kCalibrationNotAvailableError);
832 return;
833 }
834 switch (op) {
835 case mojom::DisplayConfigOperation::kStart: {
836 DVLOG(1) << "OverscanCalibrationStart: " << display_id;
837 gfx::Insets insets =
838 Shell::Get()->window_tree_host_manager()->GetOverscanInsets(
839 display.id());
840 if (calibrator)
841 DVLOG(1) << "Replacing existing calibrator for id: " << display_id;
842 overscan_calibrators_[display_id] =
843 std::make_unique<OverscanCalibrator>(display, insets);
844 break;
845 }
846 case mojom::DisplayConfigOperation::kAdjust:
847 DVLOG(1) << "OverscanCalibrationAdjust: " << display_id;
848 if (!delta) {
849 LOG(ERROR) << "Delta not provided for for adjust: " << display_id;
850 std::move(callback).Run(
851 mojom::DisplayConfigResult::kCalibrationFailedError);
852 return;
853 }
854 calibrator->UpdateInsets(calibrator->insets() + *delta);
855 break;
856 case mojom::DisplayConfigOperation::kReset:
857 DVLOG(1) << "OverscanCalibrationReset: " << display_id;
858 calibrator->Reset();
859 break;
860 case mojom::DisplayConfigOperation::kComplete:
861 DVLOG(1) << "OverscanCalibrationComplete: " << display_id;
862 calibrator->Commit();
863 overscan_calibrators_[display_id].reset();
864 break;
865 case mojom::DisplayConfigOperation::kShowNative:
866 LOG(ERROR) << "Operation not supported: " << op;
867 std::move(callback).Run(
868 mojom::DisplayConfigResult::kInvalidOperationError);
869 return;
870 return;
871 }
872 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
873 }
874
TouchCalibration(const std::string & display_id,mojom::DisplayConfigOperation op,mojom::TouchCalibrationPtr calibration,TouchCalibrationCallback callback)875 void CrosDisplayConfig::TouchCalibration(const std::string& display_id,
876 mojom::DisplayConfigOperation op,
877 mojom::TouchCalibrationPtr calibration,
878 TouchCalibrationCallback callback) {
879 display::Display display = GetDisplay(display_id);
880 if (display.id() == display::kInvalidDisplayId) {
881 std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError);
882 return;
883 }
884 if (display.IsInternal()) {
885 LOG(ERROR) << "Internal display cannot be calibrated for touch: "
886 << display_id;
887 std::move(callback).Run(
888 mojom::DisplayConfigResult::kCalibrationNotAvailableError);
889 return;
890 }
891 if (!display::HasExternalTouchscreenDevice()) {
892 LOG(ERROR)
893 << "Touch calibration called with no external touch screen device.";
894 std::move(callback).Run(
895 mojom::DisplayConfigResult::kCalibrationNotAvailableError);
896 return;
897 }
898
899 if (op == mojom::DisplayConfigOperation::kStart ||
900 op == mojom::DisplayConfigOperation::kShowNative) {
901 if (touch_calibrator_ && touch_calibrator_->IsCalibrating()) {
902 LOG(ERROR) << "Touch calibration already active.";
903 std::move(callback).Run(
904 mojom::DisplayConfigResult::kCalibrationInProgressError);
905 return;
906 }
907 if (!touch_calibrator_)
908 touch_calibrator_ = std::make_unique<TouchCalibratorController>();
909 if (op == mojom::DisplayConfigOperation::kShowNative) {
910 // For native calibration, |callback| is not run until calibration
911 // completes.
912 touch_calibrator_->StartCalibration(
913 display, /*is_custom_calibration=*/false,
914 base::BindOnce(
915 [](TouchCalibrationCallback callback, bool result) {
916 std::move(callback).Run(
917 result
918 ? mojom::DisplayConfigResult::kSuccess
919 : mojom::DisplayConfigResult::kCalibrationFailedError);
920 },
921 std::move(callback)));
922 return;
923 }
924 // For custom calibration, start calibration and run |callback| now.
925 touch_calibrator_->StartCalibration(display, /*is_custom_calibration=*/true,
926 base::OnceCallback<void(bool)>());
927 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
928 return;
929 }
930
931 if (op == mojom::DisplayConfigOperation::kReset) {
932 Shell::Get()->display_manager()->ClearTouchCalibrationData(display.id(),
933 base::nullopt);
934 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
935 return;
936 }
937
938 if (op != mojom::DisplayConfigOperation::kComplete) {
939 LOG(ERROR) << "Unknown operation: " << op;
940 std::move(callback).Run(
941 mojom::DisplayConfigResult::kCalibrationNotStartedError);
942 return;
943 }
944
945 if (!touch_calibrator_) {
946 LOG(ERROR) << "Touch calibration not active.";
947 std::move(callback).Run(
948 mojom::DisplayConfigResult::kCalibrationNotStartedError);
949 return;
950 }
951
952 if (!calibration || calibration->pairs.size() != 4) {
953 LOG(ERROR) << "Touch calibration requires four calibration pairs.";
954 std::move(callback).Run(
955 mojom::DisplayConfigResult::kCalibrationInvalidDataError);
956 return;
957 }
958
959 Shell::Get()->touch_transformer_controller()->SetForCalibration(false);
960
961 display::TouchCalibrationData::CalibrationPointPairQuad calibration_points;
962 calibration_points[0] = GetCalibrationPair(*calibration->pairs[0]);
963 calibration_points[1] = GetCalibrationPair(*calibration->pairs[1]);
964 calibration_points[2] = GetCalibrationPair(*calibration->pairs[2]);
965 calibration_points[3] = GetCalibrationPair(*calibration->pairs[3]);
966
967 gfx::Size bounds = calibration->bounds;
968 for (size_t row = 0; row < calibration_points.size(); row++) {
969 // Coordinates for display and touch point cannot be negative.
970 if (calibration_points[row].first.x() < 0 ||
971 calibration_points[row].first.y() < 0 ||
972 calibration_points[row].second.x() < 0 ||
973 calibration_points[row].second.y() < 0) {
974 LOG(ERROR)
975 << "Display points and touch points cannot have negative coordinates";
976 touch_calibrator_->StopCalibrationAndResetParams();
977 std::move(callback).Run(
978 mojom::DisplayConfigResult::kCalibrationInvalidDataError);
979 return;
980 }
981 // Coordinates for display points cannot be greater than the screen
982 // bounds.
983 if (calibration_points[row].first.x() > bounds.width() ||
984 calibration_points[row].first.y() > bounds.height()) {
985 LOG(ERROR) << "Display point coordinates cannot be more than size of the "
986 "display.";
987 touch_calibrator_->StopCalibrationAndResetParams();
988 std::move(callback).Run(
989 mojom::DisplayConfigResult::kCalibrationInvalidDataError);
990 return;
991 }
992 }
993
994 touch_calibrator_->CompleteCalibration(calibration_points, bounds);
995 std::move(callback).Run(mojom::DisplayConfigResult::kSuccess);
996 }
997
GetOverscanCalibrator(const std::string & id)998 OverscanCalibrator* CrosDisplayConfig::GetOverscanCalibrator(
999 const std::string& id) {
1000 auto iter = overscan_calibrators_.find(id);
1001 return iter == overscan_calibrators_.end() ? nullptr : iter->second.get();
1002 }
1003
HighlightDisplay(int64_t display_id)1004 void CrosDisplayConfig::HighlightDisplay(int64_t display_id) {
1005 DCHECK(base::FeatureList::IsEnabled(features::kDisplayIdentification));
1006
1007 Shell::Get()->display_highlight_controller()->SetHighlightedDisplay(
1008 display_id);
1009 }
1010
DragDisplayDelta(int64_t display_id,int32_t delta_x,int32_t delta_y)1011 void CrosDisplayConfig::DragDisplayDelta(int64_t display_id,
1012 int32_t delta_x,
1013 int32_t delta_y) {
1014 DCHECK(features::IsDisplayAlignmentAssistanceEnabled());
1015 Shell::Get()->display_alignment_controller()->DisplayDragged(
1016 display_id, delta_x, delta_y);
1017 }
1018
1019 } // namespace ash
1020