| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwindowsclipboard.h" |
| #include "qwindowscontext.h" |
| #include "qwindowsole.h" |
| #include "qwindowsmime.h" |
| |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qclipboard.h> |
| #include <QtGui/qcolor.h> |
| #include <QtGui/qimage.h> |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qmimedata.h> |
| #include <QtCore/qstringlist.h> |
| #include <QtCore/qthread.h> |
| #include <QtCore/qvariant.h> |
| #include <QtCore/qurl.h> |
| |
| #include <QtEventDispatcherSupport/private/qwindowsguieventdispatcher_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \class QWindowsClipboard |
| \brief Clipboard implementation. |
| |
| Registers a non-visible clipboard viewer window that |
| receives clipboard events in its own window procedure to be |
| able to receive clipboard-changed events, which |
| QPlatformClipboard needs to emit. That requires housekeeping |
| of the next in the viewer chain. |
| |
| \note The OLE-functions used in this class require OleInitialize(). |
| |
| \internal |
| */ |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| static QDebug operator<<(QDebug d, const QMimeData *mimeData) |
| { |
| QDebugStateSaver saver(d); |
| d.nospace(); |
| d << "QMimeData("; |
| if (mimeData) { |
| const QStringList formats = mimeData->formats(); |
| d << "formats=" << formats.join(u", "); |
| if (mimeData->hasText()) |
| d << ", text=" << mimeData->text(); |
| if (mimeData->hasHtml()) |
| d << ", html=" << mimeData->html(); |
| if (mimeData->hasColor()) |
| d << ", colorData=" << qvariant_cast<QColor>(mimeData->colorData()); |
| if (mimeData->hasImage()) |
| d << ", imageData=" << qvariant_cast<QImage>(mimeData->imageData()); |
| if (mimeData->hasUrls()) |
| d << ", urls=" << mimeData->urls(); |
| } else { |
| d << '0'; |
| } |
| d << ')'; |
| return d; |
| } |
| #endif // !QT_NO_DEBUG_STREAM |
| |
| /*! |
| \class QWindowsClipboardRetrievalMimeData |
| \brief Special mime data class managing delayed retrieval of clipboard data. |
| |
| Implementation of QWindowsInternalMimeDataBase that obtains the |
| IDataObject from the clipboard. |
| |
| \sa QWindowsInternalMimeDataBase, QWindowsClipboard |
| \internal |
| */ |
| |
| IDataObject *QWindowsClipboardRetrievalMimeData::retrieveDataObject() const |
| { |
| enum : int { attempts = 3 }; |
| IDataObject * pDataObj = nullptr; |
| // QTBUG-53979, retry in case the other application has clipboard locked |
| for (int i = 1; i <= attempts; ++i) { |
| if (SUCCEEDED(OleGetClipboard(&pDataObj))) { |
| if (QWindowsContext::verbose > 1) |
| qCDebug(lcQpaMime) << __FUNCTION__ << pDataObj; |
| return pDataObj; |
| } |
| qCWarning(lcQpaMime, i == attempts |
| ? "Unable to obtain clipboard." |
| : "Retrying to obtain clipboard."); |
| QThread::msleep(50); |
| } |
| |
| return nullptr; |
| } |
| |
| void QWindowsClipboardRetrievalMimeData::releaseDataObject(IDataObject *dataObject) const |
| { |
| dataObject->Release(); |
| } |
| |
| extern "C" LRESULT QT_WIN_CALLBACK qClipboardViewerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
| { |
| LRESULT result = 0; |
| if (QWindowsClipboard::instance() |
| && QWindowsClipboard::instance()->clipboardViewerWndProc(hwnd, message, wParam, lParam, &result)) |
| return result; |
| return DefWindowProc(hwnd, message, wParam, lParam); |
| } |
| |
| // QTBUG-36958, ensure the clipboard is flushed before |
| // QGuiApplication is destroyed since OleFlushClipboard() |
| // might query the data again which causes problems |
| // for QMimeData-derived classes using QPixmap/QImage. |
| static void cleanClipboardPostRoutine() |
| { |
| if (QWindowsClipboard *cl = QWindowsClipboard::instance()) |
| cl->cleanup(); |
| } |
| |
| QWindowsClipboard *QWindowsClipboard::m_instance = nullptr; |
| |
| QWindowsClipboard::QWindowsClipboard() |
| { |
| QWindowsClipboard::m_instance = this; |
| qAddPostRoutine(cleanClipboardPostRoutine); |
| } |
| |
| QWindowsClipboard::~QWindowsClipboard() |
| { |
| cleanup(); |
| QWindowsClipboard::m_instance = nullptr; |
| } |
| |
| void QWindowsClipboard::cleanup() |
| { |
| unregisterViewer(); // Should release data if owner. |
| releaseIData(); |
| } |
| |
| void QWindowsClipboard::releaseIData() |
| { |
| if (m_data) { |
| delete m_data->mimeData(); |
| m_data->releaseQt(); |
| m_data->Release(); |
| m_data = nullptr; |
| } |
| } |
| |
| void QWindowsClipboard::registerViewer() |
| { |
| m_clipboardViewer = QWindowsContext::instance()-> |
| createDummyWindow(QStringLiteral("ClipboardView"), L"QtClipboardView", |
| qClipboardViewerWndProc, WS_OVERLAPPED); |
| |
| // Try format listener API (Vista onwards) first. |
| if (QWindowsContext::user32dll.addClipboardFormatListener && QWindowsContext::user32dll.removeClipboardFormatListener) { |
| m_formatListenerRegistered = QWindowsContext::user32dll.addClipboardFormatListener(m_clipboardViewer); |
| if (!m_formatListenerRegistered) |
| qErrnoWarning("AddClipboardFormatListener() failed."); |
| } |
| |
| if (!m_formatListenerRegistered) |
| m_nextClipboardViewer = SetClipboardViewer(m_clipboardViewer); |
| |
| qCDebug(lcQpaMime) << __FUNCTION__ << "m_clipboardViewer:" << m_clipboardViewer |
| << "format listener:" << m_formatListenerRegistered |
| << "next:" << m_nextClipboardViewer; |
| } |
| |
| void QWindowsClipboard::unregisterViewer() |
| { |
| if (m_clipboardViewer) { |
| if (m_formatListenerRegistered) { |
| QWindowsContext::user32dll.removeClipboardFormatListener(m_clipboardViewer); |
| m_formatListenerRegistered = false; |
| } else { |
| ChangeClipboardChain(m_clipboardViewer, m_nextClipboardViewer); |
| m_nextClipboardViewer = nullptr; |
| } |
| DestroyWindow(m_clipboardViewer); |
| m_clipboardViewer = nullptr; |
| } |
| } |
| |
| // ### FIXME: Qt 6: Remove the clipboard chain handling code and make the |
| // format listener the default. |
| |
| static bool isProcessBeingDebugged(HWND hwnd) |
| { |
| DWORD pid = 0; |
| if (!GetWindowThreadProcessId(hwnd, &pid) || !pid) |
| return false; |
| const HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); |
| if (!processHandle) |
| return false; |
| BOOL debugged = FALSE; |
| CheckRemoteDebuggerPresent(processHandle, &debugged); |
| CloseHandle(processHandle); |
| return debugged != FALSE; |
| } |
| |
| void QWindowsClipboard::propagateClipboardMessage(UINT message, WPARAM wParam, LPARAM lParam) const |
| { |
| if (!m_nextClipboardViewer) |
| return; |
| // In rare cases, a clipboard viewer can hang (application crashed, |
| // suspended by a shell prompt 'Select' or debugger). |
| if (IsHungAppWindow(m_nextClipboardViewer)) { |
| qWarning("Cowardly refusing to send clipboard message to hung application..."); |
| return; |
| } |
| // Do not block if the process is being debugged, specifically, if it is |
| // displaying a runtime assert, which is not caught by isHungAppWindow(). |
| if (isProcessBeingDebugged(m_nextClipboardViewer)) |
| PostMessage(m_nextClipboardViewer, message, wParam, lParam); |
| else |
| SendMessage(m_nextClipboardViewer, message, wParam, lParam); |
| } |
| |
| /*! |
| \brief Windows procedure of the clipboard viewer. Emits changed and does |
| housekeeping of the viewer chain. |
| */ |
| |
| bool QWindowsClipboard::clipboardViewerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) |
| { |
| enum { wMClipboardUpdate = 0x031D }; |
| |
| *result = 0; |
| if (QWindowsContext::verbose) |
| qCDebug(lcQpaMime) << __FUNCTION__ << hwnd << message << QWindowsGuiEventDispatcher::windowsMessageName(message); |
| |
| switch (message) { |
| case WM_CHANGECBCHAIN: { |
| const HWND toBeRemoved = reinterpret_cast<HWND>(wParam); |
| if (toBeRemoved == m_nextClipboardViewer) { |
| m_nextClipboardViewer = reinterpret_cast<HWND>(lParam); |
| } else { |
| propagateClipboardMessage(message, wParam, lParam); |
| } |
| } |
| return true; |
| case wMClipboardUpdate: // Clipboard Format listener (Vista onwards) |
| case WM_DRAWCLIPBOARD: { // Clipboard Viewer Chain handling (up to XP) |
| const bool owned = ownsClipboard(); |
| qCDebug(lcQpaMime) << "Clipboard changed owned " << owned; |
| emitChanged(QClipboard::Clipboard); |
| // clean up the clipboard object if we no longer own the clipboard |
| if (!owned && m_data) |
| releaseIData(); |
| if (!m_formatListenerRegistered) |
| propagateClipboardMessage(message, wParam, lParam); |
| } |
| return true; |
| case WM_DESTROY: |
| // Recommended shutdown |
| if (ownsClipboard()) { |
| qCDebug(lcQpaMime) << "Clipboard owner on shutdown, releasing."; |
| OleFlushClipboard(); |
| releaseIData(); |
| } |
| return true; |
| } // switch (message) |
| return false; |
| } |
| |
| QMimeData *QWindowsClipboard::mimeData(QClipboard::Mode mode) |
| { |
| qCDebug(lcQpaMime) << __FUNCTION__ << mode; |
| if (mode != QClipboard::Clipboard) |
| return nullptr; |
| if (ownsClipboard()) |
| return m_data->mimeData(); |
| return &m_retrievalData; |
| } |
| |
| void QWindowsClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode) |
| { |
| qCDebug(lcQpaMime) << __FUNCTION__ << mode << mimeData; |
| if (mode != QClipboard::Clipboard) |
| return; |
| |
| const bool newData = !m_data || m_data->mimeData() != mimeData; |
| if (newData) { |
| releaseIData(); |
| if (mimeData) |
| m_data = new QWindowsOleDataObject(mimeData); |
| } |
| |
| HRESULT src = S_FALSE; |
| int attempts = 0; |
| for (; attempts < 3; ++attempts) { |
| src = OleSetClipboard(m_data); |
| if (src != CLIPBRD_E_CANT_OPEN || QWindowsContext::isSessionLocked()) |
| break; |
| QThread::msleep(100); |
| } |
| |
| if (src != S_OK) { |
| QString mimeDataFormats = mimeData ? |
| mimeData->formats().join(u", ") : QString(QStringLiteral("NULL")); |
| qErrnoWarning("OleSetClipboard: Failed to set mime data (%s) on clipboard: %s", |
| qPrintable(mimeDataFormats), |
| QWindowsContext::comErrorString(src).constData()); |
| releaseIData(); |
| return; |
| } |
| } |
| |
| void QWindowsClipboard::clear() |
| { |
| const HRESULT src = OleSetClipboard(nullptr); |
| if (src != S_OK) |
| qErrnoWarning("OleSetClipboard: Failed to clear the clipboard: 0x%lx", src); |
| } |
| |
| bool QWindowsClipboard::supportsMode(QClipboard::Mode mode) const |
| { |
| return mode == QClipboard::Clipboard; |
| } |
| |
| // Need a non-virtual in destructor. |
| bool QWindowsClipboard::ownsClipboard() const |
| { |
| return m_data && OleIsCurrentClipboard(m_data) == S_OK; |
| } |
| |
| bool QWindowsClipboard::ownsMode(QClipboard::Mode mode) const |
| { |
| const bool result = mode == QClipboard::Clipboard ? |
| ownsClipboard() : false; |
| qCDebug(lcQpaMime) << __FUNCTION__ << mode << result; |
| return result; |
| } |
| |
| QT_END_NAMESPACE |