Home | History | Annotate | Download | only in display
      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 
     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 // 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)
     47 bool enable_mouse_warp_in_native_coords = false;
     48 #else
     49 bool enable_mouse_warp_in_native_coords = true;
     50 #endif
     51 
     52 void ConvertPointFromScreenToNative(const aura::Window* root_window,
     53                                     gfx::Point* point) {
     54   wm::ConvertPointFromScreen(root_window, point);
     55   root_window->GetHost()->ConvertPointToNativeScreen(point);
     56 }
     57 
     58 gfx::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|.
     88 gfx::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 
     96 gfx::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 
    105 void 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.
    120 void 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
    145 bool MouseCursorEventFilter::IsMouseWarpInNativeCoordsEnabled() {
    146   return enable_mouse_warp_in_native_coords;
    147 }
    148 
    149 MouseCursorEventFilter::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 
    158 MouseCursorEventFilter::~MouseCursorEventFilter() {
    159   HideSharedEdgeIndicator();
    160   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    161 }
    162 
    163 void 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 
    184 void MouseCursorEventFilter::HideSharedEdgeIndicator() {
    185   shared_display_edge_indicator_->Hide();
    186   OnDisplayConfigurationChanged();
    187 }
    188 
    189 void MouseCursorEventFilter::OnDisplaysInitialized() {
    190   OnDisplayConfigurationChanged();
    191 }
    192 
    193 void 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 
    216 void 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 
    240 bool 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 
    262 bool 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 
    287 bool 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 
    358 void 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 
    396 void 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 
    459 void 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 
    467 bool 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