File indexing completed on 2024-05-19 16:35:35

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006     SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
0007     SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "drag_x.h"
0012 
0013 #include "databridge.h"
0014 #include "datasource.h"
0015 #include "dnd.h"
0016 #include "selection_source.h"
0017 #include "xwayland.h"
0018 
0019 #include "atoms.h"
0020 #include "wayland/datadevice_interface.h"
0021 #include "wayland/datasource_interface.h"
0022 #include "wayland/seat_interface.h"
0023 #include "wayland/surface_interface.h"
0024 #include "wayland_server.h"
0025 #include "window.h"
0026 #include "workspace.h"
0027 
0028 #include <QMouseEvent>
0029 #include <QTimer>
0030 
0031 namespace KWin
0032 {
0033 namespace Xwl
0034 {
0035 
0036 using DnDAction = KWaylandServer::DataDeviceManagerInterface::DnDAction;
0037 using DnDActions = KWaylandServer::DataDeviceManagerInterface::DnDActions;
0038 
0039 static QStringList atomToMimeTypes(xcb_atom_t atom)
0040 {
0041     QStringList mimeTypes;
0042 
0043     if (atom == atoms->utf8_string) {
0044         mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
0045     } else if (atom == atoms->text) {
0046         mimeTypes << QString::fromLatin1("text/plain");
0047     } else if (atom == atoms->uri_list) {
0048         mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri");
0049     } else if (atom == atoms->moz_url) {
0050         mimeTypes << QStringLiteral("text/x-moz-url");
0051     } else if (atom == atoms->netscape_url) {
0052         mimeTypes << QStringLiteral("_NETSCAPE_URL");
0053     } else {
0054         mimeTypes << Selection::atomName(atom);
0055     }
0056     return mimeTypes;
0057 }
0058 
0059 XToWlDrag::XToWlDrag(X11Source *source, Dnd *dnd)
0060     : m_dnd(dnd)
0061     , m_source(source)
0062 {
0063     connect(m_dnd, &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) {
0064         // we use this mechanism, because the finished call is not
0065         // reliable done by Wayland clients
0066         auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [eventTime](const QPair<xcb_timestamp_t, bool> &req) {
0067             return req.first == eventTime && req.second == false;
0068         });
0069         if (it == m_dataRequests.end()) {
0070             // transfer finished for a different drag
0071             return;
0072         }
0073         (*it).second = true;
0074         checkForFinished();
0075     });
0076     connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) {
0077         m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
0078     });
0079     connect(&m_selectionSource, &XwlDataSource::dropped, this, [this] {
0080         m_performed = true;
0081         if (m_visit) {
0082             connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
0083                 checkForFinished();
0084             });
0085 
0086             QTimer::singleShot(2000, this, [this] {
0087                 if (!m_visit->entered() || !m_visit->dropHandled()) {
0088                     // X client timed out
0089                     Q_EMIT finish(this);
0090                 } else if (m_dataRequests.size() == 0) {
0091                     // Wl client timed out
0092                     m_visit->sendFinished();
0093                     Q_EMIT finish(this);
0094                 }
0095             });
0096         }
0097         // Dave do we need this async finish check anymore?
0098         checkForFinished();
0099     });
0100     connect(&m_selectionSource, &XwlDataSource::finished, this, [this] {
0101         checkForFinished();
0102     });
0103     connect(&m_selectionSource, &XwlDataSource::cancelled, this, [this] {
0104         if (m_visit && !m_visit->leave()) {
0105             connect(m_visit, &WlVisit::finish, this, &XToWlDrag::checkForFinished);
0106         }
0107         checkForFinished();
0108     });
0109     connect(&m_selectionSource, &XwlDataSource::dataRequested, source, &X11Source::startTransfer);
0110 
0111     auto *seat = waylandServer()->seat();
0112     int serial = waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton);
0113     // we know we are the focussed surface as dnd checks
0114     seat->startDrag(&m_selectionSource, seat->focusedPointerSurface(), serial);
0115 }
0116 
0117 XToWlDrag::~XToWlDrag()
0118 {
0119 }
0120 
0121 DragEventReply XToWlDrag::moveFilter(Window *target, const QPoint &pos)
0122 {
0123     auto *seat = waylandServer()->seat();
0124 
0125     if (m_visit && m_visit->target() == target) {
0126         // still same Wl target, wait for X events
0127         return DragEventReply::Ignore;
0128     }
0129     if (m_visit) {
0130         if (m_visit->leave()) {
0131             delete m_visit;
0132         } else {
0133             connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
0134                 m_oldVisits.removeOne(visit);
0135                 delete visit;
0136             });
0137             m_oldVisits << m_visit;
0138         }
0139     }
0140     const bool hasCurrent = m_visit;
0141     m_visit = nullptr;
0142 
0143     if (!target || !target->surface() || target->surface()->client() == waylandServer()->xWaylandConnection()) {
0144         // currently there is no target or target is an Xwayland window
0145         // handled here and by X directly
0146         if (hasCurrent) {
0147             // last received enter event is now void,
0148             // wait for the next one
0149             seat->setDragTarget(nullptr, nullptr);
0150         }
0151         return DragEventReply::Ignore;
0152     }
0153     // new Wl native target
0154     auto *ac = static_cast<Window *>(target);
0155     m_visit = new WlVisit(ac, this, m_dnd);
0156     connect(m_visit, &WlVisit::offersReceived, this, &XToWlDrag::setOffers);
0157     return DragEventReply::Ignore;
0158 }
0159 
0160 bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event)
0161 {
0162     for (auto *visit : std::as_const(m_oldVisits)) {
0163         if (visit->handleClientMessage(event)) {
0164             return true;
0165         }
0166     }
0167     if (m_visit && m_visit->handleClientMessage(event)) {
0168         return true;
0169     }
0170     return false;
0171 }
0172 
0173 void XToWlDrag::setDragAndDropAction(DnDAction action)
0174 {
0175     m_selectionSource.setSupportedDndActions(action);
0176 }
0177 
0178 DnDAction XToWlDrag::selectedDragAndDropAction()
0179 {
0180     return m_selectionSource.selectedDndAction();
0181 }
0182 
0183 void XToWlDrag::setOffers(const Mimes &offers)
0184 {
0185     m_source->setOffers(offers);
0186     if (offers.isEmpty()) {
0187         // There are no offers, so just directly set the drag target,
0188         // no transfer possible anyways.
0189         setDragTarget();
0190         return;
0191     }
0192     if (m_offers == offers) {
0193         // offers had been set already by a previous visit
0194         // Wl side is already configured
0195         setDragTarget();
0196         return;
0197     }
0198 
0199     m_offers = offers;
0200     QStringList mimeTypes;
0201     mimeTypes.reserve(offers.size());
0202     for (const auto &mimePair : offers) {
0203         mimeTypes.append(mimePair.first);
0204     }
0205     m_selectionSource.setMimeTypes(mimeTypes);
0206     setDragTarget();
0207 }
0208 
0209 using Mime = QPair<QString, xcb_atom_t>;
0210 
0211 void XToWlDrag::setDragTarget()
0212 {
0213     if (!m_visit) {
0214         return;
0215     }
0216 
0217     auto *ac = m_visit->target();
0218 
0219     auto seat = waylandServer()->seat();
0220     auto dropTarget = seat->dropHandlerForSurface(ac->surface());
0221 
0222     if (!dropTarget || !ac->surface()) {
0223         return;
0224     }
0225     seat->setDragTarget(dropTarget, ac->surface(), ac->inputTransformation());
0226 }
0227 
0228 bool XToWlDrag::checkForFinished()
0229 {
0230     if (!m_visit) {
0231         // not dropped above Wl native target
0232         Q_EMIT finish(this);
0233         return true;
0234     }
0235     if (!m_visit->finished()) {
0236         return false;
0237     }
0238     if (m_dataRequests.size() == 0 && m_selectionSource.isAccepted()) {
0239         // need to wait for first data request
0240         return false;
0241     }
0242     const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(),
0243                                                [](QPair<xcb_timestamp_t, bool> req) {
0244                                                    return req.second;
0245                                                });
0246     if (transfersFinished) {
0247         m_visit->sendFinished();
0248         Q_EMIT finish(this);
0249     }
0250     return transfersFinished;
0251 }
0252 
0253 WlVisit::WlVisit(Window *target, XToWlDrag *drag, Dnd *dnd)
0254     : QObject(drag)
0255     , m_dnd(dnd)
0256     , m_target(target)
0257     , m_drag(drag)
0258 {
0259     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0260 
0261     m_window = xcb_generate_id(xcbConn);
0262     m_dnd->overwriteRequestorWindow(m_window);
0263 
0264     const uint32_t dndValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
0265     xcb_create_window(xcbConn,
0266                       XCB_COPY_FROM_PARENT,
0267                       m_window,
0268                       kwinApp()->x11RootWindow(),
0269                       0, 0,
0270                       8192, 8192, // TODO: get current screen size and connect to changes
0271                       0,
0272                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
0273                       XCB_COPY_FROM_PARENT,
0274                       XCB_CW_EVENT_MASK,
0275                       dndValues);
0276 
0277     uint32_t version = Dnd::version();
0278     xcb_change_property(xcbConn,
0279                         XCB_PROP_MODE_REPLACE,
0280                         m_window,
0281                         atoms->xdnd_aware,
0282                         XCB_ATOM_ATOM,
0283                         32, 1, &version);
0284 
0285     xcb_map_window(xcbConn, m_window);
0286     workspace()->addManualOverlay(m_window);
0287     workspace()->updateStackingOrder(true);
0288 
0289     xcb_flush(xcbConn);
0290     m_mapped = true;
0291 }
0292 
0293 WlVisit::~WlVisit()
0294 {
0295     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0296     xcb_destroy_window(xcbConn, m_window);
0297     xcb_flush(xcbConn);
0298 }
0299 
0300 bool WlVisit::leave()
0301 {
0302     m_dnd->overwriteRequestorWindow(XCB_WINDOW_NONE);
0303     unmapProxyWindow();
0304     return m_finished;
0305 }
0306 
0307 bool WlVisit::handleClientMessage(xcb_client_message_event_t *event)
0308 {
0309     if (event->window != m_window) {
0310         // different window
0311         return false;
0312     }
0313 
0314     if (event->type == atoms->xdnd_enter) {
0315         return handleEnter(event);
0316     } else if (event->type == atoms->xdnd_position) {
0317         return handlePosition(event);
0318     } else if (event->type == atoms->xdnd_drop) {
0319         return handleDrop(event);
0320     } else if (event->type == atoms->xdnd_leave) {
0321         return handleLeave(event);
0322     }
0323     return false;
0324 }
0325 
0326 static bool hasMimeName(const Mimes &mimes, const QString &name)
0327 {
0328     return std::any_of(mimes.begin(), mimes.end(),
0329                        [name](const Mime &m) {
0330                            return m.first == name;
0331                        });
0332 }
0333 
0334 bool WlVisit::handleEnter(xcb_client_message_event_t *event)
0335 {
0336     if (m_entered) {
0337         // a drag already entered
0338         return true;
0339     }
0340     m_entered = true;
0341 
0342     xcb_client_message_data_t *data = &event->data;
0343     m_srcWindow = data->data32[0];
0344     m_version = data->data32[1] >> 24;
0345 
0346     // get types
0347     Mimes offers;
0348     if (!(data->data32[1] & 1)) {
0349         // message has only max 3 types (which are directly in data)
0350         for (size_t i = 0; i < 3; i++) {
0351             xcb_atom_t mimeAtom = data->data32[2 + i];
0352             const auto mimeStrings = atomToMimeTypes(mimeAtom);
0353             for (const auto &mime : mimeStrings) {
0354                 if (!hasMimeName(offers, mime)) {
0355                     offers << Mime(mime, mimeAtom);
0356                 }
0357             }
0358         }
0359     } else {
0360         // more than 3 types -> in window property
0361         getMimesFromWinProperty(offers);
0362     }
0363 
0364     Q_EMIT offersReceived(offers);
0365     return true;
0366 }
0367 
0368 void WlVisit::getMimesFromWinProperty(Mimes &offers)
0369 {
0370     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0371     auto cookie = xcb_get_property(xcbConn,
0372                                    0,
0373                                    m_srcWindow,
0374                                    atoms->xdnd_type_list,
0375                                    XCB_GET_PROPERTY_TYPE_ANY,
0376                                    0, 0x1fffffff);
0377 
0378     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0379     if (reply == nullptr) {
0380         return;
0381     }
0382     if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) {
0383         // invalid reply value
0384         free(reply);
0385         return;
0386     }
0387 
0388     xcb_atom_t *mimeAtoms = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
0389     for (size_t i = 0; i < reply->value_len; ++i) {
0390         const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]);
0391         for (const auto &mime : mimeStrings) {
0392             if (!hasMimeName(offers, mime)) {
0393                 offers << Mime(mime, mimeAtoms[i]);
0394             }
0395         }
0396     }
0397     free(reply);
0398 }
0399 
0400 bool WlVisit::handlePosition(xcb_client_message_event_t *event)
0401 {
0402     xcb_client_message_data_t *data = &event->data;
0403     m_srcWindow = data->data32[0];
0404 
0405     if (!m_target) {
0406         // not over Wl window at the moment
0407         m_action = DnDAction::None;
0408         m_actionAtom = XCB_ATOM_NONE;
0409         sendStatus();
0410         return true;
0411     }
0412 
0413     const xcb_timestamp_t timestamp = data->data32[3];
0414     m_drag->x11Source()->setTimestamp(timestamp);
0415 
0416     xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] : atoms->xdnd_action_copy;
0417     auto action = Dnd::atomToClientAction(actionAtom);
0418 
0419     if (action == DnDAction::None) {
0420         // copy action is always possible in XDND
0421         action = DnDAction::Copy;
0422         actionAtom = atoms->xdnd_action_copy;
0423     }
0424 
0425     if (m_action != action) {
0426         m_action = action;
0427         m_actionAtom = actionAtom;
0428         m_drag->setDragAndDropAction(m_action);
0429     }
0430 
0431     sendStatus();
0432     return true;
0433 }
0434 
0435 bool WlVisit::handleDrop(xcb_client_message_event_t *event)
0436 {
0437     m_dropHandled = true;
0438 
0439     xcb_client_message_data_t *data = &event->data;
0440     m_srcWindow = data->data32[0];
0441     const xcb_timestamp_t timestamp = data->data32[2];
0442     m_drag->x11Source()->setTimestamp(timestamp);
0443 
0444     // we do nothing more here, the drop is being processed
0445     // through the X11Source object
0446     doFinish();
0447     return true;
0448 }
0449 
0450 void WlVisit::doFinish()
0451 {
0452     m_finished = true;
0453     unmapProxyWindow();
0454     Q_EMIT finish(this);
0455 }
0456 
0457 bool WlVisit::handleLeave(xcb_client_message_event_t *event)
0458 {
0459     m_entered = false;
0460     xcb_client_message_data_t *data = &event->data;
0461     m_srcWindow = data->data32[0];
0462     doFinish();
0463     return true;
0464 }
0465 
0466 void WlVisit::sendStatus()
0467 {
0468     // receive position events
0469     uint32_t flags = 1 << 1;
0470     if (targetAcceptsAction()) {
0471         // accept the drop
0472         flags |= (1 << 0);
0473     }
0474     xcb_client_message_data_t data = {};
0475     data.data32[0] = m_window;
0476     data.data32[1] = flags;
0477     data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
0478     Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data);
0479 }
0480 
0481 void WlVisit::sendFinished()
0482 {
0483     const bool accepted = m_entered && m_action != DnDAction::None;
0484     xcb_client_message_data_t data = {};
0485     data.data32[0] = m_window;
0486     data.data32[1] = accepted;
0487     data.data32[2] = accepted ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
0488     Drag::sendClientMessage(m_srcWindow, atoms->xdnd_finished, &data);
0489 }
0490 
0491 bool WlVisit::targetAcceptsAction() const
0492 {
0493     if (m_action == DnDAction::None) {
0494         return false;
0495     }
0496     const auto selAction = m_drag->selectedDragAndDropAction();
0497     return selAction == m_action || selAction == DnDAction::Copy;
0498 }
0499 
0500 void WlVisit::unmapProxyWindow()
0501 {
0502     if (!m_mapped) {
0503         return;
0504     }
0505     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0506     xcb_unmap_window(xcbConn, m_window);
0507     workspace()->removeManualOverlay(m_window);
0508     workspace()->updateStackingOrder(true);
0509     xcb_flush(xcbConn);
0510     m_mapped = false;
0511 }
0512 
0513 } // namespace Xwl
0514 } // namespace KWin