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
qquickeventreplayservice.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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
4
6
7#include <QtQuick/qquickwindow.h>
8
9#include <QtGui/qtguiglobal.h>
10#include <QtGui/qtestsupport_gui.h>
11#include <QtGui/qwindow.h>
12
14
15Q_GUI_EXPORT void qt_handleMouseEvent(
16 QWindow *window, const QPointF &local, const QPointF &global, Qt::MouseButtons state,
17 Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods, int timestamp);
18
20 QWindow *w, QEvent::Type t, int k, Qt::KeyboardModifiers mods,
21 const QString &text = QString(), bool autorep = false, ushort count = 1);
22
24 QWindow *window, const QPointF &local, const QPointF &global, QPoint pixelDelta,
25 QPoint angleDelta, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase);
26
27QQuickEventReplayServiceImpl::QQuickEventReplayServiceImpl(QObject *parent)
28 : QQuickEventReplayService(1, parent)
29{
30 m_elapsed.start();
31 start();
32}
33
34void QQuickEventReplayServiceImpl::messageReceived(const QByteArray &message)
35{
36 QQmlDebugPacket stream(message);
37 QQuickProfilerData data;
38
39 stream >> data.time >> data.messageType >> data.detailType;
40
41 if (data.messageType != Message::Event)
42 return;
43
44 switch (data.detailType) {
45 case EventType::Key:
46 case EventType::Mouse:
47 break;
48 default:
49 return;
50 }
51
52 stream >> data.inputType >> data.inputA >> data.inputB;
53
54 bool isFirstEvent = false;
55 {
56 QMutexLocker lock(&m_dataMutex);
57 isFirstEvent = m_data.isEmpty();
58 m_data.enqueue(std::move(data));
59 }
60
61 if (isFirstEvent)
62 emit dataAvailable();
63}
64
66{
67 // If we have an exposed focus window, that's the one to receive events.
68 if (QWindow *focusWindow = QGuiApplication::focusWindow()) {
69 if (focusWindow->isExposed() && focusWindow->width() != 0 && focusWindow->height() != 0)
70 return focusWindow;
71 }
72
73 // Otherwise, if we have exactly one QQuickWindow, use that.
74 // Otherwise, we don't know what to do.
75 QWindow *found = nullptr;
76 const QWindowList windowList = QGuiApplication::allWindows();
77 for (QWindow *window : windowList) {
78 if (!qobject_cast<QQuickWindow *>(window))
79 continue;
80 if (!window->isExposed() || window->width() == 0 || window->height() == 0)
81 continue;
82 if (found)
83 return nullptr;
84 found = window;
85 }
86
87 return found;
88}
89
91{
92 if (!targetWindow()) {
93 // Poll on a timer since focusing the window is not the only way to
94 // produce a unique target. Having exactly one window also works.
95 QTimer::singleShot(16, this, &QQuickEventReplayServiceImpl::start);
96
97 QMutexLocker lock(&m_dataMutex);
98 if (!m_data.isEmpty()) {
99 qWarning() << "Cannot determine target window for event replay. "
100 "Focus a window to use it.";
101 }
102 return;
103 }
104
105 QObject::connect(
106 &m_schedule, &QTimer::timeout,
107 this, &QQuickEventReplayServiceImpl::sendNextEvent);
108 QObject::connect(this, &QQuickEventReplayServiceImpl::dataAvailable, this, [this]() {
109 QMutexLocker lock(&m_dataMutex);
110 scheduleNextEvent(m_data.head());
111 });
112
113 QMutexLocker lock(&m_dataMutex);
114 if (!m_data.isEmpty())
115 scheduleNextEvent(m_data.head());
116}
117
118static QEvent::Type eventType(int profilerEventType)
119{
120 using InputEventType = QQmlProfilerDefinitions::InputEventType;
121 switch (profilerEventType) {
122 case InputEventType::InputKeyPress:
123 return QEvent::KeyPress;
124 case InputEventType::InputKeyRelease:
125 return QEvent::KeyRelease;
126 case InputEventType::InputMousePress:
127 return QEvent::MouseButtonPress;
128 case InputEventType::InputMouseDoubleClick:
129 return QEvent::MouseButtonDblClick;
130 case InputEventType::InputMouseMove:
131 return QEvent::MouseMove;
132 case InputEventType::InputMouseRelease:
133 return QEvent::MouseButtonRelease;
134 case InputEventType::InputMouseWheel:
135 return QEvent::Wheel;
136 default:
137 return QEvent::None;
138 }
139}
140
141QQuickProfilerData QQuickEventReplayServiceImpl::takeNextEvent()
142{
143 QMutexLocker lock(&m_dataMutex);
144 const QQuickProfilerData data = m_data.dequeue();
145 if (!m_data.isEmpty())
146 scheduleNextEvent(m_data.head());
147 else
148 m_schedule.stop();
149 return data;
150}
151
152void QQuickEventReplayServiceImpl::sendNextEvent()
153{
154 QWindow *window = targetWindow();
155 if (!window) {
156 qWarning() << "Target window has disappeared during event replay";
157 return;
158 }
159
160 const QQuickProfilerData data = takeNextEvent();
161 Q_ASSERT(data.messageType == Message::Event);
162
163 const QEvent::Type type = eventType(data.inputType);
164 switch (data.detailType) {
165 case EventType::Key: {
166 qt_handleKeyEvent(window, type, data.inputA, Qt::KeyboardModifiers(data.inputB));
167 break;
168 }
169 case EventType::Mouse: {
170 switch (data.inputType) {
171 case InputEventType::InputMouseMove: {
172 m_currentPos = QPoint(data.inputA, data.inputB);
173 qt_handleMouseEvent(
174 window, m_currentPos, window->mapToGlobal(m_currentPos), m_currentButtons,
175 Qt::NoButton, type, m_currentModifiers, m_elapsed.elapsed());
176 break;
177 }
178 case InputEventType::InputMouseWheel: {
179 qt_handleWheelEvent(
180 window, m_currentPos, window->mapToGlobal(m_currentPos), QPoint(),
181 QPoint(data.inputA, data.inputB), m_currentModifiers, Qt::ScrollUpdate);
182 break;
183 }
184 case InputEventType::InputMouseDoubleClick:
185 // Ingore double clicks. We consider the constituent low level events instead.
186 break;
187 default: {
188 m_currentButtons = Qt::MouseButtons(data.inputB);
189 qt_handleMouseEvent(
190 window, m_currentPos, window->mapToGlobal(m_currentPos), m_currentButtons,
191 Qt::MouseButton(data.inputA), type, m_currentModifiers, m_elapsed.elapsed());
192 }
193 }
194 }
195 }
196}
197
198void QQuickEventReplayServiceImpl::scheduleNextEvent(const QQuickProfilerData &nextEvent)
199{
200 // We assume that our timer starts at the same time as the timer of the program that
201 // recorded the events. That's really the best thing we can do because both of them
202 // effectively start at an arbitrary point in time. Trying to calculate a clock skew
203 // here is guesswork at best. The client can front-pad the timestamps if the transfer
204 // mechanism is so slow that it influences the accuracy of the playback.
205 // QTimer only has millisecond resolution but that should be good enough for input events.
206 m_schedule.start(std::max((nextEvent.time - m_elapsed.nsecsElapsed()) / 1000000ll, 1ll));
207}
208
209QT_END_NAMESPACE
void messageReceived(const QByteArray &) override
Combined button and popup list for selecting options.
static QWindow * targetWindow()
Q_GUI_EXPORT void qt_handleKeyEvent(QWindow *w, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString &text=QString(), bool autorep=false, ushort count=1)
static QEvent::Type eventType(int profilerEventType)
Q_GUI_EXPORT void qt_handleWheelEvent(QWindow *window, const QPointF &local, const QPointF &global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase)