chromium/components/exo/pointer.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/exo/pointer.h"

#include <optional>
#include <utility>

#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/wm/window_util.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "components/exo/buffer.h"
#include "components/exo/input_trace.h"
#include "components/exo/pointer_constraint_delegate.h"
#include "components/exo/pointer_delegate.h"
#include "components/exo/pointer_gesture_pinch_delegate.h"
#include "components/exo/pointer_stylus_delegate.h"
#include "components/exo/relative_pointer_delegate.h"
#include "components/exo/seat.h"
#include "components/exo/security_delegate.h"
#include "components/exo/shell_surface_base.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/wm_helper.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/base/cursor/cursor_factory.h"
#include "ui/base/cursor/cursor_size.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/cursor_util.h"

namespace exo {

namespace {

const double kLocatedEventEpsilonSquared = 1.0 / (2000.0 * 2000.0);

bool SameLocation(const gfx::PointF& location_in_target,
                  const gfx::PointF& location) {
  // TODO(crbug.com/40859165): This is no longer necessary.  Switch to
  // std::numeric_limits<float>::eplison().
  gfx::Vector2dF offset = location_in_target - location;
  return offset.LengthSquared() < (2 * kLocatedEventEpsilonSquared);
}

// Granularity for reporting force/pressure values coming from styli or other
// devices that are normalized from 0 to 1, used to limit sending noisy values.
const float kForceGranularity = 1e-2f;

// Granularity for reporting tilt values coming from styli or other devices in
// degrees, used to limit sending noisy values.
const float kTiltGranularity = 1.f;

int GetContainerIdForMouseCursor() {
  return ash::kShellWindowId_MouseCursorContainer;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// Pointer, public:

Pointer::Pointer(PointerDelegate* delegate,
                 Seat* seat,
                 std::unique_ptr<aura::Window> host_window)
    : SurfaceTreeHost("ExoPointer", std::move(host_window)),
      delegate_(delegate),
      seat_(seat),
      cursor_(ui::mojom::CursorType::kNull),
      cursor_capture_source_id_(base::UnguessableToken::Create()) {
  WMHelper* helper = WMHelper::GetInstance();
  // TODO(sky): CursorClient does not exist in mash
  // yet. https://crbug.com/631103.
  aura::client::CursorClient* cursor_client = helper->GetCursorClient();
  if (cursor_client)
    cursor_client->AddObserver(this);
  helper->AddFocusObserver(this);

  auto* drag_drop_client = helper->GetDragDropClient();
  if (drag_drop_client)
    drag_drop_client->AddObserver(this);

  ash::Shell::Get()->AddShellObserver(this);
  for (aura::Window* root : ash::Shell::GetAllRootWindows()) {
    root->AddPreTargetHandler(this);
  }

  ash::DesksController::Get()->AddObserver(this);
}

Pointer::~Pointer() {
  ash::DesksController::Get()->RemoveObserver(this);

  ash::Shell::Get()->RemoveShellObserver(this);
  for (aura::Window* root : ash::Shell::GetAllRootWindows()) {
    root->RemovePreTargetHandler(this);
  }

  WMHelper* helper = WMHelper::GetInstance();
  // Remove the pretarget handler in case the pointer is deleted
  // w/o disabling pointer capture.
  aura::Env::GetInstance()->RemovePreTargetHandler(this);

  delegate_->OnPointerDestroying(this);
  if (focus_surface_)
    focus_surface_->RemoveSurfaceObserver(this);
  if (pinch_delegate_)
    pinch_delegate_->OnPointerDestroying(this);
  if (relative_pointer_delegate_)
    relative_pointer_delegate_->OnPointerDestroying(this);
  if (pointer_constraint_delegate_) {
    pointer_constraint_delegate_->GetConstrainedSurface()
        ->RemoveSurfaceObserver(this);
    VLOG(1) << "Pointer constraint broken by pointer destruction";
    pointer_constraint_delegate_->OnConstraintBroken();
  }
  for (auto it : constraints_) {
    it.first->RemoveSurfaceObserver(this);
    it.second->OnDefunct();
  }
  if (stylus_delegate_)
    stylus_delegate_->OnPointerDestroying(this);
  // TODO(sky): CursorClient does not exist in mash
  // yet. https://crbug.com/631103.
  aura::client::CursorClient* cursor_client = helper->GetCursorClient();
  if (cursor_client)
    cursor_client->RemoveObserver(this);
  if (root_surface())
    root_surface()->RemoveSurfaceObserver(this);
  helper->RemoveFocusObserver(this);

  auto* drag_drop_client = helper->GetDragDropClient();
  if (drag_drop_client)
    drag_drop_client->RemoveObserver(this);
}

void Pointer::SetCursor(Surface* surface, const gfx::Point& hotspot) {
  if (!focus_surface_ && !capture_window_)
    return;

  // This is used to avoid unnecessary cursor changes.
  bool cursor_changed = false;

  // If surface is different than the current pointer surface then remove the
  // current surface and add the new surface.
  if (surface != root_surface()) {
    if (surface && surface->HasSurfaceDelegate()) {
      DLOG(ERROR) << "Surface has already been assigned a role";
      return;
    }
    UpdatePointerSurface(surface);
    cursor_changed = true;
  } else if (!surface && cursor_ != ui::mojom::CursorType::kNone) {
    cursor_changed = true;
  }

  if (hotspot != hotspot_) {
    hotspot_ = hotspot;
    cursor_changed = true;
  }

  // Early out if cursor did not change.
  if (!cursor_changed)
    return;

  // If |SurfaceTreeHost::root_surface_| is set then asynchronously capture a
  // snapshot of cursor, otherwise cancel pending capture and immediately set
  // the cursor to "none".
  if (root_surface()) {
    cursor_ = ui::mojom::CursorType::kCustom;
    CaptureCursor(hotspot);
  } else {
    cursor_ = ui::mojom::CursorType::kNone;
    cursor_bitmap_.reset();
    cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs();
    UpdateCursor();
  }
}

void Pointer::SetCursorType(ui::mojom::CursorType cursor_type) {
  // Early out if the pointer doesn't have a surface in focus.
  if (!focus_surface_)
    return;

  if (cursor_ == cursor_type)
    return;
  cursor_ = cursor_type;
  cursor_bitmap_.reset();
  UpdatePointerSurface(nullptr);
  cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs();
  UpdateCursor();
}

void Pointer::SetGesturePinchDelegate(PointerGesturePinchDelegate* delegate) {
  // For the |pinch_delegate_| (and |relative_pointer_delegate_| below) it is
  // possible to bind multiple extensions to the same pointer interface (not
  // that this is a particularly reasonable thing to do). When that happens we
  // choose to only keep a single binding alive, so we simulate pointer
  // destruction for the previous binding.
  if (pinch_delegate_)
    pinch_delegate_->OnPointerDestroying(this);
  pinch_delegate_ = delegate;
}

void Pointer::RegisterRelativePointerDelegate(
    RelativePointerDelegate* delegate) {
  if (relative_pointer_delegate_)
    relative_pointer_delegate_->OnPointerDestroying(this);
  relative_pointer_delegate_ = delegate;
}

void Pointer::UnregisterRelativePointerDelegate(
    RelativePointerDelegate* delegate) {
  DCHECK(relative_pointer_delegate_ == delegate);
  relative_pointer_delegate_ = nullptr;
}

bool Pointer::ConstrainPointer(PointerConstraintDelegate* delegate) {
  // Pointer lock is a chromeos-only feature (i.e. the chromeos::features
  // namespace only exists in chromeos builds). So we do not compile pointer
  // lock support unless we are on chromeos.
  Surface* constrained_surface = delegate->GetConstrainedSurface();
  if (!constrained_surface) {
    delegate->OnDefunct();
    return false;
  }

  // Permission of Pointer lock is controlled by SecurityDelegate, created per
  // server instance. Default implementation allows this for ARC and Lacros
  // windows which have their own security mechanism and are considered trusted.
  aura::Window* toplevel = constrained_surface->window()->GetToplevelWindow();

  SecurityDelegate* security_delegate =
      constrained_surface->GetSecurityDelegate();
  // |security_delegate| could be nullptr, if:
  // - the surface hasn't been assigned a role; or
  // - a role has been assigned, but that specific role doesn't set a security
  //   delegate.
  bool permitted =
      security_delegate && security_delegate->CanLockPointer(toplevel);
  if (!permitted) {
    delegate->OnDefunct();
    return false;
  }

  // Can only have one active constraint request per surface
  auto result = constraints_.try_emplace(constrained_surface, delegate);
  if (result.first->second != delegate) {
    VLOG(1) << "Pointer constraint not granted; one already exists.";
    delegate->OnAlreadyConstrained();
    delegate->OnDefunct();
    return false;
  }

  if (!constrained_surface->HasSurfaceObserver(this))
    constrained_surface->AddSurfaceObserver(this);

  bool success = EnablePointerCapture(constrained_surface);
  if (success) {
    pointer_constraint_delegate_ = delegate;
    delegate->OnConstraintActivated();
  }
  return success;
}

bool Pointer::UnconstrainPointerByUserAction() {
  // Prevent pointer capture until the next user action that permits it,
  // even if a constraint is currently not active (to prevent an app from
  // rapidly toggling pointer capture to evade such prevention).
  capture_permitted_ = false;
  UpdateCursor();  // forces the cursor to be visible in case the app hid it

  if (pointer_constraint_delegate_ && capture_window_) {
    VLOG(1) << "Pointer constraint broken by user action";
    UnconstrainPointer();
    return true;
  } else {
    VLOG(1) << "Pointer constraint forbidden by user (though none active now)";
    return false;
  }
}

void Pointer::RemoveConstraintDelegate(PointerConstraintDelegate* delegate) {
  delegate->OnDefunct();

  Surface* surface = delegate->GetConstrainedSurface();
  auto it = constraints_.find(surface);
  if (it != constraints_.end() && it->second == delegate) {
    constraints_.erase(it);
    MaybeRemoveSurfaceObserver(surface);
  }
}

void Pointer::UnconstrainPointer() {
  if (pointer_constraint_delegate_) {
    pointer_constraint_delegate_->OnConstraintBroken();
    if (!pointer_constraint_delegate_->IsPersistent()) {
      RemoveConstraintDelegate(pointer_constraint_delegate_);
    }
    pointer_constraint_delegate_ = nullptr;
    DisablePointerCapture();
  }
}

void Pointer::MaybeReactivatePointerConstraint(Surface* surface) {
  if (!pointer_constraint_delegate_ && surface) {
    auto it = constraints_.find(surface);
    if (it != constraints_.end())
      ConstrainPointer(it->second);
  }
}

void Pointer::OnPointerConstraintDelegateDestroying(
    PointerConstraintDelegate* delegate) {
  if (pointer_constraint_delegate_ == delegate) {
    DisablePointerCapture();
    pointer_constraint_delegate_ = nullptr;
  }
  RemoveConstraintDelegate(delegate);
}

bool Pointer::EnablePointerCapture(Surface* capture_surface) {
  if (!capture_permitted_) {
    VLOG(1) << "Unable to re-capture the pointer due to previous user action.";
    return false;
  }

  aura::Window* window = capture_surface->window();
  aura::Window* active_window = WMHelper::GetInstance()->GetActiveWindow();
  if (!active_window || !active_window->Contains(window)) {
    VLOG(1) << "Cannot enable pointer capture on an inactive window.";
    return false;
  }

  capture_window_ = window;

  // Add a pre-target handler that can consume all mouse events before it gets
  // sent to other targets. If there's an ongoing animation, the pre-target
  // handler will be added once `OnDeskSwitchAnimationFinished` is triggered.
  if (!ash::DesksController::Get()->animation()) {
    aura::Env::GetInstance()->AddPreTargetHandler(
        this, ui::EventTarget::Priority::kSystem);
  }

  location_when_pointer_capture_enabled_ =
      gfx::ToRoundedPoint(location_in_root_);

  if (ShouldMoveToCenter())
    MoveCursorToCenterOfActiveDisplay();

  seat_->NotifyPointerCaptureEnabled(this, window);

  return true;
}

void Pointer::DisablePointerCapture() {
  // Early out if pointer capture is not enabled.
  if (!capture_window_)
    return;

  // Remove the pre-target handler that consumes all mouse events.
  aura::Env::GetInstance()->RemovePreTargetHandler(this);

  aura::Window* root = capture_window_->GetRootWindow();
  gfx::Point p = location_when_pointer_capture_enabled_
                     ? *location_when_pointer_capture_enabled_
                     : root->bounds().CenterPoint();
  expected_next_mouse_location_ = p;
  root->MoveCursorTo(p);

  aura::Window* window = capture_window_;
  capture_window_ = nullptr;
  location_when_pointer_capture_enabled_.reset();
  UpdateCursor();

  seat_->NotifyPointerCaptureDisabled(this, window);
}

void Pointer::SetStylusDelegate(PointerStylusDelegate* delegate) {
  stylus_delegate_ = delegate;

  // Reset last reported values to default.
  last_pointer_type_ = ui::EventPointerType::kUnknown;
  last_force_ = std::numeric_limits<float>::quiet_NaN();
  last_tilt_ = gfx::Vector2dF();
}

bool Pointer::HasStylusDelegate() const {
  return !!stylus_delegate_;
}

////////////////////////////////////////////////////////////////////////////////
// SurfaceDelegate overrides:

void Pointer::OnSurfaceCommit() {
  SurfaceTreeHost::OnSurfaceCommit();

  // Capture new cursor to reflect result of commit.
  if (focus_surface_)
    CaptureCursor(hotspot_);
}

////////////////////////////////////////////////////////////////////////////////
// SurfaceObserver overrides:

void Pointer::OnSurfaceDestroying(Surface* surface) {
  bool was_correctly_subscribed = false;
  if (surface && pointer_constraint_delegate_ &&
      surface == pointer_constraint_delegate_->GetConstrainedSurface()) {
    surface->RemoveSurfaceObserver(this);
    VLOG(1) << "Pointer constraint broken by surface destruction";
    UnconstrainPointer();
    was_correctly_subscribed = true;
  }
  if (surface && surface->window() == capture_window_) {
    DisablePointerCapture();
    was_correctly_subscribed = true;
  }

  auto it = constraints_.find(surface);
  if (it != constraints_.end()) {
    it->second->OnDefunct();
    constraints_.erase(it);
    surface->RemoveSurfaceObserver(this);
    was_correctly_subscribed = true;
  }

  if (surface == focus_surface_) {
    SetFocus(nullptr, gfx::PointF(), gfx::PointF(), 0);
    was_correctly_subscribed = true;
  } else if (surface == root_surface()) {
    UpdatePointerSurface(nullptr);
    was_correctly_subscribed = true;
  }
  DCHECK(was_correctly_subscribed);
  DCHECK(!surface->HasSurfaceObserver(this));
}

////////////////////////////////////////////////////////////////////////////////
// ui::EventHandler overrides:

void Pointer::OnMouseEvent(ui::MouseEvent* event) {
  if (seat_->was_shutdown() || event->handled())
    return;
  // Ask seat instead of ash's DragDropController because it ends
  // asynchronously.
  if (seat_->IsDragDropOperationInProgress()) {
    return;
  } else if (button_flags_on_drag_drop_start_) {
    // Send release events for buttons that are released during the drag and
    // drop operation.
    int released_button_flags =
        button_flags_on_drag_drop_start_ & ~event->button_flags();
    delegate_->OnPointerButton(event->time_stamp(), released_button_flags,
                               false);
    delegate_->OnPointerFrame();
    button_flags_on_drag_drop_start_ = 0;
  }

  // Nothing to report to a client nor have to update the pointer when capture
  // changes.
  if (event->type() == ui::EventType::kMouseCaptureChanged) {
    return;
  }

  // TODO(crbug.com/1395073, crbug.com/1395256): Currently, due to a bug in
  // multi-display implementation, mouse move event sent to hide cursor is
  // sent twice occasionally. That confuses focus tracking implemented in this
  // class.
  // For the short term workaround, we ignore such events.
  // Note that this is not a *correct* implementation, because we have to send
  // the correconding wayland event to client (such as Lacros) with carrying
  // the info that it is triggered for cursor hiding to let it take an action
  // on cursor hiding (e.g. hiding hover, too).
  // We need to fix the implementation here, though, it depends on the fix of
  // multi-display event tracking.
  if (event->flags() & ui::EF_CURSOR_HIDE) {
    return;
  }

  // Fling cancel is generated very generously at every touch of the
  // touchpad. Since it's not directly supported by the delegate, we want
  // limit this event to only right after a fling start has been generated
  // to prevent erronous behavior.
  if (event->type() == ui::EventType::kScrollFlingCancel &&
      last_event_type_ != ui::EventType::kScrollFlingStart) {
    // Should we update this for above cases?
    last_event_type_ = event->type();
    return;
  }

  gfx::PointF location_in_target;
  Surface* target = GetEffectiveTargetForEvent(event, &location_in_target);
  gfx::PointF location_in_root = event->root_location_f();

  // Update focus if target is different than the current pointer focus.
  if (target != focus_surface_) {
    SetFocus(target, location_in_root, location_in_target,
             event->button_flags());
  }

  if (!focus_surface_)
    return;

  TRACE_EXO_INPUT_EVENT(event);

  bool needs_frame = false;

  const auto& details = event->pointer_details();
  if (stylus_delegate_ && last_pointer_type_ != details.pointer_type) {
    last_pointer_type_ = details.pointer_type;
    stylus_delegate_->OnPointerToolChange(details.pointer_type);
    needs_frame |= true;
  }

  if (event->IsMouseEvent()) {
    // Ordinal motion is sent only on platforms that support it, which is
    // indicated by the presence of a flag.
    std::optional<gfx::Vector2dF> ordinal_motion = std::nullopt;
    if (event->flags() & ui::EF_UNADJUSTED_MOUSE &&
        base::FeatureList::IsEnabled(ash::features::kExoOrdinalMotion)) {
      ordinal_motion = event->movement();
    }

    // Generate motion event if location changed or the location hasn't been
    // sent yet. We need to check location here as mouse movement can generate
    // both "moved" and "entered" events but OnPointerMotion should only be
    // called if location changed since OnPointerEnter was called.
    if (!CheckIfSameLocation(event->IsSynthesized(), location_in_root,
                             location_in_target)) {
      bool ignore_motion = false;
      if (expected_next_mouse_location_) {
        const gfx::Point& expected = *expected_next_mouse_location_;
        // Since MoveCursorTo() takes integer coordinates, the resulting move
        // could have a conversion error of up to 2 due to fractional scale
        // factors.
        if (std::abs(location_in_root.x() - expected.x()) <= 2 &&
            std::abs(location_in_root.y() - expected.y()) <= 2) {
          // This was a synthetic move event, so do not forward it and clear the
          // expected location.
          expected_next_mouse_location_.reset();
          ignore_motion = true;
        }
      }
      needs_frame |= !ignore_motion &&
                     HandleRelativePointerMotion(
                         event->time_stamp(), location_in_root, ordinal_motion);
      if (capture_window_) {
        if (ShouldMoveToCenter())
          MoveCursorToCenterOfActiveDisplay();
        location_in_root_ = location_in_root;
        location_in_surface_ = location_in_target;
      } else if (event->type() != ui::EventType::kMouseExited &&
                 !ignore_motion) {
        delegate_->OnPointerMotion(event->time_stamp(), location_in_target);
        needs_frame |= true;
        location_in_root_ = location_in_root;
        location_in_surface_ = location_in_target;
      }
    }
  }
  switch (event->type()) {
    case ui::EventType::kMouseReleased:
      seat_->AbortPendingDragOperation();
      [[fallthrough]];
    case ui::EventType::kMousePressed: {
      if (!capture_permitted_) {
        // Clicking any surface with a constraint delegate permits capture
        auto it = constraints_.find(focus_surface_);
        if (it != constraints_.end()) {
          capture_permitted_ = true;
          UpdateCursor();
          ConstrainPointer(it->second);
        }
      }
      delegate_->OnPointerButton(event->time_stamp(),
                                 event->changed_button_flags(),
                                 event->type() == ui::EventType::kMousePressed);
      needs_frame |= true;
      break;
    }
    case ui::EventType::kScroll: {
      ui::ScrollEvent* scroll_event = static_cast<ui::ScrollEvent*>(event);

      // Scrolling with 3+ fingers should not be handled since it will be used
      // to trigger overview mode.
      if (scroll_event->finger_count() >= 3)
        break;
      delegate_->OnPointerScroll(
          event->time_stamp(),
          gfx::Vector2dF(scroll_event->x_offset(), scroll_event->y_offset()),
          false);
      needs_frame |= true;
      break;
    }
    case ui::EventType::kMousewheel: {
      delegate_->OnPointerScroll(
          event->time_stamp(),
          static_cast<ui::MouseWheelEvent*>(event)->offset(), true);
      needs_frame |= true;
      break;
    }
    case ui::EventType::kScrollFlingStart: {
      // Fling start in chrome signals the lifting of fingers after scrolling.
      // In wayland terms this signals the end of a scroll sequence.
      delegate_->OnFingerScrollStop(event->time_stamp());
      needs_frame |= true;
      break;
    }
    case ui::EventType::kScrollFlingCancel: {
      // We emulate fling cancel by starting a new scroll sequence that
      // scrolls by 0 pixels, effectively stopping any kinetic scroll motion.
      delegate_->OnPointerScroll(event->time_stamp(), gfx::Vector2dF(), false);
      delegate_->OnPointerFrame();
      delegate_->OnFingerScrollStop(event->time_stamp());
      delegate_->OnPointerFrame();
      break;
    }
    case ui::EventType::kMouseMoved:
    case ui::EventType::kMouseDragged:
    case ui::EventType::kMouseEntered:
    case ui::EventType::kMouseExited:
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  if (stylus_delegate_) {
    // Report the force value when either:
    // - switching from a device that supports force to one that doesn't or
    //   vice-versa (since force is NaN if the device doesn't support it), OR
    // - the force value differs from the last reported force by greater than
    //   the granularity.
    // Using std::isgreaterequal for quiet error handling for NaNs.
    if (std::isnan(last_force_) != std::isnan(details.force) ||
        std::isgreaterequal(abs(last_force_ - details.force),
                            kForceGranularity)) {
      last_force_ = details.force;
      stylus_delegate_->OnPointerForce(event->time_stamp(), details.force);
      needs_frame |= true;
    }
    if (abs(last_tilt_.x() - details.tilt_x) >= kTiltGranularity ||
        abs(last_tilt_.y() - details.tilt_y) >= kTiltGranularity) {
      last_tilt_ = gfx::Vector2dF(details.tilt_x, details.tilt_y);
      stylus_delegate_->OnPointerTilt(event->time_stamp(), last_tilt_);
      needs_frame |= true;
    }
  }

  last_event_type_ = event->type();

  // Consume all mouse events when pointer capture is enabled.
  if (capture_window_) {
    event->SetHandled();
    event->StopPropagation();
  }

  if (needs_frame)
    delegate_->OnPointerFrame();
}

void Pointer::OnScrollEvent(ui::ScrollEvent* event) {
  OnMouseEvent(event);
}

void Pointer::OnGestureEvent(ui::GestureEvent* event) {
  // We don't want to handle gestures generated from touchscreen events,
  // we handle touch events in touch.cc
  if (event->details().device_type() != ui::GestureDeviceType::DEVICE_TOUCHPAD)
    return;

  if (!focus_surface_ || !pinch_delegate_)
    return;

  TRACE_EXO_INPUT_EVENT(event);

  switch (event->type()) {
    case ui::EventType::kGesturePinchBegin:
      pinch_delegate_->OnPointerPinchBegin(event->unique_touch_event_id(),
                                           event->time_stamp(), focus_surface_);
      delegate_->OnPointerFrame();
      break;
    case ui::EventType::kGesturePinchUpdate:
      pinch_delegate_->OnPointerPinchUpdate(event->time_stamp(),
                                            event->details().scale());
      delegate_->OnPointerFrame();
      break;
    case ui::EventType::kGesturePinchEnd:
      pinch_delegate_->OnPointerPinchEnd(event->unique_touch_event_id(),
                                         event->time_stamp());
      delegate_->OnPointerFrame();
      break;
    default:
      break;
  }

  // Consume all mouse events when pointer capture is enabled.
  if (capture_window_) {
    event->SetHandled();
    event->StopPropagation();
  }
}

////////////////////////////////////////////////////////////////////////////////
// aura::client::DragDropClientObserver overrides:
void Pointer::OnDragStarted() {
  button_flags_on_drag_drop_start_ =
      aura::Env::GetInstance()->mouse_button_flags();

  // Drag 'n drop operations driven by sources different than pointer/mouse
  // should have not effect here.
  WMHelper* helper = WMHelper::GetInstance();
  if (auto* drag_drop_client = helper->GetDragDropClient()) {
    if (static_cast<ash::DragDropController*>(drag_drop_client)
            ->event_source() != ui::mojom::DragEventSource::kMouse)
      return;
  }

  SetFocus(nullptr, gfx::PointF(), gfx::PointF(), 0);
}

void Pointer::OnDragCompleted(const ui::DropTargetEvent& event) {
  // Don't update the focus here as the DragDropOperation is still processing
  // the DnD and hasn't sent drop/leave events.
  // The focus has been reset upon DnD start above, and will be updated on
  // next Mouse Event.
  // This is not ideal, but the better fix should be done as a part of
  // DnD nested loop removal. (crbug.com/1160925)
}

////////////////////////////////////////////////////////////////////////////////
// aura::client::CursorClientObserver overrides:

void Pointer::OnCursorSizeChanged(ui::CursorSize cursor_size) {
  if (!focus_surface_)
    return;

  if (cursor_ != ui::mojom::CursorType::kNull)
    UpdateCursor();
}

void Pointer::OnCursorDisplayChanged(const display::Display& display) {
  UpdatePointerSurface(root_surface());

  auto* cursor_client = WMHelper::GetInstance()->GetCursorClient();
  DCHECK(cursor_client);
  if (cursor_ == ui::mojom::CursorType::kCustom &&
      cursor_ == cursor_client->GetCursor()) {
    // If the current cursor is still the one created by us,
    // it's our responsibility to update the cursor for the new display.
    // Don't check |focus_surface_| because it can be null while
    // dragging the window due to an event capture.
    UpdateCursor();
  }
}

////////////////////////////////////////////////////////////////////////////////
// aura::client::FocusChangeObserver overrides:

void Pointer::OnWindowFocused(aura::Window* gained_focus,
                              aura::Window* lost_focus) {
  if (capture_window_ && capture_window_ != gained_focus) {
    if (pointer_constraint_delegate_) {
      VLOG(1) << "Pointer constraint broken by focus change";
      UnconstrainPointer();
    } else {
      DisablePointerCapture();
    }
  }
  if (gained_focus)
    MaybeReactivatePointerConstraint(Surface::AsSurface(gained_focus));
}

////////////////////////////////////////////////////////////////////////////////
// ash::ShellObserver:
void Pointer::OnRootWindowAdded(aura::Window* root_window) {
  root_window->AddPreTargetHandler(this);
}

void Pointer::OnRootWindowWillShutdown(aura::Window* root_window) {
  root_window->RemovePreTargetHandler(this);
}

////////////////////////////////////////////////////////////////////////////////
// ash::DesksController::Observer:
void Pointer::OnDeskSwitchAnimationFinished() {
  if (capture_window_) {
    aura::Env::GetInstance()->AddPreTargetHandler(
        this, ui::EventTarget::Priority::kSystem);
  }
}

////////////////////////////////////////////////////////////////////////////////
// Pointer, private:

Surface* Pointer::GetEffectiveTargetForEvent(
    const ui::LocatedEvent* event,
    gfx::PointF* location_in_target) const {
  DCHECK(location_in_target);
  Surface* target = nullptr;
  if (capture_window_) {
    target = Surface::AsSurface(capture_window_);
  } else {
    target = GetTargetSurfaceForLocatedEvent(event);

    if (!target || !delegate_->CanAcceptPointerEventsForSurface(target))
      return nullptr;
  }

  if (target) {
    *location_in_target = event->location_f();
    aura::Window::ConvertPointToTarget(
        static_cast<aura::Window*>(event->target()), target->window(),
        location_in_target);
  }
  return target;
}

void Pointer::SetFocus(Surface* surface,
                       const gfx::PointF& root_location,
                       const gfx::PointF& surface_location,
                       int button_flags) {
  DCHECK(!surface || delegate_->CanAcceptPointerEventsForSurface(surface));
  // First generate a leave event if we currently have a target in focus.
  if (focus_surface_) {
    delegate_->OnPointerLeave(focus_surface_);
    delegate_->OnPointerFrame();
    // Require SetCursor() to be called and cursor to be re-defined in
    // response to each OnPointerEnter() call.
    Surface* old_surface = focus_surface_;
    focus_surface_ = nullptr;
    MaybeRemoveSurfaceObserver(old_surface);
    cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs();
  }
  // Second generate an enter event if focus moved to a new surface.
  if (surface) {
    // Pointer enter should not be generated during dnd session.
#if DCHECK_IS_ON()
    auto* drag_drop_controller = static_cast<ash::DragDropController*>(
        aura::client::GetDragDropClient(surface->window()->GetRootWindow()));
    DCHECK(!drag_drop_controller->IsDragDropInProgress());
#endif
    delegate_->OnPointerEnter(surface, surface_location, button_flags);
    delegate_->OnPointerFrame();
    location_in_root_ = root_location;
    location_in_surface_ = surface_location;
    focus_surface_ = surface;
    if (!focus_surface_->HasSurfaceObserver(this))
      focus_surface_->AddSurfaceObserver(this);
  }
  UpdateCursor();
}

void Pointer::UpdatePointerSurface(Surface* surface) {
  if (root_surface()) {
    host_window()->SetTransform(gfx::Transform());
    if (host_window()->parent())
      host_window()->parent()->RemoveChild(host_window());
    Surface* old_surface = root_surface();
    SetRootSurface(nullptr);
    MaybeRemoveSurfaceObserver(old_surface);
  }

  if (surface) {
    if (!surface->HasSurfaceObserver(this))
      surface->AddSurfaceObserver(this);
    // Note: Surface window needs to be added to the tree so we can take a
    // snapshot. Where in the tree is not important but we might as well use
    // the cursor container.
    WMHelper::GetInstance()
        ->GetPrimaryDisplayContainer(GetContainerIdForMouseCursor())
        ->AddChild(host_window());
    SetRootSurface(surface);
  }
}

void Pointer::CaptureCursor(const gfx::Point& hotspot) {
  DCHECK(root_surface());
  DCHECK(focus_surface_);

  // Defer capture until surface commit.
  if (host_window()->bounds().IsEmpty())
    return;

  // Return if the surface has no committed buffer.
  Buffer* buffer = root_surface()->GetBuffer();
  if (!buffer) {
    return;
  }

  // Cancel all pending captures.
  cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs();

  // If bitmap can be directly created from the buffer,
  // use the bitmap to create cursor.
  // Otherwise, send RequestCopyOfOutput request to viz
  // to capture cursor bitmap.
  if (!root_surface()->HasAcquireFence()) {
    SkBitmap bitmap = buffer->CreateBitmap();
    if (!bitmap.empty()) {
      OnCursorBitmapObtained(hotspot, bitmap, root_surface()->GetBufferScale());
      return;
    }
  }

  // Advance the surface id to ensure capturing the correct compositor frame.
  AllocateLocalSurfaceId();
  // Submit compositor frame to be captured.
  SubmitCompositorFrame();

  std::unique_ptr<viz::CopyOutputRequest> request =
      std::make_unique<viz::CopyOutputRequest>(
          viz::CopyOutputRequest::ResultFormat::RGBA,
          viz::CopyOutputRequest::ResultDestination::kSystemMemory,
          base::BindOnce(&Pointer::OnCursorCaptured,
                         cursor_capture_weak_ptr_factory_.GetWeakPtr(),
                         hotspot));
  request->set_result_task_runner(
      base::SequencedTaskRunner::GetCurrentDefault());

  request->set_source(cursor_capture_source_id_);

  aura::Env::GetInstance()
      ->context_factory()
      ->GetHostFrameSinkManager()
      ->RequestCopyOfOutput(GetSurfaceId(), std::move(request));
}

void Pointer::OnCursorCaptured(const gfx::Point& hotspot,
                               std::unique_ptr<viz::CopyOutputResult> result) {
  // Only successful captures should update the cursor.
  if (result->IsEmpty())
    return;

  OnCursorBitmapObtained(hotspot,
                         result->ScopedAccessSkBitmap().GetOutScopedBitmap(),
                         GetScaleFactor());
}

void Pointer::OnCursorBitmapObtained(const gfx::Point& hotspot,
                                     const SkBitmap& cursor_bitmap,
                                     float cursor_scale) {
  if (!focus_surface_) {
    return;
  }

  cursor_bitmap_ = cursor_bitmap;
  cursor_scale_ = cursor_scale;
  DCHECK(cursor_bitmap_.readyToDraw());
  cursor_hotspot_ = hotspot;
  UpdateCursor();
}

void Pointer::UpdateCursor() {
  WMHelper* helper = WMHelper::GetInstance();
  aura::client::CursorClient* cursor_client = helper->GetCursorClient();
  DCHECK(cursor_client);

  if (cursor_ == ui::mojom::CursorType::kCustom) {
    SkBitmap bitmap = cursor_bitmap_;

    // TODO(oshima|weidongg): Add cutsom cursor API to handle size/display
    // change without explicit management like this. https://crbug.com/721601.

    // Scaling bitmap to match the corresponding supported scale factor of ash.
    const display::Display& display = cursor_client->GetDisplay();
    const float resource_scale_factor = ui::GetScaleForResourceScaleFactor(
        ui::GetSupportedResourceScaleFactor(display.device_scale_factor()));
    const float scale = resource_scale_factor / cursor_scale_;
    gfx::Point hotspot =
        gfx::ScaleToFlooredPoint(cursor_hotspot_, cursor_scale_);
    // Use panel_rotation() rather than "natural" rotation, as it actually
    // relates to the hardware you're about to draw the cursor bitmap on.
    wm::ScaleAndRotateCursorBitmapAndHotpoint(scale, display.panel_rotation(),
                                              &bitmap, &hotspot);

    // TODO(reveman): Add interface for creating cursors from GpuMemoryBuffers
    // and use that here instead of the current bitmap API.
    // https://crbug.com/686600
    cursor_ = ui::Cursor::NewCustom(std::move(bitmap), std::move(hotspot),
                                    resource_scale_factor);
    cursor_.SetPlatformCursor(
        ui::CursorFactory::GetInstance()->CreateImageCursor(
            cursor_.type(), cursor_.custom_bitmap(), cursor_.custom_hotspot(),
            cursor_.image_scale_factor()));
  }

  // When pointer capture is broken, use the standard system cursor instead of
  // the application-requested one. But we keep the app-requested cursor around
  // for when capture becomes permitted again.
  const ui::Cursor& cursor =
      capture_permitted_ ? cursor_ : ui::mojom::CursorType::kPointer;

  // If there is a focused surface, update its widget as the views framework
  // expect that Widget knows the current cursor. Otherwise update the
  // cursor directly on CursorClient.
  if (focus_surface_) {
    aura::Window* window = focus_surface_->window();
    do {
      views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
      if (widget) {
        widget->SetCursor(cursor);
        return;
      }
      window = window->parent();
    } while (window);
  } else {
    cursor_client->SetCursor(cursor);
  }
}

bool Pointer::ShouldMoveToCenter() {
  if (!capture_window_)
    return false;

  gfx::Rect rect = capture_window_->GetRootWindow()->bounds();
  rect.Inset(gfx::Insets::VH(rect.height() / 6, rect.width() / 6));
  return !rect.Contains(location_in_root_.x(), location_in_root_.y());
}

void Pointer::MoveCursorToCenterOfActiveDisplay() {
  if (!capture_window_)
    return;
  aura::Window* root = capture_window_->GetRootWindow();
  gfx::Point p = root->bounds().CenterPoint();
  expected_next_mouse_location_ = p;
  root->MoveCursorTo(p);
}

bool Pointer::HandleRelativePointerMotion(
    base::TimeTicks time_stamp,
    gfx::PointF location_in_root,
    const std::optional<gfx::Vector2dF>& ordinal_motion) {
  if (!relative_pointer_delegate_)
    return false;

  gfx::Vector2dF delta = location_in_root - location_in_root_;
  relative_pointer_delegate_->OnPointerRelativeMotion(
      time_stamp, delta,
      ordinal_motion.has_value() ? ordinal_motion.value() : delta);
  return true;
}

bool Pointer::ShouldObserveSurface(Surface* surface) {
  if (!surface)
    return false;

  if (surface == root_surface() || surface == focus_surface_ ||
      constraints_.find(surface) != constraints_.end()) {
    return true;
  }
  return false;
}

void Pointer::MaybeRemoveSurfaceObserver(Surface* surface) {
  if (!ShouldObserveSurface(surface)) {
    surface->RemoveSurfaceObserver(this);
  }
}

bool Pointer::CheckIfSameLocation(bool is_synthesized,
                                  const gfx::PointF& location_in_root,
                                  const gfx::PointF& location_in_target) {
  // There is a specific case that location_in_root is the same
  // but location_in_target is updated with SynthesizeMouseMove
  // without the actual mouse movement when the window bounds changes.
  // To handle this case, PointerMotion event should be delievered to
  // delegate to update the current pointer location properly.
  // Hence, check either target or root has changed.
  if (!is_synthesized) {
    return SameLocation(location_in_root, location_in_root_) &&
           SameLocation(location_in_target, location_in_surface_);
  }

  // For synthesized events, they typically lack floating point precision
  // so to avoid generating mouse event jitter we consider the location of
  // these events to be the same as |location| if floored values match.
  return (gfx::ToFlooredPoint(location_in_root) ==
          gfx::ToFlooredPoint(location_in_root_)) &&
         (gfx::ToFlooredPoint(location_in_target) ==
          gfx::ToFlooredPoint(location_in_surface_));
}

}  // namespace exo