mouse_cursor_event_filter.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 "ash/display/cursor_window_controller.h"
8#include "ash/display/display_controller.h"
9#include "ash/display/display_manager.h"
10#include "ash/display/shared_display_edge_indicator.h"
11#include "ash/root_window_controller.h"
12#include "ash/screen_util.h"
13#include "ash/shell.h"
14#include "ash/wm/coordinate_conversion.h"
15#include "ash/wm/window_util.h"
16#include "ui/aura/env.h"
17#include "ui/aura/window.h"
18#include "ui/aura/window_event_dispatcher.h"
19#include "ui/base/layout.h"
20#include "ui/compositor/dip_util.h"
21#include "ui/events/event.h"
22#include "ui/gfx/screen.h"
23
24namespace ash {
25namespace {
26
27// Maximum size on the display edge that initiate snapping phantom window,
28// from the corner of the display.
29const int kMaximumSnapHeight = 16;
30
31// Minimum height of an indicator on the display edge that allows
32// dragging a window.  If two displays shares the edge smaller than
33// this, entire edge will be used as a draggable space.
34const int kMinimumIndicatorHeight = 200;
35
36const int kIndicatorThickness = 1;
37}
38
39MouseCursorEventFilter::MouseCursorEventFilter()
40    : mouse_warp_mode_(WARP_ALWAYS),
41      was_mouse_warped_(false),
42      drag_source_root_(NULL),
43      scale_when_drag_started_(1.0f),
44      shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) {
45}
46
47MouseCursorEventFilter::~MouseCursorEventFilter() {
48  HideSharedEdgeIndicator();
49}
50
51void MouseCursorEventFilter::ShowSharedEdgeIndicator(
52    const aura::Window* from) {
53  HideSharedEdgeIndicator();
54  if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) {
55    src_indicator_bounds_.SetRect(0, 0, 0, 0);
56    dst_indicator_bounds_.SetRect(0, 0, 0, 0);
57    drag_source_root_ = NULL;
58    return;
59  }
60  drag_source_root_ = from;
61
62  DisplayLayout::Position position = Shell::GetInstance()->
63      display_manager()->GetCurrentDisplayLayout().position;
64  if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
65    UpdateHorizontalIndicatorWindowBounds();
66  else
67    UpdateVerticalIndicatorWindowBounds();
68
69  shared_display_edge_indicator_->Show(src_indicator_bounds_,
70                                       dst_indicator_bounds_);
71}
72
73void MouseCursorEventFilter::HideSharedEdgeIndicator() {
74  shared_display_edge_indicator_->Hide();
75}
76
77void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) {
78  aura::Window* target = static_cast<aura::Window*>(event->target());
79  RootWindowController* rwc = GetRootWindowController(target->GetRootWindow());
80  // The root window controller is removed during the shutting down
81  // RootWindow, so don't process events futher.
82  if (!rwc) {
83    event->StopPropagation();
84    return;
85  }
86
87  if (event->type() == ui::ET_MOUSE_PRESSED) {
88    scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer());
89  } else if (event->type() == ui::ET_MOUSE_RELEASED) {
90    scale_when_drag_started_ = 1.0f;
91  }
92
93  // Handle both MOVED and DRAGGED events here because when the mouse pointer
94  // enters the other root window while dragging, the underlying window system
95  // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
96  if (event->type() != ui::ET_MOUSE_MOVED &&
97      event->type() != ui::ET_MOUSE_DRAGGED) {
98      return;
99  }
100
101  if (!(event->flags() & ui::EF_IS_SYNTHESIZED)) {
102    Shell::GetInstance()->display_controller()->
103        cursor_window_controller()->UpdateLocation();
104  }
105
106  gfx::Point point_in_screen(event->location());
107  wm::ConvertPointToScreen(target, &point_in_screen);
108  if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
109    event->StopPropagation();
110}
111
112bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
113    aura::Window* target_root,
114    const gfx::Point& point_in_screen) {
115  if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
116      mouse_warp_mode_ == WARP_NONE)
117    return false;
118
119  // Do not warp again right after the cursor was warped. Sometimes the offset
120  // is not long enough and the cursor moves at the edge of the destination
121  // display. See crbug.com/278885
122  // TODO(mukai): simplify the offset calculation below, it would not be
123  // necessary anymore with this flag.
124  if (was_mouse_warped_) {
125    was_mouse_warped_ = false;
126    return false;
127  }
128
129  aura::Window* root_at_point = wm::GetRootWindowAt(point_in_screen);
130  gfx::Point point_in_root = point_in_screen;
131  wm::ConvertPointFromScreen(root_at_point, &point_in_root);
132  gfx::Rect root_bounds = root_at_point->bounds();
133  int offset_x = 0;
134  int offset_y = 0;
135
136  // If the window is dragged between 2x display and 1x display,
137  // staring from 2x display, pointer location is rounded by the
138  // source scale factor (2x) so it will never reach the edge (which
139  // is odd). Shrink by scale factor of the display where the dragging
140  // started instead.  Only integral scale factor is supported for now.
141  int shrink = scale_when_drag_started_;
142  // Make the bounds inclusive to detect the edge.
143  root_bounds.Inset(0, 0, shrink, shrink);
144  gfx::Rect src_indicator_bounds = src_indicator_bounds_;
145  src_indicator_bounds.Inset(-shrink, -shrink, -shrink, -shrink);
146
147  if (point_in_root.x() <= root_bounds.x()) {
148    // Use -2, not -1, to avoid infinite loop of pointer warp.
149    offset_x = -2 * scale_when_drag_started_;
150  } else if (point_in_root.x() >= root_bounds.right()) {
151    offset_x = 2 * scale_when_drag_started_;
152  } else if (point_in_root.y() <= root_bounds.y()) {
153    offset_y = -2 * scale_when_drag_started_;
154  } else if (point_in_root.y() >= root_bounds.bottom()) {
155    offset_y = 2 * scale_when_drag_started_;
156  } else {
157    return false;
158  }
159
160  gfx::Point point_in_dst_screen(point_in_screen);
161  point_in_dst_screen.Offset(offset_x, offset_y);
162  aura::Window* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
163
164  // Warp the mouse cursor only if the location is in the indicator bounds
165  // or the mouse pointer is in the destination root.
166  if (mouse_warp_mode_ == WARP_DRAG &&
167      dst_root != drag_source_root_ &&
168      !src_indicator_bounds.Contains(point_in_screen)) {
169    return false;
170  }
171
172  wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
173
174  if (dst_root->bounds().Contains(point_in_dst_screen)) {
175    DCHECK_NE(dst_root, root_at_point);
176    was_mouse_warped_ = true;
177    dst_root->MoveCursorTo(point_in_dst_screen);
178    return true;
179  }
180  return false;
181}
182
183void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
184  bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
185  // GetPrimaryDisplay returns an object on stack, so copy the bounds
186  // instead of using reference.
187  const gfx::Rect primary_bounds =
188      Shell::GetScreen()->GetPrimaryDisplay().bounds();
189  const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
190  DisplayLayout::Position position = Shell::GetInstance()->
191      display_manager()->GetCurrentDisplayLayout().position;
192
193  src_indicator_bounds_.set_x(
194      std::max(primary_bounds.x(), secondary_bounds.x()));
195  src_indicator_bounds_.set_width(
196      std::min(primary_bounds.right(), secondary_bounds.right()) -
197      src_indicator_bounds_.x());
198  src_indicator_bounds_.set_height(kIndicatorThickness);
199  src_indicator_bounds_.set_y(
200      position == DisplayLayout::TOP ?
201      primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
202      primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
203
204  dst_indicator_bounds_ = src_indicator_bounds_;
205  dst_indicator_bounds_.set_height(kIndicatorThickness);
206  dst_indicator_bounds_.set_y(
207      position == DisplayLayout::TOP ?
208      primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
209      primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
210}
211
212void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
213  bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
214  // GetPrimaryDisplay returns an object on stack, so copy the bounds
215  // instead of using reference.
216  const gfx::Rect primary_bounds =
217      Shell::GetScreen()->GetPrimaryDisplay().bounds();
218  const gfx::Rect secondary_bounds = ScreenUtil::GetSecondaryDisplay().bounds();
219  DisplayLayout::Position position = Shell::GetInstance()->
220      display_manager()->GetCurrentDisplayLayout().position;
221
222  int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
223  int lower_shared_y = std::min(primary_bounds.bottom(),
224                                secondary_bounds.bottom());
225  int shared_height = lower_shared_y - upper_shared_y;
226
227  int dst_x = position == DisplayLayout::LEFT ?
228      primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
229      primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
230  dst_indicator_bounds_.SetRect(
231      dst_x, upper_shared_y, kIndicatorThickness, shared_height);
232
233  // The indicator on the source display.
234  src_indicator_bounds_.set_width(kIndicatorThickness);
235  src_indicator_bounds_.set_x(
236      position == DisplayLayout::LEFT ?
237      primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
238      primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
239
240  const gfx::Rect& source_bounds =
241      in_primary ? primary_bounds : secondary_bounds;
242  int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
243  int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
244
245  // This gives a hight that can be used without sacrifying the snap space.
246  int available_space = lower_indicator_y -
247      std::max(upper_shared_y, upper_indicator_y);
248
249  if (shared_height < kMinimumIndicatorHeight) {
250    // If the shared height is smaller than minimum height, use the
251    // entire height.
252    upper_indicator_y = upper_shared_y;
253  } else if (available_space < kMinimumIndicatorHeight) {
254    // Snap to the bottom.
255    upper_indicator_y =
256        std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
257  } else {
258    upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
259  }
260  src_indicator_bounds_.set_y(upper_indicator_y);
261  src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
262}
263
264}  // namespace ash
265