Commit bcdf53ae authored by Nick Diego Yamane's avatar Nick Diego Yamane Committed by Commit Bot

exo: extended-drag: Implement shell surface dragging

Context: Wayland Protocol needs to be extended to make it possible to
properly support full Chrome's tab dragging experience. Further details
in the Design document [1].

This is the first of a patch series which implements extended-drag in
Exo compositor. More specifically, this implements the core feature of
the protocol: Making it possible to drag toplevel shell surfaces during
DND sessions. It does it by making ExtendedDragSource an implementation
of the ash::ToplevelWindowDragDelegate interface and plumbing it into
ash's ToplevelWindowEventHandler, delegating to it the actual toplevel
window drag handling. Refer to the design doc for more details.

[1] https://docs.google.com/document/d/1s6OwTi_WC-pS21WLGQYI39yw2m42ZlVolUXBclljXB4/edit?usp=sharing

R=oshima@chromium.org

Bug: 1099418
Test: exo_unittests --gtest_filter='ExtendedDragSourceTest.*'
Change-Id: I43908dda9f542e5813d96a2e331af8df0a703efb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2401280Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Cr-Commit-Position: refs/heads/master@{#822682}
parent 3129eaf9
......@@ -281,6 +281,7 @@ source_set("unit_tests") {
"data_source_unittest.cc",
"display_unittest.cc",
"drag_drop_operation_unittest.cc",
"extended_drag_source_unittest.cc",
"gamepad_unittest.cc",
"gaming_seat_unittest.cc",
"input_method_surface_unittest.cc",
......
......@@ -8,6 +8,7 @@
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/exo/data_offer.h"
#include "components/exo/data_source.h"
#include "components/exo/extended_drag_source.h"
#include "components/exo/seat.h"
#include "components/exo/surface.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
......@@ -116,6 +117,13 @@ DragDropOperation::DragDropOperation(DataSource* source,
drag_drop_controller_->AddObserver(this);
if (auto* ext_drag_source = source_->get()->extended_drag_source()) {
#if defined(OS_CHROMEOS)
drag_drop_controller_->set_toplevel_window_drag_delegate(ext_drag_source);
#endif
ext_drag_source->AddObserver(this);
}
if (icon)
icon_ = std::make_unique<ScopedSurface>(icon, this);
......@@ -272,13 +280,20 @@ void DragDropOperation::StartDragDropOperation() {
source_->get()->DndFinished();
// Reset |source_| so it the destructor doesn't try to cancel it.
source_.reset();
ResetSource();
}
// On failure the destructor will handle canceling the data source.
delete this;
}
void DragDropOperation::ResetSource() {
DCHECK(source_);
if (source_->get()->extended_drag_source())
source_->get()->extended_drag_source()->RemoveObserver(this);
source_.reset();
}
void DragDropOperation::OnDragStarted() {
if (!started_by_this_object_)
delete this;
......@@ -305,6 +320,17 @@ void DragDropOperation::OnDragActionsChanged(int actions) {
}
#endif
void DragDropOperation::OnExtendedDragSourceDestroying(
ExtendedDragSource* source) {
#if defined(OS_CHROMEOS)
drag_drop_controller_->set_toplevel_window_drag_delegate(nullptr);
#endif
if (source_) {
DCHECK(source_->get()->extended_drag_source());
source_->get()->extended_drag_source()->RemoveObserver(this);
}
}
void DragDropOperation::OnSurfaceDestroying(Surface* surface) {
if (surface == origin_->get() || surface == icon_->get()) {
delete this;
......@@ -315,7 +341,7 @@ void DragDropOperation::OnSurfaceDestroying(Surface* surface) {
void DragDropOperation::OnDataSourceDestroying(DataSource* source) {
if (source == source_->get()) {
source_.reset();
ResetSource();
delete this;
} else {
NOTREACHED();
......
......@@ -8,6 +8,7 @@
#include "components/exo/data_device.h"
#include "components/exo/data_offer_observer.h"
#include "components/exo/data_source_observer.h"
#include "components/exo/extended_drag_source.h"
#include "components/exo/surface_observer.h"
#include "components/exo/surface_tree_host.h"
#include "components/exo/wm_helper.h"
......@@ -44,7 +45,8 @@ class ScopedDataSource;
class DragDropOperation : public DataSourceObserver,
public SurfaceTreeHost,
public SurfaceObserver,
public aura::client::DragDropClientObserver {
public aura::client::DragDropClientObserver,
public ExtendedDragSource::Observer {
public:
// Create an operation for a drag-drop originating from a wayland app.
static base::WeakPtr<DragDropOperation> Create(
......@@ -73,6 +75,9 @@ class DragDropOperation : public DataSourceObserver,
void OnDragActionsChanged(int actions) override;
#endif
// ExtendedDragSource::Observer:
void OnExtendedDragSourceDestroying(ExtendedDragSource* source) override;
private:
// A private constructor and destructor are used to prevent anyone else from
// attempting to manage the lifetime of a DragDropOperation.
......@@ -95,6 +100,8 @@ class DragDropOperation : public DataSourceObserver,
// directly. Use ScheduleStartDragDropOperation instead.
void StartDragDropOperation();
void ResetSource();
std::unique_ptr<ScopedDataSource> source_;
std::unique_ptr<ScopedSurface> icon_;
std::unique_ptr<ScopedSurface> origin_;
......
......@@ -4,24 +4,104 @@
#include "components/exo/extended_drag_source.h"
#include <memory>
#include <string>
#include "ash/shell.h"
#include "ash/wm/toplevel_window_event_handler.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/optional.h"
#include "components/exo/data_source.h"
#include "components/exo/seat.h"
#include "components/exo/surface.h"
#include "ui/aura/window_observer.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/base/hit_test.h"
#include "ui/events/event.h"
#include "ui/events/event_target.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/public/window_move_client.h"
namespace exo {
ExtendedDragSource::ExtendedDragSource(DataSource* source,
Seat* seat,
Delegate* delegate)
: delegate_(delegate), seat_(seat), source_(source) {
// Internal representation of a toplevel window, backed by an Exo shell surface,
// which is being dragged. It supports both already mapped/visible windows as
// well as newly created ones (i.e: not added to a root window yet), in which
// case OnDraggedWindowVisibilityChanging callback is called to notify when it
// is about to get visible.
class ExtendedDragSource::DraggedWindowHolder : public aura::WindowObserver {
public:
DraggedWindowHolder(Surface* surface,
const gfx::Vector2d& drag_offset,
ExtendedDragSource* source)
: surface_(surface), drag_offset_(drag_offset), source_(source) {
DCHECK(surface_);
DCHECK(surface_->window());
if (!FindToplevelWindow()) {
DVLOG(1) << "Dragged window not added to root window yet.";
surface_->window()->AddObserver(this);
}
}
DraggedWindowHolder(const DraggedWindowHolder&) = delete;
DraggedWindowHolder& operator=(const DraggedWindowHolder&) = delete;
~DraggedWindowHolder() override {
if (toplevel_window_) {
toplevel_window_->RemoveObserver(this);
toplevel_window_ = nullptr;
} else {
surface_->window()->RemoveObserver(this);
}
}
aura::Window* toplevel_window() { return toplevel_window_; }
const gfx::Vector2d& offset() const { return drag_offset_; }
private:
// aura::WindowObserver:
void OnWindowAddedToRootWindow(aura::Window* window) override {
DCHECK_EQ(window, surface_->window());
FindToplevelWindow();
DCHECK(toplevel_window_);
surface_->window()->RemoveObserver(this);
}
void OnWindowVisibilityChanging(aura::Window* window, bool visible) override {
DCHECK(window);
if (window == toplevel_window_)
source_->OnDraggedWindowVisibilityChanging(visible);
}
bool FindToplevelWindow() {
if (!surface_->window()->GetRootWindow())
return false;
toplevel_window_ = surface_->window()->GetToplevelWindow();
toplevel_window_->AddObserver(this);
return true;
}
Surface* const surface_;
gfx::Vector2d drag_offset_;
ExtendedDragSource* const source_;
aura::Window* toplevel_window_ = nullptr;
};
ExtendedDragSource::ExtendedDragSource(DataSource* source, Delegate* delegate)
: delegate_(delegate), source_(source) {
DCHECK(source_);
DCHECK(seat_);
DCHECK(delegate_);
DVLOG(1) << "ExtendedDragSource created. wl_source=" << source_;
source_->set_extended_drag_source(this);
source_->AddObserver(this);
}
......@@ -30,8 +110,10 @@ ExtendedDragSource::~ExtendedDragSource() {
for (auto& observer : observers_)
observer.OnExtendedDragSourceDestroying(this);
if (source_)
if (source_) {
source_->set_extended_drag_source(nullptr);
source_->RemoveObserver(this);
}
}
void ExtendedDragSource::AddObserver(Observer* observer) {
......@@ -50,16 +132,65 @@ void ExtendedDragSource::Drag(Surface* dragged_surface,
if (!source_)
return;
if (dragged_surface == dragged_surface_ && drag_offset == drag_offset_)
if (!dragged_surface) {
DVLOG(1) << "Unsetting dragged surface.";
dragged_window_holder_.reset();
return;
}
dragged_surface_ = dragged_surface;
drag_offset_ = drag_offset;
DVLOG(1) << "Dragged surface changed: surface=" << dragged_surface_
<< " offset=" << drag_offset_.ToString();
DVLOG(1) << "Dragged surface changed:"
<< " surface=" << dragged_surface
<< " offset=" << drag_offset.ToString();
for (auto& observer : observers_)
observer.OnDraggedSurfaceChanged(this);
// Ensure that the surface already has a "role" assigned.
DCHECK(dragged_surface->HasSurfaceDelegate());
dragged_window_holder_ =
std::make_unique<DraggedWindowHolder>(dragged_surface, drag_offset, this);
// Drag process will be started once OnDragStarted gets called.
}
bool ExtendedDragSource::IsActive() const {
return !!source_;
}
void ExtendedDragSource::OnToplevelWindowDragStarted(
const gfx::PointF& start_location,
ui::mojom::DragEventSource source) {
pointer_location_ = start_location;
drag_event_source_ = source;
if (dragged_window_holder_ && dragged_window_holder_->toplevel_window())
StartDrag(dragged_window_holder_->toplevel_window(), start_location);
}
int ExtendedDragSource::OnToplevelWindowDragDropped() {
DVLOG(1) << "OnDragDropped()";
Cleanup();
return delegate_->ShouldAllowDropAnywhere() ? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_NONE;
}
void ExtendedDragSource::OnToplevelWindowDragCancelled() {
DVLOG(1) << "OnDragCancelled()";
// TODO(crbug.com/1099418): Handle cancellation/revert.
Cleanup();
}
void ExtendedDragSource::OnToplevelWindowDragEvent(ui::LocatedEvent* event) {
DCHECK(event);
pointer_location_ = event->root_location_f();
if (!dragged_window_holder_)
return;
auto* handler = ash::Shell::Get()->toplevel_window_event_handler();
if (event->IsMouseEvent()) {
handler->OnMouseEvent(event->AsMouseEvent());
return;
}
// TODO(crbug.com/1099418): Support touch move source.
NOTIMPLEMENTED() << "Non-mouse window dragging not supported yet.";
}
void ExtendedDragSource::OnDataSourceDestroying(DataSource* source) {
......@@ -68,4 +199,71 @@ void ExtendedDragSource::OnDataSourceDestroying(DataSource* source) {
source_ = nullptr;
}
void ExtendedDragSource::StartDrag(aura::Window* toplevel,
const gfx::PointF& pointer_location) {
// Ensure |toplevel| window does skip events while it's being dragged.
event_blocker_ =
std::make_unique<aura::ScopedWindowEventTargetingBlocker>(toplevel);
DVLOG(1) << "Starting drag. pointer_loc=" << pointer_location.ToString();
auto* toplevel_handler = ash::Shell::Get()->toplevel_window_event_handler();
auto move_source = drag_event_source_ == ui::mojom::DragEventSource::kTouch
? ::wm::WINDOW_MOVE_SOURCE_TOUCH
: ::wm::WINDOW_MOVE_SOURCE_MOUSE;
toplevel_handler->AttemptToStartDrag(
toplevel, pointer_location, HTCAPTION, move_source,
ash::ToplevelWindowEventHandler::EndClosure(),
/*update_gesture_target=*/true, /*grab_capture=*/false);
}
void ExtendedDragSource::OnDraggedWindowVisibilityChanging(bool visible) {
DCHECK(dragged_window_holder_);
DVLOG(1) << "Dragged window visibility changed. visible=" << visible;
if (!visible) {
dragged_window_holder_.reset();
return;
}
aura::Window* toplevel = dragged_window_holder_->toplevel_window();
DCHECK(toplevel);
// The |toplevel| window for the dragged surface has just been created and
// it's about to be mapped. Calculate and set its position based on
// |drag_offset_| and |pointer_location_| before starting the actual drag.
auto screen_location = CalculateOrigin(toplevel);
toplevel->SetBounds({screen_location, toplevel->bounds().size()});
DVLOG(1) << "Dragged window mapped. toplevel=" << toplevel
<< " origin=" << screen_location.ToString();
gfx::PointF pointer_location(screen_location +
dragged_window_holder_->offset());
StartDrag(toplevel, pointer_location);
}
gfx::Point ExtendedDragSource::CalculateOrigin(aura::Window* target) const {
DCHECK(dragged_window_holder_);
gfx::Point screen_location = gfx::ToRoundedPoint(pointer_location_);
wm::ConvertPointToScreen(target->GetRootWindow(), &screen_location);
return screen_location - dragged_window_holder_->offset();
}
void ExtendedDragSource::Cleanup() {
event_blocker_.reset();
dragged_window_holder_.reset();
}
aura::Window* ExtendedDragSource::GetDraggedWindowForTesting() {
return dragged_window_holder_ ? dragged_window_holder_->toplevel_window()
: nullptr;
}
base::Optional<gfx::Vector2d> ExtendedDragSource::GetDragOffsetForTesting()
const {
return dragged_window_holder_
? base::Optional<gfx::Vector2d>(dragged_window_holder_->offset())
: base::nullopt;
}
} // namespace exo
......@@ -5,20 +5,38 @@
#ifndef COMPONENTS_EXO_EXTENDED_DRAG_SOURCE_H_
#define COMPONENTS_EXO_EXTENDED_DRAG_SOURCE_H_
#include <memory>
#include <string>
#include "ash/drag_drop/toplevel_window_drag_delegate.h"
#include "ash/wm/toplevel_window_event_handler.h"
#include "base/observer_list.h"
#include "base/optional.h"
#include "components/exo/data_source_observer.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/aura/scoped_window_event_targeting_blocker.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
namespace aura {
class Window;
}
namespace gfx {
class Vector2d;
}
namespace ui {
class LocatedEvent;
}
namespace exo {
class DataSource;
class Seat;
class Surface;
class ExtendedDragSource : public DataSourceObserver {
class ExtendedDragSource : public DataSourceObserver,
public ash::ToplevelWindowDragDelegate {
public:
class Delegate {
public:
......@@ -36,13 +54,12 @@ class ExtendedDragSource : public DataSourceObserver {
class Observer {
public:
virtual void OnExtendedDragSourceDestroying(ExtendedDragSource* source) = 0;
virtual void OnDraggedSurfaceChanged(ExtendedDragSource* source) = 0;
protected:
virtual ~Observer() = default;
};
ExtendedDragSource(DataSource* source, Seat* seat, Delegate* delegate);
ExtendedDragSource(DataSource* source, Delegate* delegate);
ExtendedDragSource(const ExtendedDragSource&) = delete;
ExtendedDragSource& operator=(const ExtendedDragSource&) = delete;
~ExtendedDragSource() override;
......@@ -50,28 +67,46 @@ class ExtendedDragSource : public DataSourceObserver {
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
bool should_allow_drop_anywhere() const {
return delegate_->ShouldAllowDropAnywhere();
}
bool should_lock_cursor() const { return delegate_->ShouldLockCursor(); }
void Drag(Surface* surface, const gfx::Vector2d& offset);
const gfx::Vector2d& drag_offset() const { return drag_offset_; }
bool IsActive() const;
void Drag(Surface* surface, const gfx::Vector2d& offset);
// ash::ToplevelWindowDragDelegate:
void OnToplevelWindowDragStarted(const gfx::PointF& start_location,
ui::mojom::DragEventSource source) override;
int OnToplevelWindowDragDropped() override;
void OnToplevelWindowDragCancelled() override;
void OnToplevelWindowDragEvent(ui::LocatedEvent* event) override;
private:
// DataSourceObserver:
void OnDataSourceDestroying(DataSource* source) override;
aura::Window* GetDraggedWindowForTesting();
base::Optional<gfx::Vector2d> GetDragOffsetForTesting() const;
private:
class DraggedWindowHolder;
void StartDrag(aura::Window* toplevel,
const gfx::PointF& pointer_location_in_screen);
void OnDraggedWindowVisibilityChanging(bool visible);
gfx::Point CalculateOrigin(aura::Window* target) const;
void Cleanup();
// Created and destroyed at wayland/zcr_extended_drag.cc and its lifetime is
// tied to the zcr_extended_drag_source_v1 object it's attached to.
Delegate* const delegate_;
Seat* const seat_;
DataSource* source_ = nullptr;
Surface* dragged_surface_ = nullptr;
gfx::Vector2d drag_offset_;
gfx::PointF pointer_location_;
ui::mojom::DragEventSource drag_event_source_;
std::unique_ptr<DraggedWindowHolder> dragged_window_holder_;
std::unique_ptr<aura::ScopedWindowEventTargetingBlocker> event_blocker_;
base::ObserverList<Observer>::Unchecked observers_;
base::WeakPtrFactory<ExtendedDragSource> weak_factory_{this};
};
} // namespace exo
......
This diff is collapsed.
......@@ -143,7 +143,6 @@ void extended_drag_get_extended_drag_source(wl_client* client,
uint32_t id,
wl_resource* data_source_resource,
uint32_t settings) {
Display* display = GetUserDataAs<Display>(resource);
DataSource* source = GetUserDataAs<DataSource>(data_source_resource);
wl_resource* extended_drag_source_resource =
......@@ -153,8 +152,7 @@ void extended_drag_get_extended_drag_source(wl_client* client,
SetImplementation(extended_drag_source_resource,
&extended_drag_source_implementation,
std::make_unique<ExtendedDragSource>(
source, display->seat(),
new ZcrExtendedDragSourceDelegate(
source, new ZcrExtendedDragSourceDelegate(
extended_drag_source_resource, settings)));
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment