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