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_wl.h"
0012 
0013 #include "databridge.h"
0014 #include "dnd.h"
0015 #include "xwayland.h"
0016 #include "xwldrophandler.h"
0017 
0018 #include "atoms.h"
0019 #include "wayland/datadevice_interface.h"
0020 #include "wayland/datasource_interface.h"
0021 #include "wayland/seat_interface.h"
0022 #include "wayland/surface_interface.h"
0023 #include "wayland_server.h"
0024 #include "workspace.h"
0025 #include "x11window.h"
0026 
0027 #include <QMouseEvent>
0028 #include <QTimer>
0029 
0030 using DnDAction = KWaylandServer::DataDeviceManagerInterface::DnDAction;
0031 using DnDActions = KWaylandServer::DataDeviceManagerInterface::DnDActions;
0032 
0033 namespace KWin
0034 {
0035 namespace Xwl
0036 {
0037 
0038 WlToXDrag::WlToXDrag(Dnd *dnd)
0039     : m_dnd(dnd)
0040 {
0041 }
0042 
0043 DragEventReply WlToXDrag::moveFilter(Window *target, const QPoint &pos)
0044 {
0045     return DragEventReply::Wayland;
0046 }
0047 
0048 bool WlToXDrag::handleClientMessage(xcb_client_message_event_t *event)
0049 {
0050     return m_dnd->dropHandler()->handleClientMessage(event);
0051 }
0052 
0053 Xvisit::Xvisit(Window *target, KWaylandServer::AbstractDataSource *dataSource, Dnd *dnd, QObject *parent)
0054     : QObject(parent)
0055     , m_dnd(dnd)
0056     , m_target(target)
0057     , m_dataSource(dataSource)
0058 {
0059     // first check supported DND version
0060     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0061     xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
0062                                                         0,
0063                                                         m_target->window(),
0064                                                         atoms->xdnd_aware,
0065                                                         XCB_GET_PROPERTY_TYPE_ANY,
0066                                                         0, 1);
0067     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0068     if (!reply) {
0069         doFinish();
0070         return;
0071     }
0072     if (reply->type != XCB_ATOM_ATOM) {
0073         doFinish();
0074         free(reply);
0075         return;
0076     }
0077     xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
0078     m_version = std::min(*value, Dnd::version());
0079     if (m_version < 1) {
0080         // minimal version we accept is 1
0081         doFinish();
0082         free(reply);
0083         return;
0084     }
0085     free(reply);
0086 
0087     receiveOffer();
0088 }
0089 
0090 bool Xvisit::handleClientMessage(xcb_client_message_event_t *event)
0091 {
0092     if (event->type == atoms->xdnd_status) {
0093         return handleStatus(event);
0094     } else if (event->type == atoms->xdnd_finished) {
0095         return handleFinished(event);
0096     }
0097     return false;
0098 }
0099 
0100 bool Xvisit::handleStatus(xcb_client_message_event_t *event)
0101 {
0102     xcb_client_message_data_t *data = &event->data;
0103     if (data->data32[0] != m_target->window()) {
0104         // wrong target window
0105         return false;
0106     }
0107 
0108     m_accepts = data->data32[1] & 1;
0109     xcb_atom_t actionAtom = data->data32[4];
0110 
0111     if (m_dataSource && !m_dataSource->mimeTypes().isEmpty()) {
0112         m_dataSource->accept(m_accepts ? m_dataSource->mimeTypes().constFirst() : QString());
0113     }
0114     // TODO: we could optimize via rectangle in data32[2] and data32[3]
0115 
0116     // position round trip finished
0117     m_pos.pending = false;
0118 
0119     if (!m_state.dropped) {
0120         // as long as the drop is not yet done determine requested action
0121         m_preferredAction = Dnd::atomToClientAction(actionAtom);
0122         determineProposedAction();
0123         requestDragAndDropAction();
0124     }
0125 
0126     if (m_pos.cached) {
0127         // send cached position
0128         m_pos.cached = false;
0129         sendPosition(m_pos.cache);
0130     } else if (m_state.dropped) {
0131         // drop was done in between, now close it out
0132         drop();
0133     }
0134     return true;
0135 }
0136 
0137 bool Xvisit::handleFinished(xcb_client_message_event_t *event)
0138 {
0139     xcb_client_message_data_t *data = &event->data;
0140 
0141     if (data->data32[0] != m_target->window()) {
0142         // different target window
0143         return false;
0144     }
0145 
0146     if (!m_state.dropped) {
0147         // drop was never done
0148         doFinish();
0149         return true;
0150     }
0151 
0152     if (m_dataSource) {
0153         m_dataSource->dndFinished();
0154     }
0155     doFinish();
0156     return true;
0157 }
0158 
0159 void Xvisit::sendPosition(const QPointF &globalPos)
0160 {
0161     const int16_t x = globalPos.x();
0162     const int16_t y = globalPos.y();
0163 
0164     if (m_pos.pending) {
0165         m_pos.cache = QPoint(x, y);
0166         m_pos.cached = true;
0167         return;
0168     }
0169     m_pos.pending = true;
0170 
0171     xcb_client_message_data_t data = {};
0172     data.data32[0] = m_dnd->window();
0173     data.data32[2] = (x << 16) | y;
0174     data.data32[3] = XCB_CURRENT_TIME;
0175     data.data32[4] = Dnd::clientActionToAtom(m_proposedAction);
0176 
0177     Drag::sendClientMessage(m_target->window(), atoms->xdnd_position, &data);
0178 }
0179 
0180 void Xvisit::leave()
0181 {
0182     if (m_state.dropped) {
0183         // dropped, but not yet finished, it'll be cleaned up when the drag finishes
0184         return;
0185     }
0186     if (m_state.finished) {
0187         // was already finished
0188         return;
0189     }
0190     // we only need to leave, when we entered before
0191     if (m_state.entered) {
0192         sendLeave();
0193     }
0194     doFinish();
0195 }
0196 
0197 void Xvisit::receiveOffer()
0198 {
0199     retrieveSupportedActions();
0200     connect(m_dataSource, &KWaylandServer::AbstractDataSource::supportedDragAndDropActionsChanged,
0201             this, &Xvisit::retrieveSupportedActions);
0202     enter();
0203 }
0204 
0205 void Xvisit::enter()
0206 {
0207     m_state.entered = true;
0208     // send enter event and current position to X client
0209     sendEnter();
0210     sendPosition(waylandServer()->seat()->pointerPos());
0211 
0212     // proxy future pointer position changes
0213     m_motionConnection = connect(waylandServer()->seat(),
0214                                  &KWaylandServer::SeatInterface::pointerPosChanged,
0215                                  this, &Xvisit::sendPosition);
0216 }
0217 
0218 void Xvisit::sendEnter()
0219 {
0220     if (!m_dataSource) {
0221         return;
0222     }
0223 
0224     xcb_client_message_data_t data = {};
0225     data.data32[0] = m_dnd->window();
0226     data.data32[1] = m_version << 24;
0227 
0228     const auto mimeTypesNames = m_dataSource->mimeTypes();
0229     const int mimesCount = mimeTypesNames.size();
0230     // Number of written entries in data32
0231     size_t cnt = 2;
0232     // Number of mimeTypes
0233     size_t totalCnt = 0;
0234     for (const auto &mimeName : mimeTypesNames) {
0235         // 3 mimes and less can be sent directly in the XdndEnter message
0236         if (totalCnt == 3) {
0237             break;
0238         }
0239         const auto atom = Selection::mimeTypeToAtom(mimeName);
0240 
0241         if (atom != XCB_ATOM_NONE) {
0242             data.data32[cnt] = atom;
0243             cnt++;
0244         }
0245         totalCnt++;
0246     }
0247     for (int i = cnt; i < 5; i++) {
0248         data.data32[i] = XCB_ATOM_NONE;
0249     }
0250 
0251     if (mimesCount > 3) {
0252         // need to first transfer all available mime types
0253         data.data32[1] |= 1;
0254 
0255         QVector<xcb_atom_t> targets;
0256         targets.resize(mimesCount);
0257 
0258         size_t cnt = 0;
0259         for (const auto &mimeName : mimeTypesNames) {
0260             const auto atom = Selection::mimeTypeToAtom(mimeName);
0261             if (atom != XCB_ATOM_NONE) {
0262                 targets[cnt] = atom;
0263                 cnt++;
0264             }
0265         }
0266 
0267         xcb_change_property(kwinApp()->x11Connection(),
0268                             XCB_PROP_MODE_REPLACE,
0269                             m_dnd->window(),
0270                             atoms->xdnd_type_list,
0271                             XCB_ATOM_ATOM,
0272                             32, cnt, targets.data());
0273     }
0274     Drag::sendClientMessage(m_target->window(), atoms->xdnd_enter, &data);
0275 }
0276 
0277 void Xvisit::sendDrop(uint32_t time)
0278 {
0279     xcb_client_message_data_t data = {};
0280     data.data32[0] = m_dnd->window();
0281     data.data32[2] = time;
0282 
0283     Drag::sendClientMessage(m_target->window(), atoms->xdnd_drop, &data);
0284 
0285     if (m_version < 2) {
0286         doFinish();
0287     }
0288 }
0289 
0290 void Xvisit::sendLeave()
0291 {
0292     xcb_client_message_data_t data = {};
0293     data.data32[0] = m_dnd->window();
0294     Drag::sendClientMessage(m_target->window(), atoms->xdnd_leave, &data);
0295 }
0296 
0297 void Xvisit::retrieveSupportedActions()
0298 {
0299     m_supportedActions = m_dataSource->supportedDragAndDropActions();
0300     determineProposedAction();
0301     requestDragAndDropAction();
0302 }
0303 
0304 void Xvisit::determineProposedAction()
0305 {
0306     DnDAction oldProposedAction = m_proposedAction;
0307     if (m_supportedActions.testFlag(m_preferredAction)) {
0308         m_proposedAction = m_preferredAction;
0309     } else if (m_supportedActions.testFlag(DnDAction::Copy)) {
0310         m_proposedAction = DnDAction::Copy;
0311     } else {
0312         m_proposedAction = DnDAction::None;
0313     }
0314     // send updated action to X target
0315     if (oldProposedAction != m_proposedAction && m_state.entered) {
0316         sendPosition(waylandServer()->seat()->pointerPos());
0317     }
0318 }
0319 
0320 void Xvisit::requestDragAndDropAction()
0321 {
0322     DnDAction action = m_preferredAction != DnDAction::None ? m_preferredAction : DnDAction::Copy;
0323     // we assume the X client supports Move, but this might be wrong - then
0324     // the drag just cancels, if the user tries to force it.
0325 
0326     // As we skip the client data device, we do action negotiation directly then tell the source.
0327     if (m_supportedActions.testFlag(action)) {
0328         // everything is supported, no changes are needed
0329     } else if (m_supportedActions.testFlag(DnDAction::Copy)) {
0330         action = DnDAction::Copy;
0331     } else if (m_supportedActions.testFlag(DnDAction::Move)) {
0332         action = DnDAction::Move;
0333     }
0334     if (m_dataSource) {
0335         m_dataSource->dndAction(action);
0336     }
0337 }
0338 
0339 void Xvisit::drop()
0340 {
0341     Q_ASSERT(!m_state.finished);
0342     m_state.dropped = true;
0343     // stop further updates
0344     // TODO: revisit when we allow ask action
0345     stopConnections();
0346     if (!m_state.entered) {
0347         // wait for enter (init + offers)
0348         return;
0349     }
0350     if (m_pos.pending) {
0351         // wait for pending position roundtrip
0352         return;
0353     }
0354     if (!m_accepts) {
0355         // target does not accept current action/offer
0356         sendLeave();
0357         doFinish();
0358         return;
0359     }
0360     // dnd session ended successfully
0361     sendDrop(XCB_CURRENT_TIME);
0362 }
0363 
0364 void Xvisit::doFinish()
0365 {
0366     m_state.finished = true;
0367     m_pos.cached = false;
0368     stopConnections();
0369     Q_EMIT finish(this);
0370 }
0371 
0372 void Xvisit::stopConnections()
0373 {
0374     // final outcome has been determined from Wayland side
0375     // no more updates needed
0376     disconnect(m_motionConnection);
0377     m_motionConnection = QMetaObject::Connection();
0378 }
0379 
0380 } // namespace Xwl
0381 } // namespace KWin