Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
14#include "private/qabstractvideobuffer_p.h"
15#include "private/qcapturablewindow_p.h"
16#include "private/qmemoryvideobuffer_p.h"
17#include "private/qvideoframeconversionhelper_p.h"
18
19#include <X11/Xlib.h>
20#include <sys/shm.h>
21#include <X11/extensions/XShm.h>
22#include <X11/Xutil.h>
23#include <X11/extensions/Xrandr.h>
24
25#include <optional>
26
28
29static Q_LOGGING_CATEGORY(qLcX11SurfaceCapture, "qt.multimedia.ffmpeg.qx11surfacecapture");
30
31namespace {
32
33void destroyXImage(XImage* image) {
34 XDestroyImage(image); // macro
35}
36
37template <typename T, typename D>
38std::unique_ptr<T, D> makeXUptr(T* ptr, D deleter) {
39 return std::unique_ptr<T, D>(ptr, deleter);
40}
41
42int screenNumberByName(Display *display, const QString &name)
43{
44 int size = 0;
45 auto monitors = makeXUptr(
46 XRRGetMonitors(display, XDefaultRootWindow(display), true, &size),
47 &XRRFreeMonitors);
48 const auto end = monitors.get() + size;
49 auto found = std::find_if(monitors.get(), end, [&](const XRRMonitorInfo &info) {
50 auto atomName = makeXUptr(XGetAtomName(display, info.name), &XFree);
51 return atomName && name == QString::fromUtf8(atomName.get());
52 });
53
54 return found == end ? -1 : std::distance(monitors.get(), found);
55}
56
57QVideoFrameFormat::PixelFormat xImagePixelFormat(const XImage &image)
58{
59 if (image.bits_per_pixel != 32) return QVideoFrameFormat::Format_Invalid;
60
61 if (image.red_mask == 0xff0000 &&
62 image.green_mask == 0xff00 &&
63 image.blue_mask == 0xff)
65
66 if (image.red_mask == 0xff00 &&
67 image.green_mask == 0xff0000 &&
68 image.blue_mask == 0xff000000)
70
71 if (image.blue_mask == 0xff0000 &&
72 image.green_mask == 0xff00 &&
73 image.red_mask == 0xff)
75
76 if (image.red_mask == 0xff00 &&
77 image.green_mask == 0xff0000 &&
78 image.blue_mask == 0xff000000)
80
82}
83
84} // namespace
85
87{
88public:
89 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, QScreen *screen)
90 {
91 std::unique_ptr<Grabber> result(new Grabber(capture));
92 return result->init(screen) ? std::move(result) : nullptr;
93 }
94
95 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, WId wid)
96 {
97 std::unique_ptr<Grabber> result(new Grabber(capture));
98 return result->init(wid) ? std::move(result) : nullptr;
99 }
100
101 ~Grabber() override
102 {
103 stop();
104
105 detachShm();
106 }
107
108 const QVideoFrameFormat &format() const { return m_format; }
109
110private:
112 {
115 }
116
117 bool createDisplay()
118 {
119 if (!m_display)
120 m_display.reset(XOpenDisplay(nullptr));
121
122 if (!m_display)
124 QLatin1String("Cannot open X11 display"));
125
126 return m_display != nullptr;
127 }
128
129 bool init(WId wid)
130 {
133
134 return createDisplay() && initWithXID(static_cast<XID>(wid));
135 }
136
137 bool init(QScreen *screen)
138 {
139 if (!screen) {
141 return false;
142 }
143
144 if (!createDisplay())
145 return false;
146
147 auto screenNumber = screenNumberByName(m_display.get(), screen->name());
148
149 if (screenNumber < 0)
150 return false;
151
153
154 return initWithXID(RootWindow(m_display.get(), screenNumber));
155 }
156
157 bool initWithXID(XID xid)
158 {
159 m_xid = xid;
160
161 if (update()) {
162 start();
163 return true;
164 }
165
166 return false;
167 }
168
169 void detachShm()
170 {
171 if (std::exchange(m_attached, false)) {
172 XShmDetach(m_display.get(), &m_shmInfo);
173 shmdt(m_shmInfo.shmaddr);
174 shmctl(m_shmInfo.shmid, IPC_RMID, 0);
175 }
176 }
177
178 void attachShm()
179 {
180 Q_ASSERT(!m_attached);
181
182 m_shmInfo.shmid =
183 shmget(IPC_PRIVATE, m_xImage->bytes_per_line * m_xImage->height, IPC_CREAT | 0777);
184
185 if (m_shmInfo.shmid == -1)
186 return;
187
188 m_shmInfo.readOnly = false;
189 m_shmInfo.shmaddr = m_xImage->data = (char *)shmat(m_shmInfo.shmid, 0, 0);
190
191 m_attached = XShmAttach(m_display.get(), &m_shmInfo);
192 }
193
194 bool update()
195 {
196 XWindowAttributes wndattr = {};
197 if (XGetWindowAttributes(m_display.get(), m_xid, &wndattr) == 0) {
199 QLatin1String("Cannot get window attributes"));
200 return false;
201 }
202
203 // TODO: if capture windows, we should adjust offsets and size if
204 // the window is out of the screen borders
205 // m_xOffset = ...
206 // m_yOffset = ...
207
208 // check window params for the root window as well since
209 // it potentially can be changed (e.g. on VM with resizing)
210 if (!m_xImage || wndattr.width != m_xImage->width || wndattr.height != m_xImage->height
211 || wndattr.depth != m_xImage->depth || wndattr.visual->visualid != m_visualID) {
212
213 qCDebug(qLcX11SurfaceCapture) << "recreate ximage: " << wndattr.width << wndattr.height
214 << wndattr.depth << wndattr.visual->visualid;
215
216 detachShm();
217 m_xImage.reset();
218
219 m_visualID = wndattr.visual->visualid;
220 m_xImage.reset(XShmCreateImage(m_display.get(), wndattr.visual, wndattr.depth, ZPixmap,
221 nullptr, &m_shmInfo, wndattr.width, wndattr.height));
222
223 if (!m_xImage) {
225 QLatin1String("Cannot create image"));
226 return false;
227 }
228
229 const auto pixelFormat = xImagePixelFormat(*m_xImage);
230
231 // TODO: probably, add a converter instead
232 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
234 QLatin1String("Not handled pixel format, bpp=")
235 + QString::number(m_xImage->bits_per_pixel));
236 return false;
237 }
238
239 attachShm();
240
241 if (!m_attached) {
243 QLatin1String("Cannot attach shared memory"));
244 return false;
245 }
246
247 QVideoFrameFormat format(QSize(m_xImage->width, m_xImage->height), pixelFormat);
248 format.setStreamFrameRate(frameRate());
249 m_format = format;
250 }
251
252 return m_attached;
253 }
254
255protected:
257 {
258 if (!update())
259 return {};
260
261 if (!XShmGetImage(m_display.get(), m_xid, m_xImage.get(), m_xOffset, m_yOffset,
262 AllPlanes)) {
265 "Cannot get ximage; the window may be out of the screen borders"));
266 return {};
267 }
268
269 QByteArray data(m_xImage->bytes_per_line * m_xImage->height, Qt::Uninitialized);
270
271 const auto pixelSrc = reinterpret_cast<const uint32_t *>(m_xImage->data);
272 const auto pixelDst = reinterpret_cast<uint32_t *>(data.data());
273 const auto pixelCount = data.size() / 4;
274 const auto xImageAlphaVaries = false; // In known cases it doesn't vary - it's 0xff or 0xff
275
276 qCopyPixelsWithAlphaMask(pixelDst, pixelSrc, pixelCount, m_format.pixelFormat(),
277 xImageAlphaVaries);
278
279 auto buffer = new QMemoryVideoBuffer(data, m_xImage->bytes_per_line);
280 return QVideoFrame(buffer, m_format);
281 }
282
283private:
284 std::optional<QPlatformSurfaceCapture::Error> m_prevGrabberError;
285 XID m_xid = None;
286 int m_xOffset = 0;
287 int m_yOffset = 0;
288 std::unique_ptr<Display, decltype(&XCloseDisplay)> m_display{ nullptr, &XCloseDisplay };
289 std::unique_ptr<XImage, decltype(&destroyXImage)> m_xImage{ nullptr, &destroyXImage };
290 XShmSegmentInfo m_shmInfo;
291 bool m_attached = false;
292 VisualID m_visualID = None;
293 QVideoFrameFormat m_format;
294};
295
297 : QPlatformSurfaceCapture(initialSource)
298{
299 // For debug
300 // XSetErrorHandler([](Display *, XErrorEvent * e) {
301 // qDebug() << "error handler" << e->error_code;
302 // return 0;
303 // });
304}
305
307
309{
310 return m_grabber ? m_grabber->format() : QVideoFrameFormat{};
311}
312
314{
315 qCDebug(qLcX11SurfaceCapture) << "set active" << active;
316
317 if (m_grabber)
318 m_grabber.reset();
319 else
320 std::visit([this](auto source) { activate(source); }, source());
321
322 return static_cast<bool>(m_grabber) == active;
323}
324
325void QX11SurfaceCapture::activate(ScreenSource screen)
326{
328 m_grabber = Grabber::create(*this, screen);
329}
330
331void QX11SurfaceCapture::activate(WindowSource window)
332{
334 m_grabber = Grabber::create(*this, handle ? handle->id : 0);
335}
336
338{
339 return qgetenv("XDG_SESSION_TYPE").compare(QLatin1String("x11"), Qt::CaseInsensitive) == 0;
340}
341
\inmodule QtCore
Definition qbytearray.h:57
static const QCapturableWindowPrivate * handle(const QCapturableWindow &window)
void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description)
void addFrameCallback(Object &object, Method method)
void updateError(QPlatformSurfaceCapture::Error error, const QString &description={})
QScreen * primaryScreen
the primary (or default) screen of the application.
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void updateError(Error error, const QString &errorString)
std::variant< ScreenSource, WindowSource > Source
bool checkScreenWithError(ScreenSource &screen)
void newVideoFrame(const QVideoFrame &)
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal refreshRate
the approximate vertical refresh rate of the screen in Hz
Definition qscreen.h:64
QString name
a user presentable string representing the screen
Definition qscreen.h:36
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
PixelFormat
Enumerates video data types.
QVideoFrameFormat::PixelFormat pixelFormat() const
Returns the pixel format of frames in a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
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)
QVideoFrameFormat frameFormat() const override
~QX11SurfaceCapture() override
bool setActiveInternal(bool active) override
QX11SurfaceCapture(Source initialSource)
struct wl_display * display
Definition linuxdmabuf.h:41
Combined button and popup list for selecting options.
@ CaseInsensitive
constexpr Initialization Uninitialized
Definition image.cpp:4
@ None
Definition qhash.cpp:531
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
static ControlElement< T > * ptr(QWidget *widget)
GLuint64 GLenum void * handle
GLuint * monitors
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLuint name
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLuint64EXT * result
[6]
unsigned long XID
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
QScreen * screen
[1]
Definition main.cpp:29
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
struct _XDisplay Display
void Q_MULTIMEDIA_EXPORT qCopyPixelsWithAlphaMask(uint32_t *dst, const uint32_t *src, size_t pixCount, QVideoFrameFormat::PixelFormat format, bool srcAlphaVaries)
aWidget window() -> setWindowTitle("New Window Title")
[2]
QHostInfo info
[0]