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 Rotation enum to integer.
     39 int RotationToDegrees(gfx::Display::Rotation rotation) {
     40   switch (rotation) {
     41     case gfx::Display::ROTATE_0:
     42       return 0;
     43     case gfx::Display::ROTATE_90:
     44       return 90;
     45     case gfx::Display::ROTATE_180:
     46       return 180;
     47     case gfx::Display::ROTATE_270:
     48       return 270;
     49   }
     50   return 0;
     51 }
     52 
     53 // Converts integer integer value in degrees to Rotation enum value.
     54 gfx::Display::Rotation DegreesToRotation(int degrees) {
     55   DCHECK(IsValidRotationValue(degrees));
     56   switch (degrees) {
     57     case 0:
     58       return gfx::Display::ROTATE_0;
     59     case 90:
     60       return gfx::Display::ROTATE_90;
     61     case 180:
     62       return gfx::Display::ROTATE_180;
     63     case 270:
     64       return gfx::Display::ROTATE_270;
     65     default:
     66       return gfx::Display::ROTATE_0;
     67   }
     68 }
     69 
     70 // Creates new DisplayUnitInfo struct for |display| and adds it at the end of
     71 // |list|.
     72 void AddInfoForDisplay(const gfx::Display& display,
     73                        DisplayManager* display_manager,
     74                        int64 primary_display_id,
     75                        DisplayInfo* list) {
     76   linked_ptr<extensions::api::system_display::DisplayUnitInfo> unit(
     77       new extensions::api::system_display::DisplayUnitInfo());
     78   const gfx::Rect& bounds = display.bounds();
     79   const gfx::Rect& work_area = display.work_area();
     80   const float dpi = display.device_scale_factor() * kDpi96;
     81   const gfx::Insets overscan_insets =
     82         display_manager->GetOverscanInsets(display.id());
     83 
     84   unit->id = base::Int64ToString(display.id());
     85   unit->name = display_manager->GetDisplayNameForId(display.id());
     86   unit->is_primary = (display.id() == primary_display_id);
     87   unit->is_internal = display.IsInternal();
     88   unit->is_enabled = true;
     89   if (display_manager->IsMirrored()) {
     90     unit->mirroring_source_id =
     91         base::Int64ToString(display_manager->mirrored_display().id());
     92   }
     93   unit->dpi_x = dpi;
     94   unit->dpi_y = dpi;
     95   unit->rotation = RotationToDegrees(display.rotation());
     96   unit->bounds.left = bounds.x();
     97   unit->bounds.top = bounds.y();
     98   unit->bounds.width = bounds.width();
     99   unit->bounds.height = bounds.height();
    100   unit->overscan.left = overscan_insets.left();
    101   unit->overscan.top = overscan_insets.top();
    102   unit->overscan.right = overscan_insets.right();
    103   unit->overscan.bottom = overscan_insets.bottom();
    104   unit->work_area.left = work_area.x();
    105   unit->work_area.top = work_area.y();
    106   unit->work_area.width = work_area.width();
    107   unit->work_area.height = work_area.height();
    108 
    109   list->push_back(unit);
    110 }
    111 
    112 // Checks if the given point is over the radius vector described by it's end
    113 // point |vector|. The point is over a vector if it's on its positive (left)
    114 // side. The method sees a point on the same line as the vector as being over
    115 // the vector.
    116 bool PointIsOverRadiusVector(const gfx::Point& point,
    117                              const gfx::Point& vector) {
    118   // |point| is left of |vector| if its radius vector's scalar product with a
    119   // vector orthogonal (and facing the positive side) to |vector| is positive.
    120   //
    121   // An orthogonal vector of (a, b) is (b, -a), as the scalar product of these
    122   // two is 0.
    123   // So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to
    124   // x * b >= y * a.
    125   return static_cast<int64>(point.x()) * static_cast<int64>(vector.y()) >=
    126       static_cast<int64>(point.y()) * static_cast<int64>(vector.x());
    127 }
    128 
    129 // Created ash::DisplayLayout value for |rectangle| compared to the |reference|
    130 // rectangle.
    131 // The layout consists of two values:
    132 //   - position: Whether the rectangle is positioned left, right, over or under
    133 //     the reference.
    134 //   - offset: The rectangle's offset from the reference origin along the axis
    135 //     opposite the position direction (if the rectangle is left or right along
    136 //     y-axis, otherwise along x-axis).
    137 // The rectangle's position is calculated by dividing the space in areas defined
    138 // by the |reference|'s diagonals and finding the area |rectangle|'s center
    139 // point belongs. If the |rectangle| in the calculated layout does not share a
    140 // part of the bounds with the |reference|, the |rectangle| position in set to
    141 // the more suitable neighboring position (e.g. if |rectangle| is completely
    142 // over the |reference| top bound, it will be set to TOP) and the layout is
    143 // recalculated with the new position. This is to handle case where the
    144 // rectangle shares an edge with the reference, but it's center is not in the
    145 // same area as the reference's edge, e.g.
    146 //
    147 // +---------------------+
    148 // |                     |
    149 // | REFERENCE           |
    150 // |                     |
    151 // |                     |
    152 // +---------------------+
    153 //                 +-------------------------------------------------+
    154 //                 | RECTANGLE               x                       |
    155 //                 +-------------------------------------------------+
    156 //
    157 // The rectangle shares an egde with the reference's bottom edge, but it's
    158 // center point is in the left area.
    159 ash::DisplayLayout GetLayoutForRectangles(const gfx::Rect& reference,
    160                                           const gfx::Rect& rectangle) {
    161   // Translate coordinate system so origin is in the reference's top left point
    162   // (so the reference's down-diagonal vector starts in the (0, 0)) and scale it
    163   // up by two (to avoid division when calculating the rectangle's center
    164   // point).
    165   gfx::Point center(2 * (rectangle.x() - reference.x()) + rectangle.width(),
    166                     2 * (rectangle.y() - reference.y()) + rectangle.height());
    167   gfx::Point down_diag(2 * reference.width(), 2 * reference.height());
    168 
    169   bool is_top_right = PointIsOverRadiusVector(center, down_diag);
    170 
    171   // Translate the coordinating system again, so the bottom right point of the
    172   // reference is origin (so the references up-diagonal starts at (0, 0)).
    173   // Note that the coordinate system is scaled by 2.
    174   center.Offset(0, -2 * reference.height());
    175   // Choose the vector orientation so the points on the diagonal are considered
    176   // to be left.
    177   gfx::Point up_diag(-2 * reference.width(), 2 * reference.height());
    178 
    179   bool is_bottom_right = PointIsOverRadiusVector(center, up_diag);
    180 
    181   ash::DisplayLayout::Position position;
    182   if (is_top_right) {
    183     position = is_bottom_right ? ash::DisplayLayout::RIGHT :
    184                                  ash::DisplayLayout::TOP;
    185   } else {
    186     position =
    187         is_bottom_right ? ash::DisplayLayout::BOTTOM : ash::DisplayLayout::LEFT;
    188   }
    189 
    190   // If the rectangle with the calculated position would not have common side
    191   // with the reference, try to position it so it shares another edge with the
    192   // reference.
    193   if (is_top_right == is_bottom_right) {
    194     if (rectangle.y() > reference.y() + reference.height()) {
    195       // The rectangle is left or right, but completely under the reference.
    196       position = ash::DisplayLayout::BOTTOM;
    197     } else if (rectangle.y() + rectangle.height() < reference.y()) {
    198       // The rectangle is left or right, but completely over the reference.
    199       position = ash::DisplayLayout::TOP;
    200     }
    201   } else {
    202     if (rectangle.x() > reference.x() + reference.width()) {
    203       // The rectangle is over or under, but completely right of the reference.
    204       position = ash::DisplayLayout::RIGHT;
    205     } else if (rectangle.x() + rectangle.width() < reference.x()) {
    206       // The rectangle is over or under, but completely left of the reference.
    207       position = ash::DisplayLayout::LEFT;
    208     }
    209   }
    210 
    211   if (position == ash::DisplayLayout::LEFT ||
    212       position == ash::DisplayLayout::RIGHT) {
    213     return ash::DisplayLayout::FromInts(position, rectangle.y());
    214   } else {
    215     return ash::DisplayLayout::FromInts(position, rectangle.x());
    216   }
    217 }
    218 
    219 // Updates the display layout for the target display in reference to the primary
    220 // display.
    221 void UpdateDisplayLayout(const gfx::Rect& primary_display_bounds,
    222                          int primary_display_id,
    223                          const gfx::Rect& target_display_bounds,
    224                          int target_display_id) {
    225   ash::DisplayLayout layout = GetLayoutForRectangles(primary_display_bounds,
    226                                                      target_display_bounds);
    227   ash::DisplayController* display_controller =
    228       ash::Shell::GetInstance()->display_controller();
    229   display_controller->SetLayoutForCurrentDisplays(layout);
    230 }
    231 
    232 // Validates that parameters passed to the SetInfo function are valid for the
    233 // desired display and the current display manager state.
    234 // Returns whether the parameters are valid. On failure |error| is set to the
    235 // error message.
    236 bool ValidateParamsForDisplay(const DisplayProperties& info,
    237                               const gfx::Display& display,
    238                               DisplayManager* display_manager,
    239                               int64 primary_display_id,
    240                               std::string* error) {
    241   bool is_primary = display.id() == primary_display_id ||
    242                     (info.is_primary && *info.is_primary);
    243 
    244   // If mirroring source id is set, a display with the given id should exist,
    245   // and if should not be the same as the target display's id.
    246   if (info.mirroring_source_id && !info.mirroring_source_id->empty()) {
    247     int64 mirroring_id;
    248     if (!base::StringToInt64(*info.mirroring_source_id, &mirroring_id) ||
    249         display_manager->GetDisplayForId(mirroring_id).id() ==
    250             gfx::Display::kInvalidDisplayID) {
    251       *error = "Display " + *info.mirroring_source_id + " not found.";
    252       return false;
    253     }
    254 
    255     if (*info.mirroring_source_id == base::Int64ToString(display.id())) {
    256       *error = "Not allowed to mirror self.";
    257       return false;
    258     }
    259   }
    260 
    261   // If mirroring source parameter is specified, no other parameter should be
    262   // set as when the mirroring is applied the display list could change.
    263   if (info.mirroring_source_id && (info.is_primary || info.bounds_origin_x ||
    264       info.bounds_origin_y || info.rotation || info.overscan)) {
    265     *error = "No other parameter should be set alongside mirroringSourceId.";
    266     return false;
    267   }
    268 
    269   // The bounds cannot be changed for the primary display and should be inside
    270   // a reasonable bounds. Note that the display is considered primary if the
    271   // info has 'isPrimary' parameter set, as this will be applied before bounds
    272   // origin changes.
    273   if (info.bounds_origin_x || info.bounds_origin_y) {
    274     if (is_primary) {
    275       *error = "Bounds origin not allowed for the primary display.";
    276       return false;
    277     }
    278     if (info.bounds_origin_x &&
    279         (*info.bounds_origin_x > kMaxBoundsOrigin ||
    280          *info.bounds_origin_x < -kMaxBoundsOrigin)) {
    281       *error = "Bounds origin x out of bounds.";
    282       return false;
    283     }
    284     if (info.bounds_origin_y &&
    285         (*info.bounds_origin_y > kMaxBoundsOrigin ||
    286          *info.bounds_origin_y < -kMaxBoundsOrigin)) {
    287       *error = "Bounds origin y out of bounds.";
    288       return false;
    289     }
    290   }
    291 
    292   // Verify the rotation value is valid.
    293   if (info.rotation && !IsValidRotationValue(*info.rotation)) {
    294     *error = "Invalid rotation.";
    295     return false;
    296   }
    297 
    298   // Overscan cannot be changed for the internal display, and should be at most
    299   // half of the screen size.
    300   if (info.overscan) {
    301     if (display.IsInternal()) {
    302       *error = "Overscan changes not allowed for the internal monitor.";
    303       return false;
    304     }
    305 
    306     if (info.overscan->left < 0 || info.overscan->top < 0 ||
    307         info.overscan->right < 0 || info.overscan->bottom < 0) {
    308       *error = "Negative overscan not allowed.";
    309       return false;
    310     }
    311 
    312     const gfx::Insets overscan =
    313         display_manager->GetOverscanInsets(display.id());
    314     int screen_width = display.bounds().width() + overscan.width();
    315     int screen_height = display.bounds().height() + overscan.height();
    316 
    317     if ((info.overscan->left + info.overscan->right) * 2 > screen_width) {
    318       *error = "Horizontal overscan is more than half of the screen width.";
    319       return false;
    320     }
    321 
    322     if ((info.overscan->top + info.overscan->bottom) * 2 > screen_height) {
    323       *error = "Vertical overscan is more than half of the screen height.";
    324       return false;
    325     }
    326   }
    327   return true;
    328 }
    329 
    330 // Gets the display with the provided string id.
    331 gfx::Display GetTargetDisplay(const std::string& display_id_str,
    332                               DisplayManager* manager) {
    333   int64 display_id;
    334   if (!base::StringToInt64(display_id_str, &display_id)) {
    335     // This should return invalid display.
    336     return gfx::Display();
    337   }
    338   return manager->GetDisplayForId(display_id);
    339 }
    340 
    341 // Updates the display with |display_id_str| id according to |info|. Returns
    342 // whether the display was successfully updated. On failure, no display
    343 // parameters should be changed, and |error| should be set to the error string.
    344 bool SetInfoImpl(const std::string& display_id_str,
    345                  const DisplayProperties& info,
    346                  std::string* error) {
    347   DisplayManager* display_manager =
    348       ash::Shell::GetInstance()->display_manager();
    349   DCHECK(display_manager);
    350   ash::DisplayController* display_controller =
    351       ash::Shell::GetInstance()->display_controller();
    352   DCHECK(display_controller);
    353 
    354   const gfx::Display target = GetTargetDisplay(display_id_str, display_manager);
    355 
    356   if (target.id() == gfx::Display::kInvalidDisplayID) {
    357     *error = "Display not found.";
    358     return false;
    359   }
    360 
    361   int64 display_id = target.id();
    362   const gfx::Display& primary = ash::Shell::GetScreen()->GetPrimaryDisplay();
    363 
    364   if (!ValidateParamsForDisplay(info, target, display_manager, primary.id(),
    365                                 error)) {
    366     return false;
    367   }
    368 
    369   // Process 'isPrimary' parameter.
    370   if (info.is_primary && *info.is_primary && target.id() != primary.id())
    371     display_controller->SetPrimaryDisplayId(display_id);
    372 
    373   // Process 'mirroringSourceId' parameter.
    374   if (info.mirroring_source_id &&
    375       info.mirroring_source_id->empty() == display_manager->IsMirrored()) {
    376     display_controller->ToggleMirrorMode();
    377   }
    378 
    379   // Process 'overscan' parameter.
    380   if (info.overscan) {
    381     display_manager->SetOverscanInsets(
    382         display_id,
    383         gfx::Insets(info.overscan->top, info.overscan->left,
    384                     info.overscan->bottom, info.overscan->right));
    385   }
    386 
    387   // Process 'rotation' parameter.
    388   if (info.rotation) {
    389     display_manager->SetDisplayRotation(display_id,
    390                                         DegreesToRotation(*info.rotation));
    391   }
    392 
    393   // Process new display origin parameters.
    394   gfx::Point new_bounds_origin = target.bounds().origin();
    395   if (info.bounds_origin_x)
    396     new_bounds_origin.set_x(*info.bounds_origin_x);
    397   if (info.bounds_origin_y)
    398     new_bounds_origin.set_y(*info.bounds_origin_y);
    399 
    400   if (new_bounds_origin != target.bounds().origin()) {
    401     gfx::Rect target_bounds = target.bounds();
    402     target_bounds.Offset(new_bounds_origin.x() - target.bounds().x(),
    403                          new_bounds_origin.y() - target.bounds().y());
    404     UpdateDisplayLayout(primary.bounds(), primary.id(),
    405                         target_bounds, target.id());
    406   }
    407 
    408   return true;
    409 }
    410 
    411 }  // namespace
    412 
    413 void DisplayInfoProvider::RequestInfo(const RequestInfoCallback& callback) {
    414   bool success = QueryInfo();
    415 
    416   base::MessageLoopProxy::current()->PostTask(
    417       FROM_HERE,
    418       base::Bind(callback, success));
    419 }
    420 
    421 void DisplayInfoProvider::SetInfo(const std::string& display_id,
    422                                   const DisplayProperties& info,
    423                                   const SetInfoCallback& callback) {
    424   std::string error;
    425   bool success = SetInfoImpl(display_id, info, &error);
    426   base::MessageLoopProxy::current()->PostTask(
    427       FROM_HERE,
    428       base::Bind(callback, success, error));
    429 }
    430 
    431 bool DisplayInfoProvider::QueryInfo() {
    432   info_.clear();
    433 
    434   DisplayManager* display_manager =
    435       ash::Shell::GetInstance()->display_manager();
    436   DCHECK(display_manager);
    437 
    438   int64 primary_id = ash::Shell::GetScreen()->GetPrimaryDisplay().id();
    439   for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
    440     AddInfoForDisplay(display_manager->GetDisplayAt(i), display_manager,
    441                       primary_id, &info_);
    442   }
    443 
    444   return true;
    445 }
    446 
    447 }  // namespace extensions
    448