Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsclipboard.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "qwindowsole.h"
7
8#include <QtGui/qguiapplication.h>
9#include <QtGui/qclipboard.h>
10#include <QtGui/qcolor.h>
11#include <QtGui/qimage.h>
12
13#include <QtCore/qdebug.h>
14#include <QtCore/qmimedata.h>
15#include <QtCore/qstringlist.h>
16#include <QtCore/qthread.h>
17#include <QtCore/qvariant.h>
18#include <QtCore/qurl.h>
19#include <QtCore/private/qsystemerror_p.h>
20
21#include <QtGui/private/qwindowsguieventdispatcher_p.h>
22
24
25/*!
26 \class QWindowsClipboard
27 \brief Clipboard implementation.
28
29 Registers a non-visible clipboard viewer window that
30 receives clipboard events in its own window procedure to be
31 able to receive clipboard-changed events, which
32 QPlatformClipboard needs to emit. That requires housekeeping
33 of the next in the viewer chain.
34
35 \note The OLE-functions used in this class require OleInitialize().
36
37 \internal
38*/
39
40#ifndef QT_NO_DEBUG_STREAM
41static QDebug operator<<(QDebug d, const QMimeData *mimeData)
42{
43 QDebugStateSaver saver(d);
44 d.nospace();
45 d << "QMimeData(";
46 if (mimeData) {
47 const QStringList formats = mimeData->formats();
48 d << "formats=" << formats.join(u", ");
49 if (mimeData->hasText())
50 d << ", text=" << mimeData->text();
51 if (mimeData->hasHtml())
52 d << ", html=" << mimeData->html();
53 if (mimeData->hasColor())
54 d << ", colorData=" << qvariant_cast<QColor>(mimeData->colorData());
55 if (mimeData->hasImage())
56 d << ", imageData=" << qvariant_cast<QImage>(mimeData->imageData());
57 if (mimeData->hasUrls())
58 d << ", urls=" << mimeData->urls();
59 } else {
60 d << "0x0";
61 }
62 d << ')';
63 return d;
64}
65#endif // !QT_NO_DEBUG_STREAM
66
67/*!
68 \class QWindowsClipboardRetrievalMimeData
69 \brief Special mime data class managing delayed retrieval of clipboard data.
70
71 Implementation of QWindowsInternalMimeDataBase that obtains the
72 IDataObject from the clipboard.
73
74 \sa QWindowsInternalMimeDataBase, QWindowsClipboard
75 \internal
76*/
77
79{
80 enum : int { attempts = 3 };
81 IDataObject * pDataObj = nullptr;
82 // QTBUG-53979, retry in case the other application has clipboard locked
83 for (int i = 1; i <= attempts; ++i) {
84 if (SUCCEEDED(OleGetClipboard(&pDataObj))) {
85 if (QWindowsContext::verbose > 1)
86 qCDebug(lcQpaMime) << __FUNCTION__ << pDataObj;
87 return pDataObj;
88 }
89 qCWarning(lcQpaMime, i == attempts
90 ? "Unable to obtain clipboard."
91 : "Retrying to obtain clipboard.");
92 QThread::msleep(50);
93 }
94
95 return nullptr;
96}
97
98void QWindowsClipboardRetrievalMimeData::releaseDataObject(IDataObject *dataObject) const
99{
100 dataObject->Release();
101}
102
103LRESULT QT_WIN_CALLBACK qClipboardViewerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
104{
105 LRESULT result = 0;
106 if (QWindowsClipboard::instance()
107 && QWindowsClipboard::instance()->clipboardViewerWndProc(hwnd, message, wParam, lParam, &result))
108 return result;
109 return DefWindowProc(hwnd, message, wParam, lParam);
110}
111
112// QTBUG-36958, ensure the clipboard is flushed before
113// QGuiApplication is destroyed since OleFlushClipboard()
114// might query the data again which causes problems
115// for QMimeData-derived classes using QPixmap/QImage.
121
122QWindowsClipboard *QWindowsClipboard::m_instance = nullptr;
123
124QWindowsClipboard::QWindowsClipboard()
125{
126 QWindowsClipboard::m_instance = this;
127 qAddPostRoutine(cleanClipboardPostRoutine);
128}
129
131{
132 cleanup();
133 QWindowsClipboard::m_instance = nullptr;
134}
135
137{
138 unregisterViewer(); // Should release data if owner.
139 releaseIData();
140}
141
142void QWindowsClipboard::releaseIData()
143{
144 if (m_data) {
145 delete m_data->mimeData();
146 m_data->releaseQt();
147 m_data->Release();
148 m_data = nullptr;
149 }
150}
151
153{
154 m_clipboardViewer = QWindowsContext::instance()->
155 createDummyWindow(QStringLiteral("ClipboardView"), L"QtClipboardView",
156 qClipboardViewerWndProc, WS_OVERLAPPED);
157
158 m_formatListenerRegistered = AddClipboardFormatListener(m_clipboardViewer);
159 if (!m_formatListenerRegistered)
160 qErrnoWarning("AddClipboardFormatListener() failed.");
161
162 if (!m_formatListenerRegistered)
163 m_nextClipboardViewer = SetClipboardViewer(m_clipboardViewer);
164
165 qCDebug(lcQpaMime) << __FUNCTION__ << "m_clipboardViewer:" << m_clipboardViewer
166 << "format listener:" << m_formatListenerRegistered
167 << "next:" << m_nextClipboardViewer;
168}
169
170void QWindowsClipboard::unregisterViewer()
171{
172 if (m_clipboardViewer) {
173 if (m_formatListenerRegistered) {
174 RemoveClipboardFormatListener(m_clipboardViewer);
175 m_formatListenerRegistered = false;
176 } else {
177 ChangeClipboardChain(m_clipboardViewer, m_nextClipboardViewer);
178 m_nextClipboardViewer = nullptr;
179 }
180 DestroyWindow(m_clipboardViewer);
181 m_clipboardViewer = nullptr;
182 }
183}
184
185// ### FIXME: Qt 6: Remove the clipboard chain handling code and make the
186// format listener the default.
187
188static bool isProcessBeingDebugged(HWND hwnd)
189{
190 DWORD pid = 0;
191 if (!GetWindowThreadProcessId(hwnd, &pid) || !pid)
192 return false;
193 const HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
194 if (!processHandle)
195 return false;
196 BOOL debugged = FALSE;
197 CheckRemoteDebuggerPresent(processHandle, &debugged);
198 CloseHandle(processHandle);
199 return debugged != FALSE;
200}
201
202void QWindowsClipboard::propagateClipboardMessage(UINT message, WPARAM wParam, LPARAM lParam) const
203{
204 if (!m_nextClipboardViewer)
205 return;
206 // In rare cases, a clipboard viewer can hang (application crashed,
207 // suspended by a shell prompt 'Select' or debugger).
208 if (IsHungAppWindow(m_nextClipboardViewer)) {
209 qWarning("Cowardly refusing to send clipboard message to hung application...");
210 return;
211 }
212 // Do not block if the process is being debugged, specifically, if it is
213 // displaying a runtime assert, which is not caught by isHungAppWindow().
214 if (isProcessBeingDebugged(m_nextClipboardViewer))
215 PostMessage(m_nextClipboardViewer, message, wParam, lParam);
216 else
217 SendMessage(m_nextClipboardViewer, message, wParam, lParam);
218}
219
220/*!
221 \brief Windows procedure of the clipboard viewer. Emits changed and does
222 housekeeping of the viewer chain.
223*/
224
225bool QWindowsClipboard::clipboardViewerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result)
226{
227 enum { wMClipboardUpdate = 0x031D };
228
229 *result = 0;
230 if (QWindowsContext::verbose)
231 qCDebug(lcQpaMime) << __FUNCTION__ << hwnd << message << QWindowsGuiEventDispatcher::windowsMessageName(message);
232
233 switch (message) {
234 case WM_CHANGECBCHAIN: {
235 const HWND toBeRemoved = reinterpret_cast<HWND>(wParam);
236 if (toBeRemoved == m_nextClipboardViewer) {
237 m_nextClipboardViewer = reinterpret_cast<HWND>(lParam);
238 } else {
239 propagateClipboardMessage(message, wParam, lParam);
240 }
241 }
242 return true;
243 case wMClipboardUpdate: // Clipboard Format listener (Vista onwards)
244 case WM_DRAWCLIPBOARD: { // Clipboard Viewer Chain handling (up to XP)
245 const bool owned = ownsClipboard();
246 qCDebug(lcQpaMime) << "Clipboard changed owned " << owned;
247 emitChanged(QClipboard::Clipboard);
248 // clean up the clipboard object if we no longer own the clipboard
249 if (!owned && m_data)
250 releaseIData();
251 if (!m_formatListenerRegistered)
252 propagateClipboardMessage(message, wParam, lParam);
253 }
254 return true;
255 case WM_DESTROY:
256 // Recommended shutdown
257 if (ownsClipboard()) {
258 qCDebug(lcQpaMime) << "Clipboard owner on shutdown, releasing.";
259 OleFlushClipboard();
260 releaseIData();
261 }
262 return true;
263 } // switch (message)
264 return false;
265}
266
267QMimeData *QWindowsClipboard::mimeData(QClipboard::Mode mode)
268{
269 qCDebug(lcQpaMime) << __FUNCTION__ << mode;
270 if (mode != QClipboard::Clipboard)
271 return nullptr;
272 if (ownsClipboard())
273 return m_data->mimeData();
274 return &m_retrievalData;
275}
276
277void QWindowsClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
278{
279 qCDebug(lcQpaMime) << __FUNCTION__ << mode << mimeData;
280 if (mode != QClipboard::Clipboard)
281 return;
282
283 const bool newData = !m_data || m_data->mimeData() != mimeData;
284 if (newData) {
285 releaseIData();
286 if (mimeData)
287 m_data = new QWindowsOleDataObject(mimeData);
288 }
289
290 HRESULT src = S_FALSE;
291 int attempts = 0;
292 for (; attempts < 3; ++attempts) {
293 src = OleSetClipboard(m_data);
294 if (src != CLIPBRD_E_CANT_OPEN || QWindowsContext::isSessionLocked())
295 break;
296 QThread::msleep(100);
297 }
298
299 if (src != S_OK) {
300 QString mimeDataFormats = mimeData ?
301 mimeData->formats().join(u", ") : QString(QStringLiteral("NULL"));
302 qErrnoWarning("OleSetClipboard: Failed to set mime data (%s) on clipboard: %s",
303 qPrintable(mimeDataFormats),
304 qPrintable(QSystemError::windowsComString(src)));
305 releaseIData();
306 return;
307 }
308}
309
310void QWindowsClipboard::clear()
311{
312 const HRESULT src = OleSetClipboard(nullptr);
313 if (src != S_OK)
314 qErrnoWarning("OleSetClipboard: Failed to clear the clipboard: 0x%lx", src);
315}
316
317bool QWindowsClipboard::supportsMode(QClipboard::Mode mode) const
318{
319 return mode == QClipboard::Clipboard;
320}
321
322// Need a non-virtual in destructor.
323bool QWindowsClipboard::ownsClipboard() const
324{
325 return m_data && OleIsCurrentClipboard(m_data) == S_OK;
326}
327
328bool QWindowsClipboard::ownsMode(QClipboard::Mode mode) const
329{
330 const bool result = mode == QClipboard::Clipboard ?
331 ownsClipboard() : false;
332 qCDebug(lcQpaMime) << __FUNCTION__ << mode << result;
333 return result;
334}
335
336QT_END_NAMESPACE
Special mime data class managing delayed retrieval of clipboard data.
void releaseDataObject(IDataObject *) const override
IDataObject * retrieveDataObject() const override
Clipboard implementation.
bool supportsMode(QClipboard::Mode mode) const override
QMimeData * mimeData(QClipboard::Mode mode=QClipboard::Clipboard) override
bool ownsMode(QClipboard::Mode mode) const override
static QWindowsClipboard * instance()
bool clipboardViewerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result)
Windows procedure of the clipboard viewer.
OLE data container.
Definition qwindowsole.h:22
QWindowsOleDataObject(QMimeData *mimeData)
QMimeData * mimeData() const
static bool isProcessBeingDebugged(HWND hwnd)
static void cleanClipboardPostRoutine()