mouse_cursor_event_filter.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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
29namespace ash {
30namespace {
31
32// Maximum size on the display edge that initiate snapping phantom window,
33// from the corner of the display.
34const 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.
39const int kMinimumIndicatorHeight = 200;
40
41const 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)
47bool enable_mouse_warp_in_native_coords = false;
48#else
49bool enable_mouse_warp_in_native_coords = true;
50#endif
51
52void ConvertPointFromScreenToNative(const aura::Window* root_window,
53                                    gfx::Point* point) {
54  wm::ConvertPointFromScreen(root_window, point);
55  root_window->GetHost()->ConvertPointToNativeScreen(point);
56}
57
58gfx::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|.
88gfx::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
96gfx::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
105void 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.
120void 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
145bool MouseCursorEventFilter::IsMouseWarpInNativeCoordsEnabled() {
146  return enable_mouse_warp_in_native_coords;
147}
148
149MouseCursorEventFilter::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
158MouseCursorEventFilter::~MouseCursorEventFilter() {
159  HideSharedEdgeIndicator();
160  Shell::GetInstance()->display_controller()->RemoveObserver(this);
161}
162
163void 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
184void MouseCursorEventFilter::HideSharedEdgeIndicator() {
185  shared_display_edge_indicator_->Hide();
186  OnDisplayConfigurationChanged();
187}
188
189void MouseCursorEventFilter::OnDisplaysInitialized() {
190  OnDisplayConfigurationChanged();
191}
192
193void 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
216void 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
240bool 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
262bool 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
287bool 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
358void 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
396void 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
459void 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
467bool 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