1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31#include <QtCore/QDebug>
32#include <QtCore/QFileInfo>
33#include <QtCore/QDir>
34#include <QtGui/QGuiApplication>
35#include <QtGui/QClipboard>
36#include <QtGui/QImage>
37#include <QtGui/QColor>
38#include "../../../shared/platformclipboard.h"
39
40class tst_QClipboard : public QObject
41{
42 Q_OBJECT
43private slots:
44 void initTestCase();
45#if QT_CONFIG(clipboard)
46 void init();
47#if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_QNX)
48 void copy_exit_paste();
49 void copyImage();
50#endif
51 void capabilityFunctions();
52 void modes();
53 void testSignals();
54 void setMimeData();
55 void clearBeforeSetText();
56#endif
57};
58
59void tst_QClipboard::initTestCase()
60{
61#if !QT_CONFIG(clipboard)
62 QSKIP("This test requires clipboard support");
63#endif
64 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
65 QSKIP("Wayland: Manipulating the clipboard requires real input events. Can't auto test.");
66}
67
68#if QT_CONFIG(clipboard)
69void tst_QClipboard::init()
70{
71#if QT_CONFIG(process)
72 const QString testdataDir = QFileInfo(QFINDTESTDATA("copier")).absolutePath();
73 QVERIFY2(QDir::setCurrent(testdataDir), qPrintable("Could not chdir to " + testdataDir));
74#endif
75}
76
77Q_DECLARE_METATYPE(QClipboard::Mode)
78
79/*
80 Tests that the capability functions are implemented on all
81 platforms.
82*/
83void tst_QClipboard::capabilityFunctions()
84{
85 QClipboard * const clipboard = QGuiApplication::clipboard();
86
87 clipboard->supportsSelection();
88 clipboard->supportsFindBuffer();
89 clipboard->ownsSelection();
90 clipboard->ownsClipboard();
91 clipboard->ownsFindBuffer();
92}
93
94/*
95 Test that text inserted into the clipboard in different modes is
96 kept separate.
97*/
98void tst_QClipboard::modes()
99{
100 QClipboard * const clipboard = QGuiApplication::clipboard();
101
102 if (!PlatformClipboard::isAvailable())
103 QSKIP("Native clipboard not working in this setup");
104
105 const QString defaultMode = "default mode text;";
106 clipboard->setText(defaultMode);
107 QCOMPARE(clipboard->text(), defaultMode);
108
109 if (clipboard->supportsSelection()) {
110 const QString selectionMode = "selection mode text";
111 clipboard->setText(selectionMode, mode: QClipboard::Selection);
112 QCOMPARE(clipboard->text(QClipboard::Selection), selectionMode);
113 QCOMPARE(clipboard->text(), defaultMode);
114 }
115
116 if (clipboard->supportsFindBuffer()) {
117 const QString searchMode = "find mode text";
118 clipboard->setText(searchMode, mode: QClipboard::FindBuffer);
119 QCOMPARE(clipboard->text(QClipboard::FindBuffer), searchMode);
120 QCOMPARE(clipboard->text(), defaultMode);
121 }
122}
123
124// A predicate to be used with a QSignalSpy / QTRY_VERIFY to ensure all delayed
125// notifications are eaten. It waits at least one cycle and returns true when
126// no new signals arrive.
127class EatSignalSpyNotificationsPredicate
128{
129public:
130 explicit EatSignalSpyNotificationsPredicate(QSignalSpy &spy) : m_spy(spy) { reset(); }
131
132 operator bool() const
133 {
134 if (m_timer.elapsed() && !m_spy.count())
135 return true;
136 m_spy.clear();
137 return false;
138 }
139
140 inline void reset() { m_timer.start(); }
141
142private:
143 QSignalSpy &m_spy;
144 QElapsedTimer m_timer;
145};
146
147/*
148 Test that the appropriate signals are emitted when the clipboard
149 contents is changed by calling the qt functions.
150*/
151void tst_QClipboard::testSignals()
152{
153 qRegisterMetaType<QClipboard::Mode>(typeName: "QClipboard::Mode");
154
155 if (!PlatformClipboard::isAvailable())
156 QSKIP("Native clipboard not working in this setup");
157
158 QClipboard * const clipboard = QGuiApplication::clipboard();
159
160 QSignalSpy changedSpy(clipboard, SIGNAL(changed(QClipboard::Mode)));
161 QSignalSpy dataChangedSpy(clipboard, SIGNAL(dataChanged()));
162 // Clipboard notifications are asynchronous with the new AddClipboardFormatListener
163 // in Windows Vista (5.4). Eat away all signals to ensure they don't interfere
164 // with the QTRY_COMPARE below.
165 EatSignalSpyNotificationsPredicate noLeftOverDataChanges(dataChangedSpy);
166 EatSignalSpyNotificationsPredicate noLeftOverChanges(changedSpy);
167 QTRY_VERIFY(noLeftOverChanges && noLeftOverDataChanges);
168
169 QSignalSpy searchChangedSpy(clipboard, SIGNAL(findBufferChanged()));
170 QSignalSpy selectionChangedSpy(clipboard, SIGNAL(selectionChanged()));
171
172 const QString text = "clipboard text;";
173
174 // Test the default mode signal.
175 clipboard->setText(text);
176 QTRY_COMPARE(dataChangedSpy.count(), 1);
177 QCOMPARE(searchChangedSpy.count(), 0);
178 QCOMPARE(selectionChangedSpy.count(), 0);
179 QCOMPARE(changedSpy.count(), 1);
180 QCOMPARE(changedSpy.at(0).count(), 1);
181 QCOMPARE(qvariant_cast<QClipboard::Mode>(changedSpy.at(0).at(0)), QClipboard::Clipboard);
182
183 changedSpy.clear();
184
185 // Test the selection mode signal.
186 if (clipboard->supportsSelection()) {
187 clipboard->setText(text, mode: QClipboard::Selection);
188 QCOMPARE(selectionChangedSpy.count(), 1);
189 QCOMPARE(changedSpy.count(), 1);
190 QCOMPARE(changedSpy.at(0).count(), 1);
191 QCOMPARE(qvariant_cast<QClipboard::Mode>(changedSpy.at(0).at(0)), QClipboard::Selection);
192 } else {
193 QCOMPARE(selectionChangedSpy.count(), 0);
194 }
195 QCOMPARE(dataChangedSpy.count(), 1);
196 QCOMPARE(searchChangedSpy.count(), 0);
197
198 changedSpy.clear();
199
200 // Test the search mode signal.
201 if (clipboard->supportsFindBuffer()) {
202 clipboard->setText(text, mode: QClipboard::FindBuffer);
203 QCOMPARE(searchChangedSpy.count(), 1);
204 QCOMPARE(changedSpy.count(), 1);
205 QCOMPARE(changedSpy.at(0).count(), 1);
206 QCOMPARE(qvariant_cast<QClipboard::Mode>(changedSpy.at(0).at(0)), QClipboard::FindBuffer);
207 } else {
208 QCOMPARE(searchChangedSpy.count(), 0);
209 }
210 QCOMPARE(dataChangedSpy.count(), 1);
211}
212
213#if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_QNX)
214static bool runHelper(const QString &program, const QStringList &arguments, QByteArray *errorMessage)
215{
216#if QT_CONFIG(process)
217 QProcess process;
218 process.setProcessChannelMode(QProcess::ForwardedChannels);
219 process.start(program, arguments);
220 if (!process.waitForStarted()) {
221 *errorMessage = "Unable to start '" + program.toLocal8Bit() + " ': "
222 + process.errorString().toLocal8Bit();
223 return false;
224 }
225
226 // Windows: Due to implementation changes, the event loop needs
227 // to be spun since we ourselves also need to answer the
228 // WM_DRAWCLIPBOARD message as we are in the chain of clipboard
229 // viewers. Check for running before waitForFinished() in case
230 // the process terminated while processEvents() was executed.
231 bool running = true;
232 for (int i = 0; i < 60 && running; ++i) {
233 QGuiApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
234 if (process.state() != QProcess::Running || process.waitForFinished(500))
235 running = false;
236 }
237 if (running) {
238 process.kill();
239 *errorMessage = "Timeout running '" + program.toLocal8Bit() + '\'';
240 return false;
241 }
242 if (process.exitStatus() != QProcess::NormalExit) {
243 *errorMessage = "Process '" + program.toLocal8Bit() + "' crashed.";
244 return false;
245 }
246 if (process.exitCode()) {
247 *errorMessage = "Process '" + program.toLocal8Bit() + "' returns "
248 + QByteArray::number(process.exitCode());
249 return false;
250 }
251 return true;
252#else // QT_CONFIG(process)
253 Q_UNUSED(program)
254 Q_UNUSED(arguments)
255 Q_UNUSED(errorMessage)
256 return false;
257#endif // QT_CONFIG(process)
258}
259
260// Test that pasted text remains on the clipboard after a Qt application exits.
261// This test does not make sense on X11 and embedded, copied data disappears from the clipboard when the application exits
262void tst_QClipboard::copy_exit_paste()
263{
264#if QT_CONFIG(process)
265 // ### It's still possible to test copy/paste - just keep the apps running
266 if (!PlatformClipboard::isAvailable())
267 QSKIP("Native clipboard not working in this setup");
268 const QString stringArgument(QStringLiteral("Test string."));
269 QByteArray errorMessage;
270 QVERIFY2(runHelper(QStringLiteral("copier/copier"), QStringList(stringArgument), &errorMessage),
271 errorMessage.constData());
272#ifdef Q_OS_MAC
273 // The Pasteboard needs a moment to breathe (at least on older Macs).
274 QTest::qWait(100);
275#endif // Q_OS_MAC
276 QVERIFY2(runHelper(QStringLiteral("paster/paster"),
277 QStringList() << QStringLiteral("--text") << stringArgument,
278 &errorMessage),
279 errorMessage.constData());
280#endif // QT_CONFIG(process)
281}
282
283void tst_QClipboard::copyImage()
284{
285#if QT_CONFIG(process)
286 if (!PlatformClipboard::isAvailable())
287 QSKIP("Native clipboard not working in this setup");
288 QImage image(100, 100, QImage::Format_ARGB32);
289 image.fill(QColor(Qt::transparent));
290 image.setPixel(QPoint(1, 0), QColor(Qt::blue).rgba());
291 QGuiApplication::clipboard()->setImage(image);
292#ifdef Q_OS_MACOS
293 // The Pasteboard needs a moment to breathe (at least on older Macs).
294 QTest::qWait(100);
295#endif // Q_OS_MACOS
296 // paster will perform hard-coded checks on the copied image.
297 QByteArray errorMessage;
298 QVERIFY2(runHelper(QStringLiteral("paster/paster"),
299 QStringList(QStringLiteral("--image")), &errorMessage),
300 errorMessage.constData());
301#endif // QT_CONFIG(process)
302}
303
304#endif // Q_OS_WIN || Q_OS_MAC || Q_OS_QNX
305
306void tst_QClipboard::setMimeData()
307{
308 if (!PlatformClipboard::isAvailable())
309 QSKIP("Native clipboard not working in this setup");
310 QMimeData *mimeData = new QMimeData;
311 const QString TestName(QLatin1String("tst_QClipboard::setMimeData() mimeData"));
312 mimeData->setObjectName(TestName);
313
314 QGuiApplication::clipboard()->setMimeData(data: mimeData);
315 QCOMPARE(QGuiApplication::clipboard()->mimeData(), (const QMimeData *)mimeData);
316 QCOMPARE(QGuiApplication::clipboard()->mimeData()->objectName(), TestName);
317
318 // set it to the same data again, it shouldn't delete mimeData (and crash as a result)
319 QGuiApplication::clipboard()->setMimeData(data: mimeData);
320 QCOMPARE(QGuiApplication::clipboard()->mimeData(), (const QMimeData *)mimeData);
321 QCOMPARE(QGuiApplication::clipboard()->mimeData()->objectName(), TestName);
322 QGuiApplication::clipboard()->clear();
323 const QMimeData *appMimeData = QGuiApplication::clipboard()->mimeData();
324 QVERIFY(appMimeData != mimeData || appMimeData->objectName() != TestName);
325
326 // check for crash when using the same mimedata object on several clipboards
327 QMimeData *data = new QMimeData;
328 data->setText("foo");
329
330 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::Clipboard);
331 if (QGuiApplication::clipboard()->supportsSelection())
332 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::Selection);
333 if (QGuiApplication::clipboard()->supportsFindBuffer())
334 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::FindBuffer);
335
336 QSignalSpy spySelection(QGuiApplication::clipboard(), SIGNAL(selectionChanged()));
337 QSignalSpy spyData(QGuiApplication::clipboard(), SIGNAL(dataChanged()));
338 // Clipboard notifications are asynchronous with the new AddClipboardFormatListener
339 // in Windows Vista (5.4). Eat away all signals to ensure they don't interfere
340 // with the QTRY_COMPARE below.
341 EatSignalSpyNotificationsPredicate noLeftOverDataChanges(spyData);
342 QTRY_VERIFY(noLeftOverDataChanges);
343 QSignalSpy spyFindBuffer(QGuiApplication::clipboard(), SIGNAL(findBufferChanged()));
344
345 QGuiApplication::clipboard()->clear(mode: QClipboard::Clipboard);
346 QGuiApplication::clipboard()->clear(mode: QClipboard::Selection); // used to crash on X11
347 QGuiApplication::clipboard()->clear(mode: QClipboard::FindBuffer);
348
349 if (QGuiApplication::clipboard()->supportsSelection())
350 QCOMPARE(spySelection.count(), 1);
351 else
352 QCOMPARE(spySelection.count(), 0);
353
354 if (QGuiApplication::clipboard()->supportsFindBuffer())
355 QCOMPARE(spyFindBuffer.count(), 1);
356 else
357 QCOMPARE(spyFindBuffer.count(), 0);
358
359 QTRY_COMPARE(spyData.count(), 1);
360
361 // an other crash test
362 data = new QMimeData;
363 data->setText("foo");
364
365 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::Clipboard);
366 if (QGuiApplication::clipboard()->supportsSelection())
367 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::Selection);
368 if (QGuiApplication::clipboard()->supportsFindBuffer())
369 QGuiApplication::clipboard()->setMimeData(data, mode: QClipboard::FindBuffer);
370
371 QMimeData *newData = new QMimeData;
372 newData->setText("bar");
373
374 spySelection.clear();
375 noLeftOverDataChanges.reset();
376 QTRY_VERIFY(noLeftOverDataChanges);
377 spyFindBuffer.clear();
378
379 QGuiApplication::clipboard()->setMimeData(data: newData, mode: QClipboard::Clipboard);
380 if (QGuiApplication::clipboard()->supportsSelection())
381 QGuiApplication::clipboard()->setMimeData(data: newData, mode: QClipboard::Selection); // used to crash on X11
382 if (QGuiApplication::clipboard()->supportsFindBuffer())
383 QGuiApplication::clipboard()->setMimeData(data: newData, mode: QClipboard::FindBuffer);
384
385 if (QGuiApplication::clipboard()->supportsSelection())
386 QCOMPARE(spySelection.count(), 1);
387 else
388 QCOMPARE(spySelection.count(), 0);
389
390 if (QGuiApplication::clipboard()->supportsFindBuffer())
391 QCOMPARE(spyFindBuffer.count(), 1);
392 else
393 QCOMPARE(spyFindBuffer.count(), 0);
394
395 QTRY_COMPARE(spyData.count(), 1);
396}
397
398void tst_QClipboard::clearBeforeSetText()
399{
400 QGuiApplication::processEvents();
401
402 if (!PlatformClipboard::isAvailable())
403 QSKIP("Native clipboard not working in this setup");
404
405 const QString text = "tst_QClipboard::clearBeforeSetText()";
406
407 // setText() should work after processEvents()
408 QGuiApplication::clipboard()->setText(text);
409 QCOMPARE(QGuiApplication::clipboard()->text(), text);
410 QGuiApplication::processEvents();
411 QCOMPARE(QGuiApplication::clipboard()->text(), text);
412
413 // same with clear()
414 QGuiApplication::clipboard()->clear();
415 QVERIFY(QGuiApplication::clipboard()->text().isEmpty());
416 QGuiApplication::processEvents();
417 QVERIFY(QGuiApplication::clipboard()->text().isEmpty());
418
419 // setText() again
420 QGuiApplication::clipboard()->setText(text);
421 QCOMPARE(QGuiApplication::clipboard()->text(), text);
422 QGuiApplication::processEvents();
423 QCOMPARE(QGuiApplication::clipboard()->text(), text);
424
425 // clear() immediately followed by setText() should still return the text
426 QGuiApplication::clipboard()->clear();
427 QVERIFY(QGuiApplication::clipboard()->text().isEmpty());
428 QGuiApplication::clipboard()->setText(text);
429 QCOMPARE(QGuiApplication::clipboard()->text(), text);
430 QGuiApplication::processEvents();
431 QCOMPARE(QGuiApplication::clipboard()->text(), text);
432}
433
434#endif // QT_CONFIG(clipboard)
435
436QTEST_MAIN(tst_QClipboard)
437
438#include "tst_qclipboard.moc"
439

source code of qtbase/tests/auto/gui/kernel/qclipboard/tst_qclipboard.cpp