diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 4081563887..9da2c91d1d 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -5,6 +5,8 @@ include(FixInterfaceIncludes) set(SOURCES_QT_SDL main.cpp main_shaders.h + Screen.cpp + Window.cpp CheatsDialog.cpp Config.cpp DateTimeDialog.cpp @@ -38,7 +40,7 @@ set(SOURCES_QT_SDL SaveManager.cpp CameraManager.cpp AudioInOut.cpp - + ArchiveUtil.h ArchiveUtil.cpp diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h index 366c9c39dc..2dfa4a77e3 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/Input.h @@ -19,6 +19,8 @@ #ifndef INPUT_H #define INPUT_H +#include + #include "types.h" namespace Input diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp new file mode 100644 index 0000000000..6de4d78e0a --- /dev/null +++ b/src/frontend/qt_sdl/Screen.cpp @@ -0,0 +1,564 @@ +/* + Copyright 2016-2023 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#ifndef _WIN32 +#ifndef APPLE +#include +#endif +#endif + +#include "main.h" + +#include "NDS.h" +#include "Platform.h" +#include "Config.h" + +//#include "main_shaders.h" + +#include "OSD.h" + +using namespace melonDS; + + +/*const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; +int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);*/ + +// TEMP +extern MainWindow* mainWindow; +extern EmuThread* emuThread; +extern bool RunningSomething; +extern int autoScreenSizing; + +extern int videoRenderer; +extern bool videoSettingsDirty; + + +ScreenHandler::ScreenHandler(QWidget* widget) +{ + widget->setMouseTracking(true); + widget->setAttribute(Qt::WA_AcceptTouchEvents); + QTimer* mouseTimer = setupMouseTimer(); + widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); +} + +ScreenHandler::~ScreenHandler() +{ + mouseTimer->stop(); + delete mouseTimer; +} + +void ScreenHandler::screenSetupLayout(int w, int h) +{ + int sizing = Config::ScreenSizing; + if (sizing == 3) sizing = autoScreenSizing; + + float aspectTop, aspectBot; + + for (auto ratio : aspectRatios) + { + if (ratio.id == Config::ScreenAspectTop) + aspectTop = ratio.ratio; + if (ratio.id == Config::ScreenAspectBot) + aspectBot = ratio.ratio; + } + + if (aspectTop == 0) + aspectTop = ((float) w / h) / (4.f / 3.f); + + if (aspectBot == 0) + aspectBot = ((float) w / h) / (4.f / 3.f); + + Frontend::SetupScreenLayout(w, h, + static_cast(Config::ScreenLayout), + static_cast(Config::ScreenRotation), + static_cast(sizing), + Config::ScreenGap, + Config::IntegerScaling != 0, + Config::ScreenSwap != 0, + aspectTop, + aspectBot); + + numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); +} + +QSize ScreenHandler::screenGetMinSize(int factor = 1) +{ + bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg + || Config::ScreenRotation == Frontend::screenRot_270Deg); + int gap = Config::ScreenGap * factor; + + int w = 256 * factor; + int h = 192 * factor; + + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly + || Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + return QSize(w, h); + } + + if (Config::ScreenLayout == Frontend::screenLayout_Natural) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) + { + if (isHori) + return QSize(h, w+gap+w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w+gap+w, h); + } + else // hybrid + { + if (isHori) + return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); + else + return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); + } +} + +void ScreenHandler::screenOnMousePress(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + int x = event->pos().x(); + int y = event->pos().y(); + + if (Frontend::GetTouchCoords(x, y, false)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + if (touching) + { + touching = false; + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + } +} + +void ScreenHandler::screenOnMouseMove(QMouseEvent* event) +{ + event->accept(); + + showCursor(); + + if (!(event->buttons() & Qt::LeftButton)) return; + if (!touching) return; + + int x = event->pos().x(); + int y = event->pos().y(); + + if (Frontend::GetTouchCoords(x, y, true)) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenHandler::screenHandleTablet(QTabletEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TabletPress: + case QEvent::TabletMove: + { + int x = event->x(); + int y = event->y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TabletRelease: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +void ScreenHandler::screenHandleTouch(QTouchEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + if (event->touchPoints().length() > 0) + { + QPointF lastPosition = event->touchPoints().first().lastPos(); + int x = (int)lastPosition.x(); + int y = (int)lastPosition.y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TouchEnd: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +void ScreenHandler::showCursor() +{ + mainWindow->panelWidget->setCursor(Qt::ArrowCursor); + mouseTimer->start(); +} + +QTimer* ScreenHandler::setupMouseTimer() +{ + mouseTimer = new QTimer(); + mouseTimer->setSingleShot(true); + mouseTimer->setInterval(Config::MouseHideSeconds*1000); + mouseTimer->start(); + + return mouseTimer; +} + +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) +{ + screen[0] = QImage(256, 192, QImage::Format_RGB32); + screen[1] = QImage(256, 192, QImage::Format_RGB32); + + screenTrans[0].reset(); + screenTrans[1].reset(); + + OSD::Init(false); +} + +ScreenPanelNative::~ScreenPanelNative() +{ + OSD::DeInit(); +} + +void ScreenPanelNative::setupScreenLayout() +{ + int w = width(); + int h = height(); + + screenSetupLayout(w, h); + + for (int i = 0; i < numScreens; i++) + { + float* mtx = screenMatrix[i]; + screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, + mtx[2], mtx[3], 0.f, + mtx[4], mtx[5], 1.f); + } +} + +void ScreenPanelNative::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + // fill background + painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + + if (emuThread->emuIsActive()) + { + assert(emuThread->NDS != nullptr); + emuThread->FrontBufferLock.lock(); + int frontbuf = emuThread->FrontBuffer; + if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + { + emuThread->FrontBufferLock.unlock(); + return; + } + + memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); + memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); + emuThread->FrontBufferLock.unlock(); + + QRect screenrc(0, 0, 256, 192); + + for (int i = 0; i < numScreens; i++) + { + painter.setTransform(screenTrans[i]); + painter.drawImage(screenrc, screen[screenKind[i]]); + } + } + + OSD::Update(); + OSD::DrawNative(painter); +} + +void ScreenPanelNative::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); +} + +void ScreenPanelNative::mousePressEvent(QMouseEvent* event) +{ + screenOnMousePress(event); +} + +void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) +{ + screenOnMouseRelease(event); +} + +void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) +{ + screenOnMouseMove(event); +} + +void ScreenPanelNative::tabletEvent(QTabletEvent* event) +{ + screenHandleTablet(event); +} + +bool ScreenPanelNative::event(QEvent* event) +{ + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + screenHandleTouch((QTouchEvent*)event); + return true; + } + return QWidget::event(event); +} + +void ScreenPanelNative::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} + + +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) +{ + setAutoFillBackground(false); + setAttribute(Qt::WA_NativeWindow, true); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_PaintOnScreen, true); + setAttribute(Qt::WA_KeyCompression, false); + setFocusPolicy(Qt::StrongFocus); + setMinimumSize(screenGetMinSize()); +} + +ScreenPanelGL::~ScreenPanelGL() +{} + +bool ScreenPanelGL::createContext() +{ + std::optional windowInfo = getWindowInfo(); + std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; + if (windowInfo.has_value()) + { + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +qreal ScreenPanelGL::devicePixelRatioFromScreen() const +{ + const QScreen* screen_for_ratio = window()->windowHandle()->screen(); + if (!screen_for_ratio) + screen_for_ratio = QGuiApplication::primaryScreen(); + + return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); +} + +int ScreenPanelGL::scaledWindowWidth() const +{ + return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); +} + +int ScreenPanelGL::scaledWindowHeight() const +{ + return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); +} + +std::optional ScreenPanelGL::getWindowInfo() +{ + WindowInfo wi; + + // Windows and Apple are easy here since there's no display connection. + #if defined(_WIN32) + wi.type = WindowInfo::Type::Win32; + wi.window_handle = reinterpret_cast(winId()); + #elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(winId()); + #else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) + { + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); + wi.window_handle = reinterpret_cast(winId()); + } + else if (platform_name == QStringLiteral("wayland")) + { + wi.type = WindowInfo::Type::Wayland; + QWindow* handle = windowHandle(); + if (handle == nullptr) + return std::nullopt; + + wi.display_connection = pni->nativeResourceForWindow("display", handle); + wi.window_handle = pni->nativeResourceForWindow("surface", handle); + } + else + { + qCritical() << "Unknown PNI platform " << platform_name; + return std::nullopt; + } + #endif + + wi.surface_width = static_cast(scaledWindowWidth()); + wi.surface_height = static_cast(scaledWindowHeight()); + wi.surface_scale = static_cast(devicePixelRatioFromScreen()); + + return wi; +} + + +QPaintEngine* ScreenPanelGL::paintEngine() const +{ + return nullptr; +} + +void ScreenPanelGL::setupScreenLayout() +{ + int w = width(); + int h = height(); + + screenSetupLayout(w, h); + if (emuThread) + transferLayout(emuThread); +} + +void ScreenPanelGL::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); + + QWidget::resizeEvent(event); +} + +void ScreenPanelGL::mousePressEvent(QMouseEvent* event) +{ + screenOnMousePress(event); +} + +void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) +{ + screenOnMouseRelease(event); +} + +void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) +{ + screenOnMouseMove(event); +} + +void ScreenPanelGL::tabletEvent(QTabletEvent* event) +{ + screenHandleTablet(event); +} + +bool ScreenPanelGL::event(QEvent* event) +{ + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + screenHandleTouch((QTouchEvent*)event); + return true; + } + return QWidget::event(event); +} + +void ScreenPanelGL::transferLayout(EmuThread* thread) +{ + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) + thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]); +} + +void ScreenPanelGL::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h new file mode 100644 index 0000000000..955eb2e6f5 --- /dev/null +++ b/src/frontend/qt_sdl/Screen.h @@ -0,0 +1,155 @@ +/* + Copyright 2016-2023 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 SCREEN_H +#define SCREEN_H + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class EmuThread; + + +const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; +constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); + + +class ScreenHandler +{ + Q_GADGET + +public: + ScreenHandler(QWidget* widget); + virtual ~ScreenHandler(); + QTimer* setupMouseTimer(); + void updateMouseTimer(); + QTimer* mouseTimer; + QSize screenGetMinSize(int factor); + +protected: + void screenSetupLayout(int w, int h); + + void screenOnMousePress(QMouseEvent* event); + void screenOnMouseRelease(QMouseEvent* event); + void screenOnMouseMove(QMouseEvent* event); + + void screenHandleTablet(QTabletEvent* event); + void screenHandleTouch(QTouchEvent* event); + + float screenMatrix[Frontend::MaxScreenTransforms][6]; + int screenKind[Frontend::MaxScreenTransforms]; + int numScreens; + + bool touching = false; + + void showCursor(); +}; + + +class ScreenPanelNative : public QWidget, public ScreenHandler +{ + Q_OBJECT + +public: + explicit ScreenPanelNative(QWidget* parent); + virtual ~ScreenPanelNative(); + +protected: + void paintEvent(QPaintEvent* event) override; + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + bool event(QEvent* event) override; +private slots: + void onScreenLayoutChanged(); + +private: + void setupScreenLayout(); + + QImage screen[2]; + QTransform screenTrans[Frontend::MaxScreenTransforms]; +}; + + +class ScreenPanelGL : public QWidget, public ScreenHandler +{ + Q_OBJECT + +public: + explicit ScreenPanelGL(QWidget* parent); + virtual ~ScreenPanelGL(); + + std::optional getWindowInfo(); + + bool createContext(); + + GL::Context* getContext() { return glContext.get(); } + + void transferLayout(EmuThread* thread); +protected: + + qreal devicePixelRatioFromScreen() const; + int scaledWindowWidth() const; + int scaledWindowHeight() const; + + QPaintEngine* paintEngine() const override; + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + bool event(QEvent* event) override; + +private slots: + void onScreenLayoutChanged(); + +private: + void setupScreenLayout(); + + std::unique_ptr glContext; +}; + +#endif // SCREEN_H + diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp new file mode 100644 index 0000000000..2a8f00c3ee --- /dev/null +++ b/src/frontend/qt_sdl/Window.cpp @@ -0,0 +1,2047 @@ +/* + Copyright 2016-2023 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#ifndef APPLE +#include +#endif +#endif + +#include "main.h" +#include "Input.h" +#include "CheatsDialog.h" +#include "DateTimeDialog.h" +#include "EmuSettingsDialog.h" +#include "InputConfig/InputConfigDialog.h" +#include "VideoSettingsDialog.h" +#include "CameraSettingsDialog.h" +#include "AudioSettingsDialog.h" +#include "FirmwareSettingsDialog.h" +#include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" +#include "WifiSettingsDialog.h" +#include "InterfaceSettingsDialog.h" +#include "ROMInfoDialog.h" +#include "RAMInfoDialog.h" +#include "TitleManagerDialog.h" +#include "PowerManagement/PowerManagementDialog.h" +#include "AudioInOut.h" + +#include "Platform.h" +#include "Config.h" +#include "Savestate.h" +#include "LocalMP.h" + +//#include "main_shaders.h" + +#include "ROMManager.h" +#include "ArchiveUtil.h" +#include "CameraManager.h" + +#include "OSD.h" + +using namespace melonDS; + +// TEMP +extern MainWindow* mainWindow; +extern EmuThread* emuThread; +extern bool RunningSomething; +extern int autoScreenSizing; +extern QString NdsRomMimeType; +extern QStringList NdsRomExtensions; +extern QString GbaRomMimeType; +extern QStringList GbaRomExtensions; +extern QStringList ArchiveMimeTypes; +extern QStringList ArchiveExtensions; +/*static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs); +static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames); +static bool NdsRomByExtension(const QString& filename); +static bool GbaRomByExtension(const QString& filename); +static bool SupportedArchiveByExtension(const QString& filename); +static bool NdsRomByMimetype(const QMimeType& mimetype); +static bool GbaRomByMimetype(const QMimeType& mimetype); +static bool SupportedArchiveByMimetype(const QMimeType& mimetype); +static bool ZstdNdsRomByExtension(const QString& filename); +static bool ZstdGbaRomByExtension(const QString& filename); +static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive);*/ + +extern CameraManager* camManager[2]; +extern bool camStarted[2]; + +extern int videoRenderer; +extern bool videoSettingsDirty; + + +// AAAAAAA +static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) +{ + return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { + return filename.endsWith(ext, cs); + }); +} + +static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) +{ + return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { + return mimetype.inherits(superTypeName); + }); +} + + +static bool NdsRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, NdsRomExtensions); +} + +static bool GbaRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, GbaRomExtensions); +} + +static bool SupportedArchiveByExtension(const QString& filename) +{ + return FileExtensionInList(filename, ArchiveExtensions); +} + + +static bool NdsRomByMimetype(const QMimeType& mimetype) +{ + return mimetype.inherits(NdsRomMimeType); +} + +static bool GbaRomByMimetype(const QMimeType& mimetype) +{ + return mimetype.inherits(GbaRomMimeType); +} + +static bool SupportedArchiveByMimetype(const QMimeType& mimetype) +{ + return MimeTypeInList(mimetype, ArchiveMimeTypes); +} + +static bool ZstdNdsRomByExtension(const QString& filename) +{ + return filename.endsWith(".zst", Qt::CaseInsensitive) && + NdsRomByExtension(filename.left(filename.size() - 4)); +} + +static bool ZstdGbaRomByExtension(const QString& filename) +{ + return filename.endsWith(".zst", Qt::CaseInsensitive) && + GbaRomByExtension(filename.left(filename.size() - 4)); +} + +static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false) +{ + if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename)) + return true; + + if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename)) + return true; + + const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode); + return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype); +} + + +#ifndef _WIN32 +static int signalFd[2]; +QSocketNotifier *signalSn; + +static void signalHandler(int) +{ + char a = 1; + write(signalFd[0], &a, sizeof(a)); +} +#endif + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) +{ +#ifndef _WIN32 + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + { + qFatal("Couldn't create socketpair"); + } + + signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); + connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); + + struct sigaction sa; + + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_flags |= SA_RESTART; + sigaction(SIGINT, &sa, 0); +#endif + + oldW = Config::WindowWidth; + oldH = Config::WindowHeight; + oldMax = Config::WindowMaximized; + + setWindowTitle("melonDS " MELONDS_VERSION); + setAttribute(Qt::WA_DeleteOnClose); + setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); + + QMenuBar* menubar = new QMenuBar(); + { + QMenu* menu = menubar->addMenu("File"); + + actOpenROM = menu->addAction("Open ROM..."); + connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); + actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); + + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ + + recentMenu = menu->addMenu("Open recent"); + for (int i = 0; i < 10; ++i) + { + std::string item = Config::RecentROMList[i]; + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); + } + updateRecentFilesMenu(); + + //actBootFirmware = menu->addAction("Launch DS menu"); + actBootFirmware = menu->addAction("Boot firmware"); + connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); + + menu->addSeparator(); + + actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); + + { + QMenu* submenu = menu->addMenu("Insert add-on cart"); + + actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); + actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); + connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + menu->addSeparator(); + + { + QMenu* submenu = menu->addMenu("Save state"); + + for (int i = 1; i < 9; i++) + { + actSaveState[i] = submenu->addAction(QString("%1").arg(i)); + actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); + actSaveState[i]->setData(QVariant(i)); + connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); + } + + actSaveState[0] = submenu->addAction("File..."); + actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); + actSaveState[0]->setData(QVariant(0)); + connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); + } + { + QMenu* submenu = menu->addMenu("Load state"); + + for (int i = 1; i < 9; i++) + { + actLoadState[i] = submenu->addAction(QString("%1").arg(i)); + actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); + actLoadState[i]->setData(QVariant(i)); + connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actLoadState[0] = submenu->addAction("File..."); + actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); + actLoadState[0]->setData(QVariant(0)); + connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actUndoStateLoad = menu->addAction("Undo state load"); + actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); + connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); + + menu->addSeparator(); + + actQuit = menu->addAction("Quit"); + connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); + actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); + } + { + QMenu* menu = menubar->addMenu("System"); + + actPause = menu->addAction("Pause"); + actPause->setCheckable(true); + connect(actPause, &QAction::triggered, this, &MainWindow::onPause); + + actReset = menu->addAction("Reset"); + connect(actReset, &QAction::triggered, this, &MainWindow::onReset); + + actStop = menu->addAction("Stop"); + connect(actStop, &QAction::triggered, this, &MainWindow::onStop); + + actFrameStep = menu->addAction("Frame step"); + connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); + + menu->addSeparator(); + + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + + menu->addSeparator(); + + actEnableCheats = menu->addAction("Enable cheats"); + actEnableCheats->setCheckable(true); + connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); + + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } + } + { + QMenu* menu = menubar->addMenu("Config"); + + actEmuSettings = menu->addAction("Emu settings"); + connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + +#ifdef __APPLE__ + actPreferences = menu->addAction("Preferences..."); + connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + actPreferences->setMenuRole(QAction::PreferencesRole); +#endif + + actInputConfig = menu->addAction("Input and hotkeys"); + connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); + + 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); + + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + + actWifiSettings = menu->addAction("Wifi settings"); + connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); + + actFirmwareSettings = menu->addAction("Firmware settings"); + connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + + actPathSettings = menu->addAction("Path settings"); + connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + + { + QMenu* submenu = menu->addMenu("Savestate settings"); + + actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); + actSavestateSRAMReloc->setCheckable(true); + connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); + } + + menu->addSeparator(); + + { + QMenu* submenu = menu->addMenu("Screen size"); + + for (int i = 0; i < 4; i++) + { + int data = i+1; + actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); + actScreenSize[i]->setData(QVariant(data)); + connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); + } + } + { + QMenu* submenu = menu->addMenu("Screen rotation"); + grpScreenRotation = new QActionGroup(submenu); + + for (int i = 0; i < Frontend::screenRot_MAX; i++) + { + int data = i*90; + actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); + actScreenRotation[i]->setActionGroup(grpScreenRotation); + actScreenRotation[i]->setData(QVariant(i)); + actScreenRotation[i]->setCheckable(true); + } + + connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); + } + { + QMenu* submenu = menu->addMenu("Screen gap"); + grpScreenGap = new QActionGroup(submenu); + + const int screengap[] = {0, 1, 8, 64, 90, 128}; + + for (int i = 0; i < 6; i++) + { + int data = screengap[i]; + actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); + actScreenGap[i]->setActionGroup(grpScreenGap); + actScreenGap[i]->setData(QVariant(data)); + actScreenGap[i]->setCheckable(true); + } + + connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); + } + { + QMenu* submenu = menu->addMenu("Screen layout"); + grpScreenLayout = new QActionGroup(submenu); + + const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; + + for (int i = 0; i < Frontend::screenLayout_MAX; i++) + { + actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); + actScreenLayout[i]->setActionGroup(grpScreenLayout); + actScreenLayout[i]->setData(QVariant(i)); + actScreenLayout[i]->setCheckable(true); + } + + connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); + + submenu->addSeparator(); + + actScreenSwap = submenu->addAction("Swap screens"); + actScreenSwap->setCheckable(true); + connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); + } + { + QMenu* submenu = menu->addMenu("Screen sizing"); + grpScreenSizing = new QActionGroup(submenu); + + const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; + + for (int i = 0; i < Frontend::screenSizing_MAX; i++) + { + actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); + actScreenSizing[i]->setActionGroup(grpScreenSizing); + actScreenSizing[i]->setData(QVariant(i)); + actScreenSizing[i]->setCheckable(true); + } + + connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); + + submenu->addSeparator(); + + actIntegerScaling = submenu->addAction("Force integer scaling"); + actIntegerScaling->setCheckable(true); + connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); + } + { + QMenu* submenu = menu->addMenu("Aspect ratio"); + grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction*[AspectRatiosNum]; + actScreenAspectBot = new QAction*[AspectRatiosNum]; + + for (int i = 0; i < 2; i++) + { + QActionGroup* group = grpScreenAspectTop; + QAction** actions = actScreenAspectTop; + + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < AspectRatiosNum; j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } + + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + } + } + + actScreenFiltering = menu->addAction("Screen filtering"); + actScreenFiltering->setCheckable(true); + connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + + actShowOSD = menu->addAction("Show OSD"); + actShowOSD->setCheckable(true); + connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); + + menu->addSeparator(); + + actLimitFramerate = menu->addAction("Limit framerate"); + actLimitFramerate->setCheckable(true); + connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); + + actAudioSync = menu->addAction("Audio sync"); + actAudioSync->setCheckable(true); + connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + } + setMenuBar(menubar); + + resize(Config::WindowWidth, Config::WindowHeight); + + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + +#ifdef Q_OS_MAC + QPoint screenCenter = screen()->availableGeometry().center(); + QRect frameGeo = frameGeometry(); + frameGeo.moveCenter(screenCenter); + move(frameGeo.topLeft()); +#endif + + if (oldMax) + showMaximized(); + else + show(); + + createScreenPanel(); + + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + } + + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + actImportSavefile->setEnabled(false); + + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); + + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); + + actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + + actEnableCheats->setChecked(Config::EnableCheats); + + actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); + + actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); + + actScreenRotation[Config::ScreenRotation]->setChecked(true); + + for (int i = 0; i < 6; i++) + { + if (actScreenGap[i]->data().toInt() == Config::ScreenGap) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[Config::ScreenLayout]->setChecked(true); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + actIntegerScaling->setChecked(Config::IntegerScaling); + + actScreenSwap->setChecked(Config::ScreenSwap); + + for (int i = 0; i < AspectRatiosNum; i++) + { + if (Config::ScreenAspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (Config::ScreenAspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } + + actScreenFiltering->setChecked(Config::ScreenFilter); + actShowOSD->setChecked(Config::ShowOSD); + + actLimitFramerate->setChecked(Config::LimitFPS); + actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } +} + +MainWindow::~MainWindow() +{ + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (hasOGL) + { + // we intentionally don't unpause here + emuThread->emuPause(); + emuThread->deinitContext(); + } + + QMainWindow::closeEvent(event); +} + +void MainWindow::createScreenPanel() +{ + hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + + if (hasOGL) + { + ScreenPanelGL* panelGL = new ScreenPanelGL(this); + panelGL->show(); + + panel = panelGL; + panelWidget = panelGL; + + panelGL->createContext(); + } + + if (!hasOGL) + { + ScreenPanelNative* panelNative = new ScreenPanelNative(this); + panel = panelNative; + panelWidget = panelNative; + panelWidget->show(); + } + setCentralWidget(panelWidget); + + actScreenFiltering->setEnabled(hasOGL); + + connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); + emit screenLayoutChange(); +} + +GL::Context* MainWindow::getOGLContext() +{ + if (!hasOGL) return nullptr; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->getContext(); +} + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + int w = event->size().width(); + int h = event->size().height(); + + if (!isFullScreen()) + { + // this is ugly + // thing is, when maximizing the window, we first receive the resizeEvent + // with a new size matching the screen, then the changeEvent telling us that + // the maximized flag was updated + oldW = Config::WindowWidth; + oldH = Config::WindowHeight; + oldMax = isMaximized(); + + Config::WindowWidth = w; + Config::WindowHeight = h; + } +} + +void MainWindow::changeEvent(QEvent* event) +{ + if (isMaximized() && !oldMax) + { + Config::WindowWidth = oldW; + Config::WindowHeight = oldH; + } + + Config::WindowMaximized = isMaximized() ? 1:0; +} + +void MainWindow::keyPressEvent(QKeyEvent* event) +{ + if (event->isAutoRepeat()) return; + + // TODO!! REMOVE ME IN RELEASE BUILDS!! + //if (event->key() == Qt::Key_F11) NDS::debug(0); + + Input::KeyPress(event); +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) +{ + if (event->isAutoRepeat()) return; + + Input::KeyRelease(event); +} + + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + QList urls = event->mimeData()->urls(); + if (urls.count() > 1) return; // not handling more than one file at once + + QString filename = urls.at(0).toLocalFile(); + + if (FileIsSupportedFiletype(filename)) + event->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + QList urls = event->mimeData()->urls(); + if (urls.count() > 1) return; // not handling more than one file at once + + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + const QString filename = file.last(); + const bool romInsideArchive = file.size() > 1; + const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); + + bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); + bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); + isNdsRom |= ZstdNdsRomByExtension(filename); + isGbaRom |= ZstdGbaRomByExtension(filename); + + if (isNdsRom) + { + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); + emuThread->emuUnpause(); + return; + } + + const QString barredFilename = file.join('|'); + recentFileList.removeAll(barredFilename); + recentFileList.prepend(barredFilename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); + } + else if (isGbaRom) + { + if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); + } + else + { + QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); + emuThread->emuUnpause(); + return; + } +} + +void MainWindow::focusInEvent(QFocusEvent* event) +{ + AudioInOut::AudioMute(mainWindow); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + AudioInOut::AudioMute(mainWindow); +} + +void MainWindow::onAppStateChanged(Qt::ApplicationState state) +{ + if (state == Qt::ApplicationInactive) + { + if (Config::PauseLostFocus && emuThread->emuIsRunning()) + emuThread->emuPause(); + } + else if (state == Qt::ApplicationActive) + { + if (Config::PauseLostFocus && !pausedManually) + emuThread->emuUnpause(); + } +} + +bool MainWindow::verifySetup() +{ + QString res = ROMManager::VerifySetup(); + if (!res.isEmpty()) + { + QMessageBox::critical(this, "melonDS", res); + return false; + } + + return true; +} + +bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) +{ + if (!verifySetup()) + { + return false; + } + + bool gbaloaded = false; + if (!gbafile.isEmpty()) + { + if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + return false; + } + + gbaloaded = true; + } + + bool ndsloaded = false; + if (!file.isEmpty()) + { + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + return false; + } + recentFileList.removeAll(file.join("|")); + recentFileList.prepend(file.join("|")); + updateRecentFilesMenu(); + ndsloaded = true; + } + + if (boot) + { + if (ndsloaded) + { + emuThread->NDS->Start(); + emuThread->emuRun(); + } + else + { + onBootFirmware(); + } + } + + updateCartInserted(false); + + if (gbaloaded) + { + updateCartInserted(true); + } + + return true; +} + +QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) +{ + if (filename.isEmpty()) return {}; + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (useMemberSyntax) + { + const QStringList filenameParts = filename.split('|'); + if (filenameParts.size() > 2) + { + QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); + return {}; + } + + if (filenameParts.size() == 2) + { + const QString archive = filenameParts.at(0); + if (!QFileInfo(archive).exists()) + { + QMessageBox::warning(this, "melonDS", "This archive does not exist."); + return {}; + } + + const QString subfile = filenameParts.at(1); + if (!Archive::ListArchive(archive).contains(subfile)) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); + return {}; + } + + return filenameParts; + } + } +#endif + + if (!QFileInfo(filename).exists()) + { + QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); + return {}; + } + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (SupportedArchiveByExtension(filename) + || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) + { + const QString subfile = pickFileFromArchive(filename); + if (subfile.isEmpty()) + return {}; + + return { filename, subfile }; + } +#endif + + return { filename }; +} + +QString MainWindow::pickFileFromArchive(QString archiveFileName) +{ + QVector archiveROMList = Archive::ListArchive(archiveFileName); + + if (archiveROMList.size() <= 1) + { + if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") + QMessageBox::warning(this, "melonDS", "This archive is empty."); + else + QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); + return QString(); + } + + archiveROMList.removeFirst(); + + const auto notSupportedRom = [&](const auto& filename){ + if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) + return false; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); + return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); + }; + + archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), + archiveROMList.end()); + + if (archiveROMList.isEmpty()) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); + return QString(); + } + + if (archiveROMList.size() == 1) + return archiveROMList.first(); + + bool ok; + const QString toLoad = QInputDialog::getItem( + this, "melonDS", + "This archive contains multiple files. Select which ROM you want to load.", + archiveROMList.toList(), 0, false, &ok + ); + + if (ok) return toLoad; + + // User clicked on cancel + + return QString(); +} + +QStringList MainWindow::pickROM(bool gba) +{ + const QString console = gba ? "GBA" : "DS"; + const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; + + QString rawROMs = romexts.join(" *"); + QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; + QString allROMs = rawROMs; + + QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; + extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; + allROMs += " " + zstdROMs; + +#ifdef ARCHIVE_SUPPORT_ENABLED + QString archives = "*" + ArchiveExtensions.join(" *"); + extraFilters += ";;Archives (" + archives + ")"; + allROMs += " " + archives; +#endif + extraFilters += ";;All files (*.*)"; + + const QString filename = QFileDialog::getOpenFileName( + this, "Open " + console + " ROM", + QString::fromStdString(Config::LastROMFolder), + "All supported files (*" + allROMs + ")" + extraFilters + ); + + if (filename.isEmpty()) return {}; + + Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); + return splitArchivePath(filename, false); +} + +void MainWindow::updateCartInserted(bool gba) +{ + bool inserted; + if (gba) + { + inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + actEjectGBACart->setEnabled(inserted); + } + else + { + inserted = ROMManager::CartInserted(); + actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); + actEjectCart->setEnabled(inserted); + actImportSavefile->setEnabled(inserted); + actSetupCheats->setEnabled(inserted); + actROMInfo->setEnabled(inserted); + actRAMInfo->setEnabled(inserted); + } +} + +void MainWindow::onOpenFile() +{ + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + QStringList file = pickROM(false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + QString filename = file.join('|'); + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); +} + +void MainWindow::onClearRecentFiles() +{ + recentFileList.clear(); + for (int i = 0; i < 10; i++) + Config::RecentROMList[i] = ""; + updateRecentFilesMenu(); +} + +void MainWindow::updateRecentFilesMenu() +{ + recentMenu->clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= 10) break; + + QString item_full = recentFileList.at(i); + QString item_display = item_full; + int itemlen = item_full.length(); + const int maxlen = 100; + if (itemlen > maxlen) + { + int cut_start = 0; + while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && + cut_start < itemlen) + cut_start++; + + int cut_end = itemlen-1; + while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || + (cut_start+4+(itemlen-cut_end) < maxlen)) && + cut_end > 0) + cut_end--; + + item_display.truncate(cut_start+1); + item_display += "..."; + item_display += QString(item_full).remove(0, cut_end); + } + + QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); + actRecentFile_i->setData(item_full); + connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); + + Config::RecentROMList[i] = recentFileList.at(i).toStdString(); + } + + while (recentFileList.size() > 10) + recentFileList.removeLast(); + + recentMenu->addSeparator(); + + QAction *actClearRecentList = recentMenu->addAction("Clear"); + connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); + + if (recentFileList.empty()) + actClearRecentList->setEnabled(false); + + Config::Save(); +} + +void MainWindow::onClickRecentFile() +{ + QAction *act = (QAction *)sender(); + QString filename = act->data().toString(); + + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + const QStringList file = splitArchivePath(filename, true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); +} + +void MainWindow::onBootFirmware() +{ + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::BootToMenu(emuThread)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); + emuThread->emuUnpause(); + return; + } + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); +} + +void MainWindow::onInsertCart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, false)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onEjectCart() +{ + emuThread->emuPause(); + + ROMManager::EjectCart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onInsertGBACart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onInsertGBAAddon() +{ + QAction* act = (QAction*)sender(); + int type = act->data().toInt(); + + emuThread->emuPause(); + + ROMManager::LoadGBAAddon(*emuThread->NDS, type); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onEjectGBACart() +{ + emuThread->emuPause(); + + ROMManager::EjectGBACart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onSaveState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getSaveFileName(this, + "Save state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.mln);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (ROMManager::SaveState(*emuThread->NDS, filename)) + { + char msg[64]; + if (slot > 0) sprintf(msg, "State saved to slot %d", slot); + else sprintf(msg, "State saved to file"); + OSD::AddMessage(0, msg); + + actLoadState[slot]->setEnabled(true); + } + else + { + OSD::AddMessage(0xFFA0A0, "State save failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onLoadState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getOpenFileName(this, + "Load state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.ml*);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (!Platform::FileExists(filename)) + { + char msg[64]; + if (slot > 0) sprintf(msg, "State slot %d is empty", slot); + else sprintf(msg, "State file does not exist"); + OSD::AddMessage(0xFFA0A0, msg); + + emuThread->emuUnpause(); + return; + } + + if (ROMManager::LoadState(*emuThread->NDS, filename)) + { + char msg[64]; + if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); + else sprintf(msg, "State loaded from file"); + OSD::AddMessage(0, msg); + + actUndoStateLoad->setEnabled(true); + } + else + { + OSD::AddMessage(0xFFA0A0, "State load failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onUndoStateLoad() +{ + emuThread->emuPause(); + ROMManager::UndoStateLoad(*emuThread->NDS); + emuThread->emuUnpause(); + + OSD::AddMessage(0, "State load undone"); +} + +void MainWindow::onImportSavefile() +{ + emuThread->emuPause(); + QString path = QFileDialog::getOpenFileName(this, + "Select savefile", + QString::fromStdString(Config::LastROMFolder), + "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); + + if (path.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); + if (!f) + { + QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); + emuThread->emuUnpause(); + return; + } + + if (RunningSomething) + { + if (QMessageBox::warning(this, + "melonDS", + "The emulation will be reset and the current savefile overwritten.", + QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) + { + emuThread->emuUnpause(); + return; + } + + ROMManager::Reset(emuThread); + } + + u32 len = FileLength(f); + + std::unique_ptr data = std::make_unique(len); + Platform::FileRewind(f); + Platform::FileRead(data.get(), len, 1, f); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->SetNDSSave(data.get(), len); + + CloseFile(f); + emuThread->emuUnpause(); +} + +void MainWindow::onQuit() +{ +#ifndef _WIN32 + signalSn->setEnabled(false); +#endif + QApplication::quit(); +} + + +void MainWindow::onPause(bool checked) +{ + if (!RunningSomething) return; + + if (checked) + { + emuThread->emuPause(); + OSD::AddMessage(0, "Paused"); + pausedManually = true; + } + else + { + emuThread->emuUnpause(); + OSD::AddMessage(0, "Resumed"); + pausedManually = false; + } +} + +void MainWindow::onReset() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + + actUndoStateLoad->setEnabled(false); + + ROMManager::Reset(emuThread); + + OSD::AddMessage(0, "Reset"); + emuThread->emuRun(); +} + +void MainWindow::onStop() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + emuThread->NDS->Stop(); +} + +void MainWindow::onFrameStep() +{ + if (!RunningSomething) return; + + emuThread->emuFrameStep(); +} + +void MainWindow::onOpenDateTime() +{ + DateTimeDialog* dlg = DateTimeDialog::openDlg(this); +} + +void MainWindow::onOpenPowerManagement() +{ + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); +} + +void MainWindow::onEnableCheats(bool checked) +{ + Config::EnableCheats = checked?1:0; + ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); +} + +void MainWindow::onSetupCheats() +{ + emuThread->emuPause(); + + CheatsDialog* dlg = CheatsDialog::openDlg(this); + connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); +} + +void MainWindow::onCheatsDialogFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onROMInfo() +{ + auto cart = emuThread->NDS->NDSCartSlot.GetCart(); + if (cart) + ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); +} + +void MainWindow::onRAMInfo() +{ + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); +} + +void MainWindow::onOpenTitleManager() +{ + TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); +} + +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + +void MainWindow::onOpenEmuSettings() +{ + emuThread->emuPause(); + + EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); + connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); +} + +void MainWindow::onEmuSettingsDialogFinished(int res) +{ + emuThread->emuUnpause(); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + actEjectGBACart->setEnabled(false); + } + else + { + actInsertGBACart->setEnabled(true); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(true); + actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + } + + if (EmuSettingsDialog::needsReset) + onReset(); + + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + + if (!RunningSomething) + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onOpenInputConfig() +{ + emuThread->emuPause(); + + InputConfigDialog* dlg = InputConfigDialog::openDlg(this); + connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); +} + +void MainWindow::onInputConfigFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onOpenVideoSettings() +{ + VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); + 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, emuThread->emuIsActive(), emuThread); + connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); + connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); + connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); + connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); +} + +void MainWindow::onOpenFirmwareSettings() +{ + emuThread->emuPause(); + + FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); + connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); +} + +void MainWindow::onFirmwareSettingsFinished(int res) +{ + if (FirmwareSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenPathSettings() +{ + emuThread->emuPause(); + + PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); + connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); +} + +void MainWindow::onPathSettingsFinished(int res) +{ + if (PathSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onUpdateAudioSettings() +{ + assert(emuThread->NDS != nullptr); + emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + + if (Config::AudioBitDepth == 0) + emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); + else + emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); +} + +void MainWindow::onAudioSettingsFinished(int res) +{ + AudioInOut::UpdateSettings(*emuThread->NDS); +} + +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + AudioInOut::AudioMute(mainWindow); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenWifiSettings() +{ + emuThread->emuPause(); + + WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); + connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); +} + +void MainWindow::onWifiSettingsFinished(int res) +{ + Platform::LAN_DeInit(); + Platform::LAN_Init(); + + if (WifiSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenInterfaceSettings() +{ + emuThread->emuPause(); + InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); + connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); + connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); +} + +void MainWindow::onUpdateMouseTimer() +{ + panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); +} + +void MainWindow::onInterfaceSettingsFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onChangeSavestateSRAMReloc(bool checked) +{ + Config::SavestateRelocSRAM = checked?1:0; +} + +void MainWindow::onChangeScreenSize() +{ + int factor = ((QAction*)sender())->data().toInt(); + QSize diff = size() - panelWidget->size(); + resize(panel->screenGetMinSize(factor) + diff); +} + +void MainWindow::onChangeScreenRotation(QAction* act) +{ + int rot = act->data().toInt(); + Config::ScreenRotation = rot; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenGap(QAction* act) +{ + int gap = act->data().toInt(); + Config::ScreenGap = gap; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenLayout(QAction* act) +{ + int layout = act->data().toInt(); + Config::ScreenLayout = layout; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSwap(bool checked) +{ + Config::ScreenSwap = checked?1:0; + + // Swap between top and bottom screen when displaying one screen. + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) + { + // Bottom Screen. + Config::ScreenSizing = Frontend::screenSizing_BotOnly; + actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + // Top Screen. + Config::ScreenSizing = Frontend::screenSizing_TopOnly; + actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSizing(QAction* act) +{ + int sizing = act->data().toInt(); + Config::ScreenSizing = sizing; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenAspect(QAction* act) +{ + int aspect = act->data().toInt(); + QActionGroup* group = act->actionGroup(); + + if (group == grpScreenAspectTop) + { + Config::ScreenAspectTop = aspect; + } + else + { + Config::ScreenAspectBot = aspect; + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeIntegerScaling(bool checked) +{ + Config::IntegerScaling = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenFiltering(bool checked) +{ + Config::ScreenFilter = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeShowOSD(bool checked) +{ + Config::ShowOSD = checked?1:0; +} +void MainWindow::onChangeLimitFramerate(bool checked) +{ + Config::LimitFPS = checked?1:0; +} + +void MainWindow::onChangeAudioSync(bool checked) +{ + Config::AudioSync = checked?1:0; +} + + +void MainWindow::onTitleUpdate(QString title) +{ + setWindowTitle(title); +} + +void ToggleFullscreen(MainWindow* mainWindow) +{ + if (!mainWindow->isFullScreen()) + { + mainWindow->showFullScreen(); + mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working + } + else + { + mainWindow->showNormal(); + int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); + mainWindow->menuBar()->setFixedHeight(menuBarHeight); + } +} + +void MainWindow::onFullscreenToggled() +{ + ToggleFullscreen(this); +} + +void MainWindow::onScreenEmphasisToggled() +{ + int currentSizing = Config::ScreenSizing; + if (currentSizing == Frontend::screenSizing_EmphTop) + { + Config::ScreenSizing = Frontend::screenSizing_EmphBot; + } + else if (currentSizing == Frontend::screenSizing_EmphBot) + { + Config::ScreenSizing = Frontend::screenSizing_EmphTop; + } + + emit screenLayoutChange(); +} + +void MainWindow::onEmuStart() +{ + for (int i = 1; i < 9; i++) + { + actSaveState[i]->setEnabled(true); + actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); + } + actSaveState[0]->setEnabled(true); + actLoadState[0]->setEnabled(true); + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(true); + actPause->setChecked(false); + actReset->setEnabled(true); + actStop->setEnabled(true); + actFrameStep->setEnabled(true); + + actDateTime->setEnabled(false); + actPowerManagement->setEnabled(true); + + actTitleManager->setEnabled(false); +} + +void MainWindow::onEmuStop() +{ + emuThread->emuPause(); + + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); + + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); + + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onUpdateVideoSettings(bool glchange) +{ + if (glchange) + { + emuThread->emuPause(); + if (hasOGL) emuThread->deinitContext(); + + delete panel; + createScreenPanel(); + connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); + } + + videoSettingsDirty = true; + + if (glchange) + { + if (hasOGL) emuThread->initContext(); + emuThread->emuUnpause(); + } +} diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h new file mode 100644 index 0000000000..a84bc1d43c --- /dev/null +++ b/src/frontend/qt_sdl/Window.h @@ -0,0 +1,239 @@ +/* + Copyright 2016-2023 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 WINDOW_H +#define WINDOW_H + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Screen.h" + + +class EmuThread; + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow(); + + bool hasOGL; + GL::Context* getOGLContext(); + + bool preloadROMs(QStringList file, QStringList gbafile, bool boot); + QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); + + void onAppStateChanged(Qt::ApplicationState state); + +protected: + void resizeEvent(QResizeEvent* event) override; + void changeEvent(QEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + +signals: + void screenLayoutChange(); + +private slots: + void onOpenFile(); + void onClickRecentFile(); + void onClearRecentFiles(); + void onBootFirmware(); + void onInsertCart(); + void onEjectCart(); + void onInsertGBACart(); + void onInsertGBAAddon(); + void onEjectGBACart(); + void onSaveState(); + void onLoadState(); + void onUndoStateLoad(); + void onImportSavefile(); + void onQuit(); + + void onPause(bool checked); + void onReset(); + void onStop(); + void onFrameStep(); + void onOpenPowerManagement(); + void onOpenDateTime(); + void onEnableCheats(bool checked); + void onSetupCheats(); + void onCheatsDialogFinished(int res); + void onROMInfo(); + void onRAMInfo(); + void onOpenTitleManager(); + void onMPNewInstance(); + + void onOpenEmuSettings(); + void onEmuSettingsDialogFinished(int res); + void onOpenInputConfig(); + void onInputConfigFinished(int res); + void onOpenVideoSettings(); + void onOpenCameraSettings(); + void onCameraSettingsFinished(int res); + void onOpenAudioSettings(); + 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); + void onUpdateMouseTimer(); + void onChangeSavestateSRAMReloc(bool checked); + void onChangeScreenSize(); + void onChangeScreenRotation(QAction* act); + void onChangeScreenGap(QAction* act); + void onChangeScreenLayout(QAction* act); + void onChangeScreenSwap(bool checked); + void onChangeScreenSizing(QAction* act); + void onChangeScreenAspect(QAction* act); + void onChangeIntegerScaling(bool checked); + void onChangeScreenFiltering(bool checked); + void onChangeShowOSD(bool checked); + void onChangeLimitFramerate(bool checked); + void onChangeAudioSync(bool checked); + + void onTitleUpdate(QString title); + + void onEmuStart(); + void onEmuStop(); + + void onUpdateVideoSettings(bool glchange); + + void onFullscreenToggled(); + void onScreenEmphasisToggled(); + +private: + virtual void closeEvent(QCloseEvent* event) override; + + QStringList currentROM; + QStringList currentGBAROM; + QList recentFileList; + QMenu *recentMenu; + void updateRecentFilesMenu(); + + bool verifySetup(); + QString pickFileFromArchive(QString archiveFileName); + QStringList pickROM(bool gba); + void updateCartInserted(bool gba); + + void createScreenPanel(); + + bool pausedManually = false; + + int oldW, oldH; + bool oldMax; + +public: + ScreenHandler* panel; + QWidget* panelWidget; + + QAction* actOpenROM; + QAction* actBootFirmware; + QAction* actCurrentCart; + QAction* actInsertCart; + QAction* actEjectCart; + QAction* actCurrentGBACart; + QAction* actInsertGBACart; + QAction* actInsertGBAAddon[1]; + QAction* actEjectGBACart; + QAction* actImportSavefile; + QAction* actSaveState[9]; + QAction* actLoadState[9]; + QAction* actUndoStateLoad; + QAction* actQuit; + + QAction* actPause; + QAction* actReset; + QAction* actStop; + QAction* actFrameStep; + QAction* actPowerManagement; + QAction* actDateTime; + QAction* actEnableCheats; + QAction* actSetupCheats; + QAction* actROMInfo; + QAction* actRAMInfo; + QAction* actTitleManager; + QAction* actMPNewInstance; + + QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif + QAction* actInputConfig; + QAction* actVideoSettings; + QAction* actCameraSettings; + QAction* actAudioSettings; + QAction* actMPSettings; + QAction* actWifiSettings; + QAction* actFirmwareSettings; + QAction* actPathSettings; + QAction* actInterfaceSettings; + QAction* actSavestateSRAMReloc; + QAction* actScreenSize[4]; + QActionGroup* grpScreenRotation; + QAction* actScreenRotation[Frontend::screenRot_MAX]; + QActionGroup* grpScreenGap; + QAction* actScreenGap[6]; + QActionGroup* grpScreenLayout; + QAction* actScreenLayout[Frontend::screenLayout_MAX]; + QAction* actScreenSwap; + QActionGroup* grpScreenSizing; + QAction* actScreenSizing[Frontend::screenSizing_MAX]; + QAction* actIntegerScaling; + QActionGroup* grpScreenAspectTop; + QAction** actScreenAspectTop; + QActionGroup* grpScreenAspectBot; + QAction** actScreenAspectBot; + QAction* actScreenFiltering; + QAction* actShowOSD; + QAction* actLimitFramerate; + QAction* actAudioSync; +}; + +void ToggleFullscreen(MainWindow* mainWindow); + +#endif // WINDOW_H diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 725a75f09e..3f53f3cca3 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -109,14 +109,15 @@ // TODO: uniform variable spelling using namespace melonDS; -const QString NdsRomMimeType = "application/x-nintendo-ds-rom"; -const QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; +QString NdsRomMimeType = "application/x-nintendo-ds-rom"; +QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; + +QString GbaRomMimeType = "application/x-gba-rom"; +QStringList GbaRomExtensions { ".gba", ".agb" }; -const QString GbaRomMimeType = "application/x-gba-rom"; -const QStringList GbaRomExtensions { ".gba", ".agb" }; // This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). -const QStringList ArchiveMimeTypes +QStringList ArchiveMimeTypes { #ifdef ARCHIVE_SUPPORT_ENABLED "application/zip", @@ -138,7 +139,7 @@ const QStringList ArchiveMimeTypes #endif }; -const QStringList ArchiveExtensions +QStringList ArchiveExtensions { #ifdef ARCHIVE_SUPPORT_ENABLED ".zip", ".7z", ".rar", ".tar", @@ -171,15 +172,7 @@ bool videoSettingsDirty; CameraManager* camManager[2]; bool camStarted[2]; -const struct { int id; float ratio; const char* label; } aspectRatios[] = -{ - { 0, 1, "4:3 (native)" }, - { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, - { 1, (16.f / 9) / (4.f / 3), "16:9" }, - { 2, (21.f / 9) / (4.f / 3), "21:9" }, - { 3, 0, "window" } -}; -constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); +//extern int AspectRatiosNum; EmuThread::EmuThread(QObject* parent) : QThread(parent) @@ -1038,501 +1031,7 @@ void EmuThread::drawScreenGL() oglContext->SwapBuffers(); } -ScreenHandler::ScreenHandler(QWidget* widget) -{ - widget->setMouseTracking(true); - widget->setAttribute(Qt::WA_AcceptTouchEvents); - QTimer* mouseTimer = setupMouseTimer(); - widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); -} - -ScreenHandler::~ScreenHandler() -{ - mouseTimer->stop(); - delete mouseTimer; -} - -void ScreenHandler::screenSetupLayout(int w, int h) -{ - int sizing = Config::ScreenSizing; - if (sizing == 3) sizing = autoScreenSizing; - - float aspectTop, aspectBot; - - for (auto ratio : aspectRatios) - { - if (ratio.id == Config::ScreenAspectTop) - aspectTop = ratio.ratio; - if (ratio.id == Config::ScreenAspectBot) - aspectBot = ratio.ratio; - } - - if (aspectTop == 0) - aspectTop = ((float) w / h) / (4.f / 3.f); - - if (aspectBot == 0) - aspectBot = ((float) w / h) / (4.f / 3.f); - - Frontend::SetupScreenLayout(w, h, - static_cast(Config::ScreenLayout), - static_cast(Config::ScreenRotation), - static_cast(sizing), - Config::ScreenGap, - Config::IntegerScaling != 0, - Config::ScreenSwap != 0, - aspectTop, - aspectBot); - - numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); -} - -QSize ScreenHandler::screenGetMinSize(int factor = 1) -{ - bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg - || Config::ScreenRotation == Frontend::screenRot_270Deg); - int gap = Config::ScreenGap * factor; - - int w = 256 * factor; - int h = 192 * factor; - - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly - || Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - return QSize(w, h); - } - - if (Config::ScreenLayout == Frontend::screenLayout_Natural) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) - { - if (isHori) - return QSize(h, w+gap+w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w+gap+w, h); - } - else // hybrid - { - if (isHori) - return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); - else - return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); - } -} - -void ScreenHandler::screenOnMousePress(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - int x = event->pos().x(); - int y = event->pos().y(); - - if (Frontend::GetTouchCoords(x, y, false)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - if (touching) - { - touching = false; - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - } -} - -void ScreenHandler::screenOnMouseMove(QMouseEvent* event) -{ - event->accept(); - - showCursor(); - - if (!(event->buttons() & Qt::LeftButton)) return; - if (!touching) return; - - int x = event->pos().x(); - int y = event->pos().y(); - - if (Frontend::GetTouchCoords(x, y, true)) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenHandleTablet(QTabletEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TabletPress: - case QEvent::TabletMove: - { - int x = event->x(); - int y = event->y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TabletRelease: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::screenHandleTouch(QTouchEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - if (event->touchPoints().length() > 0) - { - QPointF lastPosition = event->touchPoints().first().lastPos(); - int x = (int)lastPosition.x(); - int y = (int)lastPosition.y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TouchEnd: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::showCursor() -{ - mainWindow->panelWidget->setCursor(Qt::ArrowCursor); - mouseTimer->start(); -} - -QTimer* ScreenHandler::setupMouseTimer() -{ - mouseTimer = new QTimer(); - mouseTimer->setSingleShot(true); - mouseTimer->setInterval(Config::MouseHideSeconds*1000); - mouseTimer->start(); - - return mouseTimer; -} - -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - screen[0] = QImage(256, 192, QImage::Format_RGB32); - screen[1] = QImage(256, 192, QImage::Format_RGB32); - - screenTrans[0].reset(); - screenTrans[1].reset(); - - OSD::Init(false); -} - -ScreenPanelNative::~ScreenPanelNative() -{ - OSD::DeInit(); -} - -void ScreenPanelNative::setupScreenLayout() -{ - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - - for (int i = 0; i < numScreens; i++) - { - float* mtx = screenMatrix[i]; - screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, - mtx[2], mtx[3], 0.f, - mtx[4], mtx[5], 1.f); - } -} - -void ScreenPanelNative::paintEvent(QPaintEvent* event) -{ - QPainter painter(this); - - // fill background - painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); - - if (emuThread->emuIsActive()) - { - assert(emuThread->NDS != nullptr); - emuThread->FrontBufferLock.lock(); - int frontbuf = emuThread->FrontBuffer; - if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) - { - emuThread->FrontBufferLock.unlock(); - return; - } - - memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); - memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); - emuThread->FrontBufferLock.unlock(); - - QRect screenrc(0, 0, 256, 192); - - for (int i = 0; i < numScreens; i++) - { - painter.setTransform(screenTrans[i]); - painter.drawImage(screenrc, screen[screenKind[i]]); - } - } - - OSD::Update(); - OSD::DrawNative(painter); -} - -void ScreenPanelNative::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); -} - -void ScreenPanelNative::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelNative::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelNative::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelNative::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} - - -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - setAutoFillBackground(false); - setAttribute(Qt::WA_NativeWindow, true); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_PaintOnScreen, true); - setAttribute(Qt::WA_KeyCompression, false); - setFocusPolicy(Qt::StrongFocus); - setMinimumSize(screenGetMinSize()); -} - -ScreenPanelGL::~ScreenPanelGL() -{} - -bool ScreenPanelGL::createContext() -{ - std::optional windowInfo = getWindowInfo(); - std::array versionsToTry = { - GL::Context::Version{GL::Context::Profile::Core, 4, 3}, - GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; - if (windowInfo.has_value()) - { - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - glContext->DoneCurrent(); - } - - return glContext != nullptr; -} - -qreal ScreenPanelGL::devicePixelRatioFromScreen() const -{ - const QScreen* screen_for_ratio = window()->windowHandle()->screen(); - if (!screen_for_ratio) - screen_for_ratio = QGuiApplication::primaryScreen(); - - return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); -} - -int ScreenPanelGL::scaledWindowWidth() const -{ - return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); -} - -int ScreenPanelGL::scaledWindowHeight() const -{ - return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); -} - -std::optional ScreenPanelGL::getWindowInfo() -{ - WindowInfo wi; - - // Windows and Apple are easy here since there's no display connection. - #if defined(_WIN32) - wi.type = WindowInfo::Type::Win32; - wi.window_handle = reinterpret_cast(winId()); - #elif defined(__APPLE__) - wi.type = WindowInfo::Type::MacOS; - wi.window_handle = reinterpret_cast(winId()); - #else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - const QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("xcb")) - { - wi.type = WindowInfo::Type::X11; - wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); - wi.window_handle = reinterpret_cast(winId()); - } - else if (platform_name == QStringLiteral("wayland")) - { - wi.type = WindowInfo::Type::Wayland; - QWindow* handle = windowHandle(); - if (handle == nullptr) - return std::nullopt; - - wi.display_connection = pni->nativeResourceForWindow("display", handle); - wi.window_handle = pni->nativeResourceForWindow("surface", handle); - } - else - { - qCritical() << "Unknown PNI platform " << platform_name; - return std::nullopt; - } - #endif - - wi.surface_width = static_cast(scaledWindowWidth()); - wi.surface_height = static_cast(scaledWindowHeight()); - wi.surface_scale = static_cast(devicePixelRatioFromScreen()); - - return wi; -} - - -QPaintEngine* ScreenPanelGL::paintEngine() const -{ - return nullptr; -} - -void ScreenPanelGL::setupScreenLayout() -{ - int w = width(); - int h = height(); - screenSetupLayout(w, h); - if (emuThread) - transferLayout(emuThread); -} - -void ScreenPanelGL::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); - - QWidget::resizeEvent(event); -} - -void ScreenPanelGL::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelGL::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelGL::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelGL::transferLayout(EmuThread* thread) -{ - std::optional windowInfo = getWindowInfo(); - if (windowInfo.has_value()) - thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]); -} - -void ScreenPanelGL::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) @@ -1607,1863 +1106,7 @@ static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive } -#ifndef _WIN32 -static int signalFd[2]; -QSocketNotifier *signalSn; - -static void signalHandler(int) -{ - char a = 1; - write(signalFd[0], &a, sizeof(a)); -} -#endif - -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) -{ -#ifndef _WIN32 - if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) - { - qFatal("Couldn't create socketpair"); - } - - signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); - connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); - - struct sigaction sa; - - sa.sa_handler = signalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_flags |= SA_RESTART; - sigaction(SIGINT, &sa, 0); -#endif - - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized; - - setWindowTitle("melonDS " MELONDS_VERSION); - setAttribute(Qt::WA_DeleteOnClose); - setAcceptDrops(true); - setFocusPolicy(Qt::ClickFocus); - - int inst = Platform::InstanceID(); - - QMenuBar* menubar = new QMenuBar(); - { - QMenu* menu = menubar->addMenu("File"); - - actOpenROM = menu->addAction("Open ROM..."); - connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); - actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - - /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); - connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - - recentMenu = menu->addMenu("Open recent"); - for (int i = 0; i < 10; ++i) - { - std::string item = Config::RecentROMList[i]; - if (!item.empty()) - recentFileList.push_back(QString::fromStdString(item)); - } - updateRecentFilesMenu(); - - //actBootFirmware = menu->addAction("Launch DS menu"); - actBootFirmware = menu->addAction("Boot firmware"); - connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); - - menu->addSeparator(); - - actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); - actCurrentCart->setEnabled(false); - actInsertCart = menu->addAction("Insert cart..."); - connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); - - actEjectCart = menu->addAction("Eject cart"); - connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); - - menu->addSeparator(); - - actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); - actCurrentGBACart->setEnabled(false); - - actInsertGBACart = menu->addAction("Insert ROM cart..."); - connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - - { - QMenu* submenu = menu->addMenu("Insert add-on cart"); - - actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); - actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); - connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - } - - actEjectGBACart = menu->addAction("Eject cart"); - connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); - - menu->addSeparator(); - - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Save state"); - - for (int i = 1; i < 9; i++) - { - actSaveState[i] = submenu->addAction(QString("%1").arg(i)); - actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); - actSaveState[i]->setData(QVariant(i)); - connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); - } - - actSaveState[0] = submenu->addAction("File..."); - actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); - actSaveState[0]->setData(QVariant(0)); - connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); - } - { - QMenu* submenu = menu->addMenu("Load state"); - - for (int i = 1; i < 9; i++) - { - actLoadState[i] = submenu->addAction(QString("%1").arg(i)); - actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); - actLoadState[i]->setData(QVariant(i)); - connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actLoadState[0] = submenu->addAction("File..."); - actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); - actLoadState[0]->setData(QVariant(0)); - connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actUndoStateLoad = menu->addAction("Undo state load"); - actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); - connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); - - menu->addSeparator(); - - actQuit = menu->addAction("Quit"); - connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); - actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); - } - { - QMenu* menu = menubar->addMenu("System"); - - actPause = menu->addAction("Pause"); - actPause->setCheckable(true); - connect(actPause, &QAction::triggered, this, &MainWindow::onPause); - - actReset = menu->addAction("Reset"); - connect(actReset, &QAction::triggered, this, &MainWindow::onReset); - - actStop = menu->addAction("Stop"); - connect(actStop, &QAction::triggered, this, &MainWindow::onStop); - - actFrameStep = menu->addAction("Frame step"); - connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); - - menu->addSeparator(); - - actPowerManagement = menu->addAction("Power management"); - connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); - - actDateTime = menu->addAction("Date and time"); - connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); - - menu->addSeparator(); - - actEnableCheats = menu->addAction("Enable cheats"); - actEnableCheats->setCheckable(true); - connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - - //if (inst == 0) - { - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); - } - - { - menu->addSeparator(); - QMenu* submenu = menu->addMenu("Multiplayer"); - - actMPNewInstance = submenu->addAction("Launch new instance"); - connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); - } - } - { - QMenu* menu = menubar->addMenu("Config"); - - actEmuSettings = menu->addAction("Emu settings"); - connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - -#ifdef __APPLE__ - actPreferences = menu->addAction("Preferences..."); - connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - actPreferences->setMenuRole(QAction::PreferencesRole); -#endif - - actInputConfig = menu->addAction("Input and hotkeys"); - connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); - - 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); - - actMPSettings = menu->addAction("Multiplayer settings"); - connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); - - actWifiSettings = menu->addAction("Wifi settings"); - connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - - actFirmwareSettings = menu->addAction("Firmware settings"); - connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); - - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - - actPathSettings = menu->addAction("Path settings"); - connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); - - { - QMenu* submenu = menu->addMenu("Savestate settings"); - - actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); - actSavestateSRAMReloc->setCheckable(true); - connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); - } - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Screen size"); - - for (int i = 0; i < 4; i++) - { - int data = i+1; - actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); - actScreenSize[i]->setData(QVariant(data)); - connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); - } - } - { - QMenu* submenu = menu->addMenu("Screen rotation"); - grpScreenRotation = new QActionGroup(submenu); - - for (int i = 0; i < Frontend::screenRot_MAX; i++) - { - int data = i*90; - actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); - actScreenRotation[i]->setActionGroup(grpScreenRotation); - actScreenRotation[i]->setData(QVariant(i)); - actScreenRotation[i]->setCheckable(true); - } - - connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); - } - { - QMenu* submenu = menu->addMenu("Screen gap"); - grpScreenGap = new QActionGroup(submenu); - - const int screengap[] = {0, 1, 8, 64, 90, 128}; - - for (int i = 0; i < 6; i++) - { - int data = screengap[i]; - actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); - actScreenGap[i]->setActionGroup(grpScreenGap); - actScreenGap[i]->setData(QVariant(data)); - actScreenGap[i]->setCheckable(true); - } - - connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); - } - { - QMenu* submenu = menu->addMenu("Screen layout"); - grpScreenLayout = new QActionGroup(submenu); - - const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; - - for (int i = 0; i < Frontend::screenLayout_MAX; i++) - { - actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); - actScreenLayout[i]->setActionGroup(grpScreenLayout); - actScreenLayout[i]->setData(QVariant(i)); - actScreenLayout[i]->setCheckable(true); - } - - connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); - - submenu->addSeparator(); - - actScreenSwap = submenu->addAction("Swap screens"); - actScreenSwap->setCheckable(true); - connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); - } - { - QMenu* submenu = menu->addMenu("Screen sizing"); - grpScreenSizing = new QActionGroup(submenu); - - const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - - for (int i = 0; i < Frontend::screenSizing_MAX; i++) - { - actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); - actScreenSizing[i]->setActionGroup(grpScreenSizing); - actScreenSizing[i]->setData(QVariant(i)); - actScreenSizing[i]->setCheckable(true); - } - - connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); - - submenu->addSeparator(); - - actIntegerScaling = submenu->addAction("Force integer scaling"); - actIntegerScaling->setCheckable(true); - connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); - } - { - QMenu* submenu = menu->addMenu("Aspect ratio"); - grpScreenAspectTop = new QActionGroup(submenu); - grpScreenAspectBot = new QActionGroup(submenu); - actScreenAspectTop = new QAction*[AspectRatiosNum]; - actScreenAspectBot = new QAction*[AspectRatiosNum]; - - for (int i = 0; i < 2; i++) - { - QActionGroup* group = grpScreenAspectTop; - QAction** actions = actScreenAspectTop; - - if (i == 1) - { - group = grpScreenAspectBot; - submenu->addSeparator(); - actions = actScreenAspectBot; - } - - for (int j = 0; j < AspectRatiosNum; j++) - { - auto ratio = aspectRatios[j]; - QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); - actions[j] = submenu->addAction(label); - actions[j]->setActionGroup(group); - actions[j]->setData(QVariant(ratio.id)); - actions[j]->setCheckable(true); - } - - connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); - } - } - - actScreenFiltering = menu->addAction("Screen filtering"); - actScreenFiltering->setCheckable(true); - connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); - - actShowOSD = menu->addAction("Show OSD"); - actShowOSD->setCheckable(true); - connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); - - menu->addSeparator(); - - actLimitFramerate = menu->addAction("Limit framerate"); - actLimitFramerate->setCheckable(true); - connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); - - actAudioSync = menu->addAction("Audio sync"); - actAudioSync->setCheckable(true); - connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); - } - setMenuBar(menubar); - - resize(Config::WindowWidth, Config::WindowHeight); - - if (Config::FirmwareUsername == "Arisotura") - actMPNewInstance->setText("Fart"); - -#ifdef Q_OS_MAC - QPoint screenCenter = screen()->availableGeometry().center(); - QRect frameGeo = frameGeometry(); - frameGeo.moveCenter(screenCenter); - move(frameGeo.topLeft()); -#endif - - if (oldMax) - showMaximized(); - else - show(); - - createScreenPanel(); - - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - } - - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); - - actEnableCheats->setChecked(Config::EnableCheats); - - actROMInfo->setEnabled(false); - actRAMInfo->setEnabled(false); - - actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); - - actScreenRotation[Config::ScreenRotation]->setChecked(true); - - for (int i = 0; i < 6; i++) - { - if (actScreenGap[i]->data().toInt() == Config::ScreenGap) - { - actScreenGap[i]->setChecked(true); - break; - } - } - - actScreenLayout[Config::ScreenLayout]->setChecked(true); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling); - - actScreenSwap->setChecked(Config::ScreenSwap); - - for (int i = 0; i < AspectRatiosNum; i++) - { - if (Config::ScreenAspectTop == aspectRatios[i].id) - actScreenAspectTop[i]->setChecked(true); - if (Config::ScreenAspectBot == aspectRatios[i].id) - actScreenAspectBot[i]->setChecked(true); - } - - actScreenFiltering->setChecked(Config::ScreenFilter); - actShowOSD->setChecked(Config::ShowOSD); - - actLimitFramerate->setChecked(Config::LimitFPS); - actAudioSync->setChecked(Config::AudioSync); - - if (inst > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); - -#ifdef __APPLE__ - actPreferences->setEnabled(false); -#endif // __APPLE__ - } -} - -MainWindow::~MainWindow() -{ - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; -} - -void MainWindow::closeEvent(QCloseEvent* event) -{ - if (hasOGL) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } - - QMainWindow::closeEvent(event); -} - -void MainWindow::createScreenPanel() -{ - hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); - - if (hasOGL) - { - ScreenPanelGL* panelGL = new ScreenPanelGL(this); - panelGL->show(); - - panel = panelGL; - panelWidget = panelGL; - - panelGL->createContext(); - } - - if (!hasOGL) - { - ScreenPanelNative* panelNative = new ScreenPanelNative(this); - panel = panelNative; - panelWidget = panelNative; - panelWidget->show(); - } - setCentralWidget(panelWidget); - - actScreenFiltering->setEnabled(hasOGL); - - connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); - emit screenLayoutChange(); -} - -GL::Context* MainWindow::getOGLContext() -{ - if (!hasOGL) return nullptr; - - ScreenPanelGL* glpanel = static_cast(panel); - return glpanel->getContext(); -} - -void MainWindow::resizeEvent(QResizeEvent* event) -{ - int w = event->size().width(); - int h = event->size().height(); - - if (!isFullScreen()) - { - // this is ugly - // thing is, when maximizing the window, we first receive the resizeEvent - // with a new size matching the screen, then the changeEvent telling us that - // the maximized flag was updated - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = isMaximized(); - - Config::WindowWidth = w; - Config::WindowHeight = h; - } -} - -void MainWindow::changeEvent(QEvent* event) -{ - if (isMaximized() && !oldMax) - { - Config::WindowWidth = oldW; - Config::WindowHeight = oldH; - } - - Config::WindowMaximized = isMaximized() ? 1:0; -} - -void MainWindow::keyPressEvent(QKeyEvent* event) -{ - if (event->isAutoRepeat()) return; - - // TODO!! REMOVE ME IN RELEASE BUILDS!! - //if (event->key() == Qt::Key_F11) NDS::debug(0); - - Input::KeyPress(event); -} - -void MainWindow::keyReleaseEvent(QKeyEvent* event) -{ - if (event->isAutoRepeat()) return; - - Input::KeyRelease(event); -} - - -void MainWindow::dragEnterEvent(QDragEnterEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - QList urls = event->mimeData()->urls(); - if (urls.count() > 1) return; // not handling more than one file at once - - QString filename = urls.at(0).toLocalFile(); - - if (FileIsSupportedFiletype(filename)) - event->acceptProposedAction(); -} - -void MainWindow::dropEvent(QDropEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - QList urls = event->mimeData()->urls(); - if (urls.count() > 1) return; // not handling more than one file at once - - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - const QString filename = file.last(); - const bool romInsideArchive = file.size() > 1; - const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); - - bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); - bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); - isNdsRom |= ZstdNdsRomByExtension(filename); - isGbaRom |= ZstdGbaRomByExtension(filename); - - if (isNdsRom) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); - emuThread->emuUnpause(); - return; - } - - const QString barredFilename = file.join('|'); - recentFileList.removeAll(barredFilename); - recentFileList.prepend(barredFilename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); - } - else if (isGbaRom) - { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(true); - } - else - { - QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); - emuThread->emuUnpause(); - return; - } -} - -void MainWindow::focusInEvent(QFocusEvent* event) -{ - AudioInOut::AudioMute(mainWindow); -} - -void MainWindow::focusOutEvent(QFocusEvent* event) -{ - AudioInOut::AudioMute(mainWindow); -} - -void MainWindow::onAppStateChanged(Qt::ApplicationState state) -{ - if (state == Qt::ApplicationInactive) - { - if (Config::PauseLostFocus && emuThread->emuIsRunning()) - emuThread->emuPause(); - } - else if (state == Qt::ApplicationActive) - { - if (Config::PauseLostFocus && !pausedManually) - emuThread->emuUnpause(); - } -} - -bool MainWindow::verifySetup() -{ - QString res = ROMManager::VerifySetup(); - if (!res.isEmpty()) - { - QMessageBox::critical(this, "melonDS", res); - return false; - } - - return true; -} - -bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) -{ - if (!verifySetup()) - { - return false; - } - - bool gbaloaded = false; - if (!gbafile.isEmpty()) - { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - return false; - } - - gbaloaded = true; - } - - bool ndsloaded = false; - if (!file.isEmpty()) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; - } - recentFileList.removeAll(file.join("|")); - recentFileList.prepend(file.join("|")); - updateRecentFilesMenu(); - ndsloaded = true; - } - - if (boot) - { - if (ndsloaded) - { - emuThread->NDS->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } - } - - updateCartInserted(false); - - if (gbaloaded) - { - updateCartInserted(true); - } - - return true; -} - -QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) -{ - if (filename.isEmpty()) return {}; - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (useMemberSyntax) - { - const QStringList filenameParts = filename.split('|'); - if (filenameParts.size() > 2) - { - QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); - return {}; - } - - if (filenameParts.size() == 2) - { - const QString archive = filenameParts.at(0); - if (!QFileInfo(archive).exists()) - { - QMessageBox::warning(this, "melonDS", "This archive does not exist."); - return {}; - } - - const QString subfile = filenameParts.at(1); - if (!Archive::ListArchive(archive).contains(subfile)) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); - return {}; - } - - return filenameParts; - } - } -#endif - - if (!QFileInfo(filename).exists()) - { - QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); - return {}; - } - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (SupportedArchiveByExtension(filename) - || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) - { - const QString subfile = pickFileFromArchive(filename); - if (subfile.isEmpty()) - return {}; - - return { filename, subfile }; - } -#endif - - return { filename }; -} - -QString MainWindow::pickFileFromArchive(QString archiveFileName) -{ - QVector archiveROMList = Archive::ListArchive(archiveFileName); - - if (archiveROMList.size() <= 1) - { - if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") - QMessageBox::warning(this, "melonDS", "This archive is empty."); - else - QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); - return QString(); - } - - archiveROMList.removeFirst(); - - const auto notSupportedRom = [&](const auto& filename){ - if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) - return false; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); - return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); - }; - - archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), - archiveROMList.end()); - - if (archiveROMList.isEmpty()) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); - return QString(); - } - - if (archiveROMList.size() == 1) - return archiveROMList.first(); - - bool ok; - const QString toLoad = QInputDialog::getItem( - this, "melonDS", - "This archive contains multiple files. Select which ROM you want to load.", - archiveROMList.toList(), 0, false, &ok - ); - - if (ok) return toLoad; - - // User clicked on cancel - - return QString(); -} - -QStringList MainWindow::pickROM(bool gba) -{ - const QString console = gba ? "GBA" : "DS"; - const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; - - QString rawROMs = romexts.join(" *"); - QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; - QString allROMs = rawROMs; - - QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; - extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; - allROMs += " " + zstdROMs; - -#ifdef ARCHIVE_SUPPORT_ENABLED - QString archives = "*" + ArchiveExtensions.join(" *"); - extraFilters += ";;Archives (" + archives + ")"; - allROMs += " " + archives; -#endif - extraFilters += ";;All files (*.*)"; - - const QString filename = QFileDialog::getOpenFileName( - this, "Open " + console + " ROM", - QString::fromStdString(Config::LastROMFolder), - "All supported files (*" + allROMs + ")" + extraFilters - ); - - if (filename.isEmpty()) return {}; - - Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); - return splitArchivePath(filename, false); -} - -void MainWindow::updateCartInserted(bool gba) -{ - bool inserted; - if (gba) - { - inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - actEjectGBACart->setEnabled(inserted); - } - else - { - inserted = ROMManager::CartInserted(); - actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); - actEjectCart->setEnabled(inserted); - actImportSavefile->setEnabled(inserted); - actSetupCheats->setEnabled(inserted); - actROMInfo->setEnabled(inserted); - actRAMInfo->setEnabled(inserted); - } -} - -void MainWindow::onOpenFile() -{ - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - QStringList file = pickROM(false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - QString filename = file.join('|'); - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); -} - -void MainWindow::onClearRecentFiles() -{ - recentFileList.clear(); - for (int i = 0; i < 10; i++) - Config::RecentROMList[i] = ""; - updateRecentFilesMenu(); -} - -void MainWindow::updateRecentFilesMenu() -{ - recentMenu->clear(); - - for (int i = 0; i < recentFileList.size(); ++i) - { - if (i >= 10) break; - - QString item_full = recentFileList.at(i); - QString item_display = item_full; - int itemlen = item_full.length(); - const int maxlen = 100; - if (itemlen > maxlen) - { - int cut_start = 0; - while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && - cut_start < itemlen) - cut_start++; - - int cut_end = itemlen-1; - while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || - (cut_start+4+(itemlen-cut_end) < maxlen)) && - cut_end > 0) - cut_end--; - - item_display.truncate(cut_start+1); - item_display += "..."; - item_display += QString(item_full).remove(0, cut_end); - } - - QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); - actRecentFile_i->setData(item_full); - connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - - Config::RecentROMList[i] = recentFileList.at(i).toStdString(); - } - - while (recentFileList.size() > 10) - recentFileList.removeLast(); - - recentMenu->addSeparator(); - - QAction *actClearRecentList = recentMenu->addAction("Clear"); - connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); - - if (recentFileList.empty()) - actClearRecentList->setEnabled(false); - - Config::Save(); -} - -void MainWindow::onClickRecentFile() -{ - QAction *act = (QAction *)sender(); - QString filename = act->data().toString(); - - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - const QStringList file = splitArchivePath(filename, true); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); -} - -void MainWindow::onBootFirmware() -{ - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::BootToMenu(emuThread)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); - emuThread->emuUnpause(); - return; - } - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); -} - -void MainWindow::onInsertCart() -{ - emuThread->emuPause(); - - QStringList file = pickROM(false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, false)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(false); -} - -void MainWindow::onEjectCart() -{ - emuThread->emuPause(); - - ROMManager::EjectCart(*emuThread->NDS); - - emuThread->emuUnpause(); - - updateCartInserted(false); -} - -void MainWindow::onInsertGBACart() -{ - emuThread->emuPause(); - - QStringList file = pickROM(true); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onInsertGBAAddon() -{ - QAction* act = (QAction*)sender(); - int type = act->data().toInt(); - - emuThread->emuPause(); - - ROMManager::LoadGBAAddon(*emuThread->NDS, type); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onEjectGBACart() -{ - emuThread->emuPause(); - - ROMManager::EjectGBACart(*emuThread->NDS); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onSaveState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, - "Save state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (ROMManager::SaveState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State saved to slot %d", slot); - else sprintf(msg, "State saved to file"); - OSD::AddMessage(0, msg); - - actLoadState[slot]->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State save failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onLoadState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, - "Load state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (!Platform::FileExists(filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State slot %d is empty", slot); - else sprintf(msg, "State file does not exist"); - OSD::AddMessage(0xFFA0A0, msg); - - emuThread->emuUnpause(); - return; - } - - if (ROMManager::LoadState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); - else sprintf(msg, "State loaded from file"); - OSD::AddMessage(0, msg); - - actUndoStateLoad->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State load failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onUndoStateLoad() -{ - emuThread->emuPause(); - ROMManager::UndoStateLoad(*emuThread->NDS); - emuThread->emuUnpause(); - - OSD::AddMessage(0, "State load undone"); -} - -void MainWindow::onImportSavefile() -{ - emuThread->emuPause(); - QString path = QFileDialog::getOpenFileName(this, - "Select savefile", - QString::fromStdString(Config::LastROMFolder), - "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); - - if (path.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); - if (!f) - { - QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); - return; - } - - if (RunningSomething) - { - if (QMessageBox::warning(this, - "melonDS", - "The emulation will be reset and the current savefile overwritten.", - QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) - { - emuThread->emuUnpause(); - return; - } - - ROMManager::Reset(emuThread); - } - - u32 len = FileLength(f); - - std::unique_ptr data = std::make_unique(len); - Platform::FileRewind(f); - Platform::FileRead(data.get(), len, 1, f); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->SetNDSSave(data.get(), len); - - CloseFile(f); - emuThread->emuUnpause(); -} - -void MainWindow::onQuit() -{ -#ifndef _WIN32 - signalSn->setEnabled(false); -#endif - QApplication::quit(); -} - - -void MainWindow::onPause(bool checked) -{ - if (!RunningSomething) return; - - if (checked) - { - emuThread->emuPause(); - OSD::AddMessage(0, "Paused"); - pausedManually = true; - } - else - { - emuThread->emuUnpause(); - OSD::AddMessage(0, "Resumed"); - pausedManually = false; - } -} - -void MainWindow::onReset() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - - actUndoStateLoad->setEnabled(false); - - ROMManager::Reset(emuThread); - - OSD::AddMessage(0, "Reset"); - emuThread->emuRun(); -} - -void MainWindow::onStop() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - emuThread->NDS->Stop(); -} - -void MainWindow::onFrameStep() -{ - if (!RunningSomething) return; - - emuThread->emuFrameStep(); -} - -void MainWindow::onOpenDateTime() -{ - DateTimeDialog* dlg = DateTimeDialog::openDlg(this); -} - -void MainWindow::onOpenPowerManagement() -{ - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); -} - -void MainWindow::onEnableCheats(bool checked) -{ - Config::EnableCheats = checked?1:0; - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); -} - -void MainWindow::onSetupCheats() -{ - emuThread->emuPause(); - - CheatsDialog* dlg = CheatsDialog::openDlg(this); - connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); -} - -void MainWindow::onCheatsDialogFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onROMInfo() -{ - auto cart = emuThread->NDS->NDSCartSlot.GetCart(); - if (cart) - ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); -} - -void MainWindow::onRAMInfo() -{ - RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); -} - -void MainWindow::onOpenTitleManager() -{ - TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); -} - -void MainWindow::onMPNewInstance() -{ - //QProcess::startDetached(QApplication::applicationFilePath()); - QProcess newinst; - newinst.setProgram(QApplication::applicationFilePath()); - newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); - -#ifdef __WIN32__ - newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) - { - args->flags |= CREATE_NEW_CONSOLE; - }); -#endif - - newinst.startDetached(); -} - -void MainWindow::onOpenEmuSettings() -{ - emuThread->emuPause(); - - EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); - connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); -} - -void MainWindow::onEmuSettingsDialogFinished(int res) -{ - emuThread->emuUnpause(); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - actEjectGBACart->setEnabled(false); - } - else - { - actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); - actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); - } - - if (EmuSettingsDialog::needsReset) - onReset(); - - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - - if (!RunningSomething) - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onOpenInputConfig() -{ - emuThread->emuPause(); - - InputConfigDialog* dlg = InputConfigDialog::openDlg(this); - connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); -} - -void MainWindow::onInputConfigFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onOpenVideoSettings() -{ - VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); - 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, emuThread->emuIsActive(), emuThread); - connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); - connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); - connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); - connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); -} - -void MainWindow::onOpenFirmwareSettings() -{ - emuThread->emuPause(); - - FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); - connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); -} - -void MainWindow::onFirmwareSettingsFinished(int res) -{ - if (FirmwareSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenPathSettings() -{ - emuThread->emuPause(); - - PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); - connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); -} - -void MainWindow::onPathSettingsFinished(int res) -{ - if (PathSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onUpdateAudioSettings() -{ - assert(emuThread->NDS != nullptr); - emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); - - if (Config::AudioBitDepth == 0) - emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); - else - emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); -} - -void MainWindow::onAudioSettingsFinished(int res) -{ - AudioInOut::UpdateSettings(*emuThread->NDS); -} - -void MainWindow::onOpenMPSettings() -{ - emuThread->emuPause(); - - MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); - connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); -} - -void MainWindow::onMPSettingsFinished(int res) -{ - AudioInOut::AudioMute(mainWindow); - LocalMP::SetRecvTimeout(Config::MPRecvTimeout); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenWifiSettings() -{ - emuThread->emuPause(); - - WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); - connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); -} - -void MainWindow::onWifiSettingsFinished(int res) -{ - Platform::LAN_DeInit(); - Platform::LAN_Init(); - - if (WifiSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenInterfaceSettings() -{ - emuThread->emuPause(); - InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); - connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); - connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); -} - -void MainWindow::onUpdateMouseTimer() -{ - panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); -} - -void MainWindow::onInterfaceSettingsFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onChangeSavestateSRAMReloc(bool checked) -{ - Config::SavestateRelocSRAM = checked?1:0; -} - -void MainWindow::onChangeScreenSize() -{ - int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panelWidget->size(); - resize(panel->screenGetMinSize(factor) + diff); -} - -void MainWindow::onChangeScreenRotation(QAction* act) -{ - int rot = act->data().toInt(); - Config::ScreenRotation = rot; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenGap(QAction* act) -{ - int gap = act->data().toInt(); - Config::ScreenGap = gap; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenLayout(QAction* act) -{ - int layout = act->data().toInt(); - Config::ScreenLayout = layout; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSwap(bool checked) -{ - Config::ScreenSwap = checked?1:0; - - // Swap between top and bottom screen when displaying one screen. - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) - { - // Bottom Screen. - Config::ScreenSizing = Frontend::screenSizing_BotOnly; - actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - // Top Screen. - Config::ScreenSizing = Frontend::screenSizing_TopOnly; - actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSizing(QAction* act) -{ - int sizing = act->data().toInt(); - Config::ScreenSizing = sizing; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenAspect(QAction* act) -{ - int aspect = act->data().toInt(); - QActionGroup* group = act->actionGroup(); - - if (group == grpScreenAspectTop) - { - Config::ScreenAspectTop = aspect; - } - else - { - Config::ScreenAspectBot = aspect; - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeIntegerScaling(bool checked) -{ - Config::IntegerScaling = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenFiltering(bool checked) -{ - Config::ScreenFilter = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeShowOSD(bool checked) -{ - Config::ShowOSD = checked?1:0; -} -void MainWindow::onChangeLimitFramerate(bool checked) -{ - Config::LimitFPS = checked?1:0; -} - -void MainWindow::onChangeAudioSync(bool checked) -{ - Config::AudioSync = checked?1:0; -} - - -void MainWindow::onTitleUpdate(QString title) -{ - setWindowTitle(title); -} - -void ToggleFullscreen(MainWindow* mainWindow) -{ - if (!mainWindow->isFullScreen()) - { - mainWindow->showFullScreen(); - mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working - } - else - { - mainWindow->showNormal(); - int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); - mainWindow->menuBar()->setFixedHeight(menuBarHeight); - } -} - -void MainWindow::onFullscreenToggled() -{ - ToggleFullscreen(this); -} - -void MainWindow::onScreenEmphasisToggled() -{ - int currentSizing = Config::ScreenSizing; - if (currentSizing == Frontend::screenSizing_EmphTop) - { - Config::ScreenSizing = Frontend::screenSizing_EmphBot; - } - else if (currentSizing == Frontend::screenSizing_EmphBot) - { - Config::ScreenSizing = Frontend::screenSizing_EmphTop; - } - - emit screenLayoutChange(); -} - -void MainWindow::onEmuStart() -{ - for (int i = 1; i < 9; i++) - { - actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); - } - actSaveState[0]->setEnabled(true); - actLoadState[0]->setEnabled(true); - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(true); - actPause->setChecked(false); - actReset->setEnabled(true); - actStop->setEnabled(true); - actFrameStep->setEnabled(true); - - actDateTime->setEnabled(false); - actPowerManagement->setEnabled(true); - - actTitleManager->setEnabled(false); -} - -void MainWindow::onEmuStop() -{ - emuThread->emuPause(); - - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onUpdateVideoSettings(bool glchange) -{ - if (glchange) - { - emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); - - delete panel; - createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); - } - - videoSettingsDirty = true; - - if (glchange) - { - if (hasOGL) emuThread->initContext(); - emuThread->emuUnpause(); - } -} void emuStop() diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index ee2f720115..9703c178ac 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -37,6 +37,7 @@ #include #include +#include "Window.h" #include "FrontendUtil.h" #include "duckstation/gl/context.h" @@ -156,110 +157,6 @@ class EmuThread : public QThread int lastScreenWidth = -1, lastScreenHeight = -1; }; - -class ScreenHandler -{ - Q_GADGET - -public: - ScreenHandler(QWidget* widget); - virtual ~ScreenHandler(); - QTimer* setupMouseTimer(); - void updateMouseTimer(); - QTimer* mouseTimer; - QSize screenGetMinSize(int factor); - -protected: - void screenSetupLayout(int w, int h); - - void screenOnMousePress(QMouseEvent* event); - void screenOnMouseRelease(QMouseEvent* event); - void screenOnMouseMove(QMouseEvent* event); - - void screenHandleTablet(QTabletEvent* event); - void screenHandleTouch(QTouchEvent* event); - - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - - bool touching = false; - - void showCursor(); -}; - - -class ScreenPanelNative : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelNative(QWidget* parent); - virtual ~ScreenPanelNative(); - -protected: - void paintEvent(QPaintEvent* event) override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - QImage screen[2]; - QTransform screenTrans[Frontend::MaxScreenTransforms]; -}; - - -class ScreenPanelGL : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelGL(QWidget* parent); - virtual ~ScreenPanelGL(); - - std::optional getWindowInfo(); - - bool createContext(); - - GL::Context* getContext() { return glContext.get(); } - - void transferLayout(EmuThread* thread); -protected: - - qreal devicePixelRatioFromScreen() const; - int scaledWindowWidth() const; - int scaledWindowHeight() const; - - QPaintEngine* paintEngine() const override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; - -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - std::unique_ptr glContext; -}; - class MelonApplication : public QApplication { Q_OBJECT @@ -269,199 +166,4 @@ class MelonApplication : public QApplication bool event(QEvent* event) override; }; -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget* parent = nullptr); - ~MainWindow(); - - bool hasOGL; - GL::Context* getOGLContext(); - - bool preloadROMs(QStringList file, QStringList gbafile, bool boot); - QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); - - void onAppStateChanged(Qt::ApplicationState state); - -protected: - void resizeEvent(QResizeEvent* event) override; - void changeEvent(QEvent* event) override; - - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; - - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - -signals: - void screenLayoutChange(); - -private slots: - void onOpenFile(); - void onClickRecentFile(); - void onClearRecentFiles(); - void onBootFirmware(); - void onInsertCart(); - void onEjectCart(); - void onInsertGBACart(); - void onInsertGBAAddon(); - void onEjectGBACart(); - void onSaveState(); - void onLoadState(); - void onUndoStateLoad(); - void onImportSavefile(); - void onQuit(); - - void onPause(bool checked); - void onReset(); - void onStop(); - void onFrameStep(); - void onOpenPowerManagement(); - void onOpenDateTime(); - void onEnableCheats(bool checked); - void onSetupCheats(); - void onCheatsDialogFinished(int res); - void onROMInfo(); - void onRAMInfo(); - void onOpenTitleManager(); - void onMPNewInstance(); - - void onOpenEmuSettings(); - void onEmuSettingsDialogFinished(int res); - void onOpenInputConfig(); - void onInputConfigFinished(int res); - void onOpenVideoSettings(); - void onOpenCameraSettings(); - void onCameraSettingsFinished(int res); - void onOpenAudioSettings(); - 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); - void onUpdateMouseTimer(); - void onChangeSavestateSRAMReloc(bool checked); - void onChangeScreenSize(); - void onChangeScreenRotation(QAction* act); - void onChangeScreenGap(QAction* act); - void onChangeScreenLayout(QAction* act); - void onChangeScreenSwap(bool checked); - void onChangeScreenSizing(QAction* act); - void onChangeScreenAspect(QAction* act); - void onChangeIntegerScaling(bool checked); - void onChangeScreenFiltering(bool checked); - void onChangeShowOSD(bool checked); - void onChangeLimitFramerate(bool checked); - void onChangeAudioSync(bool checked); - - void onTitleUpdate(QString title); - - void onEmuStart(); - void onEmuStop(); - - void onUpdateVideoSettings(bool glchange); - - void onFullscreenToggled(); - void onScreenEmphasisToggled(); - -private: - virtual void closeEvent(QCloseEvent* event) override; - - QStringList currentROM; - QStringList currentGBAROM; - QList recentFileList; - QMenu *recentMenu; - void updateRecentFilesMenu(); - - bool verifySetup(); - QString pickFileFromArchive(QString archiveFileName); - QStringList pickROM(bool gba); - void updateCartInserted(bool gba); - - void createScreenPanel(); - - bool pausedManually = false; - - int oldW, oldH; - bool oldMax; - -public: - ScreenHandler* panel; - QWidget* panelWidget; - - QAction* actOpenROM; - QAction* actBootFirmware; - QAction* actCurrentCart; - QAction* actInsertCart; - QAction* actEjectCart; - QAction* actCurrentGBACart; - QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; - QAction* actEjectGBACart; - QAction* actImportSavefile; - QAction* actSaveState[9]; - QAction* actLoadState[9]; - QAction* actUndoStateLoad; - QAction* actQuit; - - QAction* actPause; - QAction* actReset; - QAction* actStop; - QAction* actFrameStep; - QAction* actPowerManagement; - QAction* actDateTime; - QAction* actEnableCheats; - QAction* actSetupCheats; - QAction* actROMInfo; - QAction* actRAMInfo; - QAction* actTitleManager; - QAction* actMPNewInstance; - - QAction* actEmuSettings; -#ifdef __APPLE__ - QAction* actPreferences; -#endif - QAction* actInputConfig; - QAction* actVideoSettings; - QAction* actCameraSettings; - QAction* actAudioSettings; - QAction* actMPSettings; - QAction* actWifiSettings; - QAction* actFirmwareSettings; - QAction* actPathSettings; - QAction* actInterfaceSettings; - QAction* actSavestateSRAMReloc; - QAction* actScreenSize[4]; - QActionGroup* grpScreenRotation; - QAction* actScreenRotation[Frontend::screenRot_MAX]; - QActionGroup* grpScreenGap; - QAction* actScreenGap[6]; - QActionGroup* grpScreenLayout; - QAction* actScreenLayout[Frontend::screenLayout_MAX]; - QAction* actScreenSwap; - QActionGroup* grpScreenSizing; - QAction* actScreenSizing[Frontend::screenSizing_MAX]; - QAction* actIntegerScaling; - QActionGroup* grpScreenAspectTop; - QAction** actScreenAspectTop; - QActionGroup* grpScreenAspectBot; - QAction** actScreenAspectBot; - QAction* actScreenFiltering; - QAction* actShowOSD; - QAction* actLimitFramerate; - QAction* actAudioSync; -}; - #endif // MAIN_H