KWin
Loading...
Searching...
No Matches
drag_wl.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6 SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
7 SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11#include "drag_wl.h"
12
13#include "databridge.h"
14#include "dnd.h"
15#include "xwayland.h"
16#include "xwldrophandler.h"
17
18#include "atoms.h"
19#include "wayland/datadevice.h"
20#include "wayland/datasource.h"
21#include "wayland/seat.h"
22#include "wayland/surface.h"
23#include "wayland_server.h"
24#include "workspace.h"
25#include "x11window.h"
26
27#include <QMouseEvent>
28#include <QTimer>
29
31using DnDActions = KWin::DataDeviceManagerInterface::DnDActions;
32
33namespace KWin
34{
35namespace Xwl
36{
37
39 : m_dnd(dnd)
40{
41}
42
47
48bool WlToXDrag::handleClientMessage(xcb_client_message_event_t *event)
49{
50 return m_dnd->dropHandler()->handleClientMessage(event);
51}
52
53Xvisit::Xvisit(X11Window *target, AbstractDataSource *dataSource, Dnd *dnd, QObject *parent)
54 : QObject(parent)
55 , m_dnd(dnd)
56 , m_target(target)
57 , m_dataSource(dataSource)
58{
59 // first check supported DND version
60 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
61 xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
62 0,
63 m_target->window(),
65 XCB_GET_PROPERTY_TYPE_ANY,
66 0, 1);
67 auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
68 if (!reply) {
69 doFinish();
70 return;
71 }
72 if (reply->type != XCB_ATOM_ATOM) {
73 doFinish();
74 free(reply);
75 return;
76 }
77 xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
78 m_version = std::min(*value, Dnd::version());
79 if (m_version < 1) {
80 // minimal version we accept is 1
81 doFinish();
82 free(reply);
83 return;
84 }
85 free(reply);
86
87 receiveOffer();
88}
89
90bool Xvisit::handleClientMessage(xcb_client_message_event_t *event)
91{
92 if (event->type == atoms->xdnd_status) {
93 return handleStatus(event);
94 } else if (event->type == atoms->xdnd_finished) {
95 return handleFinished(event);
96 }
97 return false;
98}
99
100bool Xvisit::handleStatus(xcb_client_message_event_t *event)
101{
102 xcb_client_message_data_t *data = &event->data;
103 if (data->data32[0] != m_target->window()) {
104 // wrong target window
105 return false;
106 }
107
108 m_accepts = data->data32[1] & 1;
109 xcb_atom_t actionAtom = data->data32[4];
110
111 if (m_dataSource && !m_dataSource->mimeTypes().isEmpty()) {
112 m_dataSource->accept(m_accepts ? m_dataSource->mimeTypes().constFirst() : QString());
113 }
114 // TODO: we could optimize via rectangle in data32[2] and data32[3]
115
116 // position round trip finished
117 m_pos.pending = false;
118
119 if (!m_state.dropped) {
120 // as long as the drop is not yet done determine requested action
121 m_preferredAction = Dnd::atomToClientAction(actionAtom);
122 determineProposedAction();
123 requestDragAndDropAction();
124 }
125
126 if (m_pos.cached) {
127 // send cached position
128 m_pos.cached = false;
129 sendPosition(m_pos.cache);
130 } else if (m_state.dropped) {
131 // drop was done in between, now close it out
132 drop();
133 }
134 return true;
135}
136
137bool Xvisit::handleFinished(xcb_client_message_event_t *event)
138{
139 xcb_client_message_data_t *data = &event->data;
140
141 if (data->data32[0] != m_target->window()) {
142 // different target window
143 return false;
144 }
145
146 if (!m_state.dropped) {
147 // drop was never done
148 doFinish();
149 return true;
150 }
151
152 if (m_dataSource) {
153 m_dataSource->dndFinished();
154 }
155 doFinish();
156 return true;
157}
158
159void Xvisit::sendPosition(const QPointF &globalPos)
160{
161 const int16_t x = globalPos.x();
162 const int16_t y = globalPos.y();
163
164 if (m_pos.pending) {
165 m_pos.cache = QPoint(x, y);
166 m_pos.cached = true;
167 return;
168 }
169 m_pos.pending = true;
170
171 xcb_client_message_data_t data = {};
172 data.data32[0] = m_dnd->window();
173 data.data32[2] = (x << 16) | y;
174 data.data32[3] = XCB_CURRENT_TIME;
175 data.data32[4] = Dnd::clientActionToAtom(m_proposedAction);
176
178}
179
181{
182 if (m_state.dropped) {
183 // dropped, but not yet finished, it'll be cleaned up when the drag finishes
184 return;
185 }
186 if (m_state.finished) {
187 // was already finished
188 return;
189 }
190 // we only need to leave, when we entered before
191 if (m_state.entered) {
192 sendLeave();
193 }
194 doFinish();
195}
196
197void Xvisit::receiveOffer()
198{
199 retrieveSupportedActions();
201 this, &Xvisit::retrieveSupportedActions);
202 enter();
203}
204
205void Xvisit::enter()
206{
207 m_state.entered = true;
208 // send enter event and current position to X client
209 sendEnter();
210 sendPosition(waylandServer()->seat()->pointerPos());
211
212 // proxy future pointer position changes
213 m_motionConnection = connect(waylandServer()->seat(),
215 this, &Xvisit::sendPosition);
216}
217
218void Xvisit::sendEnter()
219{
220 if (!m_dataSource) {
221 return;
222 }
223
224 xcb_client_message_data_t data = {};
225 data.data32[0] = m_dnd->window();
226 data.data32[1] = m_version << 24;
227
228 const auto mimeTypesNames = m_dataSource->mimeTypes();
229 const int mimesCount = mimeTypesNames.size();
230 // Number of written entries in data32
231 size_t cnt = 2;
232 // Number of mimeTypes
233 size_t totalCnt = 0;
234 for (const auto &mimeName : mimeTypesNames) {
235 // 3 mimes and less can be sent directly in the XdndEnter message
236 if (totalCnt == 3) {
237 break;
238 }
239 const auto atom = Selection::mimeTypeToAtom(mimeName);
240
241 if (atom != XCB_ATOM_NONE) {
242 data.data32[cnt] = atom;
243 cnt++;
244 }
245 totalCnt++;
246 }
247 for (int i = cnt; i < 5; i++) {
248 data.data32[i] = XCB_ATOM_NONE;
249 }
250
251 if (mimesCount > 3) {
252 // need to first transfer all available mime types
253 data.data32[1] |= 1;
254
255 QList<xcb_atom_t> targets;
256 targets.resize(mimesCount);
257
258 size_t cnt = 0;
259 for (const auto &mimeName : mimeTypesNames) {
260 const auto atom = Selection::mimeTypeToAtom(mimeName);
261 if (atom != XCB_ATOM_NONE) {
262 targets[cnt] = atom;
263 cnt++;
264 }
265 }
266
267 xcb_change_property(kwinApp()->x11Connection(),
268 XCB_PROP_MODE_REPLACE,
269 m_dnd->window(),
271 XCB_ATOM_ATOM,
272 32, cnt, targets.data());
273 }
274 Drag::sendClientMessage(m_target->window(), atoms->xdnd_enter, &data);
275}
276
277void Xvisit::sendDrop(uint32_t time)
278{
279 xcb_client_message_data_t data = {};
280 data.data32[0] = m_dnd->window();
281 data.data32[2] = time;
282
283 Drag::sendClientMessage(m_target->window(), atoms->xdnd_drop, &data);
284
285 if (m_version < 2) {
286 doFinish();
287 }
288}
289
290void Xvisit::sendLeave()
291{
292 xcb_client_message_data_t data = {};
293 data.data32[0] = m_dnd->window();
294 Drag::sendClientMessage(m_target->window(), atoms->xdnd_leave, &data);
295}
296
297void Xvisit::retrieveSupportedActions()
298{
299 m_supportedActions = m_dataSource->supportedDragAndDropActions();
300 determineProposedAction();
301 requestDragAndDropAction();
302}
303
304void Xvisit::determineProposedAction()
305{
306 DnDAction oldProposedAction = m_proposedAction;
307 if (m_supportedActions.testFlag(m_preferredAction)) {
308 m_proposedAction = m_preferredAction;
309 } else if (m_supportedActions.testFlag(DnDAction::Copy)) {
310 m_proposedAction = DnDAction::Copy;
311 } else {
312 m_proposedAction = DnDAction::None;
313 }
314 // send updated action to X target
315 if (oldProposedAction != m_proposedAction && m_state.entered) {
316 sendPosition(waylandServer()->seat()->pointerPos());
317 }
318}
319
320void Xvisit::requestDragAndDropAction()
321{
322 DnDAction action = m_preferredAction != DnDAction::None ? m_preferredAction : DnDAction::Copy;
323 // we assume the X client supports Move, but this might be wrong - then
324 // the drag just cancels, if the user tries to force it.
325
326 // As we skip the client data device, we do action negotiation directly then tell the source.
327 if (m_supportedActions.testFlag(action)) {
328 // everything is supported, no changes are needed
329 } else if (m_supportedActions.testFlag(DnDAction::Copy)) {
330 action = DnDAction::Copy;
331 } else if (m_supportedActions.testFlag(DnDAction::Move)) {
332 action = DnDAction::Move;
333 }
334 if (m_dataSource) {
335 m_dataSource->dndAction(action);
336 }
337}
338
340{
341 Q_ASSERT(!m_state.finished);
342 m_state.dropped = true;
343 // stop further updates
344 // TODO: revisit when we allow ask action
345 stopConnections();
346 if (!m_state.entered) {
347 // wait for enter (init + offers)
348 return;
349 }
350 if (m_pos.pending) {
351 // wait for pending position roundtrip
352 return;
353 }
354 if (!m_accepts) {
355 // target does not accept current action/offer
356 sendLeave();
357 doFinish();
358 return;
359 }
360 // dnd session ended successfully
361 sendDrop(XCB_CURRENT_TIME);
362}
363
364void Xvisit::doFinish()
365{
366 m_state.finished = true;
367 m_pos.cached = false;
368 stopConnections();
369 Q_EMIT finish(this);
370}
371
372void Xvisit::stopConnections()
373{
374 // final outcome has been determined from Wayland side
375 // no more updates needed
376 disconnect(m_motionConnection);
377 m_motionConnection = QMetaObject::Connection();
378}
379
380} // namespace Xwl
381} // namespace KWin
382
383#include "moc_drag_wl.cpp"
The AbstractDataSource class abstracts the data that can be transferred to another client.
void supportedDragAndDropActionsChanged()
Xcb::Atom xdnd_finished
Definition atoms.h:53
Xcb::Atom xdnd_leave
Definition atoms.h:52
Xcb::Atom xdnd_enter
Definition atoms.h:44
Xcb::Atom xdnd_drop
Definition atoms.h:51
Xcb::Atom xdnd_aware
Definition atoms.h:43
Xcb::Atom xdnd_status
Definition atoms.h:47
Xcb::Atom xdnd_position
Definition atoms.h:46
Xcb::Atom xdnd_type_list
Definition atoms.h:45
void pointerPosChanged(const QPointF &pos)
xcb_window_t window() const
static uint32_t version()
Definition dnd.cpp:38
static DnDAction atomToClientAction(xcb_atom_t atom)
Definition dnd.cpp:185
XwlDropHandler * dropHandler() const
Definition dnd.cpp:43
static xcb_atom_t clientActionToAtom(DnDAction action)
Definition dnd.cpp:199
static void sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data)
Definition drag.cpp:28
xcb_window_t window() const
Definition selection.h:63
static xcb_atom_t mimeTypeToAtom(const QString &mimeType)
Definition selection.cpp:29
WlToXDrag(Dnd *dnd)
Definition drag_wl.cpp:38
bool handleClientMessage(xcb_client_message_event_t *event) override
Definition drag_wl.cpp:48
DragEventReply moveFilter(Window *target) override
Definition drag_wl.cpp:43
bool handleFinished(xcb_client_message_event_t *event)
Definition drag_wl.cpp:137
void sendPosition(const QPointF &globalPos)
Definition drag_wl.cpp:159
bool handleStatus(xcb_client_message_event_t *event)
Definition drag_wl.cpp:100
bool handleClientMessage(xcb_client_message_event_t *event)
Definition drag_wl.cpp:90
void finish(Xvisit *self)
Xvisit(X11Window *target, AbstractDataSource *dataSource, Dnd *dnd, QObject *parent)
Definition drag_wl.cpp:53
bool handleClientMessage(xcb_client_message_event_t *event)
KWin::DataDeviceManagerInterface::DnDActions DnDActions
Definition drag_wl.cpp:31
KWayland::Client::Seat * seat
WaylandServer * waylandServer()
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74