| // 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/touch.h" |
| |
| #include <algorithm> |
| |
| #include "base/trace_event/trace_event.h" |
| #include "components/exo/input_trace.h" |
| #include "components/exo/seat.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface.h" |
| #include "components/exo/touch_delegate.h" |
| #include "components/exo/touch_stylus_delegate.h" |
| #include "components/exo/wm_helper.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/events/event.h" |
| #include "ui/wm/core/capture_controller.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace exo { |
| namespace { |
| |
| gfx::PointF EventLocationInWindow(ui::TouchEvent* event, aura::Window* window) { |
| ui::Layer* root = window->GetRootWindow()->layer(); |
| ui::Layer* target = window->layer(); |
| |
| gfx::Transform transform; |
| target->GetTargetTransformRelativeTo(root, &transform); |
| gfx::PointF point = event->root_location_f(); |
| return transform.InverseMapPoint(point).value_or(point); |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Touch, public: |
| |
| Touch::Touch(TouchDelegate* delegate, Seat* seat) |
| : delegate_(delegate), seat_(seat) { |
| ash::Shell::Get()->AddShellObserver(this); |
| for (aura::Window* root : ash::Shell::GetAllRootWindows()) { |
| root->AddPreTargetHandler(this); |
| } |
| } |
| |
| Touch::~Touch() { |
| ash::Shell::Get()->RemoveShellObserver(this); |
| for (aura::Window* root : ash::Shell::GetAllRootWindows()) { |
| root->RemovePreTargetHandler(this); |
| } |
| delegate_->OnTouchDestroying(this); |
| if (HasStylusDelegate()) |
| stylus_delegate_->OnTouchDestroying(this); |
| CancelAllTouches(); |
| } |
| |
| void Touch::SetStylusDelegate(TouchStylusDelegate* delegate) { |
| stylus_delegate_ = delegate; |
| } |
| |
| bool Touch::HasStylusDelegate() const { |
| return !!stylus_delegate_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ui::EventHandler overrides: |
| |
| void Touch::OnTouchEvent(ui::TouchEvent* event) { |
| if (seat_->was_shutdown() || event->handled()) { |
| return; |
| } |
| |
| // TODO(crbug.com/40061238): Investigate if we need to do something similar to |
| // the filter in `Pointer::OnMouseEvent` when dragging. (not sending touch |
| // events during drag) |
| |
| bool send_details = false; |
| |
| auto event_type = event->type(); |
| if ((event->flags() & ui::EF_RESERVED_FOR_GESTURE) != 0) { |
| event_type = ui::EventType::kTouchCancelled; |
| } |
| |
| const int touch_pointer_id = event->pointer_details().id; |
| switch (event_type) { |
| case ui::EventType::kTouchPressed: { |
| // Early out if event doesn't contain a valid target for touch device. |
| // TODO(b/147848270): Verify GetEffectiveTargetForEvent gets the correct |
| // surface when input is captured. |
| Surface* target = GetEffectiveTargetForEvent(event); |
| if (!target) |
| return; |
| |
| TRACE_EXO_INPUT_EVENT(event); |
| DCHECK(touch_points_surface_map_.find(touch_pointer_id) == |
| touch_points_surface_map_.end()); |
| |
| touch_points_surface_map_.emplace(touch_pointer_id, target); |
| |
| // Update the count of pointers on the target surface. |
| auto it = surface_touch_count_map_.find(target); |
| if (it == surface_touch_count_map_.end()) { |
| target->AddSurfaceObserver(this); |
| surface_touch_count_map_.emplace(target, 1); |
| } else { |
| it->second++; |
| } |
| |
| // Convert location to target surface coordinate space. |
| const gfx::PointF location = |
| EventLocationInWindow(event, target->window()); |
| |
| // Generate a touch down event for the target surface. |
| delegate_->OnTouchDown(target, event->time_stamp(), touch_pointer_id, |
| location); |
| if (stylus_delegate_ && event->pointer_details().pointer_type != |
| ui::EventPointerType::kTouch) { |
| stylus_delegate_->OnTouchTool(touch_pointer_id, |
| event->pointer_details().pointer_type); |
| } |
| send_details = true; |
| } break; |
| case ui::EventType::kTouchReleased: { |
| auto it = touch_points_surface_map_.find(touch_pointer_id); |
| if (it == touch_points_surface_map_.end()) |
| return; |
| |
| Surface* target = it->second; |
| DCHECK(target); |
| |
| TRACE_EXO_INPUT_EVENT(event); |
| |
| touch_points_surface_map_.erase(it); |
| |
| // Update the count of pointers on the target surface. |
| auto count_it = surface_touch_count_map_.find(target); |
| if (count_it == surface_touch_count_map_.end()) |
| return; |
| if ((--count_it->second) <= 0) { |
| surface_touch_count_map_.erase(target); |
| target->RemoveSurfaceObserver(this); |
| } |
| |
| delegate_->OnTouchUp(event->time_stamp(), touch_pointer_id); |
| seat_->AbortPendingDragOperation(); |
| } break; |
| case ui::EventType::kTouchMoved: { |
| auto it = touch_points_surface_map_.find(touch_pointer_id); |
| if (it == touch_points_surface_map_.end()) |
| return; |
| |
| Surface* target = it->second; |
| DCHECK(target); |
| |
| TRACE_EXO_INPUT_EVENT(event); |
| |
| // Convert location to focus surface coordinate space. |
| gfx::PointF location = EventLocationInWindow(event, target->window()); |
| delegate_->OnTouchMotion(event->time_stamp(), touch_pointer_id, location); |
| send_details = true; |
| } break; |
| case ui::EventType::kTouchCancelled: { |
| TRACE_EXO_INPUT_EVENT(event); |
| // Cancel the full set of touch sequences as soon as one is canceled. |
| CancelAllTouches(); |
| delegate_->OnTouchCancel(); |
| |
| seat_->AbortPendingDragOperation(); |
| } break; |
| default: |
| NOTREACHED(); |
| } |
| if (send_details) { |
| // Some devices do not report radius_y/minor. We assume a circular shape |
| // in that case. |
| float major = event->pointer_details().radius_x * 2.0f; |
| float minor = event->pointer_details().radius_y * 2.0f; |
| if (!minor) |
| minor = major; |
| delegate_->OnTouchShape(touch_pointer_id, major, minor); |
| |
| if (stylus_delegate_ && |
| event->pointer_details().pointer_type != ui::EventPointerType::kTouch) { |
| if (!std::isnan(event->pointer_details().force)) { |
| stylus_delegate_->OnTouchForce(event->time_stamp(), touch_pointer_id, |
| event->pointer_details().force); |
| } |
| stylus_delegate_->OnTouchTilt( |
| event->time_stamp(), touch_pointer_id, |
| gfx::Vector2dF(event->pointer_details().tilt_x, |
| event->pointer_details().tilt_y)); |
| } |
| } |
| // TODO(denniskempin): Extend ui::TouchEvent to signal end of sequence of |
| // touch events to send TouchFrame once after all touches have been updated. |
| delegate_->OnTouchFrame(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceObserver overrides: |
| |
| void Touch::OnSurfaceDestroying(Surface* surface) { |
| // TODO(b/147848407): Do not cancel touches on surfaces of different clients |
| // when this surface dies. |
| CancelAllTouches(); |
| delegate_->OnTouchCancel(); |
| } |
| |
| // ash::ShellObserver: |
| void Touch::OnRootWindowAdded(aura::Window* root_window) { |
| root_window->AddPreTargetHandler(this); |
| } |
| |
| void Touch::OnRootWindowWillShutdown(aura::Window* root_window) { |
| root_window->RemovePreTargetHandler(this); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Touch, private: |
| |
| Surface* Touch::GetEffectiveTargetForEvent(ui::LocatedEvent* event) const { |
| Surface* target = GetTargetSurfaceForLocatedEvent(event); |
| |
| if (!target) |
| return nullptr; |
| |
| return delegate_->CanAcceptTouchEventsForSurface(target) ? target : nullptr; |
| } |
| |
| void Touch::CancelAllTouches() { |
| std::ranges::for_each(surface_touch_count_map_, [this](auto& it) { |
| it.first->RemoveSurfaceObserver(this); |
| }); |
| touch_points_surface_map_.clear(); |
| surface_touch_count_map_.clear(); |
| } |
| |
| } // namespace exo |