selection.cpp 10.5 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
10
11
12
13
14
#include "selection.h"
#include "databridge.h"
#include "selection_source.h"
#include "transfer.h"

#include "atoms.h"
15
#include "workspace.h"
16
17
#include "x11client.h"
#include "xcbutils.h"
18
19
20
21
22
23

#include <xcb/xcb_event.h>
#include <xcb/xfixes.h>

#include <QTimer>

24
25
26
27
namespace KWin
{
namespace Xwl
{
28
29
30
31
32

xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType)
{
    if (mimeType == QLatin1String("text/plain;charset=utf-8")) {
        return atoms->utf8_string;
33
34
    }
    if (mimeType == QLatin1String("text/plain")) {
35
        return atoms->text;
36
37
    }
    if (mimeType == QLatin1String("text/x-uri")) {
38
39
40
41
42
43
44
45
46
47
        return atoms->uri_list;
    }
    return mimeTypeToAtomLiteral(mimeType);
}

xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
{
    return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
}

48
49
QString Selection::atomName(xcb_atom_t atom)
{
50
    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
51
    xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom);
52
53
    xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr);
    if (!nameReply) {
54
55
56
        return QString();
    }

57
58
    const size_t length = xcb_get_atom_name_name_length(nameReply);
    QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length);
59
60
61
62
    free(nameReply);
    return name;
}

63
64
65
66
67
68
69
70
71
72
73
QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
{
    QStringList mimeTypes;

    if (atom == atoms->utf8_string) {
        mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
    } else if (atom == atoms->text) {
        mimeTypes << QString::fromLatin1("text/plain");
    } else if (atom == atoms->uri_list) {
        mimeTypes << "text/uri-list" << "text/x-uri";
    } else {
74
        mimeTypes << atomName(atom);
75
76
77
78
79
    }
    return mimeTypes;
}

Selection::Selection(xcb_atom_t atom, QObject *parent)
80
81
    : QObject(parent)
    , m_atom(atom)
82
{
83
    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
84
    m_window = xcb_generate_id(kwinApp()->x11Connection());
85
    m_requestorWindow = m_window;
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    xcb_flush(xcbConn);
}

bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
{
    if (event->window != m_window) {
        return false;
    }
    if (event->selection != m_atom) {
        return false;
    }
    if (m_disownPending) {
        // notify of our own disown - ignore it
        m_disownPending = false;
        return true;
    }
102
    if (event->owner == m_window && m_waylandSource) {
103
104
105
        // When we claim a selection we must use XCB_TIME_CURRENT,
        // grab the actual timestamp here to answer TIMESTAMP requests
        // correctly
106
        m_waylandSource->setTimestamp(event->timestamp);
107
108
109
110
111
112
113
114
115
116
117
118
119
        m_timestamp = event->timestamp;
        return true;
    }

    // Being here means some other X window has claimed the selection.
    doHandleXfixesNotify(event);
    return true;
}

bool Selection::filterEvent(xcb_generic_event_t *event)
{
    switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
    case XCB_SELECTION_NOTIFY:
120
        return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event));
121
    case XCB_PROPERTY_NOTIFY:
122
        return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event));
123
    case XCB_SELECTION_REQUEST:
124
        return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event));
125
    case XCB_CLIENT_MESSAGE:
126
        return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event));
127
    default:
128
129
130
        if (event->response_type == Xcb::Extensions::self()->fixesSelectionNotifyEvent()) {
            return handleXfixesNotify(reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event));
        }
131
132
133
134
        return false;
    }
}

135
void Selection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
136
137
138
139
140
141
142
143
{
    xcb_selection_notify_event_t notify;
    notify.response_type = XCB_SELECTION_NOTIFY;
    notify.sequence = 0;
    notify.time = event->time;
    notify.requestor = event->requestor;
    notify.selection = event->selection;
    notify.target = event->target;
144
    notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);
145

146
    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
147
148
149
150
151
152
153
154
155
156
    xcb_send_event(xcbConn,
                   0,
                   event->requestor,
                   XCB_EVENT_MASK_NO_EVENT,
                   (const char *)&notify);
    xcb_flush(xcbConn);
}

void Selection::registerXfixes()
{
157
    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
158
159
160
    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;
161
    xcb_xfixes_select_selection_input(xcbConn,
162
163
164
165
166
167
                                      m_window,
                                      m_atom,
                                      mask);
    xcb_flush(xcbConn);
}

168
void Selection::setWlSource(WlSource *source)
169
{
170
171
172
173
174
175
176
    delete m_waylandSource;
    delete m_xSource;
    m_waylandSource = nullptr;
    m_xSource = nullptr;
    if (source) {
        m_waylandSource = source;
        connect(source, &WlSource::transferReady, this, &Selection::startTransferToX);
177
178
179
180
181
    }
}

