Home | History | Annotate | Download | only in system_display
      1 // Copyright 2013 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/api/system_display/display_info_provider.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 "content/public/browser/browser_thread.h"
     13 #include "ui/gfx/display.h"
     14 #include "ui/gfx/point.h"
     15 #include "ui/gfx/rect.h"
     16 
     17 using ash::internal::DisplayManager;
     18 
     19 namespace extensions {
     20 
     21 using api::system_display::Bounds;
     22 using api::system_display::DisplayUnitInfo;
     23 using api::system_display::DisplayProperties;
     24 using api::system_display::Insets;
     25 
     26 namespace {
     27 
     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;
     32 
     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;
     36 }
     37 
     38 // Converts integer integer value in degrees to Rotation enum value.
     39 gfx::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.
     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.
     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.
    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
    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 = is_bottom_right ? ash::DisplayLayout::RIGHT :
    127                                  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.
    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 = GetLayoutForRectangles(primary_display_bounds,
    169                                                      target_display_bounds);
    170   ash::Shell::GetInstance()->display_manager()->
    171       SetLayoutForCurrentDisplays(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.
    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);
    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 && (info.is_primary || info.bounds_origin_x ||
    206       info.bounds_origin_y || info.rotation || info.overscan)) {
    207     *error = "No other parameter should be set alongside mirroringSourceId.";
    208     return false;
    209   }
    210 
    211   // The bounds cannot be changed for the primary display and should be inside
    212   // a reasonable bounds. Note that the display is considered primary if the
    213   // info has 'isPrimary' parameter set, as this will be applied before bounds
    214   // origin changes.
    215   if (info.bounds_origin_x || info.bounds_origin_y) {
    216     if (is_primary) {
    217       *error = "Bounds origin not allowed for the primary display.";
    218       return false;
    219     }
    220     if (info.bounds_origin_x &&
    221         (*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 &&
    227         (*info.bounds_origin_y > kMaxBoundsOrigin ||
    228          *info.bounds_origin_y < -kMaxBoundsOrigin)) {
    229       *error = "Bounds origin y out of bounds.";
    230       return false;
    231     }
    232   }
    233 
    234   // Verify the rotation value is valid.
    235   if (info.rotation && !IsValidRotationValue(*info.rotation)) {
    236     *error = "Invalid rotation.";
    237     return false;
    238   }
    239 
    240   // Overscan cannot be changed for the internal display, and should be at most
    241   // half of the screen size.
    242   if (info.overscan) {
    243     if (display.IsInternal()) {
    244       *error = "Overscan changes not allowed for the internal monitor.";
    245       return false;
    246     }
    247 
    248     if (info.overscan->left < 0 || info.overscan->top < 0 ||
    249         info.overscan->right < 0 || info.overscan->bottom < 0) {
    250       *error = "Negative overscan not allowed.";
    251       return false;
    252     }
    253 
    254     const gfx::Insets overscan =
    255         display_manager->GetOverscanInsets(display.id());
    256     int screen_width = display.bounds().width() + overscan.width();
    257     int screen_height = display.bounds().height() + overscan.height();
    258 
    259     if ((info.overscan->left + info.overscan->right) * 2 > screen_width) {
    260       *error = "Horizontal overscan is more than half of the screen width.";
    261       return false;
    262     }
    263 
    264     if ((info.overscan->top + info.overscan->bottom) * 2 > screen_height) {
    265       *error = "Vertical overscan is more than half of the screen height.";
    266       return false;
    267     }
    268   }
    269   return true;
    270 }
    271 
    272 // Gets the display with the provided string id.
    273 gfx::Display GetTargetDisplay(const std::string& display_id_str,
    274                               DisplayManager* manager) {
    275   int64 display_id;
    276   if (!base::StringToInt64(display_id_str, &display_id)) {
    277     // This should return invalid display.
    278     return gfx::Display();
    279   }
    280   return manager->GetDisplayForId(display_id);
    281 }
    282 
    283 // Updates the display with |display_id_str| id according to |info|. Returns
    284 // whether the display was successfully updated. On failure, no display
    285 // parameters should be changed, and |error| should be set to the error string.
    286 bool SetInfoImpl(const std::string& display_id_str,
    287                  const DisplayProperties& info,
    288                  std::string* error) {
    289   DisplayManager* display_manager =
    290       ash::Shell::GetInstance()->display_manager();
    291   DCHECK(display_manager);
    292   ash::DisplayController* display_controller =
    293       ash::Shell::GetInstance()->display_controller();
    294   DCHECK(display_controller);
    295 
    296   const gfx::Display target = GetTargetDisplay(display_id_str, display_manager);
    297 
    298   if (target.id() == gfx::Display::kInvalidDisplayID) {
    299     *error = "Display not found.";
    300     return false;
    301   }
    302 
    303   int64 display_id = target.id();
    304   // TODO(scottmg): Native is wrong http://crbug.com/133312
    305   const gfx::Display& primary =
    306       gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
    307 
    308   if (!ValidateParamsForDisplay(info, target, display_manager, primary.id(),
    309                                 error)) {
    310     return false;
    311   }
    312 
    313   // Process 'isPrimary' parameter.
    314   if (info.is_primary && *info.is_primary && target.id() != primary.id())
    315     display_controller->SetPrimaryDisplayId(display_id);
    316 
    317   // Process 'mirroringSourceId' parameter.
    318   if (info.mirroring_source_id &&
    319       info.mirroring_source_id->empty() == display_manager->IsMirrored()) {
    320     display_controller->ToggleMirrorMode();
    321   }
    322 
    323   // Process 'overscan' parameter.
    324   if (info.overscan) {
    325     display_manager->SetOverscanInsets(
    326         display_id,
    327         gfx::Insets(info.overscan->top, info.overscan->left,
    328                     info.overscan->bottom, info.overscan->right));
    329   }
    330 
    331   // Process 'rotation' parameter.
    332   if (info.rotation) {
    333     display_manager->SetDisplayRotation(display_id,
    334                                         DegreesToRotation(*info.rotation));
    335   }
    336 
    337   // Process new display origin parameters.
    338   gfx::Point new_bounds_origin = target.bounds().origin();
    339   if (info.bounds_origin_x)
    340     new_bounds_origin.set_x(*info.bounds_origin_x);
    341   if (info.bounds_origin_y)
    342     new_bounds_origin.set_y(*info.bounds_origin_y);
    343 
    344   if (new_bounds_origin != target.bounds().origin()) {
    345     gfx::Rect target_bounds = target.bounds();
    346     target_bounds.Offset(new_bounds_origin.x() - target.bounds().x(),
    347                          new_bounds_origin.y() - target.bounds().y());
    348     UpdateDisplayLayout(primary.bounds(), primary.id(),
    349                         target_bounds, target.id());
    350   }
    351 
    352   return true;
    353 }
    354 
    355 }  // namespace
    356 
    357 bool DisplayInfoProvider::SetInfo(const std::string& display_id,
    358                                   const DisplayProperties& info,
    359                                   std::string* error) {
    360   return SetInfoImpl(display_id, info, error);
    361 }
    362 
    363 void DisplayInfoProvider::UpdateDisplayUnitInfoForPlatform(
    364     const gfx::Display& display,
    365     extensions::api::system_display::DisplayUnitInfo* unit) {
    366 
    367   ash::internal::DisplayManager* display_manager
    368       = ash::Shell::GetInstance()->display_manager();
    369   unit->name = display_manager->GetDisplayNameForId(display.id());
    370   if (display_manager->IsMirrored()) {
    371     unit->mirroring_source_id =
    372         base::Int64ToString(display_manager->mirrored_display_id());
    373   }
    374 
    375   const float dpi = display.device_scale_factor() * kDpi96;
    376   unit->dpi_x = dpi;
    377   unit->dpi_y = dpi;
    378 
    379   const gfx::Insets overscan_insets =
    380       display_manager->GetOverscanInsets(display.id());
    381   unit->overscan.left = overscan_insets.left();
    382   unit->overscan.top = overscan_insets.top();
    383   unit->overscan.right = overscan_insets.right();
    384   unit->overscan.bottom = overscan_insets.bottom();
    385 }
    386 
    387 }  // namespace extensions
    388