File indexing completed on 2024-11-10 04:57:39
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.h" 0021 #include "wayland/datasource.h" 0022 #include "wayland/seat.h" 0023 #include "wayland/surface.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 = KWin::DataDeviceManagerInterface::DnDAction; 0037 using DnDActions = KWin::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) 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 0515 0516 #include "moc_drag_x.cpp"