// 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 "qscreencapture.h"

#include <QtCore/private/qobject_p.h>

#include <QtMultimedia/qmediacapturesession.h>
#include <QtMultimedia/private/qplatformmediaintegration_p.h>
#include <QtMultimedia/private/qplatformsurfacecapture_p.h>

QT_BEGIN_NAMESPACE

static QScreenCapture::Error toScreenCaptureError(QPlatformSurfaceCapture::Error error)
{
    return static_cast<QScreenCapture::Error>(error);
}

class QScreenCapturePrivate : public QObjectPrivate
{
public:
    QMediaCaptureSession *captureSession = nullptr;
    std::unique_ptr<QPlatformSurfaceCapture> platformScreenCapture;
};

/*!
    \class QScreenCapture
    \inmodule QtMultimedia
    \ingroup multimedia
    \ingroup multimedia_video
    \since 6.5

    \brief This class is used for capturing a screen.

    The class captures a screen. It is managed by
    the \l QMediaCaptureSession class where the captured screen can be displayed
    in a video preview object or recorded to a file.

    The following snippet shows how to capture the primary screen and display
    the result in a QVideoWidget:

    \snippet multimedia-snippets/screencapturesnippets.cpp Basic setup

    \include qscreencapture-limitations.qdocinc {content} {Q}

    \sa QWindowCapture, QMediaCaptureSession
*/
/*!
    \qmltype ScreenCapture
    \nativetype QScreenCapture
    \brief This type is used for capturing a screen.

    \inqmlmodule QtMultimedia
    \ingroup multimedia_qml
    \ingroup multimedia_video_qml
    \since 6.5

    ScreenCapture captures a screen. It is managed by
    \l{QtMultimedia::CaptureSession}{CaptureSession} where the captured
    screen can be displayed in a video preview object or recorded to a
    file.

    The code below shows a simple capture session with ScreenCapture playing
    back the captured primary screen view in \l {QtMultimedia::VideoOutput}{VideoOutput}.

    \snippet multimedia-snippets/screencapturesnippets.qml Basic setup

    \include qscreencapture-limitations.qdocinc {content} {}

    \sa WindowCapture, CaptureSession
*/

QScreenCapture::QScreenCapture(QObject *parent)
    : QObject(*new QScreenCapturePrivate, parent)
{
    Q_D(QScreenCapture);

    auto platformCapture = QPlatformMediaIntegration::instance()->createScreenCapture(this);
    if (platformCapture) {
        connect(platformCapture, &QPlatformSurfaceCapture::activeChanged, this,
                &QScreenCapture::activeChanged);
        connect(platformCapture, &QPlatformSurfaceCapture::errorChanged, this,
                &QScreenCapture::errorChanged);
        connect(platformCapture, &QPlatformSurfaceCapture::errorOccurred, this,
                [this](QPlatformSurfaceCapture::Error error, const QString &errorString) {
            emit errorOccurred(toScreenCaptureError(error), errorString);
        });

        connect(platformCapture,
                qOverload<QPlatformSurfaceCapture::ScreenSource>(
                        &QPlatformSurfaceCapture::sourceChanged),
                this, &QScreenCapture::screenChanged);

        connect(platformCapture, &QPlatformSurfaceCapture::frameRateChanged, this,
                &QScreenCapture::maximumFrameRateChanged);

        d->platformScreenCapture.reset(platformCapture);
    }
}

QScreenCapture::~QScreenCapture()
{
    Q_D(QScreenCapture);

    // Reset platformScreenCapture in the destructor to avoid having broken ref in the object.
    d->platformScreenCapture.reset();

    if (d->captureSession)
        d->captureSession->setScreenCapture(nullptr);
}

/*!
    \enum QScreenCapture::Error

    Enumerates error codes that can be signaled by the QScreenCapture class.
    errorString() provides detailed information about the error cause.

    \value NoError                      No error
    \value InternalError                Internal screen capturing driver error
    \value CapturingNotSupported        Capturing is not supported
    \value CaptureFailed                Capturing screen failed
    \value NotFound                     Selected screen not found
*/

/*!
    Returns the capture session this QScreenCapture is connected to.

    Use QMediaCaptureSession::setScreenCapture() to connect the screen capture
    to a session.
*/
QMediaCaptureSession *QScreenCapture::captureSession() const
{
    Q_D(const QScreenCapture);

    return d->captureSession;
}

/*!
    \qmlproperty bool QtMultimedia::ScreenCapture::active
    Describes whether the capturing is currently active.

    \sa start(), stop()
*/

/*!
    \property QScreenCapture::active
    \brief whether the capturing is currently active.

    \sa start(), stop()
*/
void QScreenCapture::setActive(bool active)
{
    Q_D(QScreenCapture);

    if (d->platformScreenCapture)
        d->platformScreenCapture->setActive(active);
}

bool QScreenCapture::isActive() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture && d->platformScreenCapture->isActive();
}

