17
17
#include "clipboard.h"
23
#include <QDBusConnection>
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.
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))
44
const int maxFormatsCount = 16;
50
QByteArray serializeMimeData(QMimeData *mimeData)
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;
57
for (int i = 0; i < formatCount; i++)
58
bufferSize += formats[i].size() + mimeData->data(formats[i]).size();
61
QByteArray serializedMimeData(bufferSize, 0 /* char to fill with */);
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;
83
return serializedMimeData;
86
QMimeData *deserializeMimeData(const QByteArray &serializedMimeData)
88
if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
93
QMimeData *mimeData = new QMimeData;
95
const char* const buffer = serializedMimeData.constData();
96
const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
98
const int count = qMin(header[0], maxFormatsCount);
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];
106
if (formatOffset + formatSize <= serializedMimeData.size()
107
&& dataOffset + dataSize <= serializedMimeData.size()) {
109
QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
110
QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
112
mimeData->setData(mimeType, mimeDataBytes);
119
/************************************ DBusClipboard *****************************************/
121
bool DBusClipboard::skipDBusRegistration = false;
123
DBusClipboard::DBusClipboard(QObject *parent)
126
if (!skipDBusRegistration) {
127
performDBusRegistration();
131
void DBusClipboard::setContents(QByteArray newContents)
133
setContentsHelper(std::move(newContents));
136
void DBusClipboard::SetContents(QByteArray newContents)
138
qCDebug(QTMIR_CLIPBOARD, "D-Bus SetContents - %d bytes", newContents.size());
140
if (setContentsHelper(std::move(newContents))) {
141
Q_EMIT contentsChangedRemotely();
145
bool DBusClipboard::setContentsHelper(QByteArray newContents)
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);
154
if (newContents != m_contents) {
155
m_contents = std::move(newContents);
156
Q_EMIT ContentsChanged(m_contents);
163
QByteArray DBusClipboard::GetContents() const
165
qCDebug(QTMIR_CLIPBOARD, "D-Bus GetContents - returning %d bytes", m_contents.size());
169
void DBusClipboard::performDBusRegistration()
171
QDBusConnection connection = QDBusConnection::sessionBus();
172
const char *serviceName = "com.canonical.QtMir";
173
const char *objectName = "/com/canonical/QtMir/Clipboard";
175
bool serviceOk = connection.registerService(serviceName);
177
QDBusError error = connection.lastError();
178
QString errorMessage;
179
if (error.isValid()) {
180
errorMessage = error.message();
182
qCCritical(QTMIR_CLIPBOARD, "Failed to register service %s. %s", serviceName, qPrintable(errorMessage));
185
bool objectOk = connection.registerObject(objectName, this,
186
QDBusConnection::ExportScriptableSignals
187
| QDBusConnection::ExportScriptableSlots);
189
QDBusError error = connection.lastError();
190
QString errorMessage;
191
if (error.isValid()) {
192
errorMessage = error.message();
194
qCCritical(QTMIR_CLIPBOARD, "Failed to register object %s. %s", objectName, qPrintable(errorMessage));
197
if (serviceOk && objectOk) {
198
qCDebug(QTMIR_CLIPBOARD, "D-Bus registration successful.");
202
/************************************ Clipboard *****************************************/
204
Clipboard::Clipboard(QObject *parent)
206
, m_dbusClipboard(nullptr)
210
QMimeData *Clipboard::mimeData(QClipboard::Mode mode)
212
if (mode == QClipboard::Clipboard) {
213
return QPlatformClipboard::mimeData(mode);
219
void Clipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
18
#include "shelluuid.h"
20
#include <QDBusPendingCallWatcher>
21
#include <QSignalBlocker>
24
#include <com/ubuntu/content/hub.h>
26
// get this cumbersome nested namespace out of the way
27
using namespace com::ubuntu::content;
29
using namespace qtmir;
31
Clipboard::Clipboard()
33
, m_mimeData(new QMimeData)
34
, m_contentHub(Hub::Client::instance())
36
connect(m_contentHub, &Hub::pasteboardChanged, this, [this]() {
37
if (m_clipboardState == Clipboard::SyncedClipboard) {
38
m_clipboardState = Clipboard::OutdatedClipboard;
46
Clipboard::~Clipboard()
50
QMimeData* Clipboard::mimeData(QClipboard::Mode mode)
221
52
if (mode != QClipboard::Clipboard)
224
if (m_dbusClipboard) {
225
QByteArray serializedMimeData = serializeMimeData(data);
226
m_dbusClipboard->setContents(std::move(serializedMimeData));
229
QPlatformClipboard::setMimeData(data, mode);
232
void Clipboard::setupDBusService()
234
Q_ASSERT(!m_dbusClipboard);
236
m_dbusClipboard = new DBusClipboard(this);
238
connect(m_dbusClipboard, &DBusClipboard::contentsChangedRemotely,
239
this, &Clipboard::setMimeDataWithDBusClibpboardContents);
242
void Clipboard::setMimeDataWithDBusClibpboardContents()
244
Q_ASSERT(m_dbusClipboard);
245
QMimeData *newMimeData = deserializeMimeData(m_dbusClipboard->contents());
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
251
QPlatformClipboard::setMimeData(newMimeData, QClipboard::Clipboard);
253
qCWarning(QTMIR_CLIPBOARD, "Failed to deserialize D-Bus clipboard contents (%d bytes)",
254
m_dbusClipboard->contents().size());
55
return m_mimeData.data();
58
void Clipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
60
if (mode == QClipboard::Clipboard && mimeData != nullptr) {
61
QDBusPendingCall reply = m_contentHub->createPaste(ShellUuId::toString(), *mimeData);
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);
71
m_mimeData.reset(mimeData);
72
m_clipboardState = SyncedClipboard;
73
emitChanged(QClipboard::Clipboard);
77
bool Clipboard::supportsMode(QClipboard::Mode mode) const
79
return mode == QClipboard::Clipboard;
82
bool Clipboard::ownsMode(QClipboard::Mode mode) const
88
void Clipboard::updateMimeData()
90
m_mimeData.reset(m_contentHub->latestPaste(ShellUuId::toString()));
91
m_clipboardState = SyncedClipboard;
92
emitChanged(QClipboard::Clipboard);
95
void Clipboard::requestMimeData()
97
QDBusPendingCall reply = m_contentHub->requestLatestPaste(ShellUuId::toString());
98
m_clipboardState = SyncingClipboard;
100
m_pasteReply = new QDBusPendingCallWatcher(reply, this);
101
connect(m_pasteReply, &QDBusPendingCallWatcher::finished,
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);