| OLD | NEW |
| (Empty) |
| 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 "ui/gfx/display.h" | |
| 13 #include "ui/gfx/point.h" | |
| 14 #include "ui/gfx/rect.h" | |
| 15 | |
| 16 using ash::DisplayManager; | |
| 17 | |
| 18 namespace extensions { | |
| 19 | |
| 20 using api::system_display::Bounds; | |
| 21 using api::system_display::DisplayUnitInfo; | |
| 22 using api::system_display::DisplayProperties; | |
| 23 using api::system_display::Insets; | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 // TODO(hshi): determine the DPI of the screen. | |
| 28 const float kDpi96 = 96.0; | |
| 29 // Maximum allowed bounds origin absolute value. | |
| 30 const int kMaxBoundsOrigin = 200 * 1000; | |
| 31 | |
| 32 // Checks if the given integer value is valid display rotation in degrees. | |
| 33 bool IsValidRotationValue(int rotation) { | |
| 34 return rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270; | |
| 35 } | |
| 36 | |
| 37 // Converts integer integer value in degrees to Rotation enum value. | |
| 38 gfx::Display::Rotation DegreesToRotation(int degrees) { | |
| 39 DCHECK(IsValidRotationValue(degrees)); | |
| 40 switch (degrees) { | |
| 41 case 0: | |
| 42 return gfx::Display::ROTATE_0; | |
| 43 case 90: | |
| 44 return gfx::Display::ROTATE_90; | |
| 45 case 180: | |
| 46 return gfx::Display::ROTATE_180; | |
| 47 case 270: | |
| 48 return gfx::Display::ROTATE_270; | |
| 49 default: | |
| 50 return gfx::Display::ROTATE_0; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 // Checks if the given point is over the radius vector described by it's end | |
| 55 // point |vector|. The point is over a vector if it's on its positive (left) | |
| 56 // side. The method sees a point on the same line as the vector as being over | |
| 57 // the vector. | |
| 58 bool PointIsOverRadiusVector(const gfx::Point& point, | |
| 59 const gfx::Point& vector) { | |
| 60 // |point| is left of |vector| if its radius vector's scalar product with a | |
| 61 // vector orthogonal (and facing the positive side) to |vector| is positive. | |
| 62 // | |
| 63 // An orthogonal vector of (a, b) is (b, -a), as the scalar product of these | |
| 64 // two is 0. | |
| 65 // So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to | |
| 66 // x * b >= y * a. | |
| 67 return static_cast<int64>(point.x()) * static_cast<int64>(vector.y()) >= | |
| 68 static_cast<int64>(point.y()) * static_cast<int64>(vector.x()); | |
| 69 } | |
| 70 | |
| 71 // Created ash::DisplayLayout value for |rectangle| compared to the |reference| | |
| 72 // rectangle. | |
| 73 // The layout consists of two values: | |
| 74 // - position: Whether the rectangle is positioned left, right, over or under | |
| 75 // the reference. | |
| 76 // - offset: The rectangle's offset from the reference origin along the axis | |
| 77 // opposite the position direction (if the rectangle is left or right along | |
| 78 // y-axis, otherwise along x-axis). | |
| 79 // The rectangle's position is calculated by dividing the space in areas defined | |
| 80 // by the |reference|'s diagonals and finding the area |rectangle|'s center | |
| 81 // point belongs. If the |rectangle| in the calculated layout does not share a | |
| 82 // part of the bounds with the |reference|, the |rectangle| position in set to | |
| 83 // the more suitable neighboring position (e.g. if |rectangle| is completely | |
| 84 // over the |reference| top bound, it will be set to TOP) and the layout is | |
| 85 // recalculated with the new position. This is to handle case where the | |
| 86 // rectangle shares an edge with the reference, but it's center is not in the | |
| 87 // same area as the reference's edge, e.g. | |
| 88 // | |
| 89 // +---------------------+ | |
| 90 // | | | |
| 91 // | REFERENCE | | |
| 92 // | | | |
| 93 // | | | |
| 94 // +---------------------+ | |
| 95 // +-------------------------------------------------+ | |
| 96 // | RECTANGLE x | | |
| 97 // +-------------------------------------------------+ | |
| 98 // | |
| 99 // The rectangle shares an egde with the reference's bottom edge, but it's | |
| 100 // center point is in the left area. | |
| 101 ash::DisplayLayout GetLayoutForRectangles(const gfx::Rect& reference, | |
| 102 const gfx::Rect& rectangle) { | |
| 103 // Translate coordinate system so origin is in the reference's top left point | |
| 104 // (so the reference's down-diagonal vector starts in the (0, 0)) and scale it | |
| 105 // up by two (to avoid division when calculating the rectangle's center | |
| 106 // point). | |
| 107 gfx::Point center(2 * (rectangle.x() - reference.x()) + rectangle.width(), | |
| 108 2 * (rectangle.y() - reference.y()) + rectangle.height()); | |
| 109 gfx::Point down_diag(2 * reference.width(), 2 * reference.height()); | |
| 110 | |
| 111 bool is_top_right = PointIsOverRadiusVector(center, down_diag); | |
| 112 | |
| 113 // Translate the coordinating system again, so the bottom right point of the | |
| 114 // reference is origin (so the references up-diagonal starts at (0, 0)). | |
| 115 // Note that the coordinate system is scaled by 2. | |
| 116 center.Offset(0, -2 * reference.height()); | |
| 117 // Choose the vector orientation so the points on the diagonal are considered | |
| 118 // to be left. | |
| 119 gfx::Point up_diag(-2 * reference.width(), 2 * reference.height()); | |
| 120 | |
| 121 bool is_bottom_right = PointIsOverRadiusVector(center, up_diag); | |
| 122 | |
| 123 ash::DisplayLayout::Position position; | |
| 124 if (is_top_right) { | |
| 125 position = is_bottom_right ? ash::DisplayLayout::RIGHT : | |
| 126 ash::DisplayLayout::TOP; | |
| 127 } else { | |
| 128 position = | |
| 129 is_bottom_right ? ash::DisplayLayout::BOTTOM : ash::DisplayLayout::LEFT; | |
| 130 } | |
| 131 | |
| 132 // If the rectangle with the calculated position would not have common side | |
| 133 // with the reference, try to position it so it shares another edge with the | |
| 134 // reference. | |
| 135 if (is_top_right == is_bottom_right) { | |
| 136 if (rectangle.y() > reference.y() + reference.height()) { | |
| 137 // The rectangle is left or right, but completely under the reference. | |
| 138 position = ash::DisplayLayout::BOTTOM; | |
| 139 } else if (rectangle.y() + rectangle.height() < reference.y()) { | |
| 140 // The rectangle is left or right, but completely over the reference. | |
| 141 position = ash::DisplayLayout::TOP; | |
| 142 } | |
| 143 } else { | |
| 144 if (rectangle.x() > reference.x() + reference.width()) { | |
| 145 // The rectangle is over or under, but completely right of the reference. | |
| 146 position = ash::DisplayLayout::RIGHT; | |
| 147 } else if (rectangle.x() + rectangle.width() < reference.x()) { | |
| 148 // The rectangle is over or under, but completely left of the reference. | |
| 149 position = ash::DisplayLayout::LEFT; | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 if (position == ash::DisplayLayout::LEFT || | |
| 154 position == ash::DisplayLayout::RIGHT) { | |
| 155 return ash::DisplayLayout::FromInts(position, rectangle.y()); | |
| 156 } else { | |
| 157 return ash::DisplayLayout::FromInts(position, rectangle.x()); | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 // Updates the display layout for the target display in reference to the primary | |
| 162 // display. | |
| 163 void UpdateDisplayLayout(const gfx::Rect& primary_display_bounds, | |
| 164 int primary_display_id, | |
| 165 const gfx::Rect& target_display_bounds, | |
| 166 int target_display_id) { | |
| 167 ash::DisplayLayout layout = GetLayoutForRectangles(primary_display_bounds, | |
| 168 target_display_bounds); | |
| 169 ash::Shell::GetInstance()->display_manager()-> | |
| 170 SetLayoutForCurrentDisplays(layout); | |
| 171 } | |
| 172 | |
| 173 // Validates that parameters passed to the SetInfo function are valid for the | |
| 174 // desired display and the current display manager state. | |
| 175 // Returns whether the parameters are valid. On failure |error| is set to the | |
| 176 // error message. | |
| 177 bool ValidateParamsForDisplay(const DisplayProperties& info, | |
| 178 const gfx::Display& display, | |
| 179 DisplayManager* display_manager, | |
| 180 int64 primary_display_id, | |
| 181 std::string* error) { | |
| 182 bool is_primary = display.id() == primary_display_id || | |
| 183 (info.is_primary && *info.is_primary); | |
| 184 | |
| 185 // If mirroring source id is set, a display with the given id should exist, | |
| 186 // and if should not be the same as the target display's id. | |
| 187 if (info.mirroring_source_id && !info.mirroring_source_id->empty()) { | |
| 188 int64 mirroring_id; | |
| 189 if (!base::StringToInt64(*info.mirroring_source_id, &mirroring_id) || | |
| 190 display_manager->GetDisplayForId(mirroring_id).id() == | |
| 191 gfx::Display::kInvalidDisplayID) { | |
| 192 *error = "Display " + *info.mirroring_source_id + " not found."; | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 196 if (*info.mirroring_source_id == base::Int64ToString(display.id())) { | |
| 197 *error = "Not allowed to mirror self."; | |
| 198 return false; | |
| 199 } | |
| 200 } | |
| 201 | |
| 202 // If mirroring source parameter is specified, no other parameter should be | |
| 203 // set as when the mirroring is applied the display list could change. | |
| 204 if (info.mirroring_source_id && (info.is_primary || info.bounds_origin_x || | |
| 205 info.bounds_origin_y || info.rotation || info.overscan)) { | |
| 206 *error = "No other parameter should be set alongside mirroringSourceId."; | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 // The bounds cannot be changed for the primary display and should be inside | |
| 211 // a reasonable bounds. Note that the display is considered primary if the | |
| 212 // info has 'isPrimary' parameter set, as this will be applied before bounds | |
| 213 // origin changes. | |
| 214 if (info.bounds_origin_x || info.bounds_origin_y) { | |
| 215 if (is_primary) { | |
| 216 *error = "Bounds origin not allowed for the primary display."; | |
| 217 return false; | |
| 218 } | |
| 219 if (info.bounds_origin_x && | |
| 220 (*info.bounds_origin_x > kMaxBoundsOrigin || | |
| 221 *info.bounds_origin_x < -kMaxBoundsOrigin)) { | |
| 222 *error = "Bounds origin x out of bounds."; | |
| 223 return false; | |
| 224 } | |
| 225 if (info.bounds_origin_y && | |
| 226 (*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. | |
| 272 gfx::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 // Updates the display with |display_id_str| id according to |info|. Returns | |
| 283 // whether the display was successfully updated. On failure, no display | |
| 284 // parameters should be changed, and |error| should be set to the error string. | |
| 285 bool SetInfoImpl(const std::string& display_id_str, | |
| 286 const DisplayProperties& info, | |
| 287 std::string* error) { | |
| 288 DisplayManager* display_manager = | |
| 289 ash::Shell::GetInstance()->display_manager(); | |
| 290 DCHECK(display_manager); | |
| 291 ash::DisplayController* display_controller = | |
| 292 ash::Shell::GetInstance()->display_controller(); | |
| 293 DCHECK(display_controller); | |
| 294 | |
| 295 const gfx::Display target = GetTargetDisplay(display_id_str, display_manager); | |
| 296 | |
| 297 if (target.id() == gfx::Display::kInvalidDisplayID) { | |
| 298 *error = "Display not found."; | |
| 299 return false; | |
| 300 } | |
| 301 | |
| 302 int64 display_id = target.id(); | |
| 303 // TODO(scottmg): Native is wrong http://crbug.com/133312 | |
| 304 const gfx::Display& primary = | |
| 305 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); | |
| 306 | |
| 307 if (!ValidateParamsForDisplay(info, target, display_manager, primary.id(), | |
| 308 error)) { | |
| 309 return false; | |
| 310 } | |
| 311 | |
| 312 // Process 'isPrimary' parameter. | |
| 313 if (info.is_primary && *info.is_primary && target.id() != primary.id()) | |
| 314 display_controller->SetPrimaryDisplayId(display_id); | |
| 315 | |
| 316 // Process 'mirroringSourceId' parameter. | |
| 317 if (info.mirroring_source_id && | |
| 318 info.mirroring_source_id->empty() == display_manager->IsMirrored()) { | |
| 319 display_controller->ToggleMirrorMode(); | |
| 320 } | |
| 321 | |
| 322 // Process 'overscan' parameter. | |
| 323 if (info.overscan) { | |
| 324 display_manager->SetOverscanInsets( | |
| 325 display_id, | |
| 326 gfx::Insets(info.overscan->top, info.overscan->left, | |
| 327 info.overscan->bottom, info.overscan->right)); | |
| 328 } | |
| 329 | |
| 330 // Process 'rotation' parameter. | |
| 331 if (info.rotation) { | |
| 332 display_manager->SetDisplayRotation(display_id, | |
| 333 DegreesToRotation(*info.rotation)); | |
| 334 } | |
| 335 | |
| 336 // Process new display origin parameters. | |
| 337 gfx::Point new_bounds_origin = target.bounds().origin(); | |
| 338 if (info.bounds_origin_x) | |
| 339 new_bounds_origin.set_x(*info.bounds_origin_x); | |
| 340 if (info.bounds_origin_y) | |
| 341 new_bounds_origin.set_y(*info.bounds_origin_y); | |
| 342 | |
| 343 if (new_bounds_origin != target.bounds().origin()) { | |
| 344 gfx::Rect target_bounds = target.bounds(); | |
| 345 target_bounds.Offset(new_bounds_origin.x() - target.bounds().x(), | |
| 346 new_bounds_origin.y() - target.bounds().y()); | |
| 347 UpdateDisplayLayout(primary.bounds(), primary.id(), | |
| 348 target_bounds, target.id()); | |
| 349 } | |
| 350 | |
| 351 return true; | |
| 352 } | |
| 353 | |
| 354 } // namespace | |
| 355 | |
| 356 bool DisplayInfoProvider::SetInfo(const std::string& display_id, | |
| 357 const DisplayProperties& info, | |
| 358 std::string* error) { | |
| 359 return SetInfoImpl(display_id, info, error); | |
| 360 } | |
| 361 | |
| 362 void DisplayInfoProvider::UpdateDisplayUnitInfoForPlatform( | |
| 363 const gfx::Display& display, | |
| 364 extensions::api::system_display::DisplayUnitInfo* unit) { | |
| 365 | |
| 366 ash::DisplayManager* display_manager = | |
| 367 ash::Shell::GetInstance()->display_manager(); | |
| 368 unit->name = display_manager->GetDisplayNameForId(display.id()); | |
| 369 if (display_manager->IsMirrored()) { | |
| 370 unit->mirroring_source_id = | |
| 371 base::Int64ToString(display_manager->mirrored_display_id()); | |
| 372 } | |
| 373 | |
| 374 const float dpi = display.device_scale_factor() * kDpi96; | |
| 375 unit->dpi_x = dpi; | |
| 376 unit->dpi_y = dpi; | |
| 377 | |
| 378 const gfx::Insets overscan_insets = | |
| 379 display_manager->GetOverscanInsets(display.id()); | |
| 380 unit->overscan.left = overscan_insets.left(); | |
| 381 unit->overscan.top = overscan_insets.top(); | |
| 382 unit->overscan.right = overscan_insets.right(); | |
| 383 unit->overscan.bottom = overscan_insets.bottom(); | |
| 384 } | |
| 385 | |
| 386 } // namespace extensions | |
| OLD | NEW |