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>
bb10
Friedemann Kleint 2014-05-06 17:24:23 +02:00 committed by The Qt Project
parent f618dbdd04
commit 6a61a00ddb
5 changed files with 88 additions and 15 deletions

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()