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_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.h" 0020 #include "wayland/datasource.h" 0021 #include "wayland/seat.h" 0022 #include "wayland/surface.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 = KWin::DataDeviceManagerInterface::DnDAction; 0031 using DnDActions = KWin::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) 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(X11Window *target, 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, &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 &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 QList<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 0382 0383 #include "moc_drag_wl.cpp"