// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include "qwaylanddisplay_p.h"
#include "qwaylandscreen_p.h"
#include "qwaylandscreencopy_p.h"
#include "qwaylandshm_p.h"

#include <sys/mman.h>

#include <QFile>
#include <QTemporaryFile>
#include <QStandardPaths>

QT_BEGIN_NAMESPACE

namespace QtWaylandClient
{

QWaylandScreenCopyManagerV1::QWaylandScreenCopyManagerV1(QWaylandDisplay *display, uint id, uint version)
    : QtWayland::zwlr_screencopy_manager_v1(display->wl_registry(), id, version)
    , mDisplay(display)
{
}

QPixmap QWaylandScreenCopyManagerV1::captureScreen(wl_output *output, bool hasCursor)
{
    QWaylandScreenCopyFrameV1 frame(mDisplay, capture_output(hasCursor ? 1 : 0, output));
    while (!frame.copyDone())
        wl_display_dispatch(mDisplay->wl_display());

    return frame.pixmap();
}

QPixmap QWaylandScreenCopyManagerV1::captureScreenRegion(wl_output *output, int32_t x, int32_t y, int32_t width, int32_t height, bool hasCursor)
{
    QWaylandScreenCopyFrameV1 frame(mDisplay, capture_output_region(hasCursor ? 1 : 0, output, x, y, width, height));
    while (!frame.copyDone())
        wl_display_dispatch(mDisplay->wl_display());

    return frame.pixmap();
}

QWaylandScreenCopyFrameV1::QWaylandScreenCopyFrameV1(QWaylandDisplay *display, struct ::zwlr_screencopy_frame_v1 *object)
    : QtWayland::zwlr_screencopy_frame_v1(object)
    , mDisplay(display)
{
}

QWaylandScreenCopyFrameV1::~QWaylandScreenCopyFrameV1()
{
    if (mBuffer)
        wl_buffer_destroy(mBuffer);

    if (mImage.constBits())
        munmap((void *) mImage.constBits(), mImage.sizeInBytes());

    destroy();
}

QPixmap QWaylandScreenCopyFrameV1::pixmap()
{
    if (mYInvert)
        mImage = mImage.mirrored(false, true);

    return QPixmap::fromImage(mImage);
}

void QWaylandScreenCopyFrameV1::zwlr_screencopy_frame_v1_ready(uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
{
    mCopyDone = true;
}

void QWaylandScreenCopyFrameV1::QWaylandScreenCopyFrameV1::zwlr_screencopy_frame_v1_buffer(uint32_t format, uint32_t width, uint32_t height, uint32_t stride)
{
    int fd = -1;
    int alloc = stride * height;
    QScopedPointer<QFile> filePointer;

    if (fd == -1) {
        auto tmpFile = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) +
                                       QLatin1String("/wayland-screencopy-XXXXXX"));
        tmpFile->open();
        filePointer.reset(tmpFile);
    } else {
        auto file = new QFile;
        file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle);
        filePointer.reset(file);
    }
    if (!filePointer->isOpen() || !filePointer->resize(alloc)) {
        qWarning("QWaylandShmBuffer: failed: %s", qUtf8Printable(filePointer->errorString()));
        return;
    }
    fd = filePointer->handle();

    // map ourselves: QFile::map() will unmap when the object is destroyed,
    // but we want this mapping to persist (unmapping in destructor)
    uchar *data = (uchar *)
            mmap(nullptr, alloc, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == (uchar *) MAP_FAILED) {
        qErrnoWarning("QWaylandShmBuffer: mmap failed");
        return;
    }

    QWaylandShm* shm = mDisplay->shm();
    QImage::Format imgFormat = QWaylandShm::formatFrom(wl_shm_format(format));
    mImage = QImage(data, width, height, stride, imgFormat);

    wl_shm_pool *shmPool = wl_shm_create_pool(shm->object(), fd, alloc);
    if (mBuffer) {
        wl_buffer_destroy(mBuffer);
        delete mBuffer;
        mBuffer = nullptr;
    }

    mBuffer = wl_shm_pool_create_buffer(shmPool, 0, width, height, stride, format);
    copy(mBuffer);
    wl_shm_pool_destroy(shmPool);
}

void QWaylandScreenCopyFrameV1::zwlr_screencopy_frame_v1_flags(uint32_t flags)
{
    mYInvert = flags & flags_y_invert;
}

void QWaylandScreenCopyFrameV1::zwlr_screencopy_frame_v1_failed()
{
    qWarning() << "Screen copy failed";
}

} // namespace QtWaylandClient

QT_END_NAMESPACE
