blob: b23904e97804410da42d82b924b31c42dbd996b1 [file] [log] [blame]
/****************************************************************************
**
** 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