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 a focus window, that's the one to receive events.
68 if (QWindow *focusWindow = QGuiApplication::focusWindow())
69 return focusWindow;
70
71 // Otherwise, if we have exactly one QQuickWindow, use that.
72 // Otherwise, we don't know what to do.
73 QWindow *found = nullptr;
74 const QWindowList windowList = QGuiApplication::allWindows();
75 for (QWindow *window : windowList) {
76 if (!qobject_cast<QQuickWindow *>(window))
77 continue;
78 if (found)
79 return nullptr;
80 found = window;
81 }
82
83 return found;
84}
85
87{
88 if (!targetWindow()) {
89 qWarning() << "Cannot determine target window for event replay. "
90 "Focus a window to use it.";
91 QObject::connect(
92 qApp, &QGuiApplication::focusWindowChanged,
93 this, &QQuickEventReplayServiceImpl::start,
94 Qt::SingleShotConnection);
95 return;
96 }
97
98 QObject::connect(
99 &m_schedule, &QTimer::timeout,
100 this, &QQuickEventReplayServiceImpl::sendNextEvent);
101 QObject::connect(this, &QQuickEventReplayServiceImpl::dataAvailable, this, [this]() {
102 QMutexLocker lock(&m_dataMutex);
103 scheduleNextEvent(m_data.head());
104 });
105
106 QMutexLocker lock(&m_dataMutex);
107 if (!m_data.isEmpty())
108 scheduleNextEvent(m_data.head());
109}
110
111static QEvent::Type eventType(int profilerEventType)
112{
113 using InputEventType = QQmlProfilerDefinitions::InputEventType;
114 switch (profilerEventType) {
115 case InputEventType::InputKeyPress:
116 return QEvent::KeyPress;
117 case InputEventType::InputKeyRelease:
118 return QEvent::KeyRelease;
119 case InputEventType::InputMousePress:
120 return QEvent::MouseButtonPress;
121 case InputEventType::InputMouseDoubleClick:
122 return QEvent::MouseButtonDblClick;
123 case InputEventType::InputMouseMove:
124 return QEvent::MouseMove;
125 case InputEventType::InputMouseRelease:
126 return QEvent::MouseButtonRelease;
127 case InputEventType::InputMouseWheel:
128 return QEvent::Wheel;
129 default:
130 return QEvent::None;
131 }
132}
133
134QQuickProfilerData QQuickEventReplayServiceImpl::takeNextEvent()
135{
136 QMutexLocker lock(&m_dataMutex);
137 const QQuickProfilerData data = m_data.dequeue();
138 if (!m_data.isEmpty())
139 scheduleNextEvent(m_data.head());
140 else
141 m_schedule.stop();
142 return data;
143}
144
145void QQuickEventReplayServiceImpl::sendNextEvent()
146{
147 QWindow *window = targetWindow();
148 if (!window) {
149 qWarning() << "Target window has disappeared during event replay";
150 return;
151 }
152
153 const QQuickProfilerData data = takeNextEvent();
154 Q_ASSERT(data.messageType == Message::Event);
155
156 const QEvent::Type type = eventType(data.inputType);
157 switch (data.detailType) {
158 case EventType::Key: {
159 qt_handleKeyEvent(window, type, data.inputA, Qt::KeyboardModifiers(data.inputB));
160 break;
161 }
162 case EventType::Mouse: {
163 switch (data.inputType) {
164 case InputEventType::InputMouseMove: {
165 m_currentPos = QPoint(data.inputA, data.inputB);
166 qt_handleMouseEvent(
167 window, m_currentPos, window->mapToGlobal(m_currentPos), m_currentButtons,
168 Qt::NoButton, type, m_currentModifiers, m_elapsed.elapsed());
169 break;
170 }
171 case InputEventType::InputMouseWheel: {
172 qt_handleWheelEvent(
173 window, m_currentPos, window->mapToGlobal(m_currentPos), QPoint(),
174 QPoint(data.inputA, data.inputB), m_currentModifiers, Qt::ScrollUpdate);
175 break;
176 }
177 default: {
178 m_currentButtons = Qt::MouseButtons(data.inputB);
179 qt_handleMouseEvent(
180 window, m_currentPos, window->mapToGlobal(m_currentPos), m_currentButtons,
181 Qt::MouseButton(data.inputA), type, m_currentModifiers, m_elapsed.elapsed());
182 }
183 }
184 }
185 }
186}
187
188void QQuickEventReplayServiceImpl::scheduleNextEvent(const QQuickProfilerData &nextEvent)
189{
190 // We assume that our timer starts at the same time as the timer of the program that
191 // recorded the events. That's really the best thing we can do because both of them
192 // effectively start at an arbitrary point in time. Trying to calculate a clock skew
193 // here is guesswork at best. The client can front-pad the timestamps if the transfer
194 // mechanism is so slow that it influences the accuracy of the playback.
195 // QTimer only has millisecond resolution but that should be good enough for input events.
196 m_schedule.start(std::max((nextEvent.time - m_elapsed.nsecsElapsed()) / 1000000ll, 1ll));
197}
198
199QT_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)