/*!
    \qmlproperty Screen QtMultimedia::ScreenCapture::screen
    Describes the screen for capturing.

    If null \c Screen is set, the primary screen will be selected
    when the \c ScreenCapture instance gets activated.

    \sa {QtQuick::Application::screens}{Application.screens}
*/

/*!
    \property QScreenCapture::screen
    \brief the screen for capturing.

    If null \l QScreen is set, \l QGuiApplication::primaryScreen will be selected
    when the \c QScreenCapture instance gets activated.

    \sa QGuiApplication::screens(), QGuiApplication::primaryScreen()
*/

void QScreenCapture::setScreen(QScreen *screen)
{
    Q_D(QScreenCapture);

    if (d->platformScreenCapture)
        d->platformScreenCapture->setSource(QPlatformSurfaceCapture::ScreenSource(screen));
}

QScreen *QScreenCapture::screen() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture
            ? d->platformScreenCapture->source<QPlatformSurfaceCapture::ScreenSource>()
            : nullptr;
}

/*!
    \qmlsignal QtMultimedia::ScreenCapture::errorChanged()

    This signal is emitted when the \l{error} or \l{errorString} properties are changed.

    This signal is not emitted whenever multiple identical errors are raised. To track such
    errors, use the signal \l errorOccurred.
*/

/*!
    \fn void QScreenCapture::errorChanged()

    This signal is emitted when the \l{error} or \l{errorString} properties are changed.

    This signal is not emitted whenever multiple identical errors are raised. To track such
    errors, use the signal \l errorOccurred.
*/

/*!
    \qmlproperty enumeration QtMultimedia::ScreenCapture::error
    Returns a code of the last error.

    \qmlenumeratorsfrom QScreenCapture::Error
*/

/*!
    \property QScreenCapture::error
    \brief the code of the last error.
*/
QScreenCapture::Error QScreenCapture::error() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture ? toScreenCaptureError(d->platformScreenCapture->error())
                                    : CapturingNotSupported;
}

/*!
    \qmlsignal QtMultimedia::ScreenCapture::errorOccurred(int error, string errorString)

    Signals when an \a error occurs, along with the \a errorString.

    For the error parameter, see the enumeration table in
    \l {QtMultimedia::ScreenCapture::error}{error} for what values may
    be passed.

    \sa {QtMultimedia::ScreenCapture::error}{error}
*/
/*!
    \fn void QScreenCapture::errorOccurred(QScreenCapture::Error error, const QString &errorString)

    Signals when an \a error occurs, along with the \a errorString.
*/
/*!
    \qmlproperty string QtMultimedia::ScreenCapture::errorString
    Returns a human readable string describing the cause of error.
*/

/*!
    \property QScreenCapture::errorString
    \brief a human readable string describing the cause of error.
*/
QString QScreenCapture::errorString() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture ? d->platformScreenCapture->errorString()
                                    : QLatin1StringView("Capturing is not supported on this platform");
}

/*!
    \since 6.12
    \property QScreenCapture::maximumFrameRate
    \brief The screen capture frame rate upper limit.

    This can be set to override the capture frame rate used by default based on
    e.g. display refresh rate, but only as an upper limit since screen capture
    produces frames at a variable rate. Setting this higher than the display
    refresh rate is not recommended and can cause errors.

    Any changes to this property are applied the next time the QScreenCapture
    goes active.
*/
void QScreenCapture::setMaximumFrameRate(std::optional<qreal> frameRate)
{
    Q_D(QScreenCapture);

    if (d->platformScreenCapture)
        d->platformScreenCapture->setFrameRate(frameRate);
}

std::optional<qreal> QScreenCapture::maximumFrameRate() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture ? d->platformScreenCapture->frameRate() : std::nullopt;
}

/*!
    \qmlmethod void QtMultimedia::ScreenCapture::start()

    Starts capturing the \l screen.

    This is equivalent to setting the \l active property to \c true.
*/

/*!
    \fn void QScreenCapture::start()

    Starts capturing the \l screen.

    This is equivalent to setting the \l active property to true.
*/

/*!
    \qmlmethod void QtMultimedia::ScreenCapture::stop()

    Stops capturing.

    This is equivalent to setting the \l active property to \c false.
*/

/*!
    \fn void QScreenCapture::stop()

    Stops capturing.

    This is equivalent to setting the \l active property to false.
*/
/*!
    \internal
*/
void QScreenCapture::setCaptureSession(QMediaCaptureSession *captureSession)
{
    Q_D(QScreenCapture);

    d->captureSession = captureSession;
}

/*!
    \internal
*/
class QPlatformSurfaceCapture *QScreenCapture::platformScreenCapture() const
{
    Q_D(const QScreenCapture);

    return d->platformScreenCapture.get();
}

QT_END_NAMESPACE

#include "moc_qscreencapture.cpp"
