1 // Copyright 2014 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.
5 #include "chrome/browser/extensions/display_info_provider_chromeos.h"
7 #include "ash/display/display_controller.h"
8 #include "ash/display/display_manager.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "extensions/common/api/system_display.h"
13 #include "ui/gfx/display.h"
14 #include "ui/gfx/point.h"
15 #include "ui/gfx/rect.h"
17 using ash::DisplayManager;
19 namespace extensions {
21 using core_api::system_display::Bounds;
22 using core_api::system_display::DisplayUnitInfo;
23 using core_api::system_display::DisplayProperties;
24 using core_api::system_display::Insets;
28 // TODO(hshi): determine the DPI of the screen.
29 const float kDpi96 = 96.0;
30 // Maximum allowed bounds origin absolute value.
31 const int kMaxBoundsOrigin = 200 * 1000;
33 // Checks if the given integer value is valid display rotation in degrees.
34 bool IsValidRotationValue(int rotation) {
35 return rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270;
38 // Converts integer integer value in degrees to Rotation enum value.
39 gfx::Display::Rotation DegreesToRotation(int degrees) {
40 DCHECK(IsValidRotationValue(degrees));
43 return gfx::Display::ROTATE_0;
45 return gfx::Display::ROTATE_90;
47 return gfx::Display::ROTATE_180;
49 return gfx::Display::ROTATE_270;
51 return gfx::Display::ROTATE_0;
55 // Checks if the given point is over the radius vector described by it's end
56 // point |vector|. The point is over a vector if it's on its positive (left)
57 // side. The method sees a point on the same line as the vector as being over
59 bool PointIsOverRadiusVector(const gfx::Point& point,
60 const gfx::Point& vector) {
61 // |point| is left of |vector| if its radius vector's scalar product with a
62 // vector orthogonal (and facing the positive side) to |vector| is positive.
64 // An orthogonal vector of (a, b) is (b, -a), as the scalar product of these
66 // So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to
68 return static_cast<int64>(point.x()) * static_cast<int64>(vector.y()) >=
69 static_cast<int64>(point.y()) * static_cast<int64>(vector.x());
72 // Created ash::DisplayLayout value for |rectangle| compared to the |reference|
74 // The layout consists of two values:
75 // - position: Whether the rectangle is positioned left, right, over or under
77 // - offset: The rectangle's offset from the reference origin along the axis
78 // opposite the position direction (if the rectangle is left or right along
79 // y-axis, otherwise along x-axis).
80 // The rectangle's position is calculated by dividing the space in areas defined
81 // by the |reference|'s diagonals and finding the area |rectangle|'s center
82 // point belongs. If the |rectangle| in the calculated layout does not share a
83 // part of the bounds with the |reference|, the |rectangle| position in set to
84 // the more suitable neighboring position (e.g. if |rectangle| is completely
85 // over the |reference| top bound, it will be set to TOP) and the layout is
86 // recalculated with the new position. This is to handle case where the
87 // rectangle shares an edge with the reference, but it's center is not in the
88 // same area as the reference's edge, e.g.
90 // +---------------------+
95 // +---------------------+
96 // +-------------------------------------------------+
98 // +-------------------------------------------------+
100 // The rectangle shares an egde with the reference's bottom edge, but it's
101 // center point is in the left area.
102 ash::DisplayLayout GetLayoutForRectangles(const gfx::Rect& reference,
103 const gfx::Rect& rectangle) {
104 // Translate coordinate system so origin is in the reference's top left point
105 // (so the reference's down-diagonal vector starts in the (0, 0)) and scale it
106 // up by two (to avoid division when calculating the rectangle's center
108 gfx::Point center(2 * (rectangle.x() - reference.x()) + rectangle.width(),
109 2 * (rectangle.y() - reference.y()) + rectangle.height());
110 gfx::Point down_diag(2 * reference.width(), 2 * reference.height());
112 bool is_top_right = PointIsOverRadiusVector(center, down_diag);
114 // Translate the coordinating system again, so the bottom right point of the
115 // reference is origin (so the references up-diagonal starts at (0, 0)).
116 // Note that the coordinate system is scaled by 2.
117 center.Offset(0, -2 * reference.height());
118 // Choose the vector orientation so the points on the diagonal are considered
120 gfx::Point up_diag(-2 * reference.width(), 2 * reference.height());
122 bool is_bottom_right = PointIsOverRadiusVector(center, up_diag);
124 ash::DisplayLayout::Position position;
127 is_bottom_right ? ash::DisplayLayout::RIGHT : ash::DisplayLayout::TOP;
130 is_bottom_right ? ash::DisplayLayout::BOTTOM : ash::DisplayLayout::LEFT;
133 // If the rectangle with the calculated position would not have common side
134 // with the reference, try to position it so it shares another edge with the
136 if (is_top_right == is_bottom_right) {
137 if (rectangle.y() > reference.y() + reference.height()) {
138 // The rectangle is left or right, but completely under the reference.
139 position = ash::DisplayLayout::BOTTOM;
140 } else if (rectangle.y() + rectangle.height() < reference.y()) {
141 // The rectangle is left or right, but completely over the reference.
142 position = ash::DisplayLayout::TOP;
145 if (rectangle.x() > reference.x() + reference.width()) {
146 // The rectangle is over or under, but completely right of the reference.
147 position = ash::DisplayLayout::RIGHT;
148 } else if (rectangle.x() + rectangle.width() < reference.x()) {
149 // The rectangle is over or under, but completely left of the reference.
150 position = ash::DisplayLayout::LEFT;
154 if (position == ash::DisplayLayout::LEFT ||
155 position == ash::DisplayLayout::RIGHT) {
156 return ash::DisplayLayout::FromInts(position, rectangle.y());
158 return ash::DisplayLayout::FromInts(position, rectangle.x());
162 // Updates the display layout for the target display in reference to the primary
164 void UpdateDisplayLayout(const gfx::Rect& primary_display_bounds,
165 int primary_display_id,
166 const gfx::Rect& target_display_bounds,
167 int target_display_id) {
168 ash::DisplayLayout layout =
169 GetLayoutForRectangles(primary_display_bounds, target_display_bounds);
170 ash::Shell::GetInstance()->display_manager()->SetLayoutForCurrentDisplays(
174 // Validates that parameters passed to the SetInfo function are valid for the
175 // desired display and the current display manager state.
176 // Returns whether the parameters are valid. On failure |error| is set to the
178 bool ValidateParamsForDisplay(const DisplayProperties& info,
179 const gfx::Display& display,
180 DisplayManager* display_manager,
181 int64 primary_display_id,
182 std::string* error) {
183 bool is_primary = display.id() == primary_display_id ||
184 (info.is_primary && *info.is_primary);
186 // If mirroring source id is set, a display with the given id should exist,
187 // and if should not be the same as the target display's id.
188 if (info.mirroring_source_id && !info.mirroring_source_id->empty()) {
190 if (!base::StringToInt64(*info.mirroring_source_id, &mirroring_id) ||
191 display_manager->GetDisplayForId(mirroring_id).id() ==
192 gfx::Display::kInvalidDisplayID) {
193 *error = "Display " + *info.mirroring_source_id + " not found.";
197 if (*info.mirroring_source_id == base::Int64ToString(display.id())) {
198 *error = "Not allowed to mirror self.";
203 // If mirroring source parameter is specified, no other parameter should be
204 // set as when the mirroring is applied the display list could change.
205 if (info.mirroring_source_id &&
206 (info.is_primary || info.bounds_origin_x || info.bounds_origin_y ||
207 info.rotation || info.overscan)) {
208 *error = "No other parameter should be set alongside mirroringSourceId.";
212 // The bounds cannot be changed for the primary display and should be inside
213 // a reasonable bounds. Note that the display is considered primary if the
214 // info has 'isPrimary' parameter set, as this will be applied before bounds
216 if (info.bounds_origin_x || info.bounds_origin_y) {
218 *error = "Bounds origin not allowed for the primary display.";
221 if (info.bounds_origin_x && (*info.bounds_origin_x > kMaxBoundsOrigin ||
222 *info.bounds_origin_x < -kMaxBoundsOrigin)) {
223 *error = "Bounds origin x out of bounds.";
226 if (info.bounds_origin_y && (*info.bounds_origin_y > kMaxBoundsOrigin ||
227 *info.bounds_origin_y < -kMaxBoundsOrigin)) {
228 *error = "Bounds origin y out of bounds.";
233 // Verify the rotation value is valid.
234 if (info.rotation && !IsValidRotationValue(*info.rotation)) {
235 *error = "Invalid rotation.";
239 // Overscan cannot be changed for the internal display, and should be at most
240 // half of the screen size.
242 if (display.IsInternal()) {
243 *error = "Overscan changes not allowed for the internal monitor.";
247 if (info.overscan->left < 0 || info.overscan->top < 0 ||
248 info.overscan->right < 0 || info.overscan->bottom < 0) {
249 *error = "Negative overscan not allowed.";
253 const gfx::Insets overscan =
254 display_manager->GetOverscanInsets(display.id());
255 int screen_width = display.bounds().width() + overscan.width();
256 int screen_height = display.bounds().height() + overscan.height();
258 if ((info.overscan->left + info.overscan->right) * 2 > screen_width) {
259 *error = "Horizontal overscan is more than half of the screen width.";
263 if ((info.overscan->top + info.overscan->bottom) * 2 > screen_height) {
264 *error = "Vertical overscan is more than half of the screen height.";
271 // Gets the display with the provided string id.
272 gfx::Display GetTargetDisplay(const std::string& display_id_str,
273 DisplayManager* manager) {
275 if (!base::StringToInt64(display_id_str, &display_id)) {
276 // This should return invalid display.
277 return gfx::Display();
279 return manager->GetDisplayForId(display_id);
284 DisplayInfoProviderChromeOS::DisplayInfoProviderChromeOS() {
287 DisplayInfoProviderChromeOS::~DisplayInfoProviderChromeOS() {
290 bool DisplayInfoProviderChromeOS::SetInfo(const std::string& display_id_str,
291 const DisplayProperties& info,
292 std::string* error) {
293 DisplayManager* display_manager =
294 ash::Shell::GetInstance()->display_manager();
295 DCHECK(display_manager);
296 ash::DisplayController* display_controller =
297 ash::Shell::GetInstance()->display_controller();
298 DCHECK(display_controller);
300 const gfx::Display target = GetTargetDisplay(display_id_str, display_manager);
302 if (target.id() == gfx::Display::kInvalidDisplayID) {
303 *error = "Display not found.";
307 int64 display_id = target.id();
308 // TODO(scottmg): Native is wrong http://crbug.com/133312
309 const gfx::Display& primary =
310 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
312 if (!ValidateParamsForDisplay(
313 info, target, display_manager, primary.id(), error)) {
317 // Process 'isPrimary' parameter.
318 if (info.is_primary && *info.is_primary && target.id() != primary.id())
319 display_controller->SetPrimaryDisplayId(display_id);
321 // Process 'mirroringSourceId' parameter.
322 if (info.mirroring_source_id &&
323 info.mirroring_source_id->empty() == display_manager->IsMirrored()) {
324 display_controller->ToggleMirrorMode();
327 // Process 'overscan' parameter.
329 display_manager->SetOverscanInsets(display_id,
330 gfx::Insets(info.overscan->top,
332 info.overscan->bottom,
333 info.overscan->right));
336 // Process 'rotation' parameter.
338 display_manager->SetDisplayRotation(display_id,
339 DegreesToRotation(*info.rotation));
342 // Process new display origin parameters.
343 gfx::Point new_bounds_origin = target.bounds().origin();
344 if (info.bounds_origin_x)
345 new_bounds_origin.set_x(*info.bounds_origin_x);
346 if (info.bounds_origin_y)
347 new_bounds_origin.set_y(*info.bounds_origin_y);
349 if (new_bounds_origin != target.bounds().origin()) {
350 gfx::Rect target_bounds = target.bounds();
351 target_bounds.Offset(new_bounds_origin.x() - target.bounds().x(),
352 new_bounds_origin.y() - target.bounds().y());
354 primary.bounds(), primary.id(), target_bounds, target.id());
360 void DisplayInfoProviderChromeOS::UpdateDisplayUnitInfoForPlatform(
361 const gfx::Display& display,
362 extensions::core_api::system_display::DisplayUnitInfo* unit) {
363 ash::DisplayManager* display_manager =
364 ash::Shell::GetInstance()->display_manager();
365 unit->name = display_manager->GetDisplayNameForId(display.id());
366 if (display_manager->IsMirrored()) {
367 unit->mirroring_source_id =
368 base::Int64ToString(display_manager->mirrored_display_id());
371 const float dpi = display.device_scale_factor() * kDpi96;
375 const gfx::Insets overscan_insets =
376 display_manager->GetOverscanInsets(display.id());
377 unit->overscan.left = overscan_insets.left();
378 unit->overscan.top = overscan_insets.top();
379 unit->overscan.right = overscan_insets.right();
380 unit->overscan.bottom = overscan_insets.bottom();
383 gfx::Screen* DisplayInfoProviderChromeOS::GetActiveScreen() {
384 return ash::Shell::GetScreen();
388 DisplayInfoProvider* DisplayInfoProvider::Create() {
389 return new DisplayInfoProviderChromeOS();
392 } // namespace extensions