~alan-griffiths/miral/0.4

« back to all changes in this revision

Viewing changes to miral-qt/src/platforms/mirserver/clipboard.cpp

  • Committer: Alan Griffiths
  • Author(s): Gerry Boland
  • Date: 2016-09-13 13:29:17 UTC
  • mfrom: (315.5.5 qtmir-558)
  • Revision ID: alan@octopull.co.uk-20160913132917-06gw9fr1204mnzlf
[miral-qt] Merge Rev 556 of qtmir: Use content-hub for clipboard services. By Daniel d'Andrada.

And remove our own implementation of such service (LP: #1471998)

Taken from https://code.launchpad.net/~dandrader/qtmir/content-hub-clipboard/+merge/303863

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
 
 * Copyright (C) 2014-2015 Canonical, Ltd.
 
2
 * Copyright (C) 2014,2016 Canonical, Ltd.
3
3
 *
4
4
 * This program is free software: you can redistribute it and/or modify it under
5
5
 * the terms of the GNU Lesser General Public License version 3, as published by
15
15
 */
16
16
 
17
17
#include "clipboard.h"
18
 
#include "logging.h"
19
 
 
20
 
// C++ std lib
21
 
#include <utility>
22
 
 
23
 
#include <QDBusConnection>
24
 
#include <QDBusError>
25
 
#include <QMimeData>
26
 
 
27
 
// FIXME(loicm) The clipboard data format is not defined by Ubuntu Platform API
28
 
//     which makes it impossible to have non-Qt applications communicate with Qt
29
 
//     applications through the clipboard API. The solution would be to have
30
 
//     Ubuntu Platform define the data format or propose an API that supports
31
 
//     embedding different mime types in the clipboard.
32
 
 
33
 
// Data format:
34
 
//   number of mime types      (sizeof(int))
35
 
//   data layout               ((4 * sizeof(int)) * number of mime types)
36
 
//     mime type string offset (sizeof(int))
37
 
//     mime type string size   (sizeof(int))
38
 
//     data offset             (sizeof(int))
39
 
//     data size               (sizeof(int))
40
 
//   data                      (n bytes)
41
 
 
42
 
namespace {
43
 
 
44
 
const int maxFormatsCount = 16;
45
 
 
46
 
}
47
 
 
48
 
namespace qtmir {
49
 
 
50
 
QByteArray serializeMimeData(QMimeData *mimeData)
51
 
{
52
 
    const QStringList formats = mimeData->formats();
53
 
    const int formatCount = qMin(formats.size(), maxFormatsCount);
54
 
    const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int));
55
 
    int bufferSize = headerSize;
56
 
 
57
 
    for (int i = 0; i < formatCount; i++)
58
 
        bufferSize += formats[i].size() + mimeData->data(formats[i]).size();
59
 
 
60
 
    // Serialize data.
61
 
    QByteArray serializedMimeData(bufferSize, 0 /* char to fill with */);
62
 
    {
63
 
        char *buffer = serializedMimeData.data();
64
 
        int* header = reinterpret_cast<int*>(serializedMimeData.data());
65
 
        int offset = headerSize;
66
 
        header[0] = formatCount;
67
 
        for (int i = 0; i < formatCount; i++) {
68
 
            const QByteArray data = mimeData->data(formats[i]);
69
 
            const int formatOffset = offset;
70
 
            const int formatSize = formats[i].size();
71
 
            const int dataOffset = offset + formatSize;
72
 
            const int dataSize = data.size();
73
 
            memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize);
74
 
            memcpy(&buffer[dataOffset], data.data(), dataSize);
75
 
            header[i*4+1] = formatOffset;
76
 
            header[i*4+2] = formatSize;
77
 
            header[i*4+3] = dataOffset;
78
 
            header[i*4+4] = dataSize;
79
 
            offset += formatSize + dataSize;
80
 
        }
81
 
    }
82
 
 
83
 
    return serializedMimeData;
84
 
}
85
 
 
86
 
QMimeData *deserializeMimeData(const QByteArray &serializedMimeData)
87
 
{
88
 
    if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
89
 
        // Data is invalid
90
 
        return nullptr;
91
 
    }
92
 
 
93
 
    QMimeData *mimeData = new QMimeData;
94
 
 
95
 
    const char* const buffer = serializedMimeData.constData();
96
 
    const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
97
 
 
98
 
    const int count = qMin(header[0], maxFormatsCount);
99
 
 
100
 
    for (int i = 0; i < count; i++) {
101
 
        const int formatOffset = header[i*4+1];
102
 
        const int formatSize = header[i*4+2];
103
 
        const int dataOffset = header[i*4+3];
104
 
        const int dataSize = header[i*4+4];
105
 
 
106
 
        if (formatOffset + formatSize <= serializedMimeData.size()
107
 
                && dataOffset + dataSize <= serializedMimeData.size()) {
108
 
 
109
 
            QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
110
 
            QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
111
 
 
112
 
            mimeData->setData(mimeType, mimeDataBytes);
113
 
        }
114
 
    }
