// Copyright 2015 The Chromium Authors. All rights reserved. // 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/public/cpp/shell_window_ids.h" #include "base/bind.h" #include "base/feature_list.h" #include "components/exo/pointer_delegate.h" #include "components/exo/pointer_gesture_pinch_delegate.h" #include "components/exo/relative_pointer_delegate.h" #include "components/exo/shell_surface_util.h" #include "components/exo/surface.h" #include "components/exo/wm_helper.h" #include "components/exo/wm_helper_chromeos.h" #include "components/viz/common/frame_sinks/copy_output_request.h" #include "components/viz/common/frame_sinks/copy_output_result.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/base/cursor/cursor_util.h" #include "ui/display/manager/display_manager.h" #include "ui/display/manager/managed_display_info.h" #include "ui/display/screen.h" #include "ui/events/event.h" #include "ui/gfx/geometry/vector2d_conversions.h" #include "ui/gfx/transform_util.h" #include "ui/views/widget/widget.h" #if defined(USE_OZONE) #include "ui/ozone/public/cursor_factory_ozone.h" #endif #if defined(USE_X11) #include "ui/base/cursor/cursor_loader_x11.h" #endif namespace exo { // Controls Pointer capture in exo/wayland. const base::Feature kPointerCapture{"ExoPointerCapture", base::FEATURE_ENABLED_BY_DEFAULT}; namespace { // TODO(oshima): Some accessibility features, including large cursors, disable // hardware cursors. Ash does not support compositing for custom cursors, so it // replaces them with the default cursor. As a result, this scale has no effect // for now. See crbug.com/708378. const float kLargeCursorScale = 2.8f; const double kLocatedEventEpsilonSquared = 1.0 / (2000.0 * 2000.0); bool SameLocation(const gfx::PointF& location_in_target, const gfx::PointF& location) { // In general, it is good practice to compare floats using an epsilon. // In particular, the mouse location_f() could differ between the // MOUSE_PRESSED and MOUSE_RELEASED events. At MOUSE_RELEASED, it will have a // targeter() already cached, while at MOUSE_PRESSED, it will have to // calculate it passing through all the hierarchy of windows, and that could // generate rounding error. std::numeric_limits::epsilon() is not big // enough to catch this rounding error. gfx::Vector2dF offset = location_in_target - location; return offset.LengthSquared() < (2 * kLocatedEventEpsilonSquared); } display::ManagedDisplayInfo GetCaptureDisplayInfo() { display::ManagedDisplayInfo capture_info; for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { const auto& info = WMHelper::GetInstance()->GetDisplayInfo(display.id()); if (info.device_scale_factor() >= capture_info.device_scale_factor()) capture_info = info; } return capture_info; } } // namespace //////////////////////////////////////////////////////////////////////////////// // Pointer, public: Pointer::Pointer(PointerDelegate* delegate) : SurfaceTreeHost("ExoPointer"), delegate_(delegate), cursor_(ui::CursorType::kNull), capture_scale_(GetCaptureDisplayInfo().device_scale_factor()), capture_ratio_(GetCaptureDisplayInfo().GetDensityRatio()), cursor_capture_source_id_(base::UnguessableToken::Create()), cursor_capture_weak_ptr_factory_(this) { WMHelperChromeOS* helper = WMHelperChromeOS::GetInstance(); helper->AddPreTargetHandler(this); helper->AddDisplayConfigurationObserver(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->AddObserver(this); helper->AddFocusObserver(this); } Pointer::~Pointer() { 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); WMHelperChromeOS* helper = WMHelperChromeOS::GetInstance(); helper->RemoveDisplayConfigurationObserver(this); helper->RemovePreTargetHandler(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); } void Pointer::SetCursor(Surface* surface, const gfx::Point& hotspot) { // Early out if the pointer doesn't have a surface in focus. if (!focus_surface_) 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::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::CursorType::kCustom; CaptureCursor(hotspot); } else { cursor_ = ui::CursorType::kNone; cursor_bitmap_.reset(); cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs(); UpdateCursor(); } } void Pointer::SetCursorType(ui::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) { pinch_delegate_ = delegate; } void Pointer::EnablePointerCapture(RelativePointerDelegate* delegate) { if (!base::FeatureList::IsEnabled(kPointerCapture)) return; if (!delegate) { DLOG(ERROR) << "Failed to enable pointer capture: " "relative pointer delegate is null"; return; } // If pointer capture is already enabled, disable it first. if (relative_pointer_delegate_) DisablePointerCapture(); // TODO(b/124059008): Find the correct window to set capture. aura::Window* active_window = WMHelper::GetInstance()->GetActiveWindow(); if (!active_window) { LOG(ERROR) << "Failed to enable pointer capture: " "active window not found"; return; } auto* top_level_widget = views::Widget::GetTopLevelWidgetForNativeView(active_window); if (!top_level_widget) { LOG(ERROR) << "Failed to enable pointer capture: " "active window does not have associated widget"; return; } Surface* root_surface = GetShellMainSurface(top_level_widget->GetNativeWindow()); if (!root_surface || !delegate_->CanAcceptPointerEventsForSurface(root_surface)) { LOG(ERROR) << "Failed to enable pointer capture: " "cannot find window for capture"; return; } capture_window_ = root_surface->window(); auto* capture_client = WMHelper::GetInstance()->GetCaptureClient(); capture_client->SetCapture(capture_window_); capture_client->AddObserver(this); auto* cursor_client = WMHelper::GetInstance()->GetCursorClient(); cursor_client->HideCursor(); cursor_client->LockCursor(); relative_pointer_delegate_ = delegate; location_when_pointer_capture_enabled_ = gfx::ToRoundedPoint(location_); if (ShouldMoveToCenter()) MoveCursorToCenterOfActiveDisplay(); } void Pointer::DisablePointerCapture() { // Early out if pointer capture is not enabled. if (!relative_pointer_delegate_) return; auto* capture_client = WMHelper::GetInstance()->GetCaptureClient(); capture_client->RemoveObserver(this); if (capture_window_ && capture_window_->HasCapture()) capture_client->ReleaseCapture(capture_window_); capture_window_ = nullptr; auto* cursor_client = WMHelper::GetInstance()->GetCursorClient(); cursor_client->UnlockCursor(); cursor_client->ShowCursor(); aura::Window* focusedWindow = WMHelper::GetInstance()->GetFocusedWindow(); aura::Window* root = focusedWindow->GetRootWindow(); gfx::Point p = location_when_pointer_capture_enabled_ ? *location_when_pointer_capture_enabled_ : root->bounds().CenterPoint(); root->MoveCursorTo(p); focus_surface_ = nullptr; location_when_pointer_capture_enabled_.reset(); UpdateCursor(); relative_pointer_delegate_ = nullptr; } //////////////////////////////////////////////////////////////////////////////// // 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) { if (surface == focus_surface_) { SetFocus(nullptr, gfx::PointF(), 0); return; } if (surface == root_surface()) { UpdatePointerSurface(nullptr); return; } NOTREACHED(); } //////////////////////////////////////////////////////////////////////////////// // ui::EventHandler overrides: void Pointer::OnMouseEvent(ui::MouseEvent* event) { Surface* target = GetEffectiveTargetForEvent(event); gfx::PointF location_in_target = event->location_f(); if (target) { aura::Window::ConvertPointToTarget( static_cast(event->target()), target->window(), &location_in_target); } // Update focus if target is different than the current pointer focus. if (target != focus_surface_) SetFocus(target, location_in_target, event->button_flags()); gfx::PointF location_in_root = GetLocationInRoot(target, location_in_target); if (!focus_surface_) return; if (event->IsMouseEvent() && event->type() != ui::ET_MOUSE_EXITED && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { // Generate motion event if location changed. 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. // 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. bool same_location = !event->IsSynthesized() ? SameLocation(location_in_root, location_) : gfx::ToFlooredPoint(location_in_root) == gfx::ToFlooredPoint(location_); if (!same_location) { if (relative_pointer_delegate_) HandleRelativePointerMotion(event->time_stamp(), location_in_root); else delegate_->OnPointerMotion(event->time_stamp(), location_in_target); location_ = location_in_root; delegate_->OnPointerFrame(); } } switch (event->type()) { case ui::ET_MOUSE_PRESSED: case ui::ET_MOUSE_RELEASED: { delegate_->OnPointerButton(event->time_stamp(), event->changed_button_flags(), event->type() == ui::ET_MOUSE_PRESSED); delegate_->OnPointerFrame(); 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); delegate_->OnPointerFrame(); break; } case ui::ET_MOUSEWHEEL: { delegate_->OnPointerScroll( event->time_stamp(), static_cast(event)->offset(), true); delegate_->OnPointerFrame(); 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_->OnPointerScrollStop(event->time_stamp()); delegate_->OnPointerFrame(); break; } case ui::ET_SCROLL_FLING_CANCEL: { // Fling cancel is generated very generously at every touch of the // touchpad. Since it's not directly supported by the delegate, we do not // want limit this event to only right after a fling start has been // generated to prevent erronous behavior. if (last_event_type_ == ui::ET_SCROLL_FLING_START) { // 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_->OnPointerScrollStop(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: case ui::ET_MOUSE_CAPTURE_CHANGED: break; default: NOTREACHED(); break; } last_event_type_ = event->type(); } 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; 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; } } //////////////////////////////////////////////////////////////////////////////// // aura::client::CaptureClientObserver overrides: void Pointer::OnCaptureChanged(aura::Window* lost_capture, aura::Window* gained_capture) { // Note: This observer is only set when pointer capture in enabled. if (relative_pointer_delegate_ && gained_capture != capture_window_) DisablePointerCapture(); } //////////////////////////////////////////////////////////////////////////////// // aura::client::CursorClientObserver overrides: void Pointer::OnCursorSizeChanged(ui::CursorSize cursor_size) { if (!focus_surface_) return; if (cursor_ != ui::CursorType::kNull) UpdateCursor(); } void Pointer::OnCursorDisplayChanged(const display::Display& display) { auto* cursor_client = WMHelper::GetInstance()->GetCursorClient(); // TODO(crbug.com/631103): CursorClient does not exist in mash yet. if (!cursor_client) return; if (cursor_ == ui::CursorType::kCustom && cursor_client->GetCursor() == 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 (relative_pointer_delegate_) DisablePointerCapture(); } //////////////////////////////////////////////////////////////////////////////// // ash::WindowTreeHostManager::Observer overrides: void Pointer::OnDisplayConfigurationChanged() { UpdatePointerSurface(root_surface()); auto info = GetCaptureDisplayInfo(); capture_scale_ = info.device_scale_factor(); capture_ratio_ = info.GetDensityRatio(); } //////////////////////////////////////////////////////////////////////////////// // Pointer, private: Surface* Pointer::GetEffectiveTargetForEvent(ui::LocatedEvent* event) const { Surface* target = GetTargetSurfaceForLocatedEvent(event); if (!target) return nullptr; return delegate_->CanAcceptPointerEventsForSurface(target) ? target : nullptr; } void Pointer::SetFocus(Surface* surface, const gfx::PointF& location, int button_flags) { // First generate a leave event if we currently have a target in focus. if (focus_surface_) { delegate_->OnPointerLeave(focus_surface_); focus_surface_->RemoveSurfaceObserver(this); // Require SetCursor() to be called and cursor to be re-defined in // response to each OnPointerEnter() call. focus_surface_ = nullptr; cursor_capture_weak_ptr_factory_.InvalidateWeakPtrs(); } // Second generate an enter event if focus moved to a new surface. if (surface) { delegate_->OnPointerEnter(surface, location, button_flags); location_ = GetLocationInRoot(surface, location); focus_surface_ = surface; focus_surface_->AddSurfaceObserver(this); } delegate_->OnPointerFrame(); } void Pointer::UpdatePointerSurface(Surface* surface) { if (root_surface()) { host_window()->SetTransform(gfx::Transform()); if (host_window()->parent()) host_window()->parent()->RemoveChild(host_window()); root_surface()->RemoveSurfaceObserver(this); SetRootSurface(nullptr); } if (surface) { 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(ash::kShellWindowId_MouseCursorContainer) ->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; // Submit compositor frame to be captured. SubmitCompositorFrame(); // Surface size is in DIPs, while layer size is in pseudo-DIP units that // depend on the DSF of the display mode. Scale the layer to capture the // surface at a constant pixel size, regardless of the primary display's // display mode DSF. display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay(); float scale = capture_scale_ / display.device_scale_factor(); host_window()->SetTransform(gfx::GetScaleTransform(gfx::Point(), scale)); std::unique_ptr request = std::make_unique( viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP, base::BindOnce(&Pointer::OnCursorCaptured, cursor_capture_weak_ptr_factory_.GetWeakPtr(), hotspot)); request->set_source(cursor_capture_source_id_); host_window()->layer()->RequestCopyOfOutput(std::move(request)); } void Pointer::OnCursorCaptured(const gfx::Point& hotspot, std::unique_ptr result) { if (!focus_surface_) return; // Only successful captures should update the cursor. if (result->IsEmpty()) return; cursor_bitmap_ = result->AsSkBitmap(); DCHECK(cursor_bitmap_.readyToDraw()); cursor_hotspot_ = hotspot; UpdateCursor(); } void Pointer::UpdateCursor() { WMHelper* helper = WMHelper::GetInstance(); aura::client::CursorClient* cursor_client = helper->GetCursorClient(); // TODO(crbug.com/631103): CursorClient does not exist in mash yet. if (!cursor_client) return; if (cursor_ == ui::CursorType::kCustom) { SkBitmap bitmap = cursor_bitmap_; gfx::Point hotspot = gfx::ScaleToFlooredPoint(cursor_hotspot_, capture_ratio_); // TODO(oshima|weidongg): Add cutsom cursor API to handle size/display // change without explicit management like this. https://crbug.com/721601. const display::Display& display = cursor_client->GetDisplay(); float scale = helper->GetDisplayInfo(display.id()).GetDensityRatio() / capture_ratio_; if (cursor_client->GetCursorSize() == ui::CursorSize::kLarge) scale *= kLargeCursorScale; ui::ScaleAndRotateCursorBitmapAndHotpoint(scale, display.rotation(), &bitmap, &hotspot); ui::PlatformCursor platform_cursor; #if defined(USE_OZONE) // TODO(reveman): Add interface for creating cursors from GpuMemoryBuffers // and use that here instead of the current bitmap API. // https://crbug.com/686600 platform_cursor = ui::CursorFactoryOzone::GetInstance()->CreateImageCursor( bitmap, hotspot, 0); #elif defined(USE_X11) XcursorImage* image = ui::SkBitmapToXcursorImage(&bitmap, hotspot); platform_cursor = ui::CreateReffedCustomXCursor(image); #endif cursor_.SetPlatformCursor(platform_cursor); cursor_.set_custom_bitmap(bitmap); cursor_.set_custom_hotspot(hotspot); #if defined(USE_OZONE) ui::CursorFactoryOzone::GetInstance()->UnrefImageCursor(platform_cursor); #elif defined(USE_X11) ui::UnrefCustomXCursor(platform_cursor); #endif } // 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_); } } gfx::PointF Pointer::GetLocationInRoot(Surface* target, gfx::PointF location_in_target) { if (!target || !target->window()) return location_in_target; aura::Window* w = target->window(); gfx::PointF p(location_in_target.x(), location_in_target.y()); aura::Window::ConvertPointToTarget(w, w->GetRootWindow(), &p); return gfx::PointF(p.x(), p.y()); } bool Pointer::ShouldMoveToCenter() { // Early out if the pointer doesn't have a surface in focus. if (!focus_surface_) return false; gfx::Rect rect = WMHelper::GetInstance()->GetFocusedWindow()->GetRootWindow()->bounds(); rect.Inset(rect.width() / 6, rect.height() / 6); return !rect.Contains(location_.x(), location_.y()); } void Pointer::MoveCursorToCenterOfActiveDisplay() { aura::Window* focusedWindow = WMHelper::GetInstance()->GetFocusedWindow(); aura::Window* root = focusedWindow->GetRootWindow(); gfx::Point p = root->bounds().CenterPoint(); location_synthetic_move_ = p; root->MoveCursorTo(p); } void Pointer::HandleRelativePointerMotion(base::TimeTicks time_stamp, gfx::PointF location_in_root) { if (location_synthetic_move_) { gfx::Point synthetic = *location_synthetic_move_; // 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() - synthetic.x()) <= 2 && std::abs(location_in_root.y() - synthetic.y()) <= 2) { // This was a synthetic move event, so do not forward it and clear the // synthetic move. location_synthetic_move_.reset(); return; } } gfx::PointF delta(location_in_root.x() - location_.x(), location_in_root.y() - location_.y()); relative_pointer_delegate_->OnPointerRelativeMotion(time_stamp, delta); if (ShouldMoveToCenter()) MoveCursorToCenterOfActiveDisplay(); } } // namespace exo