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 16using ash::DisplayManager; 17 18namespace extensions { 19 20using api::system_display::Bounds; 21using api::system_display::DisplayUnitInfo; 22using api::system_display::DisplayProperties; 23using api::system_display::Insets; 24 25namespace { 26 27// TODO(hshi): determine the DPI of the screen. 28const float kDpi96 = 96.0; 29// Maximum allowed bounds origin absolute value. 30const int kMaxBoundsOrigin = 200 * 1000; 31 32// Checks if the given integer value is valid display rotation in degrees. 33bool 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. 38gfx::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. 58bool 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. 101ash::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. 163void 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. 177bool 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. 272gfx::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. 285bool 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 356bool DisplayInfoProvider::SetInfo(const std::string& display_id, 357 const DisplayProperties& info, 358 std::string* error) { 359 return SetInfoImpl(display_id, info, error); 360} 361 362void 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 387