// 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 #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 "third_party/abseil-cpp/absl/types/optional.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/1354573): This is no longer necessary. Switch to // std::numeric_limits::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 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); } } Pointer::~Pointer() { 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. 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::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::ET_MOUSE_CAPTURE_CHANGED) 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::ET_SCROLL_FLING_CANCEL && last_event_type_ != ui::ET_SCROLL_FLING_START) { // 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. absl::optional ordinal_motion = absl::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::ET_MOUSE_EXITED && !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::ET_MOUSE_RELEASED: seat_->AbortPendingDragOperation(); [[fallthrough]]; case ui::ET_MOUSE_PRESSED: { 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::ET_MOUSE_PRESSED); needs_frame |= true; break; } case ui::ET_SCROLL: { ui::ScrollEvent* scroll_event = static_cast(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::ET_MOUSEWHEEL: { delegate_->OnPointerScroll( event->time_stamp(), static_cast(event)->offset(), true); needs_frame |= true; break; } case ui::ET_SCROLL_FLING_START: { // 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::ET_SCROLL_FLING_CANCEL: { // 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::ET_MOUSE_MOVED: case ui::ET_MOUSE_DRAGGED: case ui::ET_MOUSE_ENTERED: case ui::ET_MOUSE_EXITED: break; default: NOTREACHED(); 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::ET_GESTURE_PINCH_BEGIN: pinch_delegate_->OnPointerPinchBegin(event->unique_touch_event_id(), event->time_stamp(), focus_surface_); delegate_->OnPointerFrame(); break; case ui::ET_GESTURE_PINCH_UPDATE: pinch_delegate_->OnPointerPinchUpdate(event->time_stamp(), event->details().scale()); delegate_->OnPointerFrame(); break; case ui::ET_GESTURE_PINCH_END: 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(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); } //////////////////////////////////////////////////////////////////////////////// // 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(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( 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 request = std::make_unique( 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 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 absl::optional& 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