mouse_cursor_event_filter.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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
43void ConvertPointFromScreenToNative(const aura::Window* root_window,
44                                    gfx::Point* point) {
45  wm::ConvertPointFromScreen(root_window, point);
46  root_window->GetHost()->ConvertPointToNativeScreen(point);
47}
48
49gfx::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|.
79gfx::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
87gfx::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
96void 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
111MouseCursorEventFilter::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
120MouseCursorEventFilter::~MouseCursorEventFilter() {
121  HideSharedEdgeIndicator();
122  Shell::GetInstance()->display_controller()->RemoveObserver(this);
123}
124
125void 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
146void MouseCursorEventFilter::HideSharedEdgeIndicator() {
147  shared_display_edge_indicator_->Hide();
148  OnDisplayConfigurationChanged();
149}
150
151void MouseCursorEventFilter::OnDisplaysInitialized() {
152  OnDisplayConfigurationChanged();
153}
154
155#if !defined(USE_OZONE)
156void MouseCursorEventFilter::OnDisplayConfigurationChanged() {
157  // Extra check for |num_connected_displays()| is for SystemDisplayApiTest
158  // that injects MockScreen.
159  if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
160      Shell::GetInstance()->display_manager()->num_connected_displays() <= 1) {
161    src_edge_bounds_in_native_.SetRect(0, 0, 0, 0);
162    dst_edge_bounds_in_native_.SetRect(0, 0, 0, 0);
163    return;
164  }
165
166  drag_source_root_ = NULL;
167  DisplayLayout::Position position = Shell::GetInstance()
168                                         ->display_manager()
169                                         ->GetCurrentDisplayLayout()
170                                         .position;
171
172  if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
173    UpdateHorizontalEdgeBounds();
174  else
175    UpdateVerticalEdgeBounds();
176}
177#endif
178
179void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) {
180  aura::Window* target = static_cast<aura::Window*>(event->target());
181
182  if (event->type() == ui::ET_MOUSE_PRESSED) {
183    scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer());
184  } else if (event->type() == ui::ET_MOUSE_RELEASED) {
185    scale_when_drag_started_ = 1.0f;
186  }
187
188  // Handle both MOVED and DRAGGED events here because when the mouse pointer
189  // enters the other root window while dragging, the underlying window system
190  // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
191  if (event->type() != ui::ET_MOUSE_MOVED &&
192      event->type() != ui::ET_MOUSE_DRAGGED) {
193      return;
194  }
195
196  Shell::GetInstance()->display_controller()->
197      cursor_window_controller()->UpdateLocation();
198
199  if (WarpMouseCursorIfNecessary(event))
200    event->StopPropagation();
201}
202
203// static
204void MouseCursorEventFilter::MoveCursorTo(aura::Window* root,
205                                          const gfx::Point& point_in_screen) {
206  gfx::Point point_in_native = point_in_screen;
207  wm::ConvertPointFromScreen(root, &point_in_native);
208  root->GetHost()->ConvertPointToNativeScreen(&point_in_native);
209
210  // now fit the point inside the native bounds.
211  gfx::Rect native_bounds = root->GetHost()->GetBounds();
212  gfx::Point native_origin = native_bounds.origin();
213  native_bounds.Inset(
214      GetRootWindowController(root)->ash_host()->GetHostInsets());
215  // Shrink further so that the mouse doesn't warp on the
216  // edge. The right/bottom needs to be shrink by 2 to subtract
217  // the 1 px from width/height value.
218  native_bounds.Inset(1, 1, 2, 2);
219
220  MovePointInside(native_bounds, &point_in_native);
221  gfx::Point point_in_host = point_in_native;
222
223  point_in_host.Offset(-native_origin.x(), -native_origin.y());
224  root->GetHost()->MoveCursorToHostLocation(point_in_host);
225}
226
227#if !defined(USE_OZONE)
228bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(ui::MouseEvent* event) {
229  if (!event->HasNativeEvent())
230    return false;
231
232  gfx::Point point_in_native =
233      ui::EventSystemLocationFromNative(event->native_event());
234
235  gfx::Point point_in_screen = event->location();
236  aura::Window* target = static_cast<aura::Window*>(event->target());
237  wm::ConvertPointToScreen(target, &point_in_screen);
238
239  return WarpMouseCursorInNativeCoords(point_in_native, point_in_screen);
240}
241
242bool MouseCursorEventFilter::WarpMouseCursorInNativeCoords(
243    const gfx::Point& point_in_native,
244    const gfx::Point& point_in_screen) {
245  if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
246      mouse_warp_mode_ == WARP_NONE)
247    return false;
248
249  bool in_src_edge = src_edge_bounds_in_native_.Contains(point_in_native);
250  bool in_dst_edge = dst_edge_bounds_in_native_.Contains(point_in_native);
251  if (!in_src_edge && !in_dst_edge)
252    return false;
253
254  // The mouse must move.
255  aura::Window* src_root = NULL;
256  aura::Window* dst_root = NULL;
257  GetSrcAndDstRootWindows(&src_root, &dst_root);
258
259  if (in_src_edge)
260    MoveCursorTo(dst_root, point_in_screen);
261  else
262    MoveCursorTo(src_root, point_in_screen);
263
264  return true;
265}
266#endif
267
268void MouseCursorEventFilter::UpdateHorizontalEdgeBounds() {
269  bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
270  // GetPrimaryDisplay returns an object on stack, so copy the bounds
271  // instead of using reference.
272  const gfx::Rect primary_bounds =
273      Shell::GetScreen()->GetPrimaryDisplay().bounds();
274  const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
275  DisplayLayout::Position position = Shell::GetInstance()->
276      display_manager()->GetCurrentDisplayLayout().position;
277
278  src_indicator_bounds_.set_x(
279      std::max(primary_bounds.x(), secondary_bounds.x()));
280  src_indicator_bounds_.set_width(
281      std::min(primary_bounds.right(), secondary_bounds.right()) -
282      src_indicator_bounds_.x());
283  src_indicator_bounds_.set_height(kIndicatorThickness);
284  src_indicator_bounds_.set_y(
285      position == DisplayLayout::TOP ?
286      primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
287      primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
288
289  dst_indicator_bounds_ = src_indicator_bounds_;
290  dst_indicator_bounds_.set_height(kIndicatorThickness);
291  dst_indicator_bounds_.set_y(
292      position == DisplayLayout::TOP ?
293      primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
294      primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
295
296  aura::Window* src_root = NULL;
297  aura::Window* dst_root = NULL;
298  GetSrcAndDstRootWindows(&src_root, &dst_root);
299
300  src_edge_bounds_in_native_ =
301      CreateHorizontalEdgeBoundsInNative(src_root, src_indicator_bounds_);
302  dst_edge_bounds_in_native_ =
303      CreateHorizontalEdgeBoundsInNative(dst_root, dst_indicator_bounds_);
304}
305
306void MouseCursorEventFilter::UpdateVerticalEdgeBounds() {
307  int snap_height = drag_source_root_ ? kMaximumSnapHeight : 0;
308  bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
309  // GetPrimaryDisplay returns an object on stack, so copy the bounds
310  // instead of using reference.
311  const gfx::Rect primary_bounds =
312      Shell::GetScreen()->GetPrimaryDisplay().bounds();
313  const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
314  DisplayLayout::Position position = Shell::GetInstance()->
315      display_manager()->GetCurrentDisplayLayout().position;
316
317  int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
318  int lower_shared_y = std::min(primary_bounds.bottom(),
319                                secondary_bounds.bottom());
320  int shared_height = lower_shared_y - upper_shared_y;
321
322  int dst_x = position == DisplayLayout::LEFT ?
323      primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
324      primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
325  dst_indicator_bounds_.SetRect(
326      dst_x, upper_shared_y, kIndicatorThickness, shared_height);
327
328  // The indicator on the source display.
329  src_indicator_bounds_.set_width(kIndicatorThickness);
330  src_indicator_bounds_.set_x(
331      position == DisplayLayout::LEFT ?
332      primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
333      primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
334
335  const gfx::Rect& source_bounds =
336      in_primary ? primary_bounds : secondary_bounds;
337  int upper_indicator_y = source_bounds.y() + snap_height;
338  int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
339
340  // This gives a hight that can be used without sacrifying the snap space.
341  int available_space = lower_indicator_y -
342      std::max(upper_shared_y, upper_indicator_y);
343
344  if (shared_height < kMinimumIndicatorHeight) {
345    // If the shared height is smaller than minimum height, use the
346    // entire height.
347    upper_indicator_y = upper_shared_y;
348  } else if (available_space < kMinimumIndicatorHeight) {
349    // Snap to the bottom.
350    upper_indicator_y =
351        std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
352  } else {
353    upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
354  }
355  src_indicator_bounds_.set_y(upper_indicator_y);
356  src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
357
358  aura::Window* src_root = NULL;
359  aura::Window* dst_root = NULL;
360  GetSrcAndDstRootWindows(&src_root, &dst_root);
361
362  // Native
363  src_edge_bounds_in_native_ =
364      CreateVerticalEdgeBoundsInNative(src_root, src_indicator_bounds_);
365  dst_edge_bounds_in_native_ =
366      CreateVerticalEdgeBoundsInNative(dst_root, dst_indicator_bounds_);
367}
368
369void MouseCursorEventFilter::GetSrcAndDstRootWindows(aura::Window** src_root,
370                                                     aura::Window** dst_root) {
371  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
372  *src_root = drag_source_root_ ? drag_source_root_
373                                : Shell::GetInstance()->GetPrimaryRootWindow();
374  *dst_root = root_windows[0] == *src_root ? root_windows[1] : root_windows[0];
375}
376
377#if !defined(USE_OZONE)
378bool MouseCursorEventFilter::WarpMouseCursorIfNecessaryForTest(
379    aura::Window* target_root,
380    const gfx::Point& point_in_screen) {
381  gfx::Point native = point_in_screen;
382  wm::ConvertPointFromScreen(target_root, &native);
383  target_root->GetHost()->ConvertPointToNativeScreen(&native);
384  return WarpMouseCursorInNativeCoords(native, point_in_screen);
385}
386#endif
387
388}  // namespace ash
389