1 | // Copyright (C) 2022 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 "qvideoframe.h" |
5 | #include "qgrabwindowsurfacecapture_p.h" |
6 | #include "qscreencapture.h" |
7 | #include "qffmpegsurfacecapturegrabber_p.h" |
8 | |
9 | #include "private/qimagevideobuffer_p.h" |
10 | #include "private/qcapturablewindow_p.h" |
11 | #include "private/qvideoframe_p.h" |
12 | |
13 | #include "qscreen.h" |
14 | #include "qmutex.h" |
15 | #include "qwaitcondition.h" |
16 | #include "qpixmap.h" |
17 | #include "qguiapplication.h" |
18 | #include "qwindow.h" |
19 | #include "qpointer.h" |
20 | |
21 | #include <QtCore/qloggingcategory.h> |
22 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | using namespace Qt::StringLiterals; |
26 | |
27 | namespace { |
28 | |
29 | using WindowUPtr = std::unique_ptr<QWindow>; |
30 | |
31 | } // namespace |
32 | |
33 | class QGrabWindowSurfaceCapture::Grabber : public QFFmpegSurfaceCaptureGrabber |
34 | { |
35 | public: |
36 | Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen) : Grabber(capture, screen, nullptr) |
37 | { |
38 | Q_ASSERT(screen); |
39 | } |
40 | |
41 | Grabber(QGrabWindowSurfaceCapture &capture, WindowUPtr window) |
42 | : Grabber(capture, nullptr, std::move(window)) |
43 | { |
44 | Q_ASSERT(m_window); |
45 | } |
46 | |
47 | ~Grabber() override { |
48 | stop(); |
49 | |
50 | Q_ASSERT(!m_screenRemovingLocked); |
51 | } |
52 | |
53 | const QVideoFrameFormat &format() |
54 | { |
55 | QMutexLocker locker(&m_formatMutex); |
56 | while (!m_format) |
57 | m_waitForFormat.wait(lockedMutex: &m_formatMutex); |
58 | return *m_format; |
59 | } |
60 | |
61 | private: |
62 | Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen, WindowUPtr window) |
63 | : QFFmpegSurfaceCaptureGrabber( |
64 | QGuiApplication::platformName() == QLatin1String("eglfs" ) |
65 | ? QFFmpegSurfaceCaptureGrabber::UseCurrentThread |
66 | : QFFmpegSurfaceCaptureGrabber::CreateGrabbingThread), |
67 | m_capture(capture), |
68 | m_screen(screen), |
69 | m_window(std::move(window)) |
70 | { |
71 | connect(qApp, signal: &QGuiApplication::screenRemoved, context: this, slot: &Grabber::onScreenRemoved); |
72 | addFrameCallback(object&: m_capture, method: &QGrabWindowSurfaceCapture::newVideoFrame); |
73 | connect(sender: this, signal: &Grabber::errorUpdated, context: &m_capture, slot: &QGrabWindowSurfaceCapture::updateError); |
74 | } |
75 | |
76 | void onScreenRemoved(QScreen *screen) |
77 | { |
78 | /* The hack allows to lock screens removing while QScreen::grabWindow is in progress. |
79 | * The current solution works since QGuiApplication::screenRemoved is emitted from |
80 | * the destructor of QScreen before destruction members of the object. |
81 | * Note, QGuiApplication works with screens in the main thread, and any removing of a screen |
82 | * must be synchronized with grabbing thread. |
83 | */ |
84 | QMutexLocker locker(&m_screenRemovingMutex); |
85 | |
86 | if (m_screenRemovingLocked) { |
87 | qDebug() << "Screen" << screen->name() |
88 | << "is removed while screen window grabbing lock is active" ; |
89 | } |
90 | |
91 | while (m_screenRemovingLocked) |
92 | m_screenRemovingWc.wait(lockedMutex: &m_screenRemovingMutex); |
93 | } |
94 | |
95 | void setScreenRemovingLocked(bool locked) |
96 | { |
97 | Q_ASSERT(locked != m_screenRemovingLocked); |
98 | |
99 | { |
100 | QMutexLocker locker(&m_screenRemovingMutex); |
101 | m_screenRemovingLocked = locked; |
102 | } |
103 | |
104 | if (!locked) |
105 | m_screenRemovingWc.wakeAll(); |
106 | } |
107 | |
108 | void updateFormat(const QVideoFrameFormat &format) |
109 | { |
110 | if (m_format && m_format->isValid()) |
111 | return; |
112 | |
113 | { |
114 | QMutexLocker locker(&m_formatMutex); |
115 | m_format = format; |
116 | } |
117 | |
118 | m_waitForFormat.wakeAll(); |
119 | } |
120 | |
121 | QVideoFrame grabFrame() override |
122 | { |
123 | setScreenRemovingLocked(true); |
124 | auto screenGuard = qScopeGuard(f: [this] { |
125 | setScreenRemovingLocked(false); |
126 | }); |
127 | |
128 | WId wid = m_window ? m_window->winId() : 0; |
129 | QScreen *screen = m_window ? m_window->screen() : m_screen ? m_screen.data() : nullptr; |
130 | |
131 | if (!screen) { |
132 | updateError(error: QPlatformSurfaceCapture::CaptureFailed, description: u"Screen not found"_s ); |
133 | return {}; |
134 | } |
135 | |
136 | setFrameRate(screen->refreshRate()); |
137 | |
138 | QPixmap p = screen->grabWindow(window: wid); |
139 | auto buffer = std::make_unique<QImageVideoBuffer>(args: p.toImage()); |
140 | const auto img = buffer->underlyingImage(); |
141 | |
142 | QVideoFrameFormat format(img.size(), |
143 | QVideoFrameFormat::pixelFormatFromImageFormat(format: img.format())); |
144 | format.setStreamFrameRate(screen->refreshRate()); |
145 | updateFormat(format); |
146 | |
147 | if (!format.isValid()) { |
148 | updateError(error: QPlatformSurfaceCapture::CaptureFailed, |
149 | description: u"Failed to grab the screen content"_s ); |
150 | return {}; |
151 | } |
152 | |
153 | return QVideoFramePrivate::createFrame(buffer: std::move(buffer), format: std::move(format)); |
154 | } |
155 | |
156 | private: |
157 | QGrabWindowSurfaceCapture &m_capture; |
158 | QPointer<QScreen> m_screen; |
159 | WindowUPtr m_window; |
160 | |
161 | QMutex m_formatMutex; |
162 | QWaitCondition m_waitForFormat; |
163 | std::optional<QVideoFrameFormat> m_format; |
164 | |
165 | QMutex m_screenRemovingMutex; |
166 | bool m_screenRemovingLocked = false; |
167 | QWaitCondition m_screenRemovingWc; |
168 | }; |
169 | |
170 | QGrabWindowSurfaceCapture::QGrabWindowSurfaceCapture(Source initialSource) |
171 | : QPlatformSurfaceCapture(initialSource) |
172 | { |
173 | } |
174 | |
175 | QGrabWindowSurfaceCapture::~QGrabWindowSurfaceCapture() = default; |
176 | |
177 | QVideoFrameFormat QGrabWindowSurfaceCapture::frameFormat() const |
178 | { |
179 | if (m_grabber) |
180 | return m_grabber->format(); |
181 | else |
182 | return {}; |
183 | } |
184 | |
185 | bool QGrabWindowSurfaceCapture::setActiveInternal(bool active) |
186 | { |
187 | if (active == static_cast<bool>(m_grabber)) |
188 | return true; |
189 | |
190 | if (m_grabber) |
191 | m_grabber.reset(); |
192 | else |
193 | std::visit(visitor: [this](auto source) { activate(source); }, variants: source()); |
194 | |
195 | return static_cast<bool>(m_grabber) == active; |
196 | } |
197 | |
198 | void QGrabWindowSurfaceCapture::activate(ScreenSource screen) |
199 | { |
200 | if (!checkScreenWithError(screen)) |
201 | return; |
202 | |
203 | m_grabber = std::make_unique<Grabber>(args&: *this, args&: screen); |
204 | m_grabber->start(); |
205 | } |
206 | |
207 | void QGrabWindowSurfaceCapture::activate(WindowSource window) |
208 | { |
209 | auto handle = QCapturableWindowPrivate::handle(window); |
210 | auto wid = handle ? handle->id : 0; |
211 | if (auto wnd = WindowUPtr(QWindow::fromWinId(id: wid))) { |
212 | if (!wnd->screen()) { |
213 | updateError(error: InternalError, |
214 | errorString: u"Window " + QString::number(wid) + u" doesn't belong to any screen" ); |
215 | } else { |
216 | m_grabber = std::make_unique<Grabber>(args&: *this, args: std::move(wnd)); |
217 | m_grabber->start(); |
218 | } |
219 | } else { |
220 | updateError(error: NotFound, |
221 | errorString: u"Window " + QString::number(wid) + u"doesn't exist or permissions denied" ); |
222 | } |
223 | } |
224 | |
225 | QT_END_NAMESPACE |
226 | |