void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
{
182
183
184
185
    delete m_waylandSource;
    delete m_xSource;
    m_waylandSource = nullptr;
    m_xSource = nullptr;
186
    if (!event || event->owner == XCB_WINDOW_NONE) {
187
188
        return;
    }
189
    m_xSource = new X11Source(this, event);
190

191
192
    connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged);
    connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland);
193
194
195
196
}

void Selection::ownSelection(bool own)
{
197
    xcb_connection_t *xcbConn = kwinApp()->x11Connection();
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    if (own) {
        xcb_set_selection_owner(xcbConn,
                                m_window,
                                m_atom,
                                XCB_TIME_CURRENT_TIME);
    } else {
        m_disownPending = true;
        xcb_set_selection_owner(xcbConn,
                                XCB_WINDOW_NONE,
                                m_atom,
                                m_timestamp);
    }
    xcb_flush(xcbConn);
}

213
214
void Selection::overwriteRequestorWindow(xcb_window_t window)
{
215
    Q_ASSERT(m_xSource);
216
217
218
219
220
    if (window == XCB_WINDOW_NONE) {
        // reset
        window = m_window;
    }
    m_requestorWindow = window;
221
    m_xSource->setRequestor(window);
222
223
}

224
bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event)
225
226
227
228
229
{
    if (event->selection != m_atom) {
        return false;
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
230
    if (qobject_cast<X11Client *>(workspace()->activeClient()) == nullptr) {
231
232
        // Receiving Wayland selection not allowed when no Xwayland surface active
        // filter the event, but don't act upon it
233
        sendSelectionNotify(event, false);
234
235
236
        return true;
    }

237
    if (m_window != event->owner || !m_waylandSource) {
238
239
240
        if (event->time < m_timestamp) {
            // cancel earlier attempts at receiving a selection
            // TODO: is this for sure without problems?
241
            sendSelectionNotify(event, false);
242
243
244
245
            return true;
        }
        return false;
    }
246
    return m_waylandSource->handleSelectionRequest(event);
247
248
}

249
bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event)
250
{
251
    if (m_xSource && m_xSource->handleSelectionNotify(event)) {
252
253
        return true;
    }
254
255
    for (TransferXtoWl *transfer : m_xToWlTransfers) {
        if (transfer->handleSelectionNotify(event)) {
256
257
258
259
260
261
            return true;
        }
    }
    return false;
}

262
bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event)
263
{
264
265
    for (TransferXtoWl *transfer : m_xToWlTransfers) {
        if (transfer->handlePropertyNotify(event)) {
266
267
268
            return true;
        }
    }
269
270
    for (TransferWltoX *transfer : m_wlToXTransfers) {
        if (transfer->handlePropertyNotify(event)) {
271
272
273
274
275
276
277
278
279
            return true;
        }
    }
    return false;
}

void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
{
    // create new x to wl data transfer object
280
    auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this);
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    m_xToWlTransfers << transfer;

    connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
        Q_EMIT transferFinished(transfer->timestamp());
        delete transfer;
        m_xToWlTransfers.removeOne(transfer);
        endTimeoutTransfersTimer();
    });
    startTimeoutTransfersTimer();
}

void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd)
{
    // create new wl to x data transfer object
    auto *transfer = new TransferWltoX(m_atom, event, fd, this);

297
    connect(transfer, &TransferWltoX::selectionNotify, this, &Selection::sendSelectionNotify);
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    connect(transfer, &TransferWltoX::finished, this, [this, transfer]() {
        Q_EMIT transferFinished(transfer->timestamp());

        // TODO: serialize? see comment below.
//        const bool wasActive = (transfer == m_wlToXTransfers[0]);
        delete transfer;
        m_wlToXTransfers.removeOne(transfer);
        endTimeoutTransfersTimer();
//        if (wasActive && !m_wlToXTransfers.isEmpty()) {
//            m_wlToXTransfers[0]->startTransferFromSource();
//        }
    });

    // add it to list of queued transfers
    m_wlToXTransfers.append(transfer);

    // TODO: Do we need to serialize the transfers, or can we do
    //       them in parallel as we do it right now?
    transfer->startTransferFromSource();
//    if (m_wlToXTransfers.size() == 1) {
//        transfer->startTransferFromSource();
//    }
    startTimeoutTransfersTimer();
}

void Selection::startTimeoutTransfersTimer()
{
    if (m_timeoutTransfers) {
        return;
    }
    m_timeoutTransfers = new QTimer(this);
    connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers);
    m_timeoutTransfers->start(5000);
}

void Selection::endTimeoutTransfersTimer()
{
    if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) {
        delete m_timeoutTransfers;
        m_timeoutTransfers = nullptr;
    }
}

void Selection::timeoutTransfers()
{
343
    for (TransferXtoWl *transfer : m_xToWlTransfers) {
344
345
        transfer->timeout();
    }
346
    for (TransferWltoX *transfer : m_wlToXTransfers) {
347
348
349
350
        transfer->timeout();
    }
}

351
352
} // namespace Xwl
} // namespace KWin