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
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
155void 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
177void 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
202void 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
225bool 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
247bool 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
272void 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
310void 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
373void 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
381bool 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