Revision control

++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qvideoframe.h"
++ #include "qgrabwindowsurfacecapture_p.h"
++ #include "qscreencapture.h"
++ #include <QtMultimedia/private/qsurfacecapturegrabber_p.h>
++
++ #include "private/qimagevideobuffer_p.h"
++ #include "private/qcapturablewindow_p.h"
++ #include "private/qvideoframe_p.h"
++
++ #include "qscreen.h"
++ #include "qmutex.h"
++ #include "qwaitcondition.h"
++ #include "qpixmap.h"
++ #include "qguiapplication.h"
++ #include "qwindow.h"
++ #include "qpointer.h"
++
++ #include <QtCore/qloggingcategory.h>
++
++ #include <utility>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ namespace {
++
++ using WindowUPtr = std::unique_ptr<QWindow>;
++
++ } // namespace
++
++ class QGrabWindowSurfaceCapture::Grabber : public QSurfaceCaptureGrabber
++ {
++ public:
++ Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen) : Grabber(capture, screen, nullptr)
++ {
++ Q_ASSERT(screen);
++ }
++
++ Grabber(QGrabWindowSurfaceCapture &capture, WindowUPtr window)
++ : Grabber(capture, nullptr, std::move(window))
++ {
++ Q_ASSERT(m_window);
++ }
++
++ ~Grabber() override {
++ stop();
++
++ Q_ASSERT(!m_screenRemovingLocked);
++ }
++
++ const QVideoFrameFormat &format()
++ {
++ QMutexLocker locker(&m_formatMutex);
++ while (!m_format)
++ m_waitForFormat.wait(&m_formatMutex);
++ return *m_format;
++ }
++
++ private:
++ Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen, WindowUPtr window)
++ : QSurfaceCaptureGrabber(
++ QGuiApplication::platformName() == QLatin1String("eglfs")
++ ? QSurfaceCaptureGrabber::UseCurrentThread
++ : QSurfaceCaptureGrabber::CreateGrabbingThread),
++ m_screen(screen),
++ m_window(std::move(window))
++ {
++ connect(qApp, &QGuiApplication::screenRemoved, this, &Grabber::onScreenRemoved);
++ addFrameCallback(&capture, &QGrabWindowSurfaceCapture::newVideoFrame);
++ connect(this, &Grabber::errorUpdated, &capture, &QGrabWindowSurfaceCapture::updateError);
++
++ const auto defaultRate = screen ? qMin(screen->refreshRate(), DefaultScreenCaptureFrameRate)
++ : DefaultScreenCaptureFrameRate;
++ setFrameRate(capture.frameRate().value_or(defaultRate));
++ }
++
++ void onScreenRemoved(QScreen *screen)
++ {
++ /* The hack allows to lock screens removing while QScreen::grabWindow is in progress.
++ * The current solution works since QGuiApplication::screenRemoved is emitted from
++ * the destructor of QScreen before destruction members of the object.
++ * Note, QGuiApplication works with screens in the main thread, and any removing of a screen
++ * must be synchronized with grabbing thread.
++ */
++ QMutexLocker locker(&m_screenRemovingMutex);
++
++ if (m_screenRemovingLocked) {
++ qDebug() << "Screen" << screen->name()
++ << "is removed while screen window grabbing lock is active";
++ }
++
++ while (m_screenRemovingLocked)
++ m_screenRemovingWc.wait(&m_screenRemovingMutex);
++ }
++
++ void setScreenRemovingLocked(bool locked)
++ {
++ Q_ASSERT(locked != m_screenRemovingLocked);
++
++ {
++ QMutexLocker locker(&m_screenRemovingMutex);
++ m_screenRemovingLocked = locked;
++ }
++
++ if (!locked)
++ m_screenRemovingWc.wakeAll();
++ }
++
++ void updateFormat(const QVideoFrameFormat &format)
++ {
++ if (m_format && m_format->isValid())
++ return;
++
++ {
++ QMutexLocker locker(&m_formatMutex);
++ m_format = format;
++ }
++
++ m_waitForFormat.wakeAll();
++ }
++
++ QVideoFrame grabFrame() override
++ {
++ setScreenRemovingLocked(true);
++ auto screenGuard = qScopeGuard([this] {
++ setScreenRemovingLocked(false);
++ });
++
++ WId wid = m_window ? m_window->winId() : 0;
++ QScreen *screen = m_window ? m_window->screen() : m_screen ? m_screen.data() : nullptr;
++
++ if (!screen) {
++ updateError(QPlatformSurfaceCapture::Error::CaptureFailed, u"Screen not found"_s);
++ return {};
++ }
++
++ QPixmap p = screen->grabWindow(wid);
++ auto buffer = std::make_unique<QImageVideoBuffer>(p.toImage());
++ const auto img = buffer->underlyingImage();
++
++ QVideoFrameFormat format(img.size(),
++ QVideoFrameFormat::pixelFormatFromImageFormat(img.format()));
++ format.setStreamFrameRate(activeFrameRate());
++ updateFormat(format);
++
++ if (!format.isValid()) {
++ updateError(QPlatformSurfaceCapture::Error::CaptureFailed,
++ u"Failed to grab the screen content"_s);
++ return {};
++ }
++
++ return QVideoFramePrivate::createFrame(std::move(buffer), std::move(format));
++ }
++
++ private:
++ QPointer<QScreen> m_screen;
++ WindowUPtr m_window;
++
++ QMutex m_formatMutex;
++ QWaitCondition m_waitForFormat;
++ std::optional<QVideoFrameFormat> m_format;
++
++ QMutex m_screenRemovingMutex;
++ bool m_screenRemovingLocked = false;
++ QWaitCondition m_screenRemovingWc;
++ };
++
++ QGrabWindowSurfaceCapture::QGrabWindowSurfaceCapture(Source initialSource)
++ : QPlatformSurfaceCapture(std::move(initialSource))
++ {
++ }
++
++ QGrabWindowSurfaceCapture::~QGrabWindowSurfaceCapture() = default;
++
++ QVideoFrameFormat QGrabWindowSurfaceCapture::frameFormat() const
++ {
++ if (m_grabber)
++ return m_grabber->format();
++ else
++ return {};
++ }
++
++ bool QGrabWindowSurfaceCapture::setActiveInternal(bool active)
++ {
++ if (active == static_cast<bool>(m_grabber))
++ return true;
++
++ if (m_grabber)
++ m_grabber.reset();
++ else
++ std::visit([this](auto source) { activate(std::move(source)); }, source());
++
++ return static_cast<bool>(m_grabber) == active;
++ }
++
++ void QGrabWindowSurfaceCapture::activate(ScreenSource screen)
++ {
++ if (!checkScreenWithError(screen))
++ return;
++
++ m_grabber = std::make_unique<Grabber>(*this, screen);
++ m_grabber->start();
++ }
++
++ void QGrabWindowSurfaceCapture::activate(const WindowSource& window)
++ {
++ auto handle = QCapturableWindowPrivate::handle(window);
++ auto wid = handle ? handle->id : 0;
++ if (auto wnd = WindowUPtr(QWindow::fromWinId(wid))) {
++ if (!wnd->screen()) {
++ updateError(QPlatformSurfaceCapture::Error::InternalError,
++ u"Window " + QString::number(wid) + u" doesn't belong to any screen");
++ } else {
++ m_grabber = std::make_unique<Grabber>(*this, std::move(wnd));
++ m_grabber->start();
++ }
++ } else {
++ updateError(QPlatformSurfaceCapture::Error::NotFound,
++ u"Window " + QString::number(wid) + u"doesn't exist or permissions denied");
++ }
++ }
++
++ QT_END_NAMESPACE