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