115
 
 
116
 
    return mimeData;
117
 
}
118
 
 
119
 
/************************************ DBusClipboard *****************************************/
120
 
 
121
 
bool DBusClipboard::skipDBusRegistration = false;
122
 
 
123
 
DBusClipboard::DBusClipboard(QObject *parent)
124
 
    : QObject(parent)
125
 
{
126
 
    if (!skipDBusRegistration) {
127
 
        performDBusRegistration();
128
 
    }
129
 
}
130
 
 
131
 
void DBusClipboard::setContents(QByteArray newContents)
132
 
{
133
 
    setContentsHelper(std::move(newContents));
134
 
}
135
 
 
136
 
void DBusClipboard::SetContents(QByteArray newContents)
137
 
{
138
 
    qCDebug(QTMIR_CLIPBOARD, "D-Bus SetContents - %d bytes", newContents.size());
139
 
 
140
 
    if (setContentsHelper(std::move(newContents))) {
141
 
        Q_EMIT contentsChangedRemotely();
142
 
    }
143
 
}
144
 
 
145
 
bool DBusClipboard::setContentsHelper(QByteArray newContents)
146
 
{
147
 
    if (newContents.size() > maxContentsSize) {
148
 
        qCWarning(QTMIR_CLIPBOARD, "D-Bus clipboard refused the new contents (%d bytes) as they're"
149
 
                " bigger than the maximum allowed size of %d bytes.",
150
 
                newContents.size(), maxContentsSize);
151
 
        return false;
152
 
    }
153
 
 
154
 
    if (newContents != m_contents) {
155
 
        m_contents = std::move(newContents);
156
 
        Q_EMIT ContentsChanged(m_contents);
157
 
        return true;
158
 
    } else {
159
 
        return false;
160
 
    }
161
 
}
162
 
 
163
 
QByteArray DBusClipboard::GetContents() const
164
 
{
165
 
    qCDebug(QTMIR_CLIPBOARD, "D-Bus GetContents - returning %d bytes", m_contents.size());
166
 
    return m_contents;
167
 
}
168
 
 
169
 
void DBusClipboard::performDBusRegistration()
170
 
{
171
 
    QDBusConnection connection = QDBusConnection::sessionBus();
172
 
    const char *serviceName = "com.canonical.QtMir";
173
 
    const char *objectName = "/com/canonical/QtMir/Clipboard";
174
 
 
175
 
    bool serviceOk = connection.registerService(serviceName);
176
 
    if (!serviceOk) {
177
 
        QDBusError error = connection.lastError();
178
 
        QString errorMessage;
179
 
        if (error.isValid()) {
180
 
            errorMessage = error.message();
181
 
        }
182
 
        qCCritical(QTMIR_CLIPBOARD, "Failed to register service %s. %s", serviceName, qPrintable(errorMessage));
183
 
    }
184
 
 
185
 
    bool objectOk = connection.registerObject(objectName, this,
186
 
                              QDBusConnection::ExportScriptableSignals
187
 
                              | QDBusConnection::ExportScriptableSlots);
188
 
    if (!objectOk) {
189
 
        QDBusError error = connection.lastError();
190
 
        QString errorMessage;
191
 
        if (error.isValid()) {
192
 
            errorMessage = error.message();
193
 
        }
194
 
        qCCritical(QTMIR_CLIPBOARD, "Failed to register object %s. %s", objectName, qPrintable(errorMessage));
195
 
    }
196
 
 
197
 
    if (serviceOk && objectOk) {
198
 
        qCDebug(QTMIR_CLIPBOARD, "D-Bus registration successful.");
199
 
    }
200
 
}
201
 
 
202
 
/************************************ Clipboard *****************************************/
203
 
 
204
 
Clipboard::Clipboard(QObject *parent)
205
 
    : QObject(parent)
206
 
    , m_dbusClipboard(nullptr)
207
 
{
208
 
}
209
 
 
210
 
QMimeData *Clipboard::mimeData(QClipboard::Mode mode)
211
 
{
212
 
    if (mode == QClipboard::Clipboard) {
213
 
        return QPlatformClipboard::mimeData(mode);
214
 
    } else {
215
 
        return nullptr;
216
 
    }
217
 
}
218
 
 
219
 
void Clipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
 
18
#include "shelluuid.h"
 
19
 
 
20
#include <QDBusPendingCallWatcher>
 
21
#include <QSignalBlocker>
 
22
 
 
23
// content-hub
 
24
#include <com/ubuntu/content/hub.h>
 
25
 
 
26
// get this cumbersome nested namespace out of the way
 
27
using namespace com::ubuntu::content;
 
28
 
 
29
using namespace qtmir;
 
30
 
 
31
Clipboard::Clipboard()
 
32
    : QObject(nullptr)
 
33
    , m_mimeData(new QMimeData)
 
34
    , m_contentHub(Hub::Client::instance())
 
