diff options
Diffstat (limited to 'vcl/qt5/QtClipboard.cxx')
-rw-r--r-- | vcl/qt5/QtClipboard.cxx | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/vcl/qt5/QtClipboard.cxx b/vcl/qt5/QtClipboard.cxx new file mode 100644 index 000000000..5d1a167a9 --- /dev/null +++ b/vcl/qt5/QtClipboard.cxx @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <QtClipboard.hxx> +#include <QtClipboard.moc> + +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +#include <QtWidgets/QApplication> + +#include <QtInstance.hxx> +#include <QtTransferable.hxx> +#include <QtTools.hxx> + +#include <cassert> +#include <map> + +QtClipboard::QtClipboard(const OUString& aModeString, const QClipboard::Mode aMode) + : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard, + css::datatransfer::clipboard::XFlushableClipboard, + XServiceInfo>(m_aMutex) + , m_aClipboardName(aModeString) + , m_aClipboardMode(aMode) + , m_bOwnClipboardChange(false) + , m_bDoClear(false) +{ + assert(isSupported(m_aClipboardMode)); + // DirectConnection guarantees the changed slot runs in the same thread as the QClipboard + connect(QApplication::clipboard(), &QClipboard::changed, this, &QtClipboard::handleChanged, + Qt::DirectConnection); + + // explicitly queue an event, so we can eventually ignore it + connect(this, &QtClipboard::clearClipboard, this, &QtClipboard::handleClearClipboard, + Qt::QueuedConnection); +} + +css::uno::Reference<css::uno::XInterface> QtClipboard::create(const OUString& aModeString) +{ + static const std::map<OUString, QClipboard::Mode> aNameToClipboardMap + = { { "CLIPBOARD", QClipboard::Clipboard }, { "PRIMARY", QClipboard::Selection } }; + + assert(QApplication::clipboard()->thread() == qApp->thread()); + + auto iter = aNameToClipboardMap.find(aModeString); + if (iter != aNameToClipboardMap.end() && isSupported(iter->second)) + return static_cast<cppu::OWeakObject*>(new QtClipboard(aModeString, iter->second)); + SAL_WARN("vcl.qt", "Ignoring unrecognized clipboard type: '" << aModeString << "'"); + return css::uno::Reference<css::uno::XInterface>(); +} + +void QtClipboard::flushClipboard() +{ + auto* pSalInst(GetQtInstance()); + SolarMutexGuard g; + pSalInst->RunInMainThread([this]() { + if (!isOwner(m_aClipboardMode)) + return; + + QClipboard* pClipboard = QApplication::clipboard(); + const QtMimeData* pQtMimeData + = dynamic_cast<const QtMimeData*>(pClipboard->mimeData(m_aClipboardMode)); + assert(pQtMimeData); + + QMimeData* pMimeCopy = nullptr; + if (pQtMimeData && pQtMimeData->deepCopy(&pMimeCopy)) + { + m_bOwnClipboardChange = true; + pClipboard->setMimeData(pMimeCopy, m_aClipboardMode); + m_bOwnClipboardChange = false; + } + }); +} + +css::uno::Reference<css::datatransfer::XTransferable> QtClipboard::getContents() +{ +#if defined(EMSCRIPTEN) + static QMimeData aMimeData; +#endif + osl::MutexGuard aGuard(m_aMutex); + + // if we're the owner, we might have the XTransferable from setContents. but + // maybe a non-LO clipboard change from within LO, like some C'n'P in the + // QFileDialog, might have invalidated m_aContents, so we need to check it too. + if (isOwner(m_aClipboardMode) && m_aContents.is()) + return m_aContents; + + // check if we can still use the shared QtClipboardTransferable + const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode); +#if defined(EMSCRIPTEN) + if (!pMimeData) + pMimeData = &aMimeData; +#endif + if (m_aContents.is()) + { + const auto* pTrans = dynamic_cast<QtClipboardTransferable*>(m_aContents.get()); + assert(pTrans); + if (pTrans && pTrans->mimeData() == pMimeData) + return m_aContents; + } + + m_aContents = new QtClipboardTransferable(m_aClipboardMode, pMimeData); + return m_aContents; +} + +void QtClipboard::handleClearClipboard() +{ + if (!m_bDoClear) + return; + QApplication::clipboard()->clear(m_aClipboardMode); +} + +void QtClipboard::setContents( + const css::uno::Reference<css::datatransfer::XTransferable>& xTrans, + const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) +{ + // it's actually possible to get a non-empty xTrans and an empty xClipboardOwner! + osl::ClearableMutexGuard aGuard(m_aMutex); + + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner); + css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents); + m_aContents = xTrans; + m_aOwner = xClipboardOwner; + + m_bDoClear = !m_aContents.is(); + if (!m_bDoClear) + { + m_bOwnClipboardChange = true; + QApplication::clipboard()->setMimeData(new QtMimeData(m_aContents), m_aClipboardMode); + m_bOwnClipboardChange = false; + } + else + { + assert(!m_aOwner.is()); + Q_EMIT clearClipboard(); + } + + aGuard.clear(); + + // we have to notify only an owner change, since handleChanged can't + // access the previous owner anymore and can just handle lost ownership. + if (xOldOwner.is() && xOldOwner != xClipboardOwner) + xOldOwner->lostOwnership(this, xOldContents); +} + +void QtClipboard::handleChanged(QClipboard::Mode aMode) +{ + if (aMode != m_aClipboardMode) + return; + + osl::ClearableMutexGuard aGuard(m_aMutex); + + // QtWayland will send a second change notification (seemingly without any + // trigger). And any C'n'P operation in the Qt file picker emits a signal, + // with LO still holding the clipboard ownership, but internally having lost + // it. So ignore any signal, which still delivers the internal QtMimeData + // as the clipboard content and is no "advertised" change. + if (!m_bOwnClipboardChange && isOwner(aMode) + && dynamic_cast<const QtMimeData*>(QApplication::clipboard()->mimeData(aMode))) + return; + + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner); + css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents); + // ownership change from LO POV is handled in setContents + if (!m_bOwnClipboardChange) + { + m_aContents.clear(); + m_aOwner.clear(); + } + + std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> aListeners( + m_aListeners); + css::datatransfer::clipboard::ClipboardEvent aEv; + aEv.Contents = getContents(); + + aGuard.clear(); + + if (!m_bOwnClipboardChange && xOldOwner.is()) + xOldOwner->lostOwnership(this, xOldContents); + for (auto const& listener : aListeners) + listener->changedContents(aEv); +} + +OUString QtClipboard::getImplementationName() { return "com.sun.star.datatransfer.QtClipboard"; } + +css::uno::Sequence<OUString> QtClipboard::getSupportedServiceNames() +{ + return { "com.sun.star.datatransfer.clipboard.SystemClipboard" }; +} + +sal_Bool QtClipboard::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +OUString QtClipboard::getName() { return m_aClipboardName; } + +sal_Int8 QtClipboard::getRenderingCapabilities() { return 0; } + +void QtClipboard::addClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::MutexGuard aGuard(m_aMutex); + m_aListeners.push_back(listener); +} + +void QtClipboard::removeClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::MutexGuard aGuard(m_aMutex); + m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), + m_aListeners.end()); +} + +bool QtClipboard::isSupported(const QClipboard::Mode aMode) +{ + const QClipboard* pClipboard = QApplication::clipboard(); + switch (aMode) + { + case QClipboard::Selection: + return pClipboard->supportsSelection(); + + case QClipboard::FindBuffer: + return pClipboard->supportsFindBuffer(); + + case QClipboard::Clipboard: + return true; + } + return false; +} + +bool QtClipboard::isOwner(const QClipboard::Mode aMode) +{ + if (!isSupported(aMode)) + return false; + + const QClipboard* pClipboard = QApplication::clipboard(); + switch (aMode) + { + case QClipboard::Selection: + return pClipboard->ownsSelection(); + + case QClipboard::FindBuffer: + return pClipboard->ownsFindBuffer(); + + case QClipboard::Clipboard: + return pClipboard->ownsClipboard(); + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |