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.
4
5#include "chrome/browser/extensions/display_info_provider_chromeos.h"
6
7#include "ash/display/display_controller.h"
8#include "ash/display/display_manager.h"
9#include "ash/shell.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"
16
17using ash::DisplayManager;
18
19namespace extensions {
20
21using core_api::system_display::Bounds;
22using core_api::system_display::DisplayUnitInfo;
23using core_api::system_display::DisplayProperties;
24using core_api::system_display::Insets;
25
26namespace {
27
28// TODO(hshi): determine the DPI of the screen.
29const float kDpi96 = 96.0;
30// Maximum allowed bounds origin absolute value.
31const int kMaxBoundsOrigin = 200 * 1000;
32
33// Checks if the given integer value is valid display rotation in degrees.
34bool IsValidRotationValue(int rotation) {
35  return rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270;
36}
37
38// Converts integer integer value in degrees to Rotation enum value.
39gfx::Display::Rotation DegreesToRotation(int degrees) {
40  DCHECK(IsValidRotationValue(degrees));
41  switch (degrees) {
42    case 0:
43      return gfx::Display::ROTATE_0;
44    case 90:
45      return gfx::Display::ROTATE_90;
46    case 180:
47      return gfx::Display::ROTATE_180;
48    case 270:
49      return gfx::Display::ROTATE_270;
50    default:
51      return gfx::Display::ROTATE_0;
52  }
53}
54
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
58// the vector.
59bool 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.
63  //
64  // An orthogonal vector of (a, b) is (b, -a), as the scalar product of these
65  // two is 0.
66  // So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to
67  // x * b >= y * a.
68  return static_cast<int64>(point.x()) * static_cast<int64>(vector.y()) >=
69         static_cast<int64>(point.y()) * static_cast<int64>(vector.x());
70}
71
72// Created ash::DisplayLayout value for |rectangle| compared to the |reference|
73// rectangle.
74// The layout consists of two values:
75//   - position: Whether the rectangle is positioned left, right, over or under
76//     the reference.
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.
89//
90// +---------------------+
91// |                     |
92// | REFERENCE           |
93// |                     |
94// |                     |
95// +---------------------+
96//                 +-------------------------------------------------+
97//                 | RECTANGLE               x                       |
98//                 +-------------------------------------------------+
99//
100// The rectangle shares an egde with the reference's bottom edge, but it's
101// center point is in the left area.
102ash::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
107  // point).
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());
111
112  bool is_top_right = PointIsOverRadiusVector(center, down_diag);
113
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
119  // to be left.
120  gfx::Point up_diag(-2 * reference.width(), 2 * reference.height());
121
122  bool is_bottom_right = PointIsOverRadiusVector(center, up_diag);
123
124  ash::DisplayLayout::Position position;
125  if (is_top_right) {
126    position =
127        is_bottom_right ? ash::DisplayLayout::RIGHT : ash::DisplayLayout::TOP;
128  } else {
129    position =
130        is_bottom_right ? ash::DisplayLayout::BOTTOM : ash::DisplayLayout::LEFT;
131  }
132
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
135  // reference.
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;
143    }
144  } else {
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;
151    }
152  }
153
154  if (position == ash::DisplayLayout::LEFT ||
155      position == ash::DisplayLayout::RIGHT) {
156    return ash::DisplayLayout::FromInts(position, rectangle.y());
157  } else {
158    return ash::DisplayLayout::FromInts(position, rectangle.x());
159  }
160}
161
162// Updates the display layout for the target display in reference to the primary
163// display.
164void 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(
171      layout);
172}
173
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
177// error message.
178bool 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);
185
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()) {
189    int64 mirroring_id;
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.";
194      return false;
195    }
196
197    if (*info.mirroring_source_id == base::Int64ToString(display.id())) {
198      *error = "Not allowed to mirror self.";
199      return false;
200    }
201  }
202
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.";
209    return false;
210  }
211
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
215  // origin changes.
216  if (info.bounds_origin_x || info.bounds_origin_y) {
217    if (is_primary) {
218      *error = "Bounds origin not allowed for the primary display.";
219      return false;
220    }
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.";
224      return false;
225    }
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.";
229      return false;
230    }
231  }
232
233  // Verify the rotation value is valid.
234  if (info.rotation && !IsValidRotationValue(*info.rotation)) {
235    *error = "Invalid rotation.";
236    return false;
237  }
238
239  // Overscan cannot be changed for the internal display, and should be at most
240  // half of the screen size.
241  if (info.overscan) {
242    if (display.IsInternal()) {
243      *error = "Overscan changes not allowed for the internal monitor.";
244      return false;
245    }
246
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.";
250      return false;
251    }
252
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();
257
258    if ((info.overscan->left + info.overscan->right) * 2 > screen_width) {
259      *error = "Horizontal overscan is more than half of the screen width.";
260      return false;
261    }
262
263    if ((info.overscan->top + info.overscan->bottom) * 2 > screen_height) {
264      *error = "Vertical overscan is more than half of the screen height.";
265      return false;
266    }
267  }
268  return true;
269}
270
271// Gets the display with the provided string id.
272gfx::Display GetTargetDisplay(const std::string& display_id_str,
273                              DisplayManager* manager) {
274  int64 display_id;
275  if (!base::StringToInt64(display_id_str, &display_id)) {
276    // This should return invalid display.
277    return gfx::Display();
278  }
279  return manager->GetDisplayForId(display_id);
280}
281
282}  // namespace
283
284DisplayInfoProviderChromeOS::DisplayInfoProviderChromeOS() {
285}
286
287DisplayInfoProviderChromeOS::~DisplayInfoProviderChromeOS() {
288}
289
290bool 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);
299
300  const gfx::Display target = GetTargetDisplay(display_id_str, display_manager);
301
302  if (target.id() == gfx::Display::kInvalidDisplayID) {
303    *error = "Display not found.";
304    return false;
305  }
306
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();
311
312  if (!ValidateParamsForDisplay(
313          info, target, display_manager, primary.id(), error)) {
314    return false;
315  }
316
317  // Process 'isPrimary' parameter.
318  if (info.is_primary && *info.is_primary && target.id() != primary.id())
319    display_controller->SetPrimaryDisplayId(display_id);
320
321  // Process 'mirroringSourceId' parameter.
322  if (info.mirroring_source_id &&
323      info.mirroring_source_id->empty() == display_manager->IsMirrored()) {
324    display_controller->ToggleMirrorMode();
325  }
326
327  // Process 'overscan' parameter.
328  if (info.overscan) {
329    display_manager->SetOverscanInsets(display_id,
330                                       gfx::Insets(info.overscan->top,
331                                                   info.overscan->left,
332                                                   info.overscan->bottom,
333                                                   info.overscan->right));
334  }
335
336  // Process 'rotation' parameter.
337  if (info.rotation) {
338    display_manager->SetDisplayRotation(display_id,
339                                        DegreesToRotation(*info.rotation));
340  }
341
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);
348
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());
353    UpdateDisplayLayout(
354        primary.bounds(), primary.id(), target_bounds, target.id());
355  }
356
357  return true;
358}
359
360void 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());
369  }
370
371  const float dpi = display.device_scale_factor() * kDpi96;
372  unit->dpi_x = dpi;
373  unit->dpi_y = dpi;
374
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();
381}
382
383gfx::Screen* DisplayInfoProviderChromeOS::GetActiveScreen() {
384  return ash::Shell::GetScreen();
385}
386
387// static
388DisplayInfoProvider* DisplayInfoProvider::Create() {
389  return new DisplayInfoProviderChromeOS();
390}
391
392}  // namespace extensions
393