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/window_util.h" 18 #include "ui/aura/env.h" 19 #include "ui/aura/window.h" 20 #include "ui/aura/window_event_dispatcher.h" 21 #include "ui/aura/window_tree_host.h" 22 #include "ui/base/layout.h" 23 #include "ui/compositor/dip_util.h" 24 #include "ui/events/event.h" 25 #include "ui/events/event_utils.h" 26 #include "ui/gfx/screen.h" 27 #include "ui/wm/core/coordinate_conversion.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 void ConvertPointFromScreenToNative(const aura::Window* root_window, 44 gfx::Point* point) { 45 ::wm::ConvertPointFromScreen(root_window, point); 46 root_window->GetHost()->ConvertPointToNativeScreen(point); 47 } 48 49 gfx::Rect GetNativeEdgeBounds(const aura::Window* root_window, 50 gfx::Point start, 51 gfx::Point end) { 52 gfx::Rect native_bounds = root_window->GetHost()->GetBounds(); 53 native_bounds.Inset( 54 GetRootWindowController(root_window)->ash_host()->GetHostInsets()); 55 56 ConvertPointFromScreenToNative(root_window, &start); 57 ConvertPointFromScreenToNative(root_window, &end); 58 if (start.x() == end.x()) { 59 // vertical in native 60 int x = std::abs(native_bounds.x() - start.x()) < 61 std::abs(native_bounds.right() - start.x()) 62 ? native_bounds.x() 63 : native_bounds.right() - 1; 64 return gfx::Rect( 65 x, std::min(start.y(), end.y()), 1, std::abs(start.y() - end.y())); 66 } else { 67 // horizontal in native 68 int y = std::abs(native_bounds.y() - start.y()) < 69 std::abs(native_bounds.bottom() - start.y()) 70 ? native_bounds.y() 71 : native_bounds.bottom() - 1; 72 return gfx::Rect( 73 std::min(start.x(), end.x()), y, std::abs(start.x() - end.x()), 1); 74 } 75 } 76 77 // Creates edge bounds from indicator bounds that fits the edge 78 // of the native window for |root_window|. 79 gfx::Rect CreateVerticalEdgeBoundsInNative(const aura::Window* root_window, 80 const gfx::Rect& indicator_bounds) { 81 gfx::Point start = indicator_bounds.origin(); 82 gfx::Point end = start; 83 end.set_y(indicator_bounds.bottom()); 84 return GetNativeEdgeBounds(root_window, start, end); 85 } 86 87 gfx::Rect CreateHorizontalEdgeBoundsInNative( 88 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_x(indicator_bounds.right()); 93 return GetNativeEdgeBounds(root_window, start, end); 94 } 95 96 void MovePointInside(const gfx::Rect& native_bounds, 97 gfx::Point* point_in_native) { 98 if (native_bounds.x() > point_in_native->x()) 99 point_in_native->set_x(native_bounds.x()); 100 if (native_bounds.right() < point_in_native->x()) 101 point_in_native->set_x(native_bounds.right()); 102 103 if (native_bounds.y() > point_in_native->y()) 104 point_in_native->set_y(native_bounds.y()); 105 if (native_bounds.bottom() < point_in_native->y()) 106 point_in_native->set_y(native_bounds.bottom()); 107 } 108 109 } // namespace 110 111 MouseCursorEventFilter::MouseCursorEventFilter() 112 : mouse_warp_mode_(WARP_ALWAYS), 113 was_mouse_warped_(false), 114 drag_source_root_(NULL), 115 scale_when_drag_started_(1.0f), 116 shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) { 117 Shell::GetInstance()->display_controller()->AddObserver(this); 118 } 119 120 MouseCursorEventFilter::~MouseCursorEventFilter() { 121 HideSharedEdgeIndicator(); 122 Shell::GetInstance()->display_controller()->RemoveObserver(this); 123 } 124 125 void MouseCursorEventFilter::ShowSharedEdgeIndicator(aura::Window* from) { 126 HideSharedEdgeIndicator(); 127 if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) { 128 src_indicator_bounds_.SetRect(0, 0, 0, 0); 129 dst_indicator_bounds_.SetRect(0, 0, 0, 0); 130 drag_source_root_ = NULL; 131 return; 132 } 133 drag_source_root_ = from; 134 135 DisplayLayout::Position position = Shell::GetInstance()-> 136 display_manager()->GetCurrentDisplayLayout().position; 137 if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) 138 UpdateHorizontalEdgeBounds(); 139 else 140 UpdateVerticalEdgeBounds(); 141 142 shared_display_edge_indicator_->Show(src_indicator_bounds_, 143 dst_indicator_bounds_); 144 } 145 146 void MouseCursorEventFilter::HideSharedEdgeIndicator() { 147 shared_display_edge_indicator_->Hide(); 148 OnDisplayConfigurationChanged(); 149 } 150 151 void MouseCursorEventFilter::OnDisplaysInitialized() { 152 OnDisplayConfigurationChanged(); 153 } 154 155 void MouseCursorEventFilter::OnDisplayConfigurationChanged() { 156 // Extra check for |num_connected_displays()| is for SystemDisplayApiTest 157 // that injects MockScreen. 158 if (Shell::GetScreen()->GetNumDisplays() <= 1 || 159 Shell::GetInstance()->display_manager()->num_connected_displays() <= 1) { 160 src_edge_bounds_in_native_.SetRect(0, 0, 0, 0); 161 dst_edge_bounds_in_native_.SetRect(0, 0, 0, 0); 162 return; 163 } 164 165 drag_source_root_ = NULL; 166 DisplayLayout::Position position = Shell::GetInstance() 167 ->display_manager() 168 ->GetCurrentDisplayLayout() 169 .position; 170 171 if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) 172 UpdateHorizontalEdgeBounds(); 173 else 174 UpdateVerticalEdgeBounds(); 175 } 176 177 void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) { 178 aura::Window* target = static_cast<aura::Window*>(event->target()); 179 180 if (event->type() == ui::ET_MOUSE_PRESSED) { 181 scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer()); 182 } else if (event->type() == ui::ET_MOUSE_RELEASED) { 183 scale_when_drag_started_ = 1.0f; 184 } 185 186 // Handle both MOVED and DRAGGED events here because when the mouse pointer 187 // enters the other root window while dragging, the underlying window system 188 // (at least X11) stops generating a ui::ET_MOUSE_MOVED event. 189 if (event->type() != ui::ET_MOUSE_MOVED && 190 event->type() != ui::ET_MOUSE_DRAGGED) { 191 return; 192 } 193 194 Shell::GetInstance()->display_controller()-> 195 cursor_window_controller()->UpdateLocation(); 196 197 if (WarpMouseCursorIfNecessary(event)) 198 event->StopPropagation(); 199 } 200 201 // static 202 void MouseCursorEventFilter::MoveCursorTo(aura::Window* root, 203 const gfx::Point& point_in_screen) { 204 gfx::Point point_in_native = point_in_screen; 205 ::wm::ConvertPointFromScreen(root, &point_in_native); 206 root->GetHost()->ConvertPointToNativeScreen(&point_in_native); 207 208 // now fit the point inside the native bounds. 209 gfx::Rect native_bounds = root->GetHost()->GetBounds(); 210 gfx::Point native_origin = native_bounds.origin(); 211 native_bounds.Inset( 212 GetRootWindowController(root)->ash_host()->GetHostInsets()); 213 // Shrink further so that the mouse doesn't warp on the 214 // edge. The right/bottom needs to be shrink by 2 to subtract 215 // the 1 px from width/height value. 216 native_bounds.Inset(1, 1, 2, 2); 217 218 MovePointInside(native_bounds, &point_in_native); 219 gfx::Point point_in_host = point_in_native; 220 221 point_in_host.Offset(-native_origin.x(), -native_origin.y()); 222 root->GetHost()->MoveCursorToHostLocation(point_in_host); 223 } 224 225 bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(ui::MouseEvent* event) { 226 if (!event->HasNativeEvent()) 227 return false; 228 229 gfx::Point point_in_native = 230 ui::EventSystemLocationFromNative(event->native_event()); 231 232 aura::Window* target = static_cast<aura::Window*>(event->target()); 233 #if defined(USE_OZONE) 234 // TODO(dnicoara): crbug.com/415680 Move cursor warping into Ozone once Ozone 235 // has access to the logical display layout. 236 // Native events in Ozone are in the native window coordinate system. We need 237 // to translate them to get the global position. 238 point_in_native.Offset(target->GetHost()->GetBounds().x(), 239 target->GetHost()->GetBounds().y()); 240 #endif 241 gfx::Point point_in_screen = event->location(); 242 ::wm::ConvertPointToScreen(target, &point_in_screen); 243 244 return WarpMouseCursorInNativeCoords(point_in_native, point_in_screen); 245 } 246 247 bool MouseCursorEventFilter::WarpMouseCursorInNativeCoords( 248 const gfx::Point& point_in_native, 249 const gfx::Point& point_in_screen) { 250 if (Shell::GetScreen()->GetNumDisplays() <= 1 || 251 mouse_warp_mode_ == WARP_NONE) 252 return false; 253 254 bool in_src_edge = src_edge_bounds_in_native_.Contains(point_in_native); 255 bool in_dst_edge = dst_edge_bounds_in_native_.Contains(point_in_native); 256 if (!in_src_edge && !in_dst_edge) 257 return false; 258 259 // The mouse must move. 260 aura::Window* src_root = NULL; 261 aura::Window* dst_root = NULL; 262 GetSrcAndDstRootWindows(&src_root, &dst_root); 263 264 if (in_src_edge) 265 MoveCursorTo(dst_root, point_in_screen); 266 else 267 MoveCursorTo(src_root, point_in_screen); 268 269 return true; 270 } 271 272 void MouseCursorEventFilter::UpdateHorizontalEdgeBounds() { 273 bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; 274 // GetPrimaryDisplay returns an object on stack, so copy the bounds 275 // instead of using reference. 276 const gfx::Rect primary_bounds = 277 Shell::GetScreen()->GetPrimaryDisplay().bounds(); 278 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds(); 279 DisplayLayout::Position position = Shell::GetInstance()-> 280 display_manager()->GetCurrentDisplayLayout().position; 281 282 src_indicator_bounds_.set_x( 283 std::max(primary_bounds.x(), secondary_bounds.x())); 284 src_indicator_bounds_.set_width( 285 std::min(primary_bounds.right(), secondary_bounds.right()) - 286 src_indicator_bounds_.x()); 287 src_indicator_bounds_.set_height(kIndicatorThickness); 288 src_indicator_bounds_.set_y( 289 position == DisplayLayout::TOP ? 290 primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) : 291 primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0)); 292 293 dst_indicator_bounds_ = src_indicator_bounds_; 294 dst_indicator_bounds_.set_height(kIndicatorThickness); 295 dst_indicator_bounds_.set_y( 296 position == DisplayLayout::TOP ? 297 primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) : 298 primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness)); 299 300 aura::Window* src_root = NULL; 301 aura::Window* dst_root = NULL; 302 GetSrcAndDstRootWindows(&src_root, &dst_root); 303 304 src_edge_bounds_in_native_ = 305 CreateHorizontalEdgeBoundsInNative(src_root, src_indicator_bounds_); 306 dst_edge_bounds_in_native_ = 307 CreateHorizontalEdgeBoundsInNative(dst_root, dst_indicator_bounds_); 308 } 309 310 void MouseCursorEventFilter::UpdateVerticalEdgeBounds() { 311 int snap_height = drag_source_root_ ? kMaximumSnapHeight : 0; 312 bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; 313 // GetPrimaryDisplay returns an object on stack, so copy the bounds 314 // instead of using reference. 315 const gfx::Rect primary_bounds = 316 Shell::GetScreen()->GetPrimaryDisplay().bounds(); 317 const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds(); 318 DisplayLayout::Position position = Shell::GetInstance()-> 319 display_manager()->GetCurrentDisplayLayout().position; 320 321 int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y()); 322 int lower_shared_y = std::min(primary_bounds.bottom(), 323 secondary_bounds.bottom()); 324 int shared_height = lower_shared_y - upper_shared_y; 325 326 int dst_x = position == DisplayLayout::LEFT ? 327 primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) : 328 primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness); 329 dst_indicator_bounds_.SetRect( 330 dst_x, upper_shared_y, kIndicatorThickness, shared_height); 331 332 // The indicator on the source display. 333 src_indicator_bounds_.set_width(kIndicatorThickness); 334 src_indicator_bounds_.set_x( 335 position == DisplayLayout::LEFT ? 336 primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) : 337 primary_bounds.right() - (in_primary ? kIndicatorThickness : 0)); 338 339 const gfx::Rect& source_bounds = 340 in_primary ? primary_bounds : secondary_bounds; 341 int upper_indicator_y = source_bounds.y() + snap_height; 342 int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y); 343 344 // This gives a hight that can be used without sacrifying the snap space. 345 int available_space = lower_indicator_y - 346 std::max(upper_shared_y, upper_indicator_y); 347 348 if (shared_height < kMinimumIndicatorHeight) { 349 // If the shared height is smaller than minimum height, use the 350 // entire height. 351 upper_indicator_y = upper_shared_y; 352 } else if (available_space < kMinimumIndicatorHeight) { 353 // Snap to the bottom. 354 upper_indicator_y = 355 std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight); 356 } else { 357 upper_indicator_y = std::max(upper_indicator_y, upper_shared_y); 358 } 359 src_indicator_bounds_.set_y(upper_indicator_y); 360 src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y); 361 362 aura::Window* src_root = NULL; 363 aura::Window* dst_root = NULL; 364 GetSrcAndDstRootWindows(&src_root, &dst_root); 365 366 // Native 367 src_edge_bounds_in_native_ = 368 CreateVerticalEdgeBoundsInNative(src_root, src_indicator_bounds_); 369 dst_edge_bounds_in_native_ = 370 CreateVerticalEdgeBoundsInNative(dst_root, dst_indicator_bounds_); 371 } 372 373 void MouseCursorEventFilter::GetSrcAndDstRootWindows(aura::Window** src_root, 374 aura::Window** dst_root) { 375 aura::Window::Windows root_windows = Shell::GetAllRootWindows(); 376 *src_root = drag_source_root_ ? drag_source_root_ 377 : Shell::GetInstance()->GetPrimaryRootWindow(); 378 *dst_root = root_windows[0] == *src_root ? root_windows[1] : root_windows[0]; 379 } 380 381 bool MouseCursorEventFilter::WarpMouseCursorIfNecessaryForTest( 382 aura::Window* target_root, 383 const gfx::Point& point_in_screen) { 384 gfx::Point native = point_in_screen; 385 ::wm::ConvertPointFromScreen(target_root, &native); 386 target_root->GetHost()->ConvertPointToNativeScreen(&native); 387 return WarpMouseCursorInNativeCoords(native, point_in_screen); 388 } 389 390 } // namespace ash 391