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
qsckwindowcapture.mm
Go to the documentation of this file.
1// Copyright (C) 2026 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
5
6#include <QtCore/qthread.h>
7#include <QtCore/private/qcore_mac_p.h>
8
9#include <QtFFmpegMediaPluginImpl/private/qmacscreencapturekit_p.h>
10
11#include <QtMultimedia/private/qcapturablewindow_p.h>
12#include <QtMultimedia/private/qvideoframe_p.h>
13
14#define AVMediaType XAVMediaType
15extern "C" {
16#include <libavutil/hwcontext_videotoolbox.h>
17}
18#undef AVMediaType
19
20using namespace Qt::Literals::StringLiterals;
21
22QT_BEGIN_NAMESPACE
23
24namespace QFFmpeg {
25
26namespace {
27
28[[nodiscard]] q23::expected<AVFScopedPointer<SCWindow>, QString> findScWindow(CGWindowID input)
29{
30 // Do a blocking enumeration of capturable items.
31 q23::expected<QMacScreenCaptureKit::CapturableItems, QString> enumerateResult =
32 QMacScreenCaptureKit::enumerateCapturableItems()
33 .get();
34
35 if (!enumerateResult)
36 return q23::unexpected(enumerateResult.error());
37
38 const std::vector<AVFScopedPointer<SCWindow>> &windows = enumerateResult->windows;
39 auto it = std::find_if(
40 windows.begin(),
41 windows.end(),
42 [&](const AVFScopedPointer<SCWindow> &item) {
43 return item.data().windowID == input;
44 });
45
46 if (it == windows.end())
47 return q23::unexpected(u"Window not found"_s);
48
49 // AVFScopedPointer doesn't have shared-ptr semantics, force a reference increment.
50 return AVFScopedPointer<SCWindow>{ [it->data() retain] };
51}
52
53void setupQMacScreenCaptureKitConnections(
54 QSckWindowCapture &windowCapture,
55 const QMacScreenCaptureKit &macScreenCaptureKit)
56{
57 // Direct connection so application developers can respond to frame directly
58 // from background thread.
59 // Remaining frames are always flushed whenever we go inactive, as a result
60 // of QMacScreenCaptureKit doing so in the destructor. Because we flush,
61 // we trust the application developer to not block the background thread.
62 QObject::connect(
63 &macScreenCaptureKit,
64 &QMacScreenCaptureKit::newVideoFrameGenerated,
65 &windowCapture,
66 [&windowCapture](int64_t, QVideoFrame videoFrame) {
67 emit windowCapture.newVideoFrame(videoFrame);
68 },
69 Qt::DirectConnection);
70
71 QObject::connect(
72 &macScreenCaptureKit,
73 &QMacScreenCaptureKit::newVideoFrameGenerated,
74 &windowCapture,
75 [&windowCapture](int64_t streamId, QVideoFrame newFrame) {
76 windowCapture.onNewFrameFormatReceived(streamId, newFrame.surfaceFormat());
77 },
78 Qt::QueuedConnection);
79
80 QObject::connect(
81 &macScreenCaptureKit,
82 &QMacScreenCaptureKit::streamStoppedWithError,
83 &windowCapture,
84 &QSckWindowCapture::onStreamStoppedWithErrorEvent,
85 Qt::QueuedConnection);
86}
87
88} // Anonymous namespace
89
90QSckWindowCapture::QSckWindowCapture() : QPlatformSurfaceCapture(WindowSource{})
91{
92}
93
95{
96 struct ErrorPair {
97 QPlatformSurfaceCapture::Error err;
98 QString msg;
99 };
100
101 using TryStartResult = q23::expected<std::unique_ptr<ActiveData>, ErrorPair>;
102
103 auto tryStartStream = [&]() -> TryStartResult {
104 QCapturableWindow capturableWindow = source<WindowSource>();
105 const QCapturableWindowPrivate *handle = QCapturableWindowPrivate::handle(capturableWindow);
106 if (!handle) {
107 return q23::unexpected{ ErrorPair{
108 QPlatformSurfaceCapture::Error::NotFound,
109 u"Selected window is null"_s } };
110 }
111 CGWindowID cgWindowId = static_cast<CGWindowID>(handle->id);
112
113 // Find the associated SCWindow we can use.
114 // This will trigger the system dialog for granting screen capture permissions.
115 q23::expected<AVFScopedPointer<SCWindow>, QString> scWindowResult = findScWindow(cgWindowId);
116 if (!scWindowResult) {
117 qCWarning(qLcMacScreenCapture)
118 << "Could not find associated SCWindow: "
119 << scWindowResult.error();
120 return q23::unexpected{ ErrorPair {
121 QPlatformSurfaceCapture::Error::NotFound,
122 u"Backend was unable to find selected QCapturableWindow"_s } };
123 }
124
125 int64_t newStreamId = m_streamIdTracker++;
126
127 AVFScopedPointer<SCWindow> &scWindow = *scWindowResult;
128
129 // Start and wait for the stream. Blocking operation.
130 using ResultType = q23::expected<std::unique_ptr<QMacScreenCaptureKit>, QString>;
131 std::future<ResultType> streamResultFuture = QMacScreenCaptureKit::createStream(
132 newStreamId,
133 scWindow.data(),
134 frameRate());
135 ResultType streamResult = streamResultFuture.get();
136 if (!streamResult) {
137 qCWarning(qLcMacScreenCapture)
138 << "Failed to start screen capture stream: "
139 << streamResult.error();
140 return q23::unexpected{ ErrorPair{
141 QPlatformSurfaceCapture::Error::CaptureFailed,
142 u"Failed to start stream due to unknown issue"_s } };
143 }
144
145 std::unique_ptr<QMacScreenCaptureKit> &macScreenCaptureKit = *streamResult;
146 setupQMacScreenCaptureKitConnections(
147 *this,
148 *macScreenCaptureKit.get());
149
150 auto newActiveData = std::make_unique<ActiveData>();
151 newActiveData->macScreenCaptureKit = std::move(macScreenCaptureKit);
152 newActiveData->scWindow = std::move(scWindow);
153 newActiveData->streamId = newStreamId;
154 return newActiveData;
155 };
156
157 if (active) {
158 Q_ASSERT(!m_activeData);
159
160 TryStartResult result = tryStartStream();
161 if (!result) {
162 const ErrorPair &error = result.error();
163 QPlatformSurfaceCapture::updateError(error.err, error.msg);
164 return false;
165 }
166
167 m_activeData = std::move(*result);
168 } else {
169 m_activeData.reset();
170 }
171
172 return true;
173}
174
176 int64_t incomingStreamId,
177 QVideoFrameFormat const &format)
178{
179 Q_ASSERT(thread()->isCurrentThread());
180
181 std::optional<int64_t> activeStreamIdOpt = activeStreamId();
182 if (activeStreamIdOpt.has_value() && *activeStreamIdOpt == incomingStreamId)
183 m_videoFrameFormat = format;
184}
185
187 int64_t incomingStreamId,
188 const QString &err)
189{
190 Q_ASSERT(thread()->isCurrentThread());
191
192 qCDebug(qLcMacScreenCapture)
193 << "Stream with ID "
194 << incomingStreamId
195 << " stopped with error: "
196 << err;
197
198 std::optional<int64_t> activeStreamIdOpt = activeStreamId();
199 if (!activeStreamIdOpt || *activeStreamIdOpt != incomingStreamId)
200 return;
201
202 // Possible improvement may be to propagate signal up to QVideoSource
203 // that we are no longer active.
204 m_activeData.reset();
205 m_videoFrameFormat.reset();
206
207 QPlatformSurfaceCapture::updateError(
208 Error::CaptureFailed,
209 u"The capture stream was closed by the system"_s);
210}
211
213{
214 Q_ASSERT(thread()->isCurrentThread());
215
216 if (m_videoFrameFormat)
217 return *m_videoFrameFormat;
218 return {};
219}
220
222{
223 Q_ASSERT(thread()->isCurrentThread());
224 return AV_PIX_FMT_VIDEOTOOLBOX;
225}
226
231
232} // namespace QFFmpeg
233
234QT_END_NAMESPACE
235
236#include "moc_qsckwindowcapture_p.cpp"
void onNewFrameFormatReceived(int64_t streamId, const QVideoFrameFormat &)
std::optional< int > ffmpegHWPixelFormat() const override
QVideoFrameFormat frameFormat() const override
void onStreamStoppedWithErrorEvent(int64_t streamId, const QString &)
bool setActiveInternal(bool active) override
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
std::unique_ptr< QPlatformSurfaceCapture > makeQSckWindowCapture()