KWin
Loading...
Searching...
No Matches
drag_x.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_x.h"
12
13#include "databridge.h"
14#include "datasource.h"
15#include "dnd.h"
16#include "selection_source.h"
17#include "xwayland.h"
18
19#include "atoms.h"
20#include "wayland/datadevice.h"
21#include "wayland/datasource.h"
22#include "wayland/seat.h"
23#include "wayland/surface.h"
24#include "wayland_server.h"
25#include "window.h"
26#include "workspace.h"
27
28#include <QMouseEvent>
29#include <QTimer>
30
31namespace KWin
32{
33namespace Xwl
34{
35
37using DnDActions = KWin::DataDeviceManagerInterface::DnDActions;
38
39static QStringList atomToMimeTypes(xcb_atom_t atom)
40{
41 QStringList mimeTypes;
42
43 if (atom == atoms->utf8_string) {
44 mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
45 } else if (atom == atoms->text) {
46 mimeTypes << QString::fromLatin1("text/plain");
47 } else if (atom == atoms->uri_list) {
48 mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri");
49 } else if (atom == atoms->moz_url) {
50 mimeTypes << QStringLiteral("text/x-moz-url");
51 } else if (atom == atoms->netscape_url) {
52 mimeTypes << QStringLiteral("_NETSCAPE_URL");
53 } else {
54 mimeTypes << Selection::atomName(atom);
55 }
56 return mimeTypes;
57}
58
60 : m_dnd(dnd)
61 , m_source(source)
62{
63 connect(m_dnd, &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) {
64 // we use this mechanism, because the finished call is not
65 // reliable done by Wayland clients
66 auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [eventTime](const QPair<xcb_timestamp_t, bool> &req) {
67 return req.first == eventTime && req.second == false;
68 });
69 if (it == m_dataRequests.end()) {
70 // transfer finished for a different drag
71 return;
72 }
73 (*it).second = true;
74 checkForFinished();
75 });
76 connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) {
77 m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
78 });
79 connect(&m_selectionSource, &XwlDataSource::dropped, this, [this] {
80 m_performed = true;
81 if (m_visit) {
82 connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
83 checkForFinished();
84 });
85
86 QTimer::singleShot(2000, this, [this] {
87 if (!m_visit->entered() || !m_visit->dropHandled()) {
88 // X client timed out
89 Q_EMIT finish(this);
90 } else if (m_dataRequests.size() == 0) {
91 // Wl client timed out
92 m_visit->sendFinished();
93 Q_EMIT finish(this);
94 }
95 });
96 }
97 // Dave do we need this async finish check anymore?
98 checkForFinished();
99 });
100 connect(&m_selectionSource, &XwlDataSource::finished, this, [this] {
101 checkForFinished();
102 });
103 connect(&m_selectionSource, &XwlDataSource::cancelled, this, [this] {
104 if (m_visit && !m_visit->leave()) {
105 connect(m_visit, &WlVisit::finish, this, &XToWlDrag::checkForFinished);
106 }
107 checkForFinished();
108 });
109 connect(&m_selectionSource, &XwlDataSource::dataRequested, source, &X11Source::startTransfer);
110
111 auto *seat = waylandServer()->seat();
112 int serial = waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton);
113 // we know we are the focussed surface as dnd checks
114 seat->startDrag(&m_selectionSource, seat->focusedPointerSurface(), serial);
115}
116
120
122{
123 auto *seat = waylandServer()->seat();
124
125 if (m_visit && m_visit->target() == target) {
126 // still same Wl target, wait for X events
128 }
129 if (m_visit) {
130 if (m_visit->leave()) {
131 delete m_visit;
132 } else {
133 connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
134 m_oldVisits.removeOne(visit);
135 delete visit;
136 });
137 m_oldVisits << m_visit;
138 }
139 }
140 const bool hasCurrent = m_visit;
141 m_visit = nullptr;
142
143 if (!target || !target->surface() || target->surface()->client() == waylandServer()->xWaylandConnection()) {
144 // currently there is no target or target is an Xwayland window
145 // handled here and by X directly
146 if (hasCurrent) {
147 // last received enter event is now void,
148 // wait for the next one
149 seat->setDragTarget(nullptr, nullptr);
150 }
152 }
153 // new Wl native target
154 auto *ac = static_cast<Window *>(target);
155 m_visit = new WlVisit(ac, this, m_dnd);
156 connect(m_visit, &WlVisit::offersReceived, this, &XToWlDrag::setOffers);
158}
159
160bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event)
161{
162 for (auto *visit : std::as_const(m_oldVisits)) {
163 if (visit->handleClientMessage(event)) {
164 return true;
165 }
166 }
167 if (m_visit && m_visit->handleClientMessage(event)) {
168 return true;
169 }
170 return false;
171}
172
174{
175 m_selectionSource.setSupportedDndActions(action);
176}
177
179{
180 return m_selectionSource.selectedDndAction();
181}
182
183void XToWlDrag::setOffers(const Mimes &offers)
184{
185 m_source->setOffers(offers);
186 if (offers.isEmpty()) {
187 // There are no offers, so just directly set the drag target,
188 // no transfer possible anyways.
189 setDragTarget();
190 return;
191 }
192 if (m_offers == offers) {
193 // offers had been set already by a previous visit
194 // Wl side is already configured
195 setDragTarget();
196 return;
197 }
198
199 m_offers = offers;
200 QStringList mimeTypes;
201 mimeTypes.reserve(offers.size());
202 for (const auto &mimePair : offers) {
203 mimeTypes.append(mimePair.first);
204 }
205 m_selectionSource.setMimeTypes(mimeTypes);
206 setDragTarget();
207}
208
209using Mime = QPair<QString, xcb_atom_t>;
210
211void XToWlDrag::setDragTarget()
212{
213 if (!m_visit) {
214 return;
215 }
216
217 auto *ac = m_visit->target();
218
219 auto seat = waylandServer()->seat();
220 auto dropTarget = seat->dropHandlerForSurface(ac->surface());
221
222 if (!dropTarget || !ac->surface()) {
223 return;
224 }
225 seat->setDragTarget(dropTarget, ac->surface(), ac->inputTransformation());
226}
227
228bool XToWlDrag::checkForFinished()
229{
230 if (!m_visit) {
231 // not dropped above Wl native target
232 Q_EMIT finish(this);
233 return true;
234 }
235 if (!m_visit->finished()) {
236 return false;
237 }
238 if (m_dataRequests.size() == 0 && m_selectionSource.isAccepted()) {
239 // need to wait for first data request
240 return false;
241 }
242 const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(),
243 [](QPair<xcb_timestamp_t, bool> req) {
244 return req.second;
245 });
246 if (transfersFinished) {
247 m_visit->sendFinished();
248 Q_EMIT finish(this);
249 }
250 return transfersFinished;
251}
252
254 : QObject(drag)
255 , m_dnd(dnd)
256 , m_target(target)
257 , m_drag(drag)
258{
259 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
260
261 m_window = xcb_generate_id(xcbConn);
262 m_dnd->overwriteRequestorWindow(m_window);
263
264 const uint32_t dndValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
265 xcb_create_window(xcbConn,
266 XCB_COPY_FROM_PARENT,
267 m_window,
268 kwinApp()->x11RootWindow(),
269 0, 0,
270 8192, 8192, // TODO: get current screen size and connect to changes
271 0,
272 XCB_WINDOW_CLASS_INPUT_OUTPUT,
273 XCB_COPY_FROM_PARENT,
274 XCB_CW_EVENT_MASK,
275 dndValues);
276
277 uint32_t version = Dnd::version();
278 xcb_change_property(xcbConn,
279 XCB_PROP_MODE_REPLACE,
280 m_window,
282 XCB_ATOM_ATOM,
283 32, 1, &version);
284
285 xcb_map_window(xcbConn, m_window);
286 workspace()->addManualOverlay(m_window);
288
289 xcb_flush(xcbConn);
290 m_mapped = true;
291}
292
294{
295 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
296 xcb_destroy_window(xcbConn, m_window);
297 xcb_flush(xcbConn);
298}
299
301{
302 m_dnd->overwriteRequestorWindow(XCB_WINDOW_NONE);
303 unmapProxyWindow();
304 return m_finished;
305}
306
307bool WlVisit::handleClientMessage(xcb_client_message_event_t *event)
308{
309 if (event->window != m_window) {
310 // different window
311 return false;
312 }
313
314 if (event->type == atoms->xdnd_enter) {
315 return handleEnter(event);
316 } else if (event->type == atoms->xdnd_position) {
317 return handlePosition(event);
318 } else if (event->type == atoms->xdnd_drop) {
319 return handleDrop(event);
320 } else if (event->type == atoms->xdnd_leave) {
321 return handleLeave(event);
322 }
323 return false;
324}
325
326static bool hasMimeName(const Mimes &mimes, const QString &name)
327{
328 return std::any_of(mimes.begin(), mimes.end(),
329 [name](const Mime &m) {
330 return m.first == name;
331 });
332}
333
334bool WlVisit::handleEnter(xcb_client_message_event_t *event)
335{
336 if (m_entered) {
337 // a drag already entered
338 return true;
339 }
340 m_entered = true;
341
342 xcb_client_message_data_t *data = &event->data;
343 m_srcWindow = data->data32[0];
344 m_version = data->data32[1] >> 24;
345
346 // get types
347 Mimes offers;
348 if (!(data->data32[1] & 1)) {
349 // message has only max 3 types (which are directly in data)
350 for (size_t i = 0; i < 3; i++) {
351 xcb_atom_t mimeAtom = data->data32[2 + i];
352 const auto mimeStrings = atomToMimeTypes(mimeAtom);
353 for (const auto &mime : mimeStrings) {
354 if (!hasMimeName(offers, mime)) {
355 offers << Mime(mime, mimeAtom);
356 }
357 }
358 }
359 } else {
360 // more than 3 types -> in window property
361 getMimesFromWinProperty(offers);
362 }
363
364 Q_EMIT offersReceived(offers);
365 return true;
366}
367
368void WlVisit::getMimesFromWinProperty(Mimes &offers)
369{
370 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
371 auto cookie = xcb_get_property(xcbConn,
372 0,
373 m_srcWindow,
375 XCB_GET_PROPERTY_TYPE_ANY,
376 0, 0x1fffffff);
377
378 auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
379 if (reply == nullptr) {
380 return;
381 }
382 if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) {
383 // invalid reply value
384 free(reply);
385 return;
386 }
387
388 xcb_atom_t *mimeAtoms = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
389 for (size_t i = 0; i < reply->value_len; ++i) {
390 const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]);
391 for (const auto &mime : mimeStrings) {
392 if (!hasMimeName(offers, mime)) {
393 offers << Mime(mime, mimeAtoms[i]);
394 }
395 }
396 }
397 free(reply);
398}
399
400bool WlVisit::handlePosition(xcb_client_message_event_t *event)
401{
402 xcb_client_message_data_t *data = &event->data;
403 m_srcWindow = data->data32[0];
404
405 if (!m_target) {
406 // not over Wl window at the moment
407 m_action = DnDAction::None;
408 m_actionAtom = XCB_ATOM_NONE;
409 sendStatus();
410 return true;
411 }
412
413 const xcb_timestamp_t timestamp = data->data32[3];
414 m_drag->x11Source()->setTimestamp(timestamp);
415
416 xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] : atoms->xdnd_action_copy;
417 auto action = Dnd::atomToClientAction(actionAtom);
418
419 if (action == DnDAction::None) {
420 // copy action is always possible in XDND
421 action = DnDAction::Copy;
422 actionAtom = atoms->xdnd_action_copy;
423 }
424
425 if (m_action != action) {
426 m_action = action;
427 m_actionAtom = actionAtom;
428 m_drag->setDragAndDropAction(m_action);
429 }
430
431 sendStatus();
432 return true;
433}
434
435bool WlVisit::handleDrop(xcb_client_message_event_t *event)
436{
437 m_dropHandled = true;
438
439 xcb_client_message_data_t *data = &event->data;
440 m_srcWindow = data->data32[0];
441 const xcb_timestamp_t timestamp = data->data32[2];
442 m_drag->x11Source()->setTimestamp(timestamp);
443
444 // we do nothing more here, the drop is being processed
445 // through the X11Source object
446 doFinish();
447 return true;
448}
449
450void WlVisit::doFinish()
451{
452 m_finished = true;
453 unmapProxyWindow();
454 Q_EMIT finish(this);
455}
456
457bool WlVisit::handleLeave(xcb_client_message_event_t *event)
458{
459 m_entered = false;
460 xcb_client_message_data_t *data = &event->data;
461 m_srcWindow = data->data32[0];
462 doFinish();
463 return true;
464}
465
466void WlVisit::sendStatus()
467{
468 // receive position events
469 uint32_t flags = 1 << 1;
470 if (targetAcceptsAction()) {
471 // accept the drop
472 flags |= (1 << 0);
473 }
474 xcb_client_message_data_t data = {};
475 data.data32[0] = m_window;
476 data.data32[1] = flags;
477 data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
478 Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data);
479}
480
482{
483 const bool accepted = m_entered && m_action != DnDAction::None;
484 xcb_client_message_data_t data = {};
485 data.data32[0] = m_window;
486 data.data32[1] = accepted;
487 data.data32[2] = accepted ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
488 Drag::sendClientMessage(m_srcWindow, atoms->xdnd_finished, &data);
489}
490
491bool WlVisit::targetAcceptsAction() const
492{
493 if (m_action == DnDAction::None) {
494 return false;
495 }
496 const auto selAction = m_drag->selectedDragAndDropAction();
497 return selAction == m_action || selAction == DnDAction::Copy;
498}
499
500void WlVisit::unmapProxyWindow()
501{
502 if (!m_mapped) {
503 return;
504 }
505 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
506 xcb_unmap_window(xcbConn, m_window);
507 workspace()->removeManualOverlay(m_window);
509 xcb_flush(xcbConn);
510 m_mapped = false;
511}
512
513} // namespace Xwl
514} // namespace KWin
515
516#include "moc_drag_x.cpp"
Xcb::Atom netscape_url
Definition atoms.h:65
Xcb::Atom xdnd_finished
Definition atoms.h:53
Xcb::Atom text
Definition atoms.h:63
Xcb::Atom xdnd_leave
Definition atoms.h:52
Xcb::Atom xdnd_enter
Definition atoms.h:44
Xcb::Atom uri_list
Definition atoms.h:64
Xcb::Atom xdnd_drop
Definition atoms.h:51
Xcb::Atom xdnd_action_copy
Definition atoms.h:48
Xcb::Atom xdnd_aware
Definition atoms.h:43
Xcb::Atom xdnd_status
Definition atoms.h:47
Xcb::Atom utf8_string
Definition atoms.h:62
Xcb::Atom moz_url
Definition atoms.h:66
Xcb::Atom xdnd_position
Definition atoms.h:46
Xcb::Atom xdnd_type_list
Definition atoms.h:45
quint32 pointerButtonSerial(quint32 button) const
Definition seat.cpp:757
ClientConnection * client() const
Definition surface.cpp:444
SeatInterface * seat() const
SurfaceInterface * surface() const
Definition window.cpp:342
void updateStackingOrder(bool propagate_new_windows=false)
Definition layers.cpp:93
void removeManualOverlay(xcb_window_t id)
Definition workspace.h:300
void addManualOverlay(xcb_window_t id)
Definition workspace.h:296
static uint32_t version()
Definition dnd.cpp:38
static DnDAction atomToClientAction(xcb_atom_t atom)
Definition dnd.cpp:185
void finish(Drag *self)
static void sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data)
Definition drag.cpp:28
void overwriteRequestorWindow(xcb_window_t window)
void transferFinished(xcb_timestamp_t eventTime)
static QString atomName(xcb_atom_t atom)
Definition selection.cpp:48
xcb_timestamp_t timestamp() const
void setTimestamp(xcb_timestamp_t time)
WlVisit(Window *target, XToWlDrag *drag, Dnd *dnd)
Definition drag_x.cpp:253
~WlVisit() override
Definition drag_x.cpp:293
bool dropHandled() const
Definition drag_x.h:98
Window * target() const
Definition drag_x.h:86
bool entered() const
Definition drag_x.h:94
void finish(WlVisit *self)
bool handleClientMessage(xcb_client_message_event_t *event)
Definition drag_x.cpp:307
bool finished() const
Definition drag_x.h:102
void offersReceived(const Mimes &offers)
void transferReady(xcb_atom_t target, qint32 fd)
void startTransfer(const QString &mimeName, qint32 fd)
void setOffers(const Mimes &offers)
bool handleClientMessage(xcb_client_message_event_t *event) override
Definition drag_x.cpp:160
void setDragAndDropAction(DataDeviceManagerInterface::DnDAction action)
Definition drag_x.cpp:173
DataDeviceManagerInterface::DnDAction selectedDragAndDropAction()
Definition drag_x.cpp:178
X11Source * x11Source() const
Definition drag_x.h:47
~XToWlDrag() override
Definition drag_x.cpp:117
DragEventReply moveFilter(Window *target) override
Definition drag_x.cpp:121
XToWlDrag(X11Source *source, Dnd *dnd)
Definition drag_x.cpp:59
void dataRequested(const QString &mimeType, qint32 fd)
bool isAccepted() const override
void setMimeTypes(const QStringList &mimeTypes)
void setSupportedDndActions(DataDeviceManagerInterface::DnDActions dndActions)
DataDeviceManagerInterface::DnDAction selectedDndAction() const override
KWin::DataDeviceManagerInterface::DnDActions DnDActions
Definition drag_wl.cpp:31
QPair< QString, xcb_atom_t > Mime
Definition drag_x.cpp:209
QList< QPair< QString, xcb_atom_t > > Mimes
Definition drag_x.h:31
constexpr int version
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74