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