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
qx11surfacecapture.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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
6
7#include <qvideoframe.h>
8#include <qscreen.h>
9#include <qwindow.h>
10#include <qdebug.h>
11#include <qguiapplication.h>
12#include <qloggingcategory.h>
13#include <qminmax.h>
14
15#include "private/qcapturablewindow_p.h"
16#include "private/qmemoryvideobuffer_p.h"
17#include "private/qvideoframeconversionhelper_p.h"
18#include "private/qvideoframe_p.h"
19
20#include <X11/Xlib.h>
21#include <sys/shm.h>
22#include <X11/extensions/XShm.h>
23#include <X11/Xutil.h>
24#include <X11/extensions/Xrandr.h>
25
26#include <optional>
27
28QT_BEGIN_NAMESPACE
29
30Q_STATIC_LOGGING_CATEGORY(qLcX11SurfaceCapture, "qt.multimedia.ffmpeg.qx11surfacecapture");
31
32namespace {
33
34void destroyXImage(XImage* image) {
35 XDestroyImage(image); // macro
36}
37
38template <typename T, typename D>
39std::unique_ptr<T, D> makeXUptr(T* ptr, D deleter) {
40 return std::unique_ptr<T, D>(ptr, deleter);
41}
42
43int screenNumberByName(Display *display, const QString &name)
44{
45 int size = 0;
46 auto monitors = makeXUptr(
47 XRRGetMonitors(display, XDefaultRootWindow(display), true, &size),
48 &XRRFreeMonitors);
49 const auto end = monitors.get() + size;
50 auto found = std::find_if(monitors.get(), end, [&](const XRRMonitorInfo &info) {
51 auto atomName = makeXUptr(XGetAtomName(display, info.name), &XFree);
52 return atomName && name == QString::fromUtf8(atomName.get());
53 });
54
55 return found == end ? -1 : std::distance(monitors.get(), found);
56}
57
58QVideoFrameFormat::PixelFormat xImagePixelFormat(const XImage &image)
59{
60 if (image.bits_per_pixel != 32) return QVideoFrameFormat::Format_Invalid;
61
62 if (image.red_mask == 0xff0000 &&
63 image.green_mask == 0xff00 &&
64 image.blue_mask == 0xff)
65 return QVideoFrameFormat::Format_BGRX8888;
66
67 if (image.red_mask == 0xff00 &&
68 image.green_mask == 0xff0000 &&
69 image.blue_mask == 0xff000000)
70 return QVideoFrameFormat::Format_XBGR8888;
71
72 if (image.blue_mask == 0xff0000 &&
73 image.green_mask == 0xff00 &&
74 image.red_mask == 0xff)
75 return QVideoFrameFormat::Format_RGBX8888;
76
77 if (image.red_mask == 0xff00 &&
78 image.green_mask == 0xff0000 &&
79 image.blue_mask == 0xff000000)
80 return QVideoFrameFormat::Format_XRGB8888;
81
82 return QVideoFrameFormat::Format_Invalid;
83}
84
85} // namespace
86
87class QX11SurfaceCapture::Grabber : private QFFmpegSurfaceCaptureGrabber
88{
89public:
90 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, QScreen *screen)
91 {
92 std::unique_ptr<Grabber> result(new Grabber(capture));
93 return result->init(screen) ? std::move(result) : nullptr;
94 }
95
96 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, WId wid)
97 {
98 std::unique_ptr<Grabber> result(new Grabber(capture));
99 return result->init(wid) ? std::move(result) : nullptr;
100 }
101
103 {
104 stop();
105
106 detachShm();
107 }
108
109 const QVideoFrameFormat &format() const { return m_format; }
110
111private:
112 Grabber(QX11SurfaceCapture &capture)
113 {
114 addFrameCallback(capture, &QX11SurfaceCapture::newVideoFrame);
115 connect(this, &Grabber::errorUpdated, &capture, &QX11SurfaceCapture::updateError);
116 }
117
118 bool createDisplay()
119 {
120 if (!m_display)
121 m_display.reset(XOpenDisplay(nullptr));
122
123 if (!m_display)
124 updateError(QPlatformSurfaceCapture::InternalError,
125 QStringLiteral("Cannot open X11 display"));
126
127 return m_display != nullptr;
128 }
129
130 bool init(WId wid)
131 {
132 if (auto screen = QGuiApplication::primaryScreen())
133 setFrameRate(screen->refreshRate());
134
135 return createDisplay() && initWithXID(static_cast<XID>(wid));
136 }
137
138 bool init(QScreen *screen)
139 {
140 if (!screen) {
141 updateError(QPlatformSurfaceCapture::NotFound, QStringLiteral("Screen Not Found"));
142 return false;
143 }
144
145 if (!createDisplay())
146 return false;
147
148 auto screenNumber = screenNumberByName(m_display.get(), screen->name());
149
150 if (screenNumber < 0)
151 return false;
152
153 setFrameRate(screen->refreshRate());
154
155 return initWithXID(RootWindow(m_display.get(), screenNumber));
156 }
157
158 bool initWithXID(XID xid)
159 {
160 m_xid = xid;
161
162 if (update()) {
163 start();
164 return true;
165 }
166
167 return false;
168 }
169
170 void detachShm()
171 {
172 if (std::exchange(m_attached, false)) {
173 XShmDetach(m_display.get(), &m_shmInfo);
174 shmdt(m_shmInfo.shmaddr);
175 shmctl(m_shmInfo.shmid, IPC_RMID, nullptr);
176 }
177 }
178
179 void attachShm()
180 {
181 Q_ASSERT(!m_attached);
182
183 m_shmInfo.shmid =
184 shmget(IPC_PRIVATE, m_xImage->bytes_per_line * m_xImage->height, IPC_CREAT | 0777);
185
186 if (m_shmInfo.shmid == -1)
187 return;
188
189 m_shmInfo.readOnly = false;
190 m_shmInfo.shmaddr = m_xImage->data = (char *)shmat(m_shmInfo.shmid, nullptr, 0);
191
192 m_attached = XShmAttach(m_display.get(), &m_shmInfo);
193 }
194
195 bool update()
196 {
197 Window win = m_xid;
198 XWindowAttributes wndattr;
199 int xPos = 0;
200 int yPos = 0;
201 int width = 0;
202 int height = 0;
203 int depth = 0;
204 Visual *visual = nullptr;
205 while (true) {
206 if (!XGetWindowAttributes(m_display.get(), win, &wndattr)) {
207 updateError(QPlatformSurfaceCapture::CaptureFailed,
208 QStringLiteral("Cannot get window attributes"));
209 return false;
210 }
211 if (win == m_xid) {
212 width = wndattr.width;
213 height = wndattr.height;
214 depth = wndattr.depth;
215 visual = wndattr.visual;
216 }
217 if (win == wndattr.root)
218 break;
219 xPos += wndattr.x;
220 yPos += wndattr.y;
221
222 Window root, *children;
223 unsigned int nchildren;
224 if (!XQueryTree(m_display.get(), win, &root, &win, &children, &nchildren)) {
225 updateError(QPlatformSurfaceCapture::CaptureFailed,
226 QStringLiteral("Cannot get parent window"));
227 return false;
228 }
229 if (children) XFree(children);
230 }
231
232 m_xOffset = qMax(0, -xPos);
233 m_yOffset = qMax(0, -yPos);
234 width = qMin(width, wndattr.width - xPos) - m_xOffset;
235 height = qMin(height, wndattr.height - yPos) - m_yOffset;
236 if (width <= 0 || height <= 0) {
237 updateError(QPlatformSurfaceCapture::CaptureFailed,
238 QStringLiteral("Window is completely out of the screen borders"));
239 return false;
240 }
241
242 // check window params for the root window as well since
243 // it potentially can be changed (e.g. on VM with resizing)
244 if (!m_xImage || width != m_xImage->width || height != m_xImage->height
245 || depth != m_xImage->depth || visual->visualid != m_visualID) {
246
247 qCDebug(qLcX11SurfaceCapture) << "recreate ximage: " << width << height
248 << depth << visual->visualid;
249
250 detachShm();
251 m_xImage.reset();
252
253 m_visualID = wndattr.visual->visualid;
254 m_xImage.reset(XShmCreateImage(m_display.get(), visual, depth, ZPixmap,
255 nullptr, &m_shmInfo, width, height));
256
257 if (!m_xImage) {
258 updateError(QPlatformSurfaceCapture::CaptureFailed,
259 QStringLiteral("Cannot create image"));
260 return false;
261 }
262
263 const auto pixelFormat = xImagePixelFormat(*m_xImage);
264
265 // TODO: probably, add a converter instead
266 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
267 updateError(QPlatformSurfaceCapture::CaptureFailed,
268 QStringLiteral("Not handled pixel format, bpp=")
269 + QString::number(m_xImage->bits_per_pixel));
270 return false;
271 }
272
273 attachShm();
274
275 if (!m_attached) {
276 updateError(QPlatformSurfaceCapture::CaptureFailed,
277 QStringLiteral("Cannot attach shared memory"));
278 return false;
279 }
280
281 QVideoFrameFormat format(QSize(m_xImage->width, m_xImage->height), pixelFormat);
282 format.setStreamFrameRate(frameRate());
283 m_format = format;
284 }
285
286 return m_attached;
287 }
288
289protected:
291 {
292 if (!update())
293 return {};
294
295 if (!XShmGetImage(m_display.get(), m_xid, m_xImage.get(), m_xOffset, m_yOffset,
296 AllPlanes)) {
297 updateError(QPlatformSurfaceCapture::CaptureFailed,
298 QStringLiteral(
299 "Cannot get ximage; the window geometry may be undergoing change"));
300 return {};
301 }
302
303 QByteArray data(m_xImage->bytes_per_line * m_xImage->height, Qt::Uninitialized);
304
305 const auto pixelSrc = reinterpret_cast<const uint32_t *>(m_xImage->data);
306 const auto pixelDst = reinterpret_cast<uint32_t *>(data.data());
307 const auto pixelCount = data.size() / 4;
308 const auto xImageAlphaVaries = false; // In known cases it doesn't vary - it's 0xff or 0xff
309
310 qCopyPixelsWithAlphaMask(pixelDst, pixelSrc, pixelCount, m_format.pixelFormat(),
311 xImageAlphaVaries);
312
313 auto buffer = std::make_unique<QMemoryVideoBuffer>(data, m_xImage->bytes_per_line);
314 return QVideoFramePrivate::createFrame(std::move(buffer), m_format);
315 }
316
317private:
318 std::optional<QPlatformSurfaceCapture::Error> m_prevGrabberError;
319 XID m_xid = None;
320 int m_xOffset = 0;
321 int m_yOffset = 0;
322 std::unique_ptr<Display, decltype(&XCloseDisplay)> m_display{ nullptr, &XCloseDisplay };
323 std::unique_ptr<XImage, decltype(&destroyXImage)> m_xImage{ nullptr, &destroyXImage };
324 XShmSegmentInfo m_shmInfo;
325 bool m_attached = false;
326 VisualID m_visualID = None;
327 QVideoFrameFormat m_format;
328};
329
330QX11SurfaceCapture::QX11SurfaceCapture(Source initialSource)
331 : QPlatformSurfaceCapture(initialSource)
332{
333 // For debug
334 // XSetErrorHandler([](Display *, XErrorEvent * e) {
335 // qDebug() << "error handler" << e->error_code;
336 // return 0;
337 // });
338}
339
340QX11SurfaceCapture::~QX11SurfaceCapture() = default;
341
342QVideoFrameFormat QX11SurfaceCapture::frameFormat() const
343{
344 return m_grabber ? m_grabber->format() : QVideoFrameFormat{};
345}
346
347bool QX11SurfaceCapture::setActiveInternal(bool active)
348{
349 qCDebug(qLcX11SurfaceCapture) << "set active" << active;
350
351 if (m_grabber)
352 m_grabber.reset();
353 else
354 std::visit([this](auto source) { activate(source); }, source());
355
356 return static_cast<bool>(m_grabber) == active;
357}
358
359void QX11SurfaceCapture::activate(ScreenSource screen)
360{
361 if (checkScreenWithError(screen))
362 m_grabber = Grabber::create(*this, screen);
363}
364
365void QX11SurfaceCapture::activate(WindowSource window)
366{
367 auto handle = QCapturableWindowPrivate::handle(window);
368 m_grabber = Grabber::create(*this, handle ? handle->id : 0);
369}
370
371bool QX11SurfaceCapture::isSupported()
372{
373 return qgetenv("XDG_SESSION_TYPE").compare(QLatin1String("x11"), Qt::CaseInsensitive) == 0;
374}
375
376QT_END_NAMESPACE
static std::unique_ptr< Grabber > create(QX11SurfaceCapture &capture, QScreen *screen)
const QVideoFrameFormat & format() const
QVideoFrame grabFrame() override
static std::unique_ptr< Grabber > create(QX11SurfaceCapture &capture, WId wid)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)