1 /*
2     SPDX-FileCopyrightText: 2020 Roman Gilg <subdiff@gmail.com>
3     SPDX-FileCopyrightText: 2021 Francesco Sorrentino <francesco.sorr@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only
6 */
7 #include "drag_pool.h"
8 #include "data_device.h"
9 #include "data_source.h"
10 #include "display.h"
11 #include "seat.h"
12 #include "seat_p.h"
13 #include "touch.h"
14 #include "utils.h"
15 
16 namespace Wrapland::Server
17 {
18 
drag_pool(Seat * seat)19 drag_pool::drag_pool(Seat* seat)
20     : seat{seat}
21 {
22 }
23 
get_source() const24 drag_source const& drag_pool::get_source() const
25 {
26     return source;
27 }
28 
get_target() const29 drag_target const& drag_pool::get_target() const
30 {
31     return target;
32 }
33 
cancel()34 void drag_pool::cancel()
35 {
36     if (target.dev) {
37         target.dev->updateDragTarget(nullptr, 0);
38         target.dev = nullptr;
39     }
40     end(0);
41 }
42 
end(uint32_t serial)43 void drag_pool::end(uint32_t serial)
44 {
45     auto trgt = target.dev;
46 
47     QObject::disconnect(source.device_destroy_notifier);
48     QObject::disconnect(source.destroy_notifier);
49 
50     if (source.dev && source.dev->dragSource()) {
51         source.dev->dragSource()->dropPerformed();
52     }
53 
54     if (trgt) {
55         trgt->drop();
56         trgt->updateDragTarget(nullptr, serial);
57     }
58 
59     source = {};
60     target = {};
61 
62     Q_EMIT seat->dragSurfaceChanged();
63     Q_EMIT seat->dragEnded();
64 }
65 
set_target(Surface * new_surface,const QMatrix4x4 & inputTransformation)66 void drag_pool::set_target(Surface* new_surface, const QMatrix4x4& inputTransformation)
67 {
68     if (source.mode == drag_mode::pointer) {
69         set_target(new_surface, seat->pointers().get_position(), inputTransformation);
70     } else {
71         assert(source.mode == drag_mode::touch);
72         set_target(
73             new_surface, seat->touches().get_focus().first_touch_position, inputTransformation);
74     }
75 }
76 
set_target(Surface * new_surface,const QPointF & globalPosition,const QMatrix4x4 & inputTransformation)77 void drag_pool::set_target(Surface* new_surface,
78                            const QPointF& globalPosition,
79                            const QMatrix4x4& inputTransformation)
80 {
81     if (new_surface == target.surface) {
82         // no change
83         return;
84     }
85     auto const serial = seat->d_ptr->display()->handle()->nextSerial();
86     if (target.dev) {
87         target.dev->updateDragTarget(nullptr, serial);
88         QObject::disconnect(target.destroy_notifier);
89         target.destroy_notifier = QMetaObject::Connection();
90     }
91 
92     // In theory we can have multiple data devices and we should send the drag to all of them, but
93     // that seems overly complicated. In practice so far the only case for multiple data devices is
94     // for clipboard overriding.
95     target.dev = interfaceForSurface(new_surface, seat->d_ptr->data_devices.devices);
96 
97     if (source.mode == drag_mode::pointer) {
98         seat->pointers().set_position(globalPosition);
99     } else if (source.mode == drag_mode::touch
100                && seat->touches().get_focus().first_touch_position != globalPosition) {
101         // TODO(romangg): instead of moving any touch point could we move with id 0? Probably yes
102         //                if we always end a drag once the id 0 touch point has been lifted.
103         seat->touches().touch_move_any(globalPosition);
104     }
105     if (target.dev) {
106         target.surface = new_surface;
107         target.transformation = inputTransformation;
108         target.dev->updateDragTarget(target.surface, serial);
109         target.destroy_notifier
110             = QObject::connect(target.dev, &DataDevice::resourceDestroyed, seat, [this] {
111                   QObject::disconnect(target.destroy_notifier);
112                   target.destroy_notifier = QMetaObject::Connection();
113                   target.dev = nullptr;
114               });
115 
116     } else {
117         target.surface = nullptr;
118     }
119     Q_EMIT seat->dragSurfaceChanged();
120 }
121 
is_in_progress() const122 bool drag_pool::is_in_progress() const
123 {
124     return source.mode != drag_mode::none;
125 }
126 
is_pointer_drag() const127 bool drag_pool::is_pointer_drag() const
128 {
129     return source.mode == drag_mode::pointer;
130 }
131 
is_touch_drag() const132 bool drag_pool::is_touch_drag() const
133 {
134     return source.mode == drag_mode::touch;
135 }
136 
perform_drag(DataDevice * dataDevice)137 void drag_pool::perform_drag(DataDevice* dataDevice)
138 {
139     const auto dragSerial = dataDevice->dragImplicitGrabSerial();
140     auto* dragSurface = dataDevice->origin();
141     auto& pointers = seat->pointers();
142 
143     if (pointers.has_implicit_grab(dragSerial)) {
144         source.mode = drag_mode::pointer;
145         source.pointer = interfaceForSurface(dragSurface, seat->pointers().get_devices());
146         target.transformation = pointers.get_focus().transformation;
147     } else if (seat->touches().has_implicit_grab(dragSerial)) {
148         source.mode = drag_mode::touch;
149         source.touch = interfaceForSurface(dragSurface, seat->touches().get_devices());
150         // TODO(unknown author): touch transformation
151     } else {
152         // no implicit grab, abort drag
153         return;
154     }
155     auto* originSurface = dataDevice->origin();
156     const bool proxied = originSurface->dataProxy();
157     if (!proxied) {
158         // origin surface
159         target.dev = dataDevice;
160         target.surface = originSurface;
161         // TODO(unknown author): transformation needs to be either pointer or touch
162         target.transformation = pointers.get_focus().transformation;
163     }
164 
165     source.dev = dataDevice;
166     source.device_destroy_notifier
167         = QObject::connect(dataDevice, &DataDevice::resourceDestroyed, seat, [this] {
168               end(seat->d_ptr->display()->handle()->nextSerial());
169           });
170 
171     if (dataDevice->dragSource()) {
172         source.destroy_notifier = QObject::connect(
173             dataDevice->dragSource(), &DataSource::resourceDestroyed, seat, [this] {
174                 const auto serial = seat->d_ptr->display()->handle()->nextSerial();
175                 if (target.dev) {
176                     target.dev->updateDragTarget(nullptr, serial);
177                     target.dev = nullptr;
178                 }
179                 end(serial);
180             });
181     } else {
182         source.destroy_notifier = QMetaObject::Connection();
183     }
184     dataDevice->updateDragTarget(proxied ? nullptr : originSurface,
185                                  dataDevice->dragImplicitGrabSerial());
186     Q_EMIT seat->dragStarted();
187     Q_EMIT seat->dragSurfaceChanged();
188 }
189 
190 }
191