35
{
 
36
    connect(m_contentHub, &Hub::pasteboardChanged, this, [this]() {
 
37
        if (m_clipboardState == Clipboard::SyncedClipboard) {
 
38
            m_clipboardState = Clipboard::OutdatedClipboard;
 
39
            requestMimeData();
 
40
        }
 
41
    });
 
42
 
 
43
    requestMimeData();
 
44
}
 
45
 
 
46
Clipboard::~Clipboard()
 
47
{
 
48
}
 
49
 
 
50
QMimeData* Clipboard::mimeData(QClipboard::Mode mode)
220
51
{
221
52
    if (mode != QClipboard::Clipboard)
222
 
        return;
223
 
 
224
 
    if (m_dbusClipboard) {
225
 
        QByteArray serializedMimeData = serializeMimeData(data);
226
 
        m_dbusClipboard->setContents(std::move(serializedMimeData));
227
 
    }
228
 
 
229
 
    QPlatformClipboard::setMimeData(data, mode);
230
 
}
231
 
 
232
 
void Clipboard::setupDBusService()
233
 
{
234
 
    Q_ASSERT(!m_dbusClipboard);
235
 
 
236
 
    m_dbusClipboard = new DBusClipboard(this);
237
 
 
238
 
    connect(m_dbusClipboard, &DBusClipboard::contentsChangedRemotely,
239
 
            this, &Clipboard::setMimeDataWithDBusClibpboardContents);
240
 
}
241
 
 
242
 
void Clipboard::setMimeDataWithDBusClibpboardContents()
243
 
{
244
 
    Q_ASSERT(m_dbusClipboard);
245
 
    QMimeData *newMimeData = deserializeMimeData(m_dbusClipboard->contents());
246
 
    if (newMimeData) {
247
 
        // Don't call Clipboard::setMimeData as it will also propagate the change
248
 
        // to the D-Bus clipboard, which doesn't make sense here as we're doing
249
 
        // the other way round (propagating the D-Bus clipboard change to the local
250
 
        // clipboard).
251
 
        QPlatformClipboard::setMimeData(newMimeData, QClipboard::Clipboard);
252
 
    } else {
253
 
        qCWarning(QTMIR_CLIPBOARD, "Failed to deserialize D-Bus clipboard contents (%d bytes)",
254
 
                m_dbusClipboard->contents().size());
255
 
    }
256
 
}
257
 
 
258
 
} // namespace qtmir
259
 
 
 
53
        return nullptr;
 
54
 
 
55
    return m_mimeData.data();
 
56
}
 
57
 
 
58
void Clipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
 
59
{
 
60
    if (mode == QClipboard::Clipboard && mimeData != nullptr) {
 
61
        QDBusPendingCall reply = m_contentHub->createPaste(ShellUuId::toString(), *mimeData);
 
62
 
 
63
        // Don't care whether it succeeded
 
64
        // But I have to keep a QDBusPendingCall instance around (such as this watcher) until
 
65
        // the call is finished otherwise the QDBusPendingCall destructor will cancel the call
 
66
        // if it's still ongoing.
 
67
        auto *watcher = new QDBusPendingCallWatcher(reply, this);
 
68
        connect(watcher, &QDBusPendingCallWatcher::finished,
 
69
                watcher, &QObject::deleteLater);
 
70
 
 
71
        m_mimeData.reset(mimeData);
 
72
        m_clipboardState = SyncedClipboard;
 
73
        emitChanged(QClipboard::Clipboard);
 
74
    }
 
75
}
 
76
 
 
77
bool Clipboard::supportsMode(QClipboard::Mode mode) const
 
78
{
 
79
    return mode == QClipboard::Clipboard;
 
80
}
 
81
 
 
82
bool Clipboard::ownsMode(QClipboard::Mode mode) const
 
83
{
 
84
    Q_UNUSED(mode);
 
85
    return false;
 
86
}
 
87
 
 
88
void Clipboard::updateMimeData()
 
89
{
 
90
    m_mimeData.reset(m_contentHub->latestPaste(ShellUuId::toString()));
 
91
    m_clipboardState = SyncedClipboard;
 
92
    emitChanged(QClipboard::Clipboard);
 
93
}
 
94
 
 
95
void Clipboard::requestMimeData()
 
96
{
 
97
    QDBusPendingCall reply = m_contentHub->requestLatestPaste(ShellUuId::toString());
 
98
    m_clipboardState = SyncingClipboard;
 
99
 
 
100
    m_pasteReply = new QDBusPendingCallWatcher(reply, this);
 
101
    connect(m_pasteReply, &QDBusPendingCallWatcher::finished,
 
102
            this, [this]() {
 
103
        m_mimeData.reset(m_contentHub->paste(*m_pasteReply));
 
104
        m_clipboardState = SyncedClipboard;
 
105
        m_pasteReply->deleteLater();
 
106
        m_pasteReply = nullptr;
 
107
        emitChanged(QClipboard::Clipboard);
 
108
    });
 
109
}