diff options
| author | Arisotura <thetotalworm@gmail.com> | 2022-10-02 16:47:57 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-02 16:47:57 +0200 | 
| commit | 3f4573574a581da849408ebc376ca97d5e82bc7d (patch) | |
| tree | e6144cc9cfcfe383faee847e54b7864f963a9373 /src/frontend | |
| parent | c1c4cbc838bcc017ea325458112582e949274f30 (diff) | |
actual DSi camera support (#1520)
basically feeding something that isn't a fixed stripe pattern, and emulating enough of the camera hardware to make this work
Diffstat (limited to 'src/frontend')
| -rw-r--r-- | src/frontend/qt_sdl/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/CameraManager.cpp | 562 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/CameraManager.h | 133 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/CameraSettingsDialog.cpp | 304 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/CameraSettingsDialog.h | 108 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/CameraSettingsDialog.ui | 170 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/Config.cpp | 13 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/Config.h | 10 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/Platform.cpp | 23 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/main.cpp | 43 | ||||
| -rw-r--r-- | src/frontend/qt_sdl/main.h | 7 | 
11 files changed, 1374 insertions, 13 deletions
| diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 5f1c490..a8d6e4b 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES_QT_SDL      InputConfig/MapButton.h      InputConfig/resources/ds.qrc      VideoSettingsDialog.cpp +	CameraSettingsDialog.cpp      AudioSettingsDialog.cpp      FirmwareSettingsDialog.cpp      PathSettingsDialog.cpp @@ -33,8 +34,9 @@ set(SOURCES_QT_SDL      Platform.cpp      QPathInput.h      ROMManager.cpp -    SaveManager.cpp - +	SaveManager.cpp +	CameraManager.cpp +          ArchiveUtil.h      ArchiveUtil.cpp @@ -58,11 +60,11 @@ if (WIN32)  endif()  if (USE_QT6) -    find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED) -    set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets) +    find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED) +    set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)  else() -    find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) -    set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network) +    find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED) +    set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)  endif()  set(CMAKE_AUTOMOC ON) diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp new file mode 100644 index 0000000..23f25a6 --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -0,0 +1,562 @@ +/* +    Copyright 2016-2022 melonDS team + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "CameraManager.h" +#include "Config.h" + + +#if QT_VERSION >= 0x060000 + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) +{ +    cam = (CameraManager*)parent; + +    connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present); +} + +void CameraFrameDumper::present(const QVideoFrame& _frame) +{ +    QVideoFrame frame(_frame); +    if (!frame.map(QVideoFrame::ReadOnly)) +        return; +    if (!frame.isReadable()) +    { +        frame.unmap(); +        return; +    } + +    switch (frame.pixelFormat()) +    { +    case QVideoFrameFormat::Format_XRGB8888: +    case QVideoFrameFormat::Format_YUYV: +        cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV); +        break; + +    case QVideoFrameFormat::Format_NV12: +        cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); +        break; +    } + +    frame.unmap(); +} + +#else + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent) +{ +    cam = (CameraManager*)parent; +} + +bool CameraFrameDumper::present(const QVideoFrame& _frame) +{ +    QVideoFrame frame(_frame); +    if (!frame.map(QAbstractVideoBuffer::ReadOnly)) +        return false; +    if (!frame.isReadable()) +    { +        frame.unmap(); +        return false; +    } + +    switch (frame.pixelFormat()) +    { +    case QVideoFrame::Format_RGB32: +    case QVideoFrame::Format_YUYV: +        cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV); +        break; + +    case QVideoFrame::Format_NV12: +        cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); +        break; +    } + +    frame.unmap(); + +    return true; +} + +QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const +{ +    QList<QVideoFrame::PixelFormat> ret; + +    ret.append(QVideoFrame::Format_RGB32); +    ret.append(QVideoFrame::Format_YUYV); +    ret.append(QVideoFrame::Format_NV12); + +    return ret; +} + +#endif + + +CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +{ +    this->num = num; + +    startNum = 0; + +    // QCamera needs to be controlled from the UI thread, hence this +    connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart())); +    connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop())); + +    frameWidth = width; +    frameHeight = height; +    frameFormatYUV = yuv; + +    int fbsize = frameWidth * frameHeight; +    if (yuv) fbsize /= 2; +    frameBuffer = new u32[fbsize]; +    tempFrameBuffer = new u32[fbsize]; + +    inputType = -1; +    xFlip = false; +    init(); +} + +CameraManager::~CameraManager() +{ +    deInit(); + +    // save settings here? + +    delete[] frameBuffer; +} + +void CameraManager::init() +{ +    if (inputType != -1) +        deInit(); + +    startNum = 0; + +    inputType = Config::Camera[num].InputType; +    imagePath = QString::fromStdString(Config::Camera[num].ImagePath); +    camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + +    camDevice = nullptr; + +    { +        // fill the framebuffer with black + +        int total = frameWidth * frameHeight; +        u32 fill = 0; +        if (frameFormatYUV) +        { +            total /= 2; +            fill = 0x80008000; +        } + +        for (int i = 0; i < total; i++) +            frameBuffer[i] = fill; +    } + +    if (inputType == 1) +    { +        // still image + +        QImage img(imagePath); +        if (!img.isNull()) +        { +            QImage imgconv = img.convertToFormat(QImage::Format_RGB32); +            if (frameFormatYUV) +            { +                copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(), +                                   frameBuffer, frameWidth, frameHeight, +                                   false); +            } +            else +            { +                copyFrame_Straight((u32*)img.bits(), img.width(), img.height(), +                                   frameBuffer, frameWidth, frameHeight, +                                   false, false); +            } +        } +    } +    else if (inputType == 2) +    { +        // physical camera + +#if QT_VERSION >= 0x060000 +        const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); +        for (const QCameraDevice& cam : cameras) +        { +            if (QString(cam.id()) == camDeviceName) +            { +                camDevice = new QCamera(cam); +                break; +            } +        } + +        if (camDevice) +        { +            const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats(); +            bool good = false; +            for (const QCameraFormat& item : supported) +            { +                if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV && +                    item.pixelFormat() != QVideoFrameFormat::Format_NV12 && +                    item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888) +                    continue; + +                if (item.resolution().width() != 640 && item.resolution().height() != 480) +                    continue; + +                camDevice->setCameraFormat(item); +                good = true; +                break; +            } + +            if (!good) +            { +                delete camDevice; +                camDevice = nullptr; +            } +            else +            { +                camDumper = new CameraFrameDumper(this); + +                camSession = new QMediaCaptureSession(this); +                camSession->setCamera(camDevice); +                camSession->setVideoOutput(camDumper); +            } +        } +#else +        camDevice = new QCamera(camDeviceName.toUtf8()); +        if (camDevice->error() != QCamera::NoError) +        { +            delete camDevice; +            camDevice = nullptr; +        } + +        if (camDevice) +        { +            camDevice->load(); + +            const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings(); +            bool good = false; +            for (const QCameraViewfinderSettings& item : supported) +            { +                if (item.pixelFormat() != QVideoFrame::Format_YUYV && +                    item.pixelFormat() != QVideoFrame::Format_NV12 && +                    item.pixelFormat() != QVideoFrame::Format_RGB32) +                    continue; + +                if (item.resolution().width() != 640 && item.resolution().height() != 480) +                    continue; + +                camDevice->setViewfinderSettings(item); +                good = true; +                break; +            } + +            camDevice->unload(); + +            if (!good) +            { +                delete camDevice; +                camDevice = nullptr; +            } +            else +            { +                camDumper = new CameraFrameDumper(this); +                camDevice->setViewfinder(camDumper); +            } +        } +#endif +    } +} + +void CameraManager::deInit() +{ +    if (inputType == 2) +    { +        if (camDevice) +        { +            camDevice->stop(); +            delete camDevice; +            delete camDumper; +#if QT_VERSION >= 0x060000 +            delete camSession; +#endif +        } +    } + +    camDevice = nullptr; +    inputType = -1; +} + +void CameraManager::start() +{ +    if (startNum == 1) return; +    startNum = 1; + +    if (inputType == 2) +    { +        emit camStartSignal(); +    } +} + +void CameraManager::stop() +{ +    if (startNum == 0) return; +    startNum = 0; + +    if (inputType == 2) +    { +        emit camStopSignal(); +    } +} + +bool CameraManager::isStarted() +{ +    return startNum != 0; +} + +void CameraManager::camStart() +{ +    if (camDevice) +        camDevice->start(); +} + +void CameraManager::camStop() +{ +    if (camDevice) +        camDevice->stop(); +} + +void CameraManager::setXFlip(bool flip) +{ +    xFlip = flip; +} + +void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv) +{ +    frameMutex.lock(); + +    if ((width == frameWidth) && +        (height == frameHeight) && +        (yuv == frameFormatYUV) && +        (!xFlip)) +    { +        int len = width * height; +        if (yuv) len /= 2; +        memcpy(frame, frameBuffer, len * sizeof(u32)); +    } +    else +    { +        if (yuv == frameFormatYUV) +        { +            copyFrame_Straight(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip, yuv); +        } +        else if (yuv) +        { +            copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip); +        } +        else +        { +            copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip); +        } +    } + +    frameMutex.unlock(); +} + +void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv) +{ +    frameMutex.lock(); + +    if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV) +    { +        int len = width * height; +        if (yuv) len /= 2; +        memcpy(frameBuffer, frame, len * sizeof(u32)); +    } +    else +    { +        if (yuv == frameFormatYUV) +        { +            copyFrame_Straight(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false, yuv); +        } +        else if (yuv) +        { +            copyFrame_RGBtoYUV(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false); +        } +        else +        { +            copyFrame_YUVtoRGB(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false); +        } +    } + +    frameMutex.unlock(); +} + +void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height) +{ +    for (int y = 0; y < frameHeight; y++) +    { +        int sy = (y * height) / frameHeight; + +        for (int x = 0; x < frameWidth; x+=2) +        { +            int sx1 = (x * width) / frameWidth; +            int sx2 = ((x+1) * width) / frameWidth; + +            u32 val; + +            u8 y1 = planeY[(sy*width) + sx1]; +            u8 y2 = planeY[(sy*width) + sx2]; + +            int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1)); +            u8 u = planeUV[uvpos << 1]; +            u8 v = planeUV[(uvpos << 1) + 1]; + +            val = y1 | (u << 8) | (y2 << 16) | (v << 24); +            tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; +        } +    } + +    feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv) +{ +    u32 alpha = 0xFF000000; + +    if (yuv) +    { +        swidth /= 2; +        dwidth /= 2; +        alpha = 0; +    } + +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx++) +        { +            int sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            dst[(dy * dwidth) + dx] = src[(sy * swidth) + sx] | alpha; +        } +    } +} + +void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx+=2) +        { +            int sx; + +            sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 pixel1 = src[sy*swidth + sx]; + +            sx = ((dx+1) * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 pixel2 = src[sy*swidth + sx]; + +            int r1 = (pixel1 >> 16) & 0xFF; +            int g1 = (pixel1 >> 8) & 0xFF; +            int b1 = pixel1 & 0xFF; + +            int r2 = (pixel2 >> 16) & 0xFF; +            int g2 = (pixel2 >> 8) & 0xFF; +            int b2 = pixel2 & 0xFF; + +            int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; +            int u1 = ((b1 - y1) * 32244) >> 16; +            int v1 = ((r1 - y1) * 57475) >> 16; + +            int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; +            int u2 = ((b2 - y2) * 32244) >> 16; +            int v2 = ((r2 - y2) * 57475) >> 16; + +            u1 += 128; v1 += 128; +            u2 += 128; v2 += 128; + +            y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); +            y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + +            // huh +            u1 = (u1 + u2) >> 1; +            v1 = (v1 + v2) >> 1; + +            dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); +        } +    } +} + +void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx+=2) +        { +            int sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 val = src[(sy*swidth + sx) / 2]; + +            int y1 = val & 0xFF; +            int u = (val >> 8) & 0xFF; +            int y2 = (val >> 16) & 0xFF; +            int v = (val >> 24) & 0xFF; + +            u -= 128; v -= 128; + +            int r1 = y1 + ((v * 91881) >> 16); +            int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); +            int b1 = y1 + ((u * 116129) >> 16); + +            int r2 = y2 + ((v * 91881) >> 16); +            int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); +            int b2 = y2 + ((u * 116129) >> 16); + +            r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); +            r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + +            u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1; +            u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2; + +            dst[dy*dwidth + dx  ] = col1; +            dst[dy*dwidth + dx+1] = col2; +        } +    } +} diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h new file mode 100644 index 0000000..36e8565 --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.h @@ -0,0 +1,133 @@ +/* +    Copyright 2016-2022 melonDS team + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERAMANAGER_H +#define CAMERAMANAGER_H + +#include <QCamera> +#if QT_VERSION >= 0x060000 +    #include <QMediaDevices> +    #include <QCameraDevice> +    #include <QMediaCaptureSession> +    #include <QVideoSink> +#else +    #include <QCameraInfo> +    #include <QAbstractVideoSurface> +    #include <QVideoSurfaceFormat> +#endif +#include <QMutex> + +#include "types.h" + +class CameraManager; + + +#if QT_VERSION >= 0x060000 + +class CameraFrameDumper : public QVideoSink +{ +    Q_OBJECT + +public: +    CameraFrameDumper(QObject* parent = nullptr); + +public slots: +    void present(const QVideoFrame& frame); + +private: +    CameraManager* cam; +}; + +#else + +class CameraFrameDumper : public QAbstractVideoSurface +{ +    Q_OBJECT + +public: +    CameraFrameDumper(QObject* parent = nullptr); + +    bool present(const QVideoFrame& frame) override; +    QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override; + +private: +    CameraManager* cam; +}; + +#endif + + +class CameraManager : public QObject +{ +    Q_OBJECT + +public: +    CameraManager(int num, int width, int height, bool yuv); +    ~CameraManager(); + +    void init(); +    void deInit(); + +    void start(); +    void stop(); +    bool isStarted(); + +    void setXFlip(bool flip); + +    void captureFrame(u32* frame, int width, int height, bool yuv); + +    void feedFrame(u32* frame, int width, int height, bool yuv); +    void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height); + +signals: +    void camStartSignal(); +    void camStopSignal(); + +private slots: +    void camStart(); +    void camStop(); + +private: +    int num; + +    int startNum; + +    int inputType; +    QString imagePath; +    QString camDeviceName; + +    QCamera* camDevice; +    CameraFrameDumper* camDumper; +#if QT_VERSION >= 0x060000 +    QMediaCaptureSession* camSession; +#endif + +    int frameWidth, frameHeight; +    bool frameFormatYUV; +    u32* frameBuffer; +    u32* tempFrameBuffer; +    QMutex frameMutex; + +    bool xFlip; + +    void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv); +    void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); +    void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); +}; + +#endif // CAMERAMANAGER_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp new file mode 100644 index 0000000..1844e0f --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -0,0 +1,304 @@ +/* +    Copyright 2016-2022 melonDS team + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <QFileDialog> +#include <QPaintEvent> +#include <QPainter> + +#include "types.h" + +#include "CameraSettingsDialog.h" +#include "ui_CameraSettingsDialog.h" + + +CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr; + +extern std::string EmuDirectory; + +extern CameraManager* camManager[2]; + + +CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent) +{ +    currentCam = nullptr; +    updateTimer = startTimer(50); +} + +CameraPreviewPanel::~CameraPreviewPanel() +{ +    killTimer(updateTimer); +} + +void CameraPreviewPanel::paintEvent(QPaintEvent* event) +{ +    QPainter painter(this); + +    if (!currentCam) +    { +        painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); +        return; +    } + +    QImage picture(256, 192, QImage::Format_RGB32); +    currentCam->captureFrame((u32*)picture.bits(), 256, 192, false); +    painter.drawImage(0, 0, picture); +} + + +CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog) +{ +    previewPanel = nullptr; +    currentCfg = nullptr; +    currentCam = nullptr; + +    ui->setupUi(this); +    setAttribute(Qt::WA_DeleteOnClose); + +    for (int i = 0; i < 2; i++) +    { +        oldCamSettings[i] = Config::Camera[i]; +    } + +    ui->cbCameraSel->addItem("DSi outer camera"); +    ui->cbCameraSel->addItem("DSi inner camera"); + +#if QT_VERSION >= 0x060000 +    const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); +    for (const QCameraDevice &cameraInfo : cameras) +    { +        QString name = cameraInfo.description(); +        QCameraDevice::Position pos = cameraInfo.position(); +        if (pos != QCameraDevice::UnspecifiedPosition) +        { +            name += " ("; +            if (pos == QCameraDevice::FrontFace) +                name += "inner camera"; +            else if (pos == QCameraDevice::BackFace) +                name += "outer camera"; +            name += ")"; +        } + +        ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id())); +    } +#else +    const QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); +    for (const QCameraInfo &cameraInfo : cameras) +    { +        QString name = cameraInfo.description(); +        QCamera::Position pos = cameraInfo.position(); +        if (pos != QCamera::UnspecifiedPosition) +        { +            name += " ("; +            if (pos == QCamera::FrontFace) +                name += "inner camera"; +            else if (pos == QCamera::BackFace) +                name += "outer camera"; +            name += ")"; +        } + +        ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName()); +    } +#endif +    ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0); + +    grpInputType = new QButtonGroup(this); +    grpInputType->addButton(ui->rbPictureNone,   0); +    grpInputType->addButton(ui->rbPictureImg,    1); +    grpInputType->addButton(ui->rbPictureCamera, 2); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) +    connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int))); +#else +    connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int))); +#endif + +    previewPanel = new CameraPreviewPanel(this); +    QVBoxLayout* previewLayout = new QVBoxLayout(); +    previewLayout->addWidget(previewPanel); +    ui->grpPreview->setLayout(previewLayout); +    previewPanel->setMinimumSize(256, 192); +    previewPanel->setMaximumSize(256, 192); + +    on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex()); +} + +CameraSettingsDialog::~CameraSettingsDialog() +{ +    delete ui; +} + +void CameraSettingsDialog::on_CameraSettingsDialog_accepted() +{ +    for (int i = 0; i < 2; i++) +    { +        camManager[i]->stop(); +    } + +    Config::Save(); + +    closeDlg(); +} + +void CameraSettingsDialog::on_CameraSettingsDialog_rejected() +{ +    for (int i = 0; i < 2; i++) +    { +        camManager[i]->stop(); +        camManager[i]->deInit(); +        Config::Camera[i] = oldCamSettings[i]; +        camManager[i]->init(); +    } + +    closeDlg(); +} + +void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id) +{ +    if (!previewPanel) return; + +    if (currentCam) +    { +        currentCam->stop(); +    } + +    currentId = id; +    currentCfg = &Config::Camera[id]; +    //currentCam = camManager[id]; +    currentCam = nullptr; +    populateCamControls(id); +    currentCam = camManager[id]; +    previewPanel->setCurrentCam(currentCam); + +    currentCam->start(); +} + +void CameraSettingsDialog::onChangeInputType(int type) +{ +    if (!currentCfg) return; + +    if (currentCam) +    { +        currentCam->stop(); +        currentCam->deInit(); +    } + +    currentCfg->InputType = type; + +    ui->txtSrcImagePath->setEnabled(type == 1); +    ui->btnSrcImageBrowse->setEnabled(type == 1); +    ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0)); + +    currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + +    if (ui->cbPhysicalCamera->count() > 0) +        currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString(); + +    if (currentCam) +    { +        currentCam->init(); +        currentCam->start(); +    } +} + +void CameraSettingsDialog::on_txtSrcImagePath_textChanged() +{ +    if (!currentCfg) return; + +    if (currentCam) +    { +        currentCam->stop(); +        currentCam->deInit(); +    } + +    currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + +    if (currentCam) +    { +        currentCam->init(); +        currentCam->start(); +    } +} + +void CameraSettingsDialog::on_btnSrcImageBrowse_clicked() +{ +    QString file = QFileDialog::getOpenFileName(this, +                                                "Select image file...", +                                                QString::fromStdString(EmuDirectory), +                                                "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)"); + +    if (file.isEmpty()) return; + +    ui->txtSrcImagePath->setText(file); +} + +void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) +{ +    if (!currentCfg) return; + +    if (currentCam) +    { +        currentCam->stop(); +        currentCam->deInit(); +    } + +    currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString(); + +    if (currentCam) +    { +        currentCam->init(); +        currentCam->start(); +    } +} + +void CameraSettingsDialog::populateCamControls(int id) +{ +    Config::CameraConfig& cfg = Config::Camera[id]; + +    int type = cfg.InputType; +    if (type < 0 || type >= grpInputType->buttons().count()) type = 0; +    grpInputType->button(type)->setChecked(true); + +    ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath)); + +    bool deviceset = false; +    QString device = QString::fromStdString(cfg.CamDeviceName); +    for (int i = 0; i < ui->cbPhysicalCamera->count(); i++) +    { +        QString itemdev = ui->cbPhysicalCamera->itemData(i).toString(); +        if (itemdev == device) +        { +            ui->cbPhysicalCamera->setCurrentIndex(i); +            deviceset = true; +            break; +        } +    } +    if (!deviceset) +        ui->cbPhysicalCamera->setCurrentIndex(0); + +    onChangeInputType(type); + +    ui->chkFlipPicture->setChecked(cfg.XFlip); +} + +void CameraSettingsDialog::on_chkFlipPicture_clicked() +{ +    if (!currentCfg) return; + +    currentCfg->XFlip = ui->chkFlipPicture->isChecked(); +    if (currentCam) currentCam->setXFlip(currentCfg->XFlip); +} diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h new file mode 100644 index 0000000..8572ac4 --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.h @@ -0,0 +1,108 @@ +/* +    Copyright 2016-2022 melonDS team + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERASETTINGSDIALOG_H +#define CAMERASETTINGSDIALOG_H + +#include <QDialog> +#include <QButtonGroup> + +#include "Config.h" +#include "CameraManager.h" + +namespace Ui { class CameraSettingsDialog; } +class CameraSettingsDialog; + +class CameraPreviewPanel : public QWidget +{ +    Q_OBJECT + +public: +    CameraPreviewPanel(QWidget* parent); +    ~CameraPreviewPanel(); + +    void setCurrentCam(CameraManager* cam) +    { +        currentCam = cam; +    } + +protected: +    void paintEvent(QPaintEvent* event) override; +    void timerEvent(QTimerEvent* event) override +    { +        repaint(); +    } + +private: +    int updateTimer; +    CameraManager* currentCam; +}; + +class CameraSettingsDialog : public QDialog +{ +    Q_OBJECT + +public: +    explicit CameraSettingsDialog(QWidget* parent); +    ~CameraSettingsDialog(); + +    static CameraSettingsDialog* currentDlg; +    static CameraSettingsDialog* openDlg(QWidget* parent) +    { +        if (currentDlg) +        { +            currentDlg->activateWindow(); +            return currentDlg; +        } + +        currentDlg = new CameraSettingsDialog(parent); +        currentDlg->open(); +        return currentDlg; +    } +    static void closeDlg() +    { +        currentDlg = nullptr; +    } + +private slots: +    void on_CameraSettingsDialog_accepted(); +    void on_CameraSettingsDialog_rejected(); + +    void on_cbCameraSel_currentIndexChanged(int id); +    void onChangeInputType(int type); +    void on_txtSrcImagePath_textChanged(); +    void on_btnSrcImageBrowse_clicked(); +    void on_cbPhysicalCamera_currentIndexChanged(int id); +    void on_chkFlipPicture_clicked(); + +private: +    Ui::CameraSettingsDialog* ui; + +    QButtonGroup* grpInputType; +    CameraPreviewPanel* previewPanel; + +    int currentId; +    Config::CameraConfig* currentCfg; +    CameraManager* currentCam; + +    Config::CameraConfig oldCamSettings[2]; + +    void populateCamControls(int id); +}; + +#endif // CAMERASETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.ui b/src/frontend/qt_sdl/CameraSettingsDialog.ui new file mode 100644 index 0000000..bbaf45b --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.ui @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CameraSettingsDialog</class> + <widget class="QDialog" name="CameraSettingsDialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>605</width> +    <height>341</height> +   </rect> +  </property> +  <property name="sizePolicy"> +   <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +    <horstretch>0</horstretch> +    <verstretch>0</verstretch> +   </sizepolicy> +  </property> +  <property name="windowTitle"> +   <string>Camera settings - melonDS</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <property name="sizeConstraint"> +    <enum>QLayout::SetFixedSize</enum> +   </property> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1"> +     <item> +      <widget class="QLabel" name="label"> +       <property name="text"> +        <string>Configure emulated camera:</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QComboBox" name="cbCameraSel"/> +     </item> +    </layout> +   </item> +   <item> +    <layout class="QGridLayout" name="gridLayout_3"> +     <item row="0" column="0"> +      <widget class="QGroupBox" name="groupBox"> +       <property name="title"> +        <string>Picture source</string> +       </property> +       <layout class="QGridLayout" name="gridLayout_2"> +        <item row="1" column="2"> +         <widget class="QLineEdit" name="txtSrcImagePath"/> +        </item> +        <item row="0" column="0" colspan="4"> +         <widget class="QRadioButton" name="rbPictureNone"> +          <property name="text"> +           <string>None (blank)</string> +          </property> +         </widget> +        </item> +        <item row="1" column="3"> +         <widget class="QPushButton" name="btnSrcImageBrowse"> +          <property name="text"> +           <string>Browse...</string> +          </property> +         </widget> +        </item> +        <item row="2" column="0"> +         <widget class="QRadioButton" name="rbPictureCamera"> +          <property name="text"> +           <string>Physical camera:</string> +          </property> +         </widget> +        </item> +        <item row="1" column="0"> +         <widget class="QRadioButton" name="rbPictureImg"> +          <property name="text"> +           <string>Image file:</string> +          </property> +         </widget> +        </item> +        <item row="2" column="2" colspan="2"> +         <widget class="QComboBox" name="cbPhysicalCamera"/> +        </item> +       </layout> +      </widget> +     </item> +     <item row="1" column="0"> +      <widget class="QGroupBox" name="groupBox_3"> +       <property name="title"> +        <string>Picture settings</string> +       </property> +       <layout class="QGridLayout" name="gridLayout_4"> +        <item row="0" column="0"> +         <widget class="QCheckBox" name="chkFlipPicture"> +          <property name="text"> +           <string>Flip horizontally</string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +     <item row="2" column="0"> +      <spacer name="verticalSpacer"> +       <property name="orientation"> +        <enum>Qt::Vertical</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>20</width> +         <height>40</height> +        </size> +       </property> +      </spacer> +     </item> +     <item row="0" column="2" rowspan="3"> +      <widget class="QGroupBox" name="grpPreview"> +       <property name="title"> +        <string>Preview</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>CameraSettingsDialog</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>248</x> +     <y>254</y> +    </hint> +    <hint type="destinationlabel"> +     <x>157</x> +     <y>274</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>CameraSettingsDialog</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>316</x> +     <y>260</y> +    </hint> +    <hint type="destinationlabel"> +     <x>286</x> +     <y>274</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index a8df8ee..8b2f3d4 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -140,6 +140,8 @@ bool DSBatteryLevelOkay;  int DSiBatteryLevel;  bool DSiBatteryCharging; +CameraConfig Camera[2]; +  const char* kConfigFile = "melonDS.ini";  const char* kUniqueConfigFile = "melonDS.%d.ini"; @@ -316,6 +318,17 @@ ConfigEntry ConfigFile[] =      {"DSiBatteryLevel",    0, &DSiBatteryLevel, 0xF, true},      {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, +    // TODO!! +    // we need a more elegant way to deal with this +    {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, +    {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false}, +    {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false}, +    {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false}, +    {"Camera1_InputType", 0, &Camera[1].InputType, 0, false}, +    {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false}, +    {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false}, +    {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false}, +      {"", -1, nullptr, 0, false}  }; diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index cc6792c..6ccae5f 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -61,6 +61,14 @@ struct ConfigEntry      bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer  }; +struct CameraConfig +{ +    int InputType; // 0=blank 1=image 2=camera +    std::string ImagePath; +    std::string CamDeviceName; +    bool XFlip; +}; +  extern int KeyMapping[12];  extern int JoyMapping[12]; @@ -175,6 +183,8 @@ extern bool DSBatteryLevelOkay;  extern int DSiBatteryLevel;  extern bool DSiBatteryCharging; +extern CameraConfig Camera[2]; +  void Load();  void Save(); diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 68bdd3e..f9eaf42 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -33,6 +33,7 @@  #include "Platform.h"  #include "Config.h"  #include "ROMManager.h" +#include "CameraManager.h"  #include "LAN_Socket.h"  #include "LAN_PCap.h"  #include "LocalMP.h" @@ -40,8 +41,11 @@  std::string EmuDirectory; +extern CameraManager* camManager[2]; +  void emuStop(); +  namespace Platform  { @@ -99,7 +103,6 @@ void IPCDeInit()          IPCBuffer->detach();          delete IPCBuffer;      } -      IPCBuffer = nullptr;  } @@ -492,8 +495,6 @@ u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)      return LocalMP::RecvReplies(data, timestamp, aidmask);  } - -  bool LAN_Init()  {      if (Config::DirectLAN) @@ -537,4 +538,20 @@ int LAN_RecvPacket(u8* data)          return LAN_Socket::RecvPacket(data);  } + +void Camera_Start(int num) +{ +    return camManager[num]->start(); +} + +void Camera_Stop(int num) +{ +    return camManager[num]->stop(); +} + +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) +{ +    return camManager[num]->captureFrame(frame, width, height, yuv); +} +  } diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 97b78fc..a9d0c4d 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -55,6 +55,7 @@  #include "EmuSettingsDialog.h"  #include "InputConfig/InputConfigDialog.h"  #include "VideoSettingsDialog.h" +#include "CameraSettingsDialog.h"  #include "AudioSettingsDialog.h"  #include "FirmwareSettingsDialog.h"  #include "PathSettingsDialog.h" @@ -88,6 +89,7 @@  #include "ROMManager.h"  #include "ArchiveUtil.h" +#include "CameraManager.h"  // TODO: uniform variable spelling @@ -115,6 +117,9 @@ u32 micExtBufferWritePos;  u32 micWavLength;  s16* micWavBuffer; +CameraManager* camManager[2]; +bool camStarted[2]; +  const struct { int id; float ratio; const char* label; } aspectRatios[] =  {      { 0, 1,                       "4:3 (native)" }, @@ -127,6 +132,7 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] =  void micCallback(void* data, Uint8* stream, int len); +  void audioCallback(void* data, Uint8* stream, int len)  {      len /= (sizeof(s16) * 2); @@ -1537,6 +1543,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)          actVideoSettings = menu->addAction("Video settings");          connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); +        actCameraSettings = menu->addAction("Camera settings"); +        connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); +          actAudioSettings = menu->addAction("Audio settings");          connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); @@ -2751,6 +2760,27 @@ void MainWindow::onOpenVideoSettings()      connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);  } +void MainWindow::onOpenCameraSettings() +{ +    emuThread->emuPause(); + +    camStarted[0] = camManager[0]->isStarted(); +    camStarted[1] = camManager[1]->isStarted(); +    if (camStarted[0]) camManager[0]->stop(); +    if (camStarted[1]) camManager[1]->stop(); + +    CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); +    connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); +} + +void MainWindow::onCameraSettingsFinished(int res) +{ +    if (camStarted[0]) camManager[0]->start(); +    if (camStarted[1]) camManager[1]->start(); + +    emuThread->emuUnpause(); +} +  void MainWindow::onOpenAudioSettings()  {      AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this); @@ -3105,7 +3135,7 @@ bool MelonApplication::event(QEvent *event)  int main(int argc, char** argv)  { -    srand(time(NULL)); +    srand(time(nullptr));      printf("melonDS " MELONDS_VERSION "\n");      printf(MELONDS_URL "\n"); @@ -3195,11 +3225,17 @@ int main(int argc, char** argv)      micDevice = 0; -      memset(micExtBuffer, 0, sizeof(micExtBuffer));      micExtBufferWritePos = 0;      micWavBuffer = nullptr; +    camStarted[0] = false; +    camStarted[1] = false; +    camManager[0] = new CameraManager(0, 640, 480, true); +    camManager[1] = new CameraManager(1, 640, 480, true); +    camManager[0]->setXFlip(Config::Camera[0].XFlip); +    camManager[1]->setXFlip(Config::Camera[1].XFlip); +      ROMManager::EnableCheats(Config::EnableCheats != 0);      Frontend::Init_Audio(audioFreq); @@ -3252,6 +3288,9 @@ int main(int argc, char** argv)      if (micWavBuffer) delete[] micWavBuffer; +    delete camManager[0]; +    delete camManager[1]; +      Config::Save();      SDL_Quit(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 5d03e54..1977b7f 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -266,16 +266,18 @@ private slots:      void onOpenInputConfig();      void onInputConfigFinished(int res);      void onOpenVideoSettings(); +    void onOpenCameraSettings(); +    void onCameraSettingsFinished(int res);      void onOpenAudioSettings(); -    void onOpenFirmwareSettings(); -    void onOpenPathSettings();      void onUpdateAudioSettings();      void onAudioSettingsFinished(int res);      void onOpenMPSettings();      void onMPSettingsFinished(int res);      void onOpenWifiSettings();      void onWifiSettingsFinished(int res); +    void onOpenFirmwareSettings();      void onFirmwareSettingsFinished(int res); +    void onOpenPathSettings();      void onPathSettingsFinished(int res);      void onOpenInterfaceSettings();      void onInterfaceSettingsFinished(int res); @@ -359,6 +361,7 @@ public:      QAction* actPowerManagement;      QAction* actInputConfig;      QAction* actVideoSettings; +    QAction* actCameraSettings;      QAction* actAudioSettings;      QAction* actMPSettings;      QAction* actWifiSettings; |