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