1 // Copyright 2014 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/display_info_provider_chromeos.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 "extensions/common/api/system_display.h" 13 #include "ui/gfx/display.h" 14 #include "ui/gfx/point.h" 15 #include "ui/gfx/rect.h" 16 17 using ash::DisplayManager; 18 19 namespace extensions { 20 21 using core_api::system_display::Bounds; 22 using core_api::system_display::DisplayUnitInfo; 23 using core_api::system_display::DisplayProperties; 24 using core_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 = 127 is_bottom_right ? ash::DisplayLayout::RIGHT : 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 = 169 GetLayoutForRectangles(primary_display_bounds, target_display_bounds); 170 ash::Shell::GetInstance()->display_manager()->SetLayoutForCurrentDisplays( 171 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 && 206 (info.is_primary || info.bounds_origin_x || info.bounds_origin_y || 207 info.rotation || info.overscan)) { 208 *error = "No other parameter should be set alongside mirroringSourceId."; 209 return false; 210 } 211 212 // The bounds cannot be changed for the primary display and should be inside 213 // a reasonable bounds. Note that the display is considered primary if the 214 // info has 'isPrimary' parameter set, as this will be applied before bounds 215 // origin changes. 216 if (info.bounds_origin_x || info.bounds_origin_y) { 217 if (is_primary) { 218 *error = "Bounds origin not allowed for the primary display."; 219 return false; 220 } 221 if (info.bounds_origin_x && (*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 && (*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 } // namespace 283 284 DisplayInfoProviderChromeOS::DisplayInfoProviderChromeOS() { 285 } 286 287 DisplayInfoProviderChromeOS::~DisplayInfoProviderChromeOS() { 288 } 289 290 bool DisplayInfoProviderChromeOS::SetInfo(const std::string& display_id_str, 291 const DisplayProperties& info, 292 std::string* error) { 293 DisplayManager* display_manager = 294 ash::Shell::GetInstance()->display_manager(); 295 DCHECK(display_manager); 296 ash::DisplayController* display_controller = 297 ash::Shell::GetInstance()->display_controller(); 298 DCHECK(display_controller); 299 300 const gfx::Display target = GetTargetDisplay(display_id_str, display_manager); 301 302 if (target.id() == gfx::Display::kInvalidDisplayID) { 303 *error = "Display not found."; 304 return false; 305 } 306 307 int64 display_id = target.id(); 308 // TODO(scottmg): Native is wrong http://crbug.com/133312 309 const gfx::Display& primary = 310 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); 311 312 if (!ValidateParamsForDisplay( 313 info, target, display_manager, primary.id(), error)) { 314 return false; 315 } 316 317 // Process 'isPrimary' parameter. 318 if (info.is_primary && *info.is_primary && target.id() != primary.id()) 319 display_controller->SetPrimaryDisplayId(display_id); 320 321 // Process 'mirroringSourceId' parameter. 322 if (info.mirroring_source_id && 323 info.mirroring_source_id->empty() == display_manager->IsMirrored()) { 324 display_controller->ToggleMirrorMode(); 325 } 326 327 // Process 'overscan' parameter. 328 if (info.overscan) { 329 display_manager->SetOverscanInsets(display_id, 330 gfx::Insets(info.overscan->top, 331 info.overscan->left, 332 info.overscan->bottom, 333 info.overscan->right)); 334 } 335 336 // Process 'rotation' parameter. 337 if (info.rotation) { 338 display_manager->SetDisplayRotation(display_id, 339 DegreesToRotation(*info.rotation)); 340 } 341 342 // Process new display origin parameters. 343 gfx::Point new_bounds_origin = target.bounds().origin(); 344 if (info.bounds_origin_x) 345 new_bounds_origin.set_x(*info.bounds_origin_x); 346 if (info.bounds_origin_y) 347 new_bounds_origin.set_y(*info.bounds_origin_y); 348 349 if (new_bounds_origin != target.bounds().origin()) { 350 gfx::Rect target_bounds = target.bounds(); 351 target_bounds.Offset(new_bounds_origin.x() - target.bounds().x(), 352 new_bounds_origin.y() - target.bounds().y()); 353 UpdateDisplayLayout( 354 primary.bounds(), primary.id(), target_bounds, target.id()); 355 } 356 357 return true; 358 } 359 360 void DisplayInfoProviderChromeOS::UpdateDisplayUnitInfoForPlatform( 361 const gfx::Display& display, 362 extensions::core_api::system_display::DisplayUnitInfo* unit) { 363 ash::DisplayManager* display_manager = 364 ash::Shell::GetInstance()->display_manager(); 365 unit->name = display_manager->GetDisplayNameForId(display.id()); 366 if (display_manager->IsMirrored()) { 367 unit->mirroring_source_id = 368 base::Int64ToString(display_manager->mirrored_display_id()); 369 } 370 371 const float dpi = display.device_scale_factor() * kDpi96; 372 unit->dpi_x = dpi; 373 unit->dpi_y = dpi; 374 375 const gfx::Insets overscan_insets = 376 display_manager->GetOverscanInsets(display.id()); 377 unit->overscan.left = overscan_insets.left(); 378 unit->overscan.top = overscan_insets.top(); 379 unit->overscan.right = overscan_insets.right(); 380 unit->overscan.bottom = overscan_insets.bottom(); 381 } 382 383 gfx::Screen* DisplayInfoProviderChromeOS::GetActiveScreen() { 384 return ash::Shell::GetScreen(); 385 } 386 387 // static 388 DisplayInfoProvider* DisplayInfoProvider::Create() { 389 return new DisplayInfoProviderChromeOS(); 390 } 391 392 } // namespace extensions 393