diff options
author | Friedemann Kleint <Friedemann.Kleint@digia.com> | 2014-05-06 17:24:23 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2014-05-15 15:31:07 +0200 |
commit | 6a61a00ddb21e79412e82069dfef50192bfd724d (patch) | |
tree | e922d83a1d27c4a00390d4f0e2a6c4b459c5a544 | |
parent | f618dbdd04d1efa087dfa9a5aa792c315a5bc500 (diff) |
Windows: Use new clipboard API for listening to changes.
The currently used clipboard chain API has various problems with non-
responsive applications and requires checks for hung/debugged applications when
sending on notifications.
The new clipboard format listener API available from Windows Vista onwards
requires less code and does not have these problems, however the change
notifications now arrive asynchronously.
Change the tst_qclipboard to be able to deal with asynchronous change
notifications.
Task-number: QTBUG-38670
Task-number: QTBUG-33492
Change-Id: I3c49e346a34310431c20f3051d12eaabf330a3ad
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
5 files changed, 88 insertions, 15 deletions
diff --git a/src/plugins/platforms/windows/qwindowsclipboard.cpp b/src/plugins/platforms/windows/qwindowsclipboard.cpp index dcfeba12fa..e43b524aa8 100644 --- a/src/plugins/platforms/windows/qwindowsclipboard.cpp +++ b/src/plugins/platforms/windows/qwindowsclipboard.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. @@ -145,7 +145,7 @@ static void cleanClipboardPostRoutine() QWindowsClipboard *QWindowsClipboard::m_instance = 0; QWindowsClipboard::QWindowsClipboard() : - m_data(0), m_clipboardViewer(0), m_nextClipboardViewer(0) + m_data(0), m_clipboardViewer(0), m_nextClipboardViewer(0), m_formatListenerRegistered(false) { QWindowsClipboard::m_instance = this; qAddPostRoutine(cleanClipboardPostRoutine); @@ -178,20 +178,40 @@ void QWindowsClipboard::registerViewer() m_clipboardViewer = QWindowsContext::instance()-> createDummyWindow(QStringLiteral("Qt5ClipboardView"), L"Qt5ClipboardView", qClipboardViewerWndProc, WS_OVERLAPPED); - m_nextClipboardViewer = SetClipboardViewer(m_clipboardViewer); - qCDebug(lcQpaMime) << __FUNCTION__ << "m_clipboardViewer: " << m_clipboardViewer << "next: " << m_nextClipboardViewer; + // 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) { - ChangeClipboardChain(m_clipboardViewer, m_nextClipboardViewer); + if (m_formatListenerRegistered) { + QWindowsContext::user32dll.removeClipboardFormatListener(m_clipboardViewer); + m_formatListenerRegistered = false; + } else { + ChangeClipboardChain(m_clipboardViewer, m_nextClipboardViewer); + m_nextClipboardViewer = 0; + } DestroyWindow(m_clipboardViewer); - m_clipboardViewer = m_nextClipboardViewer = 0; + m_clipboardViewer = 0; } } +// ### FIXME: Qt 6: Remove the clipboard chain handling code and make the +// format listener the default. + static bool isProcessBeingDebugged(HWND hwnd) { DWORD pid = 0; @@ -232,6 +252,8 @@ void QWindowsClipboard::propagateClipboardMessage(UINT message, WPARAM wParam, L 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); @@ -246,14 +268,16 @@ bool QWindowsClipboard::clipboardViewerWndProc(HWND hwnd, UINT message, WPARAM w } } return true; - case WM_DRAWCLIPBOARD: { + 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(); - propagateClipboardMessage(message, wParam, lParam); + if (!m_formatListenerRegistered) + propagateClipboardMessage(message, wParam, lParam); } return true; case WM_DESTROY: diff --git a/src/plugins/platforms/windows/qwindowsclipboard.h b/src/plugins/platforms/windows/qwindowsclipboard.h index 30bc0143f4..61c5967e98 100644 --- a/src/plugins/platforms/windows/qwindowsclipboard.h +++ b/src/plugins/platforms/windows/qwindowsclipboard.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. @@ -88,6 +88,7 @@ private: QWindowsOleDataObject *m_data; HWND m_clipboardViewer; HWND m_nextClipboardViewer; + bool m_formatListenerRegistered; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index ccff2d3e9f..63615dd4bf 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -191,7 +191,8 @@ QWindowsUser32DLL::QWindowsUser32DLL() : updateLayeredWindowIndirect(0), isHungAppWindow(0), registerTouchWindow(0), unregisterTouchWindow(0), - getTouchInputInfo(0), closeTouchInputHandle(0), setProcessDPIAware(0) + getTouchInputInfo(0), closeTouchInputHandle(0), setProcessDPIAware(0), + addClipboardFormatListener(0), removeClipboardFormatListener(0) { } @@ -207,6 +208,11 @@ void QWindowsUser32DLL::init() updateLayeredWindowIndirect = (UpdateLayeredWindowIndirect)(library.resolve("UpdateLayeredWindowIndirect")); isHungAppWindow = (IsHungAppWindow)library.resolve("IsHungAppWindow"); setProcessDPIAware = (SetProcessDPIAware)library.resolve("SetProcessDPIAware"); + + if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) { + addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener"); + removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener"); + } } bool QWindowsUser32DLL::initTouch() diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index d565a5feab..086b968255 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -94,6 +94,8 @@ struct QWindowsUser32DLL typedef BOOL (WINAPI *UpdateLayeredWindowIndirect)(HWND, const UPDATELAYEREDWINDOWINFO *); typedef BOOL (WINAPI *IsHungAppWindow)(HWND); typedef BOOL (WINAPI *SetProcessDPIAware)(); + typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); + typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); // Functions missing in Q_CC_GNU stub libraries. SetLayeredWindowAttributes setLayeredWindowAttributes; @@ -111,6 +113,10 @@ struct QWindowsUser32DLL // Windows Vista onwards SetProcessDPIAware setProcessDPIAware; + + // Clipboard listeners, Windows Vista onwards + AddClipboardFormatListener addClipboardFormatListener; + RemoveClipboardFormatListener removeClipboardFormatListener; }; struct QWindowsShell32DLL diff --git a/tests/auto/gui/kernel/qclipboard/tst_qclipboard.cpp b/tests/auto/gui/kernel/qclipboard/tst_qclipboard.cpp index cf786c1dca..8a2009a601 100644 --- a/tests/auto/gui/kernel/qclipboard/tst_qclipboard.cpp +++ b/tests/auto/gui/kernel/qclipboard/tst_qclipboard.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the test suite of the Qt Toolkit. @@ -134,6 +134,29 @@ void tst_QClipboard::modes() } } +// A predicate to be used with a QSignalSpy / QTRY_VERIFY to ensure all delayed +// notifications are eaten. It waits at least one cycle and returns true when +// no new signals arrive. +class EatSignalSpyNotificationsPredicate +{ +public: + explicit EatSignalSpyNotificationsPredicate(QSignalSpy &spy) : m_spy(spy) { reset(); } + + operator bool() const + { + if (m_timer.elapsed() && !m_spy.count()) + return true; + m_spy.clear(); + return false; + } + + inline void reset() { m_timer.start(); } + +private: + QSignalSpy &m_spy; + QElapsedTimer m_timer; +}; + /* Test that the appropriate signals are emitted when the clipboard contents is changed by calling the qt functions. @@ -149,6 +172,13 @@ void tst_QClipboard::testSignals() QSignalSpy changedSpy(clipboard, SIGNAL(changed(QClipboard::Mode))); QSignalSpy dataChangedSpy(clipboard, SIGNAL(dataChanged())); + // Clipboard notifications are asynchronous with the new AddClipboardFormatListener + // in Windows Vista (5.4). Eat away all signals to ensure they don't interfere + // with the QTRY_COMPARE below. + EatSignalSpyNotificationsPredicate noLeftOverDataChanges(dataChangedSpy); + EatSignalSpyNotificationsPredicate noLeftOverChanges(changedSpy); + QTRY_VERIFY(noLeftOverChanges && noLeftOverDataChanges); + QSignalSpy searchChangedSpy(clipboard, SIGNAL(findBufferChanged())); QSignalSpy selectionChangedSpy(clipboard, SIGNAL(selectionChanged())); @@ -156,7 +186,7 @@ void tst_QClipboard::testSignals() // Test the default mode signal. clipboard->setText(text); - QCOMPARE(dataChangedSpy.count(), 1); + QTRY_COMPARE(dataChangedSpy.count(), 1); QCOMPARE(searchChangedSpy.count(), 0); QCOMPARE(selectionChangedSpy.count(), 0); QCOMPARE(changedSpy.count(), 1); @@ -296,6 +326,11 @@ void tst_QClipboard::setMimeData() QSignalSpy spySelection(QGuiApplication::clipboard(), SIGNAL(selectionChanged())); QSignalSpy spyData(QGuiApplication::clipboard(), SIGNAL(dataChanged())); + // Clipboard notifications are asynchronous with the new AddClipboardFormatListener + // in Windows Vista (5.4). Eat away all signals to ensure they don't interfere + // with the QTRY_COMPARE below. + EatSignalSpyNotificationsPredicate noLeftOverDataChanges(spyData); + QTRY_VERIFY(noLeftOverDataChanges); QSignalSpy spyFindBuffer(QGuiApplication::clipboard(), SIGNAL(findBufferChanged())); QGuiApplication::clipboard()->clear(QClipboard::Clipboard); @@ -312,7 +347,7 @@ void tst_QClipboard::setMimeData() else QCOMPARE(spyFindBuffer.count(), 0); - QCOMPARE(spyData.count(), 1); + QTRY_COMPARE(spyData.count(), 1); // an other crash test data = new QMimeData; @@ -326,7 +361,8 @@ void tst_QClipboard::setMimeData() newData->setText("bar"); spySelection.clear(); - spyData.clear(); + noLeftOverDataChanges.reset(); + QTRY_VERIFY(noLeftOverDataChanges); spyFindBuffer.clear(); QGuiApplication::clipboard()->setMimeData(newData, QClipboard::Clipboard); @@ -343,7 +379,7 @@ void tst_QClipboard::setMimeData() else QCOMPARE(spyFindBuffer.count(), 0); - QCOMPARE(spyData.count(), 1); + QTRY_COMPARE(spyData.count(), 1); } void tst_QClipboard::clearBeforeSetText() |