1// Copyright (C) 2018 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
4#include <private/qguiapplication_p.h>
5#include <private/qeventpoint_p.h>
6
7#include <qpa/qplatformintegration.h>
8
9#include "qtestsupport_gui.h"
10#include "qwindow.h"
11
12#include <QtCore/qtestsupport_core.h>
13#include <QtCore/qthread.h>
14#include <QtCore/QDebug>
15
16#if QT_CONFIG(test_gui)
17#include <QtCore/qloggingcategory.h>
18#include <private/qinputdevicemanager_p.h>
19#include <private/qeventpoint_p.h>
20#include <private/qhighdpiscaling_p.h>
21#endif // #if QT_CONFIG(test_gui)
22
23QT_BEGIN_NAMESPACE
24
25/*!
26 \since 5.0
27 \overload
28
29 The \a timeout is in milliseconds.
30*/
31bool QTest::qWaitForWindowActive(QWindow *window, int timeout)
32{
33 return qWaitForWindowActive(window, timeout: QDeadlineTimer{timeout, Qt::TimerType::PreciseTimer});
34}
35
36/*!
37 \since 6.10
38
39 Returns \c true, if \a window is active within \a timeout. Otherwise returns \c false.
40
41 The method is useful in tests that call QWindow::show() and rely on the window actually being
42 active (i.e. being visible and having focus) before proceeding.
43
44 \note The method will time out and return \c false if another window prevents \a window from
45 becoming active.
46
47 \note Since focus is an exclusive property, \a window may loose its focus to another window at
48 any time - even after the method has returned \c true.
49
50 \sa qWaitForWindowExposed(), qWaitForWindowFocused(), QWindow::isActive()
51*/
52bool QTest::qWaitForWindowActive(QWindow *window, QDeadlineTimer timeout)
53{
54 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))) {
55 qWarning() << "qWaitForWindowActive was called on a platform that doesn't support window"
56 << "activation. This means there is an error in the test and it should either"
57 << "check for the WindowActivation platform capability before calling"
58 << "qWaitForWindowActivate, use qWaitForWindowExposed instead, or skip the test."
59 << "Falling back to qWaitForWindowExposed.";
60 return qWaitForWindowExposed(window, timeout);
61 }
62 return QTest::qWaitFor(predicate: [wp = QPointer(window)]() {
63 if (QWindow *w = wp.data(); !w)
64 return false;
65 else
66 return w->isActive();
67 }, deadline: timeout);
68}
69
70/*!
71 \since 6.10
72 \overload
73
74 This function uses the default timeout of 5 seconds.
75*/
76bool QTest::qWaitForWindowActive(QWindow *window)
77{
78 return qWaitForWindowActive(window, timeout: Internal::defaultTryTimeout);
79}
80
81/*!
82 \since 6.7
83
84 Returns \c true, if \a window is the focus window within \a timeout. Otherwise returns \c false.
85
86 The method is useful in tests that call QWindow::show() and rely on the window
87 having focus (for receiving keyboard events e.g.) before proceeding.
88
89 \note The method will time out and return \c false if another window prevents \a window from
90 becoming focused.
91
92 \note Since focus is an exclusive property, \a window may loose its focus to another window at
93 any time - even after the method has returned \c true.
94
95 \sa qWaitForWindowExposed(), qWaitForWindowActive(), QGuiApplication::focusWindow()
96*/
97Q_GUI_EXPORT bool QTest::qWaitForWindowFocused(QWindow *window, QDeadlineTimer timeout)
98{
99 return QTest::qWaitFor(predicate: [wp = QPointer(window)]() {
100 if (QWindow *w = wp.data(); !w)
101 return false;
102 else
103 return qGuiApp->focusWindow() == w;
104 }, deadline: timeout);
105}
106
107/*!
108 \since 6.10
109 \overload
110
111 This function uses the default timeout of 5 seconds.
112*/
113bool QTest::qWaitForWindowFocused(QWindow *window)
114{
115 return qWaitForWindowFocused(window, timeout: Internal::defaultTryTimeout);
116}
117
118/*!
119 \since 5.0
120 \overload
121
122 The \a timeout is in milliseconds.
123*/
124bool QTest::qWaitForWindowExposed(QWindow *window, int timeout)
125{
126 return qWaitForWindowExposed(window, timeout: std::chrono::milliseconds(timeout));
127}
128
129/*!
130 \since 6.10
131
132 Returns \c true, if \a window is exposed within \a timeout. Otherwise returns \c false.
133
134 The method is useful in tests that call QWindow::show() and rely on the window actually being
135 being visible before proceeding.
136
137 \note A window mapped to screen may still not be considered exposed, if the window client area is
138 not visible, e.g. because it is completely covered by other windows.
139 In such cases, the method will time out and return \c false.
140
141 \sa qWaitForWindowActive(), QWindow::isExposed()
142*/
143bool QTest::qWaitForWindowExposed(QWindow *window, QDeadlineTimer timeout)
144{
145 return QTest::qWaitFor(predicate: [wp = QPointer(window)]() {
146 if (QWindow *w = wp.data(); !w)
147 return false;
148 else
149 return w->isExposed();
150 }, deadline: timeout);
151}
152
153/*!
154 \since 6.10
155 \overload
156
157 This function uses the default timeout of 5 seconds.
158*/
159bool QTest::qWaitForWindowExposed(QWindow *window)
160{
161 return qWaitForWindowExposed(window, timeout: Internal::defaultTryTimeout);
162}
163
164namespace QTest {
165
166QTouchEventSequence::~QTouchEventSequence()
167{
168 if (commitWhenDestroyed)
169 QTouchEventSequence::commit();
170}
171QTouchEventSequence& QTouchEventSequence::press(int touchId, const QPoint &pt, QWindow *window)
172{
173 auto &p = point(touchId);
174 QMutableEventPoint::setGlobalPosition(p, arg: mapToScreen(window, pt));
175 QMutableEventPoint::setState(p, arg: QEventPoint::State::Pressed);
176 return *this;
177}
178QTouchEventSequence& QTouchEventSequence::move(int touchId, const QPoint &pt, QWindow *window)
179{
180 auto &p = point(touchId);
181 QMutableEventPoint::setGlobalPosition(p, arg: mapToScreen(window, pt));
182 QMutableEventPoint::setState(p, arg: QEventPoint::State::Updated);
183 return *this;
184}
185QTouchEventSequence& QTouchEventSequence::release(int touchId, const QPoint &pt, QWindow *window)
186{
187 auto &p = point(touchId);
188 QMutableEventPoint::setGlobalPosition(p, arg: mapToScreen(window, pt));
189 QMutableEventPoint::setState(p, arg: QEventPoint::State::Released);
190 return *this;
191}
192QTouchEventSequence& QTouchEventSequence::stationary(int touchId)
193{
194 auto &p = pointOrPreviousPoint(touchId);
195 QMutableEventPoint::setState(p, arg: QEventPoint::State::Stationary);
196 return *this;
197}
198
199bool QTouchEventSequence::commit(bool processEvents)
200{
201 if (points.isEmpty())
202 return false;
203 QThread::sleep(nsec: std::chrono::milliseconds{1});
204 bool ret = false;
205 if (targetWindow)
206 ret = qt_handleTouchEventv2(w: targetWindow, device, points: points.values());
207 if (processEvents)
208 QCoreApplication::processEvents();
209 previousPoints = points;
210 points.clear();
211 return ret;
212}
213
214QTouchEventSequence::QTouchEventSequence(QWindow *window, QPointingDevice *aDevice, bool autoCommit)
215 : targetWindow(window), device(aDevice), commitWhenDestroyed(autoCommit)
216{
217}
218
219QPoint QTouchEventSequence::mapToScreen(QWindow *window, const QPoint &pt)
220{
221 if (window)
222 return window->mapToGlobal(pos: pt);
223 return targetWindow ? targetWindow->mapToGlobal(pos: pt) : pt;
224}
225
226QEventPoint &QTouchEventSequence::point(int touchId)
227{
228 if (!points.contains(key: touchId))
229 points[touchId] = QEventPoint(touchId);
230 return points[touchId];
231}
232
233QEventPoint &QTouchEventSequence::pointOrPreviousPoint(int touchId)
234{
235 if (!points.contains(key: touchId)) {
236 if (previousPoints.contains(key: touchId))
237 points[touchId] = previousPoints.value(key: touchId);
238 else
239 points[touchId] = QEventPoint(touchId);
240 }
241 return points[touchId];
242}
243
244} // namespace QTest
245
246//
247// W A R N I N G
248// -------------
249//
250// The QtGuiTest namespace is not part of the Qt API. It exists purely as an
251// implementation detail. It may change from version to version without notice,
252// or even be removed.
253//
254// We mean it.
255//
256#if QT_CONFIG(test_gui)
257Q_STATIC_LOGGING_CATEGORY(lcQtGuiTest, "qt.gui.test");
258#define deb qCDebug(lcQtGuiTest)
259
260/*!
261 \internal
262 \return the application's input device manager.
263 \return nullptr and log error, if the application hasn't been initialized.
264 */
265static QInputDeviceManager *inputDeviceManager()
266{
267 if (auto *idm = QGuiApplicationPrivate::inputDeviceManager())
268 return idm;
269
270 deb << "No input device manager present.";
271 return nullptr;
272}
273
274/*!
275 \internal
276 Synthesize keyboard modifier action by passing \a modifiers
277 to the application's input device manager.
278 */
279void QtGuiTest::setKeyboardModifiers(Qt::KeyboardModifiers modifiers)
280{
281 auto *idm = inputDeviceManager();
282 if (Q_UNLIKELY(!idm))
283 return;
284
285 idm->setKeyboardModifiers(modifiers);
286 deb << "Keyboard modifiers synthesized:" << modifiers;
287}
288
289/*!
290 \internal
291 Synthesize user-initiated mouse positioning by passing \a position
292 to the application's input device manager.
293 */
294void QtGuiTest::setCursorPosition(const QPoint &position)
295{
296 auto *idm = inputDeviceManager();
297 if (Q_UNLIKELY(!idm))
298 return;
299
300 idm->setCursorPos(position);
301 deb << "Mouse curser set to" << position;
302}
303
304/*!
305 \internal
306 Synthesize an extended \a key event of \a type, with \a modifiers, \a nativeScanCode,
307 \a nativeVirtualKey and \a text on application level.
308 Log whether the synthesizing has been successful.
309
310 \note
311 The application is expected to propagate the extended key event to its focus window,
312 if one exists.
313 */
314void QtGuiTest::synthesizeExtendedKeyEvent(QEvent::Type type, int key, Qt::KeyboardModifiers modifiers,
315 quint32 nativeScanCode, quint32 nativeVirtualKey,
316 const QString &text)
317{
318 Q_ASSERT_X((type == QEvent::KeyPress
319 || type == QEvent::KeyRelease),
320 Q_FUNC_INFO,
321 "called with invalid QEvent type");
322
323 deb << "Synthesizing key event:" << type << Qt::Key(key) << modifiers << text;
324
325 if (QWindowSystemInterface::handleExtendedKeyEvent(window: nullptr, type, key, modifiers,
326 nativeScanCode, nativeVirtualKey,
327 nativeModifiers: modifiers, text, /* autorep = */ false,
328 /* count = */ 0)) {
329
330 // If the key event is a shortcut, it may cause other events to be posted.
331 // => process those.
332 QCoreApplication::sendPostedEvents();
333 deb << "(success)";
334 } else {
335 deb << "(failure)";
336 }
337}
338
339/*!
340 \internal
341 Synthesize a key event \a k of type \a t, with modifiers \a mods, \a text,
342 \a autorep and \a count on application level.
343 Log whether the synthesizing has been successful.
344
345 \note
346 The application is expected to propagate the key event to its focus window,
347 if one exists.
348 */
349bool QtGuiTest::synthesizeKeyEvent(QWindow *window, QEvent::Type t, int k, Qt::KeyboardModifiers mods,
350 const QString & text, bool autorep,
351 ushort count)
352{
353 Q_ASSERT_X((t == QEvent::KeyPress
354 || t == QEvent::KeyRelease),
355 Q_FUNC_INFO,
356 "called with invalid QEvent type");
357
358 deb << "Synthesizing key event:" << t << Qt::Key(k) << mods << text;
359
360 bool result = QWindowSystemInterface::handleKeyEvent(window, t, k, mods, text, autorep, count);
361 if (result) {
362 // If the key event is a shortcut, it may cause other events to be posted.
363 // => process those.
364 QCoreApplication::sendPostedEvents();
365 deb << "(success)";
366 } else {
367 deb << "(failure)";
368 }
369
370 return result;
371}
372
373/*!
374 \internal
375 Synthesize a mouse event of \a type, with \a button at \a position at application level.
376 Respect \a state and \a modifiers.
377
378 The application is expected to
379 \list
380 \li propagate the mouse event to its focus window,
381 if one exists.
382 \li convert a click/release squence into a double click.
383 \endlist
384
385 \note
386 QEvent::MouseButtonDoubleClick can't be explicitly synthesized.
387 */
388void QtGuiTest::synthesizeMouseEvent(const QPointF &position, Qt::MouseButtons state,
389 Qt::MouseButton button, QEvent::Type type,
390 Qt::KeyboardModifiers modifiers)
391{
392 Q_ASSERT_X((type == QEvent::MouseButtonPress
393 || type == QEvent::MouseButtonRelease
394 || type == QEvent::MouseMove),
395 Q_FUNC_INFO,
396 "called with invalid QEvent type");
397
398 deb << "Synthesizing mouse event:" << type << position << button << modifiers;
399
400 if (QWindowSystemInterface::handleMouseEvent(window: nullptr, local: position, global: position, state, button,
401 type, mods: modifiers, source: Qt::MouseEventNotSynthesized)) {
402 // If the mouse event reacts to a shortcut, it may cause other events to be posted.
403 // => process those.
404 QCoreApplication::processEvents();
405 QCoreApplication::sendPostedEvents();
406
407 deb << "(success)";
408 } else {
409 deb << "(failure)";
410 }
411}
412
413/*!
414 \internal
415 Synthesize a wheel event with \a modifiers and \a rollCount representing the number of
416 roll unit on application level.
417
418 \note
419 The application is expected to handle the wheel event, or propagate it
420 to its focus window, if one exists.
421 */
422void QtGuiTest::synthesizeWheelEvent(int rollCount, Qt::KeyboardModifiers modifiers)
423{
424 deb << "Synthesizing wheel event:" << rollCount << modifiers;
425
426 QPoint position = QCursor::pos();
427 if (QWindowSystemInterface::handleWheelEvent(window: nullptr, local: position, global: position,
428 pixelDelta: QPoint(), angleDelta: QPoint(0, -rollCount), mods: modifiers)) {
429
430 // It's unlikely that a shortcut relates to a subsequent wheel event.
431 // But it's not harmful, to send posted events here.
432 QCoreApplication::sendPostedEvents();
433 deb << "(success)";
434 } else {
435 deb << "(failure)";
436 }
437}
438
439/*!
440 \internal
441 \return the number of milliseconds since the QElapsedTimer
442 eventTime was last started.
443*/
444qint64 QtGuiTest::eventTimeElapsed()
445{
446 return QWindowSystemInterfacePrivate::eventTime.elapsed();
447}
448
449/*!
450 \internal
451 Post fake window activation with \a window representing the
452 fake window being activated.
453*/
454void QtGuiTest::postFakeWindowActivation(QWindow *window)
455{
456 Q_ASSERT_X(window,
457 Q_FUNC_INFO,
458 "called with nullptr");
459
460 deb << "Posting fake window activation:" << window;
461
462 QWindowSystemInterfacePrivate::FocusWindowEvent e(window, Qt::OtherFocusReason);
463 QGuiApplicationPrivate::processWindowSystemEvent(e: &e);
464 QWindowSystemInterface::handleFocusWindowChanged(window);
465}
466
467/*!
468 \internal
469 \return native \a window position from \a value.
470*/
471QPoint QtGuiTest::toNativePixels(const QPoint &value, const QWindow *window)
472{
473 Q_ASSERT_X(window,
474 Q_FUNC_INFO,
475 "called with nullptr");
476
477 deb << "Calculating native pixels: " << value << window;
478 return QHighDpi::toNativePixels<QPoint, QWindow>(value, context: window);
479}
480
481/*!
482 \internal
483 \return native \a window rectangle from \a value.
484*/
485QRect QtGuiTest::toNativePixels(const QRect &value, const QWindow *window)
486{
487 Q_ASSERT_X(window,
488 Q_FUNC_INFO,
489 "called with nullptr");
490
491 deb << "Calculating native pixels: " << value << window;
492 return QHighDpi::toNativePixels<QRect, QWindow>(value, context: window);
493}
494
495/*!
496 \internal
497 \return scaling factor of \a window relative to Qt.
498*/
499qreal QtGuiTest::scaleFactor(const QWindow *window)
500{
501 Q_ASSERT_X(window,
502 Q_FUNC_INFO,
503 "called with nullptr");
504
505 deb << "Calculating scaling factor: " << window;
506 return QHighDpiScaling::factor(context: window);
507}
508
509/*!
510 \internal
511 Set the id of \a p to \a arg.
512*/
513void QtGuiTest::setEventPointId(QEventPoint &p, int arg)
514{
515 QMutableEventPoint::setId(p, arg);
516}
517
518/*!
519 \internal
520 Set the pressure of \a p to \a arg.
521*/
522void QtGuiTest::setEventPointPressure(QEventPoint &p, qreal arg)
523{
524 QMutableEventPoint::setPressure(p, arg);
525}
526
527/*!
528 \internal
529 Set the state of \a p to \a arg.
530*/
531void QtGuiTest::setEventPointState(QEventPoint &p, QEventPoint::State arg)
532{
533 QMutableEventPoint::setState(p, arg);
534}
535
536/*!
537 \internal
538 Set the position of \a p to \a arg.
539*/
540void QtGuiTest::setEventPointPosition(QEventPoint &p, QPointF arg)
541{
542 QMutableEventPoint::setPosition(p, arg);
543}
544
545/*!
546 \internal
547 Set the global position of \a p to \a arg.
548*/
549void QtGuiTest::setEventPointGlobalPosition(QEventPoint &p, QPointF arg)
550{
551 QMutableEventPoint::setGlobalPosition(p, arg);
552}
553
554/*!
555 \internal
556 Set the scene position of \a p to \a arg.
557*/
558void QtGuiTest::setEventPointScenePosition(QEventPoint &p, QPointF arg)
559{
560 QMutableEventPoint::setScenePosition(p, arg);
561}
562
563/*!
564 \internal
565 Set the ellipse diameters of \a p to \a arg.
566*/
567void QtGuiTest::setEventPointEllipseDiameters(QEventPoint &p, QSizeF arg)
568{
569 QMutableEventPoint::setEllipseDiameters(p, arg);
570}
571
572#undef deb
573#endif // #if QT_CONFIG(test_gui)
574QT_END_NAMESPACE
575

source code of qtbase/src/gui/kernel/qtestsupport_gui.cpp