| // 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/ozone/public/input_controller.h" |
| #include "ui/ozone/public/ozone_platform.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; |
| } |
| |
| class ScopedCursorUnlocker { |
| public: |
| explicit ScopedCursorUnlocker(aura::client::CursorClient* cursor_client) |
| : cursor_client_(cursor_client) { |
| if (cursor_client_) { |
| cursor_client_->UnlockCursor(); |
| } |
| } |
| |
| ScopedCursorUnlocker(const ScopedCursorUnlocker&) = delete; |
| ScopedCursorUnlocker& operator=(const ScopedCursorUnlocker&) = delete; |
| |
| ~ScopedCursorUnlocker() { |
| if (cursor_client_) { |
| cursor_client_->LockCursor(); |
| } |
| } |
| |
| private: |
| raw_ptr<aura::client::CursorClient> cursor_client_; |
| }; |
| |
| } // namespace |
| |
| class Pointer::ScopedCursorLocker { |
| public: |
| explicit ScopedCursorLocker() { |
| WMHelper::GetInstance()->GetCursorClient()->LockCursor(); |
| } |
| |
| ScopedCursorLocker(const ScopedCursorLocker&) = delete; |
| ScopedCursorLocker& operator=(const ScopedCursorLocker&) = delete; |
| |
| ~ScopedCursorLocker() { |
| WMHelper::GetInstance()->GetCursorClient()->UnlockCursor(); |
| } |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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); |
| |
| cursor_locker_ = std::make_unique<ScopedCursorLocker>(); |
| // Ensure the cap to click is not paused when entering the pointer lock. |
| ui::OzonePlatform::GetInstance()->GetInputController()->SetTapToClickPaused( |
| false); |
| |
| return true; |
| } |
| |
| void Pointer::DisablePointerCapture() { |
| // Early out if pointer capture is not enabled. |
| if (!capture_window_) |
| return; |
| |
| cursor_locker_.reset(); |
| |
| // 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(); |
| } |
| |
| 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; |
| |
| // Temporarily unlock the cursor if the pointer capture is enabled. |
| ScopedCursorUnlocker unlock(capture_window_ ? helper->GetCursorClient() |
| : nullptr); |
| |
| // 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 |