1 // Copyright (c) 2012 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 "ash/display/mouse_cursor_event_filter.h" 6 7 #include <cmath> 8 9 #include "ash/display/cursor_window_controller.h" 10 #include "ash/display/display_controller.h" 11 #include "ash/display/display_manager.h" 12 #include "ash/display/shared_display_edge_indicator.h" 13 #include "ash/host/ash_window_tree_host.h" 14 #include "ash/root_window_controller.h" 15 #include "ash/screen_util.h" 16 #include "ash/shell.h" 17 #include "ash/wm/coordinate_conversion.h" 18 #include "ash/wm/window_util.h" 19 #include "ui/aura/env.h" 20 #include "ui/aura/window.h" 21 #include "ui/aura/window_event_dispatcher.h" 22 #include "ui/aura/window_tree_host.h" 23 #include "ui/base/layout.h" 24 #include "ui/compositor/dip_util.h" 25 #include "ui/events/event.h" 26 #include "ui/events/event_utils.h" 27 #include "ui/gfx/screen.h" 28 29 namespace ash { 30 namespace { 31 32 // Maximum size on the display edge that initiate snapping phantom window, 33 // from the corner of the display. 34 const int kMaximumSnapHeight = 16; 35 36 // Minimum height of an indicator on the display edge that allows 37 // dragging a window. If two displays shares the edge smaller than 38 // this, entire edge will be used as a draggable space. 39 const int kMinimumIndicatorHeight = 200; 40 41 const int kIndicatorThickness = 1; 42 43 // This is to disable the new mouse warp logic in case 44 // it caused the problem in the branch. 45 // Events from Ozone don't have a native event 46 #if defined(USE_OZONE) 47 bool enable_mouse_warp_in_native_coords = false; 48 #else 49 bool enable_mouse_warp_in_native_coords = true; 50 #endif 51 52 void ConvertPointFromScreenToNative(const aura::Window* root_window, 53 gfx::Point* point) { 54 wm::ConvertPointFromScreen(root_window, point); 55 root_window->GetHost()->ConvertPointToNativeScreen(point); 56 } 57 58 gfx::Rect GetNativeEdgeBounds(const aura::Window* root_window, 59 gfx::Point start, 60 gfx::Point end) { 61 gfx::Rect native_bounds = root_window->GetHost()->GetBounds(); 62 native_bounds.Inset( 63 GetRootWindowController(root_window)->ash_host()->GetHostInsets()); 64 65 ConvertPointFromScreenToNative(root_window, &start); 66 ConvertPointFromScreenToNative(root_window, &end); 67 if (start.x() == end.x()) { 68 // vertical in native 69 int x = std::abs(native_bounds.x() - start.x()) < 70 std::abs(native_bounds.right() - start.x()) 71 ? native_bounds.x() 72 : native_bounds.right() - 1; 73 return gfx::Rect( 74 x, std::min(start.y(), end.y()), 1, std::abs(start.y() - end.y())); 75 } else { 76 // horizontal in native 77 int y = std::abs(native_bounds.y() - start.y()) < 78 std::abs(native_bounds.bottom() - start.y()) 79 ? native_bounds.y() 80 : native_bounds.bottom() - 1; 81 return gfx::Rect( 82 std::min(start.x(), end.x()), y, std::abs(start.x() - end.x()), 1); 83 } 84 } 85 86 // Creates edge bounds from indicator bounds that fits the edge 87 // of the native window for |root_window|. 88 gfx::Rect CreateVerticalEdgeBoundsInNative(const aura::Window* root_window, 89 const gfx::Rect& indicator_bounds) { 90 gfx::Point start = indicator_bounds.origin(); 91 gfx::Point end = start; 92 end.set_y(indicator_bounds.bottom()); 93 return GetNativeEdgeBounds(root_window, start, end); 94 } 95 96 gfx::Rect CreateHorizontalEdgeBoundsInNative( 97 const aura::Window* root_window, 98 const gfx::Rect& indicator_bounds) { 99 gfx::Point start = indicator_bounds.origin(); 100 gfx::Point end = start; 101 end.set_x(indicator_bounds.right()); 102 return GetNativeEdgeBounds(root_window, start, end); 103 } 104 105 void MovePointInside(const gfx::Rect& native_bounds, 106 gfx::Point* point_in_native) { 107 if (native_bounds.x() > point_in_native->x()) 108 point_in_native->set_x(native_bounds.x()); 109 if (native_bounds.right() < point_in_native->x()) 110 point_in_native->set_x(native_bounds.right()); 111 112 if (native_bounds.y() > point_in_native->y()) 113 point_in_native->set_y(native_bounds.y()); 114 if (native_bounds.bottom() < point_in_native->y()) 115 point_in_native->set_y(native_bounds.bottom()); 116 } 117 118 // Moves the cursor to the point inside the root that is closest to 119 // the point_in_screen, which is outside of the root window. 120 void MoveCursorTo(aura::Window* root, const gfx::Point& point_in_screen) { 121 gfx::Point point_in_native = point_in_screen; 122 wm::ConvertPointFromScreen(root, &point_in_native); 123 root->GetHost()->ConvertPointToNativeScreen(&point_in_native); 124 125 // now fit the point inside the native bounds. 126 gfx::Rect native_bounds = root->GetHost()->GetBounds(); 127 gfx::Point native_origin = native_bounds.origin(); 128 native_bounds.Inset( 129 GetRootWindowController(root)->ash_host()->GetHostInsets()); 130 // Shrink further so that the mouse doesn't warp on the 131 // edge. The right/bottom needs to be shrink by 2 to subtract 132 // the 1 px from width/height value. 133 native_bounds.Inset(1, 1, 2, 2); 134 135 MovePointInside(native_bounds, &point_in_native); 136 gfx::Point point_in_host = point_in_native; 137 138 point_in_host.Offset(-native_origin.x(), -native_origin.y()); 139 root->GetHost()->MoveCursorToHostLocation(point_in_host); 140 } 141 142 } // namespace 143 144 // static 145 bool MouseCursorEventFilter::IsMouseWarpInNativeCoordsEnabled() { 146 return enable_mouse_warp_in_native_coords; 147 } 148 149 MouseCursorEventFilter::MouseCursorEventFilter() 150 : mouse_warp_mode_(WARP_ALWAYS), 151 was_mouse_warped_(false), 152 drag_source_root_(NULL), 153 scale_when_drag_started_(1.0f), 154 shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) { 155 Shell::GetInstance()->display_controller()->AddObserver(this); 156 } 157 158 MouseCursorEventFilter::~MouseCursorEventFilter() { 159 HideSharedEdgeIndicator(); 160 Shell::GetInstance()->display_controller()->RemoveObserver(this); 161 } 162 163 void MouseCursorEventFilter::ShowSharedEdgeIndicator(aura::Window* from) { 164 HideSharedEdgeIndicator(); 165 if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) { 166 src_indicator_bounds_.SetRect(0, 0, 0, 0); 167 dst_indicator_bounds_.SetRect(0, 0, 0, 0); 168 drag_source_root_ = NULL; 169 return; 170 } 171 drag_source_root_ = from; 172 173 DisplayLayout::Position position = Shell::GetInstance()-> 174 display_manager()->GetCurrentDisplayLayout().position; 175 if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) 176 UpdateHorizontalEdgeBounds(); 177 else 178 UpdateVerticalEdgeBounds(); 179 180 shared_display_edge_indicator_->Show(src_indicator_bounds_, 181 dst_indicator_bounds_); 182 } 183 184 void MouseCursorEventFilter::HideSharedEdgeIndicator() { 185 shared_display_edge_indicator_->Hide(); 186 OnDisplayConfigurationChanged(); 187 } 188 189 void MouseCursorEventFilter::OnDisplaysInitialized() { 190 OnDisplayConfigurationChanged(); 191 } 192 193 void MouseCursorEventFilter::OnDisplayConfigurationChanged() { 194 // Extra check for |num_connected_displays()| is for SystemDisplayApiTest 195 // that injects MockScreen. 196 if (Shell::GetScreen()->GetNumDisplays() <= 1 || 197 Shell::GetInstance()->display_manager()->num_connected_displays() <= 1 || 198 !enable_mouse_warp_in_native_coords) { 199 src_edge_bounds_in_native_.SetRect(0, 0, 0, 0); 200 dst_edge_bounds_in_native_.SetRect(0, 0, 0, 0); 201 return; 202 } 203 204 drag_source_root_ = NULL; 205 DisplayLayout::Position position = Shell::GetInstance() 206 ->display_manager() 207 ->GetCurrentDisplayLayout() 208 .position; 209 210 if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) 211 UpdateHorizontalEdgeBounds(); 212 else 213 UpdateVerticalEdgeBounds(); 214 } 215 216 void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) { 217 aura::Window* target = static_cast<aura::Window*>(event->target()); 218 219 if (event->type() == ui::ET_MOUSE_PRESSED) { 220 scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer()); 221 } else if (event->type() == ui::ET_MOUSE_RELEASED) { 222 scale_when_drag_started_ = 1.0f; 223 } 224 225 // Handle both MOVED and DRAGGED events here because when the mouse pointer 226 // enters the other root window while dragging, the underlying window system 227 // (at least X11) stops generating a ui::ET_MOUSE_MOVED event. 228 if (event->type() != ui::ET_MOUSE_MOVED && 229 event->type() != ui::ET_MOUSE_DRAGGED) { 230 return; 231 } 232 233 Shell::GetInstance()->display_controller()-> 234 cursor_window_controller()->UpdateLocation(); 235 236 if (WarpMouseCursorIfNecessary(event)) 237 event->StopPropagation(); 238 } 239 240 bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(ui::MouseEvent* event) { 241 if (enable_mouse_warp_in_native_coords) { 242 if (!event->HasNativeEvent()) 243 return false; 244 245 gfx::Point point_in_native = 246 ui::EventSystemLocationFromNative(event->native_event()); 247 248 gfx::Point point_in_screen = event->location(); 249 aura::Window* target = static_cast<aura::Window*>(event->target()); 250 wm::ConvertPointToScreen(target, &point_in_screen); 251 252 return WarpMouseCursorInNativeCoords(point_in_native, point_in_screen); 253 } else { 254 gfx::Point point_in_screen(event->location()); 255 aura::Window* target = static_cast<aura::Window*>(event->target()); 256 wm::ConvertPointToScreen(target, &point_in_screen); 257 return WarpMouseCursorInScreenCoords(target->GetRootWindow(), 258 point_in_screen); 259 } 260 } 261 262 bool MouseCursorEventFilter::WarpMouseCursorInNativeCoords( 263 const gfx::Point& point_in_native, 264 const gfx::Point& point_in_screen) { 265 if (Shell::GetScreen()->GetNumDisplays() <= 1 || 266 mouse_warp_mode_ == WARP_NONE) 267 return false; 268 269 bool in_src_edge = src_edge_bounds_in_native_.Contains(point_in_native); 270 bool in_dst_edge = dst_edge_bounds_in_native_.Contains(point_in_native); 271 if (!in_src_edge && !in_dst_edge) 272 return false; 273 274 // The mouse must move. 275 aura::Window* src_root = NULL; 276 aura::Window* dst_root = NULL; 277 GetSrcAndDstRootWindows(&src_root, &dst_root); 278 279 if (in_src_edge) 280 MoveCursorTo(dst_root, point_in_screen); 281 else 282 MoveCursorTo(src_root, point_in_screen); 283 284 return true; 285 } 286 287 bool MouseCursorEventFilter::WarpMouseCursorInScreenCoords( 288 aura::Window* target_root, 289 const gfx::Point& point_in_screen) { 290 if (Shell::GetScreen()->GetNumDisplays() <= 1 || 291 mouse_warp_mode_ == WARP_NONE) 292 return false; 293 294 // Do not warp again right after the cursor was warped. Sometimes the offset 295 // is not long enough and the cursor moves at the edge of the destination 296 // display. See crbug.com/278885 297 // TODO(mukai): simplify the offset calculation below, it would not be 298 // necessary anymore with this flag. 299 if (was_mouse_warped_) { 300 was_mouse_warped_ = false; 301 return false; 302 } 303 304 aura::Window* root_at_point = wm::GetRootWindowAt(point_in_screen); 305 gfx::Point point_in_root = point_in_screen; 306 wm::ConvertPointFromScreen(root_at_point, &point_in_root); 307 gfx::Rect root_bounds = root_at_point->bounds(); 308 int offset_x = 0; 309 int offset_y = 0; 310 311 // If the window is dragged between 2x display and 1x display, 312 // staring from 2x display, pointer location is rounded by the 313 // source scale factor (2x) so it will never reach the edge (which 314 // is odd). Shrink by scale factor of the display where the dragging 315 // started instead. Only integral scale factor is supported for now. 316 int shrink = scale_when_drag_started_; 317 // Make the bounds inclusive to detect the edge. 318 root_bounds.Inset(0, 0, shrink, shrink); 319 gfx::Rect src_indicator_bounds = src_indicator_bounds_; 320 src_indicator_bounds.Inset(-shrink, -shrink, -shrink, -shrink); 321 322 if (point_in_root.x() <= root_bounds.x()) { 323 // Use -2, not -1, to avoid infinite loop of pointer warp. 324 offset_x = -2 * scale_when_drag_started_; 325 } else if (point_in_root.x() >= root_bounds.right()) { 326 offset_x = 2 * scale_when_drag_started_; 327 } else if (point_in_root.y() <= root_bounds.y()) { 328 offset_y = -2 * scale_when_drag_started_; 329 } else if (point_in_root.y() >= root_bounds.bottom()) { 330 offset_y = 2 * scale_when_drag_started_; 331 } else { 332 return false; 333 } 334 335 gfx::Point point_in_dst_screen(point_in_screen); 336 point_in_dst_screen.Offset(offset_x, offset_y); 337 aura::Window* dst_root = wm::GetRootWindowAt(point_in_dst_screen); 338 339 // Warp the mouse cursor only if the location is in the indicator bounds 340 // or the mouse pointer is in the destination root. 341 if (mouse_warp_mode_ == WARP_DRAG && 342 dst_root != drag_source_root_ && 343 !src_indicator_bounds.Contains(point_in_screen)) { 344 return false; 345 } 346 347 wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen); 348 349 if (dst_root->bounds().Contains(point_in_dst_screen)) { 350 DCHECK_NE(dst_root, root_at_point); 351 was_mouse_warped_ = true; 352 dst_root->MoveCursorTo(point_in_dst_screen); 353 return true; 354 } 355 return false; 356 } 357 358 void MouseCursorEventFilter::UpdateHorizontalEdgeBounds() { 359 bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; 360 // GetPrimaryDisplay returns an object on stack, so copy the bounds 361 // instead of using reference. 362 const gfx::Rect primary_bounds = 363 Shell::GetScreen()->GetPrimaryDisplay().bounds(); 364 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds(); 365 DisplayLayout::Position position = Shell::GetInstance()-> 366 display_manager()->GetCurrentDisplayLayout().position; 367 368 src_indicator_bounds_.set_x( 369 std::max(primary_bounds.x(), secondary_bounds.x())); 370 src_indicator_bounds_.set_width( 371 std::min(primary_bounds.right(), secondary_bounds.right()) - 372 src_indicator_bounds_.x()); 373 src_indicator_bounds_.set_height(kIndicatorThickness); 374 src_indicator_bounds_.set_y( 375 position == DisplayLayout::TOP ? 376 primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) : 377 primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0)); 378 379 dst_indicator_bounds_ = src_indicator_bounds_; 380 dst_indicator_bounds_.set_height(kIndicatorThickness); 381 dst_indicator_bounds_.set_y( 382 position == DisplayLayout::TOP ? 383 primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) : 384 primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness)); 385 386 aura::Window* src_root = NULL; 387 aura::Window* dst_root = NULL; 388 GetSrcAndDstRootWindows(&src_root, &dst_root); 389 390 src_edge_bounds_in_native_ = 391 CreateHorizontalEdgeBoundsInNative(src_root, src_indicator_bounds_); 392 dst_edge_bounds_in_native_ = 393 CreateHorizontalEdgeBoundsInNative(dst_root, dst_indicator_bounds_); 394 } 395 396 void MouseCursorEventFilter::UpdateVerticalEdgeBounds() { 397 int snap_height = drag_source_root_ ? kMaximumSnapHeight : 0; 398 bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; 399 // GetPrimaryDisplay returns an object on stack, so copy the bounds 400 // instead of using reference. 401 const gfx::Rect primary_bounds = 402 Shell::GetScreen()->GetPrimaryDisplay().bounds(); 403 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds(); 404 DisplayLayout::Position position = Shell::GetInstance()-> 405 display_manager()->GetCurrentDisplayLayout().position; 406 407 int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y()); 408 int lower_shared_y = std::min(primary_bounds.bottom(), 409 secondary_bounds.bottom()); 410 int shared_height = lower_shared_y - upper_shared_y; 411 412 int dst_x = position == DisplayLayout::LEFT ? 413 primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) : 414 primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness); 415 dst_indicator_bounds_.SetRect( 416 dst_x, upper_shared_y, kIndicatorThickness, shared_height); 417 418 // The indicator on the source display. 419 src_indicator_bounds_.set_width(kIndicatorThickness); 420 src_indicator_bounds_.set_x( 421 position == DisplayLayout::LEFT ? 422 primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) : 423 primary_bounds.right() - (in_primary ? kIndicatorThickness : 0)); 424 425 const gfx::Rect& source_bounds = 426 in_primary ? primary_bounds : secondary_bounds; 427 int upper_indicator_y = source_bounds.y() + snap_height; 428 int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y); 429 430 // This gives a hight that can be used without sacrifying the snap space. 431 int available_space = lower_indicator_y - 432 std::max(upper_shared_y, upper_indicator_y); 433 434 if (shared_height < kMinimumIndicatorHeight) { 435 // If the shared height is smaller than minimum height, use the 436 // entire height. 437 upper_indicator_y = upper_shared_y; 438 } else if (available_space < kMinimumIndicatorHeight) { 439 // Snap to the bottom. 440 upper_indicator_y = 441 std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight); 442 } else { 443 upper_indicator_y = std::max(upper_indicator_y, upper_shared_y); 444 } 445 src_indicator_bounds_.set_y(upper_indicator_y); 446 src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y); 447 448 aura::Window* src_root = NULL; 449 aura::Window* dst_root = NULL; 450 GetSrcAndDstRootWindows(&src_root, &dst_root); 451 452 // Native 453 src_edge_bounds_in_native_ = 454 CreateVerticalEdgeBoundsInNative(src_root, src_indicator_bounds_); 455 dst_edge_bounds_in_native_ = 456 CreateVerticalEdgeBoundsInNative(dst_root, dst_indicator_bounds_); 457 } 458 459 void MouseCursorEventFilter::GetSrcAndDstRootWindows(aura::Window** src_root, 460 aura::Window** dst_root) { 461 aura::Window::Windows root_windows = Shell::GetAllRootWindows(); 462 *src_root = drag_source_root_ ? drag_source_root_ 463 : Shell::GetInstance()->GetPrimaryRootWindow(); 464 *dst_root = root_windows[0] == *src_root ? root_windows[1] : root_windows[0]; 465 } 466 467 bool MouseCursorEventFilter::WarpMouseCursorIfNecessaryForTest( 468 aura::Window* target_root, 469 const gfx::Point& point_in_screen) { 470 if (!enable_mouse_warp_in_native_coords) 471 return WarpMouseCursorInScreenCoords(target_root, point_in_screen); 472 gfx::Point native = point_in_screen; 473 wm::ConvertPointFromScreen(target_root, &native); 474 target_root->GetHost()->ConvertPointToNativeScreen(&native); 475 return WarpMouseCursorInNativeCoords(native, point_in_screen); 476 } 477 478 } // namespace ash 479