diff options
Diffstat (limited to 'src/plugins/platforms/mirclient/qmirclientclipboard.cpp')
-rw-r--r-- | src/plugins/platforms/mirclient/qmirclientclipboard.cpp | 303 |
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); + }); } |