KWin
Loading...
Searching...
No Matches
selection.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "selection.h"
10#include "databridge.h"
11#include "selection_source.h"
12#include "transfer.h"
13
14#include "atoms.h"
15#include "utils/xcbutils.h"
16#include "workspace.h"
17#include "x11window.h"
18
19#include <xcb/xcb_event.h>
20#include <xcb/xfixes.h>
21
22#include <QTimer>
23
24namespace KWin
25{
26namespace Xwl
27{
28
29xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType)
30{
31 if (mimeType == QLatin1String("text/plain;charset=utf-8")) {
32 return atoms->utf8_string;
33 }
34 if (mimeType == QLatin1String("text/plain")) {
35 return atoms->text;
36 }
37 if (mimeType == QLatin1String("text/x-uri")) {
38 return atoms->uri_list;
39 }
40 return mimeTypeToAtomLiteral(mimeType);
41}
42
43xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
44{
45 return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
46}
47
48QString Selection::atomName(xcb_atom_t atom)
49{
50 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
51 xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom);
52 xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr);
53 if (!nameReply) {
54 return QString();
55 }
56
57 const size_t length = xcb_get_atom_name_name_length(nameReply);
58 QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length);
59 free(nameReply);
60 return name;
61}
62
63QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
64{
65 QStringList mimeTypes;
66
67 if (atom == atoms->utf8_string) {
68 mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
69 } else if (atom == atoms->text) {
70 mimeTypes << QString::fromLatin1("text/plain");
71 } else if (atom == atoms->uri_list) {
72 mimeTypes << "text/uri-list"
73 << "text/x-uri";
74 } else {
75 mimeTypes << atomName(atom);
76 }
77 return mimeTypes;
78}
79
80Selection::Selection(xcb_atom_t atom, QObject *parent)
81 : QObject(parent)
82 , m_atom(atom)
83{
84 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
85 m_window = xcb_generate_id(kwinApp()->x11Connection());
86 m_requestorWindow = m_window;
87 xcb_flush(xcbConn);
88}
89
90bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
91{
92 if (event->window != m_window) {
93 return false;
94 }
95 if (event->selection != m_atom) {
96 return false;
97 }
98 if (m_disownPending) {
99 // notify of our own disown - ignore it
100 m_disownPending = false;
101 return true;
102 }
103 if (event->owner == m_window && m_waylandSource) {
104 // When we claim a selection we must use XCB_TIME_CURRENT,
105 // grab the actual timestamp here to answer TIMESTAMP requests
106 // correctly
107 m_waylandSource->setTimestamp(event->timestamp);
108 m_timestamp = event->timestamp;
109 return true;
110 }
111
112 // Being here means some other X window has claimed the selection.
114 return true;
115}
116
117bool Selection::filterEvent(xcb_generic_event_t *event)
118{
119 switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
120 case XCB_SELECTION_NOTIFY:
121 return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event));
122 case XCB_PROPERTY_NOTIFY:
123 return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event));
124 case XCB_SELECTION_REQUEST:
125 return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event));
126 case XCB_CLIENT_MESSAGE:
127 return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event));
128 default:
129 if (event->response_type == Xcb::Extensions::self()->fixesSelectionNotifyEvent()) {
130 return handleXfixesNotify(reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event));
131 }
132 return false;
133 }
134}
135
136void Selection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
137{
138 // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
139 // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
140 union {
141 xcb_selection_notify_event_t notify;
142 char buffer[32];
143 } u;
144 memset(&u, 0, sizeof(u));
145 static_assert(sizeof(u.notify) < 32, "wouldn't need the union otherwise");
146 u.notify.response_type = XCB_SELECTION_NOTIFY;
147 u.notify.sequence = 0;
148 u.notify.time = event->time;
149 u.notify.requestor = event->requestor;
150 u.notify.selection = event->selection;
151 u.notify.target = event->target;
152 u.notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);
153
154 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
155 xcb_send_event(xcbConn, 0, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&u);
156 xcb_flush(xcbConn);
157}
158
160{
161 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
162 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;
163 xcb_xfixes_select_selection_input(xcbConn,
164 m_window,
165 m_atom,
166 mask);
167 xcb_flush(xcbConn);
168}
169
171{
172 if (m_waylandSource) {
173 m_waylandSource->deleteLater();
174 m_waylandSource = nullptr;
175 }
176 delete m_xSource;
177 m_xSource = nullptr;
178 if (source) {
179 m_waylandSource = source;
180 connect(source, &WlSource::transferReady, this, &Selection::startTransferToX);
181 }
182}
183
184void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
185{
186 if (!event || event->owner == XCB_WINDOW_NONE) {
187 x11OfferLost();
188 setWlSource(nullptr);
189 return;
190 }
191 setWlSource(nullptr);
192
193 m_xSource = new X11Source(this, event);
194 connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged);
195 connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland);
196}
197
199{
200 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
201 if (own) {
202 xcb_set_selection_owner(xcbConn,
203 m_window,
204 m_atom,
205 XCB_TIME_CURRENT_TIME);
206 } else {
207 m_disownPending = true;
208 xcb_set_selection_owner(xcbConn,
209 XCB_WINDOW_NONE,
210 m_atom,
211 m_timestamp);
212 }
213 xcb_flush(xcbConn);
214}
215
216void Selection::overwriteRequestorWindow(xcb_window_t window)
217{
218 Q_ASSERT(m_xSource);
219 if (window == XCB_WINDOW_NONE) {
220 // reset
221 window = m_window;
222 }
223 m_requestorWindow = window;
224 m_xSource->setRequestor(window);
225}
226
227bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event)
228{
229 if (event->selection != m_atom) {
230 return false;
231 }
232
233 if (qobject_cast<X11Window *>(workspace()->activeWindow()) == nullptr) {
234 // Receiving Wayland selection not allowed when no Xwayland surface active
235 // filter the event, but don't act upon it
236 sendSelectionNotify(event, false);
237 return true;
238 }
239
240 if (m_window != event->owner || !m_waylandSource) {
241 if (event->time < m_timestamp) {
242 // cancel earlier attempts at receiving a selection
243 // TODO: is this for sure without problems?
244 sendSelectionNotify(event, false);
245 return true;
246 }
247 return false;
248 }
249 return m_waylandSource->handleSelectionRequest(event);
250}
251
252bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event)
253{
254 if (m_xSource && m_xSource->handleSelectionNotify(event)) {
255 return true;
256 }
257 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
258 if (transfer->handleSelectionNotify(event)) {
259 return true;
260 }
261 }
262 return false;
263}
264
265bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event)
266{
267 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
268 if (transfer->handlePropertyNotify(event)) {
269 return true;
270 }
271 }
272 for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
273 if (transfer->handlePropertyNotify(event)) {
274 return true;
275 }
276 }
277 return false;
278}
279
280void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
281{
282 // create new x to wl data transfer object
283 auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this);
284 m_xToWlTransfers << transfer;
285
286 connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
287 Q_EMIT transferFinished(transfer->timestamp());
288 transfer->deleteLater();
289 m_xToWlTransfers.removeOne(transfer);
290 endTimeoutTransfersTimer();
291 });
292 startTimeoutTransfersTimer();
293}
294
295void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd)
296{
297 // create new wl to x data transfer object
298 auto *transfer = new TransferWltoX(m_atom, event, fd, this);
299
301 connect(transfer, &TransferWltoX::finished, this, [this, transfer]() {
302 Q_EMIT transferFinished(transfer->timestamp());
303
304 // TODO: serialize? see comment below.
305 // const bool wasActive = (transfer == m_wlToXTransfers[0]);
306 transfer->deleteLater();
307 m_wlToXTransfers.removeOne(transfer);
308 endTimeoutTransfersTimer();
309 // if (wasActive && !m_wlToXTransfers.isEmpty()) {
310 // m_wlToXTransfers[0]->startTransferFromSource();
311 // }
312 });
313
314 // add it to list of queued transfers
315 m_wlToXTransfers.append(transfer);
316
317 // TODO: Do we need to serialize the transfers, or can we do
318 // them in parallel as we do it right now?
319 transfer->startTransferFromSource();
320 // if (m_wlToXTransfers.size() == 1) {
321 // transfer->startTransferFromSource();
322 // }
323 startTimeoutTransfersTimer();
324}
325
326void Selection::startTimeoutTransfersTimer()
327{
328 if (m_timeoutTransfers) {
329 return;
330 }
331 m_timeoutTransfers = new QTimer(this);
332 connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers);
333 m_timeoutTransfers->start(5000);
334}
335
336void Selection::endTimeoutTransfersTimer()
337{
338 if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) {
339 delete m_timeoutTransfers;
340 m_timeoutTransfers = nullptr;
341 }
342}
343
344void Selection::timeoutTransfers()
345{
346 for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
347 transfer->timeout();
348 }
349 for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
350 transfer->timeout();
351 }
352}
353
354} // namespace Xwl
355} // namespace KWin
356
357#include "moc_selection.cpp"
Xcb::Atom text
Definition atoms.h:63
Xcb::Atom uri_list
Definition atoms.h:64
Xcb::Atom utf8_string
Definition atoms.h:62
int fixesSelectionNotifyEvent() const
Definition xcbutils.cpp:519
static Extensions * self()
Definition xcbutils.cpp:346
virtual void x11OffersChanged(const QStringList &added, const QStringList &removed)=0
xcb_atom_t atom() const
Definition selection.h:59
bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
Definition selection.cpp:90
void overwriteRequestorWindow(xcb_window_t window)
static void sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
void transferFinished(xcb_timestamp_t eventTime)
static QStringList atomToMimeTypes(xcb_atom_t atom)
Definition selection.cpp:63
void createX11Source(xcb_xfixes_selection_notify_event_t *event)
static QString atomName(xcb_atom_t atom)
Definition selection.cpp:48
Selection(xcb_atom_t atom, QObject *parent)
Definition selection.cpp:80
static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType)
Definition selection.cpp:43
void setWlSource(WlSource *source)
void ownSelection(bool own)
xcb_window_t window() const
Definition selection.h:63
virtual void x11OfferLost()=0
virtual void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)=0
virtual bool handleClientMessage(xcb_client_message_event_t *event)
Definition selection.h:80
static xcb_atom_t mimeTypeToAtom(const QString &mimeType)
Definition selection.cpp:29
bool filterEvent(xcb_generic_event_t *event)
xcb_timestamp_t timestamp() const
void setTimestamp(xcb_timestamp_t time)
void selectionNotify(xcb_selection_request_event_t *event, bool success)
bool handleSelectionRequest(xcb_selection_request_event_t *event)
void transferReady(xcb_selection_request_event_t *event, qint32 fd)
bool handleSelectionNotify(xcb_selection_notify_event_t *event)
void offersChanged(const QStringList &added, const QStringList &removed)
void transferReady(xcb_atom_t target, qint32 fd)
void setRequestor(xcb_window_t window)
Workspace * workspace()
Definition workspace.h:830
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74