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 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "selection.h" 0010 #include "databridge.h" 0011 #include "selection_source.h" 0012 #include "transfer.h" 0013 0014 #include "atoms.h" 0015 #include "utils/xcbutils.h" 0016 #include "workspace.h" 0017 #include "x11window.h" 0018 0019 #include <xcb/xcb_event.h> 0020 #include <xcb/xfixes.h> 0021 0022 #include <QTimer> 0023 0024 namespace KWin 0025 { 0026 namespace Xwl 0027 { 0028 0029 xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType) 0030 { 0031 if (mimeType == QLatin1String("text/plain;charset=utf-8")) { 0032 return atoms->utf8_string; 0033 } 0034 if (mimeType == QLatin1String("text/plain")) { 0035 return atoms->text; 0036 } 0037 if (mimeType == QLatin1String("text/x-uri")) { 0038 return atoms->uri_list; 0039 } 0040 return mimeTypeToAtomLiteral(mimeType); 0041 } 0042 0043 xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType) 0044 { 0045 return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection()); 0046 } 0047 0048 QString Selection::atomName(xcb_atom_t atom) 0049 { 0050 xcb_connection_t *xcbConn = kwinApp()->x11Connection(); 0051 xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom); 0052 xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr); 0053 if (!nameReply) { 0054 return QString(); 0055 } 0056 0057 const size_t length = xcb_get_atom_name_name_length(nameReply); 0058 QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length); 0059 free(nameReply); 0060 return name; 0061 } 0062 0063 QStringList Selection::atomToMimeTypes(xcb_atom_t atom) 0064 { 0065 QStringList mimeTypes; 0066 0067 if (atom == atoms->utf8_string) { 0068 mimeTypes << QString::fromLatin1("text/plain;charset=utf-8"); 0069 } else if (atom == atoms->text) { 0070 mimeTypes << QString::fromLatin1("text/plain"); 0071 } else if (atom == atoms->uri_list) { 0072 mimeTypes << "text/uri-list" 0073 << "text/x-uri"; 0074 } else { 0075 mimeTypes << atomName(atom); 0076 } 0077 return mimeTypes; 0078 } 0079 0080 Selection::Selection(xcb_atom_t atom, QObject *parent) 0081 : QObject(parent) 0082 , m_atom(atom) 0083 { 0084 xcb_connection_t *xcbConn = kwinApp()->x11Connection(); 0085 m_window = xcb_generate_id(kwinApp()->x11Connection()); 0086 m_requestorWindow = m_window; 0087 xcb_flush(xcbConn); 0088 } 0089 0090 bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) 0091 { 0092 if (event->window != m_window) { 0093 return false; 0094 } 0095 if (event->selection != m_atom) { 0096 return false; 0097 } 0098 if (m_disownPending) { 0099 // notify of our own disown - ignore it 0100 m_disownPending = false; 0101 return true; 0102 } 0103 if (event->owner == m_window && m_waylandSource) { 0104 // When we claim a selection we must use XCB_TIME_CURRENT, 0105 // grab the actual timestamp here to answer TIMESTAMP requests 0106 // correctly 0107 m_waylandSource->setTimestamp(event->timestamp); 0108 m_timestamp = event->timestamp; 0109 return true; 0110 } 0111 0112 // Being here means some other X window has claimed the selection. 0113 doHandleXfixesNotify(event); 0114 return true; 0115 } 0116 0117 bool Selection::filterEvent(xcb_generic_event_t *event) 0118 { 0119 switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { 0120 case XCB_SELECTION_NOTIFY: 0121 return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event)); 0122 case XCB_PROPERTY_NOTIFY: 0123 return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event)); 0124 case XCB_SELECTION_REQUEST: 0125 return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event)); 0126 case XCB_CLIENT_MESSAGE: 0127 return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event)); 0128 default: 0129 if (event->response_type == Xcb::Extensions::self()->fixesSelectionNotifyEvent()) { 0130 return handleXfixesNotify(reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event)); 0131 } 0132 return false; 0133 } 0134 } 0135 0136 void Selection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success) 0137 { 0138 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy 0139 // 32 unconditionally. Use a union to ensure we don't disclose stack memory. 0140 union { 0141 xcb_selection_notify_event_t notify; 0142 char buffer[32]; 0143 } u; 0144 memset(&u, 0, sizeof(u)); 0145 static_assert(sizeof(u.notify) < 32, "wouldn't need the union otherwise"); 0146 u.notify.response_type = XCB_SELECTION_NOTIFY; 0147 u.notify.sequence = 0; 0148 u.notify.time = event->time; 0149 u.notify.requestor = event->requestor; 0150 u.notify.selection = event->selection; 0151 u.notify.target = event->target; 0152 u.notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE); 0153 0154 xcb_connection_t *xcbConn = kwinApp()->x11Connection(); 0155 xcb_send_event(xcbConn, 0, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&u); 0156 xcb_flush(xcbConn); 0157 } 0158 0159 void Selection::registerXfixes() 0160 { 0161 xcb_connection_t *xcbConn = kwinApp()->x11Connection(); 0162 const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; 0163 xcb_xfixes_select_selection_input(xcbConn, 0164 m_window, 0165 m_atom, 0166 mask); 0167 xcb_flush(xcbConn); 0168 } 0169 0170 void Selection::setWlSource(WlSource *source) 0171 { 0172 if (m_waylandSource) { 0173 m_waylandSource->deleteLater(); 0174 m_waylandSource = nullptr; 0175 } 0176 delete m_xSource; 0177 m_xSource = nullptr; 0178 if (source) { 0179 m_waylandSource = source; 0180 connect(source, &WlSource::transferReady, this, &Selection::startTransferToX); 0181 } 0182 } 0183 0184 void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event) 0185 { 0186 if (!event || event->owner == XCB_WINDOW_NONE) { 0187 x11OfferLost(); 0188 setWlSource(nullptr); 0189 return; 0190 } 0191 setWlSource(nullptr); 0192 0193 m_xSource = new X11Source(this, event); 0194 connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged); 0195 connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland); 0196 } 0197 0198 void Selection::ownSelection(bool own) 0199 { 0200 xcb_connection_t *xcbConn = kwinApp()->x11Connection(); 0201 if (own) { 0202 xcb_set_selection_owner(xcbConn, 0203 m_window, 0204 m_atom, 0205 XCB_TIME_CURRENT_TIME); 0206 } else { 0207 m_disownPending = true; 0208 xcb_set_selection_owner(xcbConn, 0209 XCB_WINDOW_NONE, 0210 m_atom, 0211 m_timestamp); 0212 } 0213 xcb_flush(xcbConn); 0214 } 0215 0216 void Selection::overwriteRequestorWindow(xcb_window_t window) 0217 { 0218 Q_ASSERT(m_xSource); 0219 if (window == XCB_WINDOW_NONE) { 0220 // reset 0221 window = m_window; 0222 } 0223 m_requestorWindow = window; 0224 m_xSource->setRequestor(window); 0225 } 0226 0227 bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event) 0228 { 0229 if (event->selection != m_atom) { 0230 return false; 0231 } 0232 0233 if (qobject_cast<X11Window *>(workspace()->activeWindow()) == nullptr) { 0234 // Receiving Wayland selection not allowed when no Xwayland surface active 0235 // filter the event, but don't act upon it 0236 sendSelectionNotify(event, false); 0237 return true; 0238 } 0239 0240 if (m_window != event->owner || !m_waylandSource) { 0241 if (event->time < m_timestamp) { 0242 // cancel earlier attempts at receiving a selection 0243 // TODO: is this for sure without problems? 0244 sendSelectionNotify(event, false); 0245 return true; 0246 } 0247 return false; 0248 } 0249 return m_waylandSource->handleSelectionRequest(event); 0250 } 0251 0252 bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event) 0253 { 0254 if (m_xSource && m_xSource->handleSelectionNotify(event)) { 0255 return true; 0256 } 0257 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) { 0258 if (transfer->handleSelectionNotify(event)) { 0259 return true; 0260 } 0261 } 0262 return false; 0263 } 0264 0265 bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event) 0266 { 0267 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) { 0268 if (transfer->handlePropertyNotify(event)) { 0269 return true; 0270 } 0271 } 0272 for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) { 0273 if (transfer->handlePropertyNotify(event)) { 0274 return true; 0275 } 0276 } 0277 return false; 0278 } 0279 0280 void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd) 0281 { 0282 // create new x to wl data transfer object 0283 auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this); 0284 m_xToWlTransfers << transfer; 0285 0286 connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() { 0287 Q_EMIT transferFinished(transfer->timestamp()); 0288 transfer->deleteLater(); 0289 m_xToWlTransfers.removeOne(transfer); 0290 endTimeoutTransfersTimer(); 0291 }); 0292 startTimeoutTransfersTimer(); 0293 } 0294 0295 void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd) 0296 { 0297 // create new wl to x data transfer object 0298 auto *transfer = new TransferWltoX(m_atom, event, fd, this); 0299 0300 connect(transfer, &TransferWltoX::selectionNotify, this, &Selection::sendSelectionNotify); 0301 connect(transfer, &TransferWltoX::finished, this, [this, transfer]() { 0302 Q_EMIT transferFinished(transfer->timestamp()); 0303 0304 // TODO: serialize? see comment below. 0305 // const bool wasActive = (transfer == m_wlToXTransfers[0]); 0306 transfer->deleteLater(); 0307 m_wlToXTransfers.removeOne(transfer); 0308 endTimeoutTransfersTimer(); 0309 // if (wasActive && !m_wlToXTransfers.isEmpty()) { 0310 // m_wlToXTransfers[0]->startTransferFromSource(); 0311 // } 0312 }); 0313 0314 // add it to list of queued transfers 0315 m_wlToXTransfers.append(transfer); 0316 0317 // TODO: Do we need to serialize the transfers, or can we do 0318 // them in parallel as we do it right now? 0319 transfer->startTransferFromSource(); 0320 // if (m_wlToXTransfers.size() == 1) { 0321 // transfer->startTransferFromSource(); 0322 // } 0323 startTimeoutTransfersTimer(); 0324 } 0325 0326 void Selection::startTimeoutTransfersTimer() 0327 { 0328 if (m_timeoutTransfers) { 0329 return; 0330 } 0331 m_timeoutTransfers = new QTimer(this); 0332 connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers); 0333 m_timeoutTransfers->start(5000); 0334 } 0335 0336 void Selection::endTimeoutTransfersTimer() 0337 { 0338 if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) { 0339 delete m_timeoutTransfers; 0340 m_timeoutTransfers = nullptr; 0341 } 0342 } 0343 0344 void Selection::timeoutTransfers() 0345 { 0346 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) { 0347 transfer->timeout(); 0348 } 0349 for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) { 0350 transfer->timeout(); 0351 } 0352 } 0353 0354 } // namespace Xwl 0355 } // namespace KWin 0356 0357 #include "moc_selection.cpp"