summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/mirclient/qmirclientclipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/mirclient/qmirclientclipboard.cpp')
-rw-r--r--src/plugins/platforms/mirclient/qmirclientclipboard.cpp303
1 files changed, 84 insertions, 219 deletions
diff --git a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp
index 4dff339f21..b9fc9b3b42 100644
--- a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp
+++ b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp
@@ -39,41 +39,36 @@
#include "qmirclientclipboard.h"
+#include "qmirclientlogging.h"
+#include "qmirclientwindow.h"
+#include <QDBusPendingCallWatcher>
+#include <QGuiApplication>
+#include <QSignalBlocker>
#include <QtCore/QMimeData>
#include <QtCore/QStringList>
-#include <QDBusInterface>
-#include <QDBusPendingCallWatcher>
-#include <QDBusPendingReply>
-
-// FIXME(loicm) The clipboard data format is not defined by Ubuntu Platform API
-// which makes it impossible to have non-Qt applications communicate with Qt
-// applications through the clipboard API. The solution would be to have
-// Ubuntu Platform define the data format or propose an API that supports
-// embedding different mime types in the clipboard.
-// Data format:
-// number of mime types (sizeof(int))
-// data layout ((4 * sizeof(int)) * number of mime types)
-// mime type string offset (sizeof(int))
-// mime type string size (sizeof(int))
-// data offset (sizeof(int))
-// data size (sizeof(int))
-// data (n bytes)
+// content-hub
+#include <com/ubuntu/content/hub.h>
-namespace {
-
-const int maxFormatsCount = 16;
-const int maxBufferSize = 4 * 1024 * 1024; // 4 Mb
-
-}
+// get this cumbersome nested namespace out of the way
+using namespace com::ubuntu::content;
QMirClientClipboard::QMirClientClipboard()
: mMimeData(new QMimeData)
- , mIsOutdated(true)
- , mUpdatesDisabled(false)
- , mDBusSetupDone(false)
+ , mContentHub(Hub::Client::instance())
{
+ connect(mContentHub, &Hub::pasteboardChanged, this, [this]() {
+ if (mClipboardState == QMirClientClipboard::SyncedClipboard) {
+ mClipboardState = QMirClientClipboard::OutdatedClipboard;
+ emitChanged(QClipboard::Clipboard);
+ }
+ });
+
+ connect(qGuiApp, &QGuiApplication::applicationStateChanged,
+ this, &QMirClientClipboard::onApplicationStateChanged);
+
+ requestMimeData();
}
QMirClientClipboard::~QMirClientClipboard()
@@ -81,236 +76,106 @@ QMirClientClipboard::~QMirClientClipboard()
delete mMimeData;
}
-void QMirClientClipboard::requestDBusClipboardContents()
+QMimeData* QMirClientClipboard::mimeData(QClipboard::Mode mode)
{
- if (!mDBusSetupDone) {
- setupDBus();
- }
-
- if (!mPendingGetContentsCall.isNull())
- return;
+ if (mode != QClipboard::Clipboard)
+ return nullptr;
- QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("GetContents"));
+ // Blocks dataChanged() signal from being emitted. Makes no sense to emit it from
+ // inside the data getter.
+ const QSignalBlocker blocker(this);
- mPendingGetContentsCall = new QDBusPendingCallWatcher(pendingCall, this);
+ if (mClipboardState == OutdatedClipboard) {
+ updateMimeData();
+ } else if (mClipboardState == SyncingClipboard) {
+ mPasteReply->waitForFinished();
+ }
- QObject::connect(mPendingGetContentsCall.data(), &QDBusPendingCallWatcher::finished,
- this, &QMirClientClipboard::onDBusClipboardGetContentsFinished);
+ return mMimeData;
}
-void QMirClientClipboard::onDBusClipboardGetContentsFinished(QDBusPendingCallWatcher* call)
+void QMirClientClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
{
- Q_ASSERT(call == mPendingGetContentsCall.data());
+ QWindow *focusWindow = QGuiApplication::focusWindow();
+ if (focusWindow && mode == QClipboard::Clipboard && mimeData != nullptr) {
+ QString surfaceId = static_cast<QMirClientWindow*>(focusWindow->handle())->persistentSurfaceId();
- QDBusPendingReply<QByteArray> reply = *call;
- if (Q_UNLIKELY(reply.isError())) {
- qCritical("QMirClientClipboard - Failed to get system clipboard contents via D-Bus. %s, %s",
- qPrintable(reply.error().name()), qPrintable(reply.error().message()));
- // TODO: Might try again later a number of times...
- } else {
- QByteArray serializedMimeData = reply.argumentAt<0>();
- updateMimeData(serializedMimeData);
- }
- call->deleteLater();
-}
+ QDBusPendingCall reply = mContentHub->createPaste(surfaceId, *mimeData);
-void QMirClientClipboard::onDBusClipboardSetContentsFinished(QDBusPendingCallWatcher *call)
-{
- QDBusPendingReply<void> reply = *call;
- if (Q_UNLIKELY(reply.isError())) {
- qCritical("QMirClientClipboard - Failed to set the system clipboard contents via D-Bus. %s, %s",
- qPrintable(reply.error().name()), qPrintable(reply.error().message()));
- // TODO: Might try again later a number of times...
- }
- call->deleteLater();
-}
-
-void QMirClientClipboard::updateMimeData(const QByteArray &serializedMimeData)
-{
- if (mUpdatesDisabled)
- return;
+ // Don't care whether it succeeded
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished,
+ watcher, &QObject::deleteLater);
- QMimeData *newMimeData = deserializeMimeData(serializedMimeData);
- if (newMimeData) {
- delete mMimeData;
- mMimeData = newMimeData;
- mIsOutdated = false;
+ mMimeData = mimeData;
+ mClipboardState = SyncedClipboard;
emitChanged(QClipboard::Clipboard);
- } else {
- qWarning("QMirClientClipboard - Got invalid serialized mime data. Ignoring it.");
}
}
-void QMirClientClipboard::setupDBus()
-{
- QDBusConnection dbusConnection = QDBusConnection::sessionBus();
-
- bool ok = dbusConnection.connect(
- QStringLiteral("com.canonical.QtMir"),
- QStringLiteral("/com/canonical/QtMir/Clipboard"),
- QStringLiteral("com.canonical.QtMir.Clipboard"),
- QStringLiteral("ContentsChanged"),
- this, SLOT(updateMimeData(QByteArray)));
- if (Q_UNLIKELY(!ok))
- qCritical("QMirClientClipboard - Failed to connect to ContentsChanged signal form the D-Bus system clipboard.");
-
- mDBusClipboard = new QDBusInterface(QStringLiteral("com.canonical.QtMir"),
- QStringLiteral("/com/canonical/QtMir/Clipboard"),
- QStringLiteral("com.canonical.QtMir.Clipboard"),
- dbusConnection);
-
- mDBusSetupDone = true;
-}
-
-QByteArray QMirClientClipboard::serializeMimeData(QMimeData *mimeData) const
+bool QMirClientClipboard::supportsMode(QClipboard::Mode mode) const
{
- Q_ASSERT(mimeData != nullptr);
-
- const QStringList formats = mimeData->formats();
- const int formatCount = qMin(formats.size(), maxFormatsCount);
- const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int));
- int bufferSize = headerSize;
-
- for (int i = 0; i < formatCount; i++)
- bufferSize += formats[i].size() + mimeData->data(formats[i]).size();
-
- QByteArray serializedMimeData;
- if (bufferSize <= maxBufferSize) {
- // Serialize data.
- serializedMimeData.resize(bufferSize);
- {
- char *buffer = serializedMimeData.data();
- int* header = reinterpret_cast<int*>(serializedMimeData.data());
- int offset = headerSize;
- header[0] = formatCount;
- for (int i = 0; i < formatCount; i++) {
- const QByteArray data = mimeData->data(formats[i]);
- const int formatOffset = offset;
- const int formatSize = formats[i].size();
- const int dataOffset = offset + formatSize;
- const int dataSize = data.size();
- memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize);
- memcpy(&buffer[dataOffset], data.data(), dataSize);
- header[i*4+1] = formatOffset;
- header[i*4+2] = formatSize;
- header[i*4+3] = dataOffset;
- header[i*4+4] = dataSize;
- offset += formatSize + dataSize;
- }
- }
- } else {
- qWarning("QMirClientClipboard: Not sending contents (%d bytes) to the global clipboard as it's"
- " bigger than the maximum allowed size of %d bytes", bufferSize, maxBufferSize);
- }
-
- return serializedMimeData;
+ return mode == QClipboard::Clipboard;
}
-QMimeData *QMirClientClipboard::deserializeMimeData(const QByteArray &serializedMimeData) const
+bool QMirClientClipboard::ownsMode(QClipboard::Mode mode) const
{
- if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
- // Data is invalid
- return nullptr;
- }
-
- QMimeData *mimeData = new QMimeData;
-
- const char* const buffer = serializedMimeData.constData();
- const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
-
- const int count = qMin(header[0], maxFormatsCount);
-
- for (int i = 0; i < count; i++) {
- const int formatOffset = header[i*4+1];
- const int formatSize = header[i*4+2];
- const int dataOffset = header[i*4+3];
- const int dataSize = header[i*4+4];
-
- if (formatOffset + formatSize <= serializedMimeData.size()
- && dataOffset + dataSize <= serializedMimeData.size()) {
-
- QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
- QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
-
- mimeData->setData(mimeType, mimeDataBytes);
- }
- }
-
- return mimeData;
+ Q_UNUSED(mode);
+ return false;
}
-QMimeData* QMirClientClipboard::mimeData(QClipboard::Mode mode)
+void QMirClientClipboard::onApplicationStateChanged(Qt::ApplicationState state)
{
- if (mode != QClipboard::Clipboard)
- return nullptr;
-
- if (mIsOutdated && mPendingGetContentsCall.isNull()) {
- requestDBusClipboardContents();
+ if (state == Qt::ApplicationActive) {
+ // Only focused or active applications might be allowed to paste, so we probably
+ // missed changes in the clipboard while we were hidden, inactive or, more importantly,
+ // suspended.
+ requestMimeData();
}
-
- // Return whatever we have at the moment instead of blocking until we have something.
- //
- // This might be called during app startup just for the sake of checking if some
- // "Paste" UI control should be enabled or not.
- // We will emit QClipboard::changed() once we finally have something.
- return mMimeData;
}
-void QMirClientClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
+void QMirClientClipboard::updateMimeData()
{
- if (mode != QClipboard::Clipboard)
+ if (qGuiApp->applicationState() != Qt::ApplicationActive) {
+ // Don't even bother asking as content-hub would probably ignore our request (and should).
return;
-
- if (!mPendingGetContentsCall.isNull()) {
- // Ignore whatever comes from the system clipboard as we are going to change it anyway
- QObject::disconnect(mPendingGetContentsCall.data(), 0, this, 0);
- mUpdatesDisabled = true;
- mPendingGetContentsCall->waitForFinished();
- mUpdatesDisabled = false;
- delete mPendingGetContentsCall.data();
}
- if (mimeData != nullptr) {
- QByteArray serializedMimeData = serializeMimeData(mimeData);
- if (!serializedMimeData.isEmpty()) {
- setDBusClipboardContents(serializedMimeData);
- }
+ delete mMimeData;
- mMimeData = mimeData;
+ QWindow *focusWindow = QGuiApplication::focusWindow();
+ if (focusWindow) {
+ QString surfaceId = static_cast<QMirClientWindow*>(focusWindow->handle())->persistentSurfaceId();
+ mMimeData = mContentHub->latestPaste(surfaceId);
+ mClipboardState = SyncedClipboard;
emitChanged(QClipboard::Clipboard);
}
}
-bool QMirClientClipboard::supportsMode(QClipboard::Mode mode) const
-{
- return mode == QClipboard::Clipboard;
-}
-
-bool QMirClientClipboard::ownsMode(QClipboard::Mode mode) const
+void QMirClientClipboard::requestMimeData()
{
- Q_UNUSED(mode);
- return false;
-}
-
-void QMirClientClipboard::setDBusClipboardContents(const QByteArray &clipboardContents)
-{
- if (!mDBusSetupDone) {
- setupDBus();
+ if (qGuiApp->applicationState() != Qt::ApplicationActive) {
+ // Don't even bother asking as content-hub would probably ignore our request (and should).
+ return;
}
- if (!mPendingSetContentsCall.isNull()) {
- // Ignore any previous set call as we are going to overwrite it anyway
- QObject::disconnect(mPendingSetContentsCall.data(), 0, this, 0);
- mUpdatesDisabled = true;
- mPendingSetContentsCall->waitForFinished();
- mUpdatesDisabled = false;
- delete mPendingSetContentsCall.data();
+ QWindow *focusWindow = QGuiApplication::focusWindow();
+ if (!focusWindow) {
+ return;
}
- QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("SetContents"), clipboardContents);
-
- mPendingSetContentsCall = new QDBusPendingCallWatcher(pendingCall, this);
+ QString surfaceId = static_cast<QMirClientWindow*>(focusWindow->handle())->persistentSurfaceId();
+ QDBusPendingCall reply = mContentHub->requestLatestPaste(surfaceId);
+ mClipboardState = SyncingClipboard;
- QObject::connect(mPendingSetContentsCall.data(), &QDBusPendingCallWatcher::finished,
- this, &QMirClientClipboard::onDBusClipboardSetContentsFinished);
+ mPasteReply = new QDBusPendingCallWatcher(reply, this);
+ connect(mPasteReply, &QDBusPendingCallWatcher::finished,
+ this, [this]() {
+ delete mMimeData;
+ mMimeData = mContentHub->paste(*mPasteReply);
+ mClipboardState = SyncedClipboard;
+ mPasteReply->deleteLater();
+ mPasteReply = nullptr;
+ emitChanged(QClipboard::Clipboard);
+ });
}