aboutsummaryrefslogtreecommitdiff
path: root/src/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend')
-rw-r--r--src/frontend/qt_sdl/CMakeLists.txt2
-rw-r--r--src/frontend/qt_sdl/Platform.cpp558
-rw-r--r--src/frontend/qt_sdl/PlatformConfig.cpp151
-rw-r--r--src/frontend/qt_sdl/PlatformConfig.h82
-rw-r--r--src/frontend/qt_sdl/main.cpp570
-rw-r--r--src/frontend/qt_sdl/main.h42
6 files changed, 1396 insertions, 9 deletions
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index 1ee7629..f24464d 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -2,6 +2,8 @@ project(qt_sdl)
SET(SOURCES_QT_SDL
main.cpp
+ Platform.cpp
+ PlatformConfig.cpp
)
if (WIN32)
diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp
new file mode 100644
index 0000000..31b5277
--- /dev/null
+++ b/src/frontend/qt_sdl/Platform.cpp
@@ -0,0 +1,558 @@
+/*
+ Copyright 2016-2020 Arisotura
+
+ This file is part of melonDS.
+
+ melonDS is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <SDL2/SDL.h>
+#include "Platform.h"
+#include "PlatformConfig.h"
+//#include "LAN_Socket.h"
+//#include "LAN_PCap.h"
+#include <string>
+
+#ifdef __WIN32__
+ #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK
+ #include <windows.h>
+ //#include <knownfolders.h> // FUCK THAT SHIT
+ extern "C" const GUID DECLSPEC_SELECTANY FOLDERID_RoamingAppData = {0x3eb685db, 0x65f9, 0x4cf6, {0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d}};
+ #include <shlobj.h>
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+ #define socket_t SOCKET
+ #define sockaddr_t SOCKADDR
+#else
+ #include <glib.h>
+ #include <unistd.h>
+ #include <arpa/inet.h>
+ #include <netinet/in.h>
+ #include <sys/select.h>
+ #include <sys/socket.h>
+ #define socket_t int
+ #define sockaddr_t struct sockaddr
+ #define closesocket close
+#endif
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET (socket_t)-1
+#endif
+
+
+extern char* EmuDirectory;
+
+void Stop(bool internal);
+
+
+namespace Platform
+{
+
+
+typedef struct
+{
+ SDL_Thread* ID;
+ void (*Func)();
+
+} ThreadData;
+
+int ThreadEntry(void* data)
+{
+ ThreadData* thread = (ThreadData*)data;
+ thread->Func();
+ return 0;
+}
+
+
+socket_t MPSocket;
+sockaddr_t MPSendAddr;
+u8 PacketBuffer[2048];
+
+#define NIFI_VER 1
+
+
+void StopEmu()
+{
+ //Stop(true);
+}
+
+
+FILE* OpenFile(const char* path, const char* mode, bool mustexist)
+{
+ FILE* ret;
+
+#ifdef __WIN32__
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
+ if (len < 1) return NULL;
+ WCHAR* fatpath = new WCHAR[len];
+ int res = MultiByteToWideChar(CP_UTF8, 0, path, -1, fatpath, len);
+ if (res != len) { delete[] fatpath; return NULL; } // checkme?
+
+ // this will be more than enough
+ WCHAR fatmode[4];
+ fatmode[0] = mode[0];
+ fatmode[1] = mode[1];
+ fatmode[2] = mode[2];
+ fatmode[3] = 0;
+
+ if (mustexist)
+ {
+ ret = _wfopen(fatpath, L"rb");
+ if (ret) ret = _wfreopen(fatpath, fatmode, ret);
+ }
+ else
+ ret = _wfopen(fatpath, fatmode);
+
+ delete[] fatpath;
+
+#else
+
+ if (mustexist)
+ {
+ ret = fopen(path, "rb");
+ if (ret) ret = freopen(path, mode, ret);
+ }
+ else
+ ret = fopen(path, mode);
+
+#endif
+
+ return ret;
+}
+
+#if !defined(UNIX_PORTABLE) && !defined(__WIN32__)
+
+FILE* OpenLocalFile(const char* path, const char* mode)
+{
+ std::string fullpath;
+ if (path[0] == '/')
+ {
+ // If it's an absolute path, just open that.
+ fullpath = std::string(path);
+ }
+ else
+ {
+ // Check user configuration directory
+ std::string confpath = std::string(g_get_user_config_dir()) + "/melonDS/";
+ g_mkdir_with_parents(confpath.c_str(), 0755);
+ fullpath = confpath + path;
+ }
+
+ return OpenFile(fullpath.c_str(), mode, mode[0] != 'w');
+}
+
+FILE* OpenDataFile(const char* path)
+{
+ const char* melondir = "melonDS";
+ const char* const* sys_dirs = g_get_system_data_dirs();
+ const char* user_dir = g_get_user_data_dir();
+
+ // First check the user's data directory
+ char* fullpath = g_build_path("/", user_dir, melondir, path, NULL);
+ if (access(fullpath, R_OK) == 0)
+ {
+ FILE* f = fopen(fullpath, "r");
+ g_free(fullpath);
+ return f;
+ }
+ free(fullpath);
+
+ // Then check the system data directories
+ for (size_t i = 0; sys_dirs[i] != NULL; i++)
+ {
+ const char* dir = sys_dirs[i];
+ char* fullpath = g_build_path("/", dir, melondir, path, NULL);
+
+ if (access(fullpath, R_OK) == 0)
+ {
+ FILE* f = fopen(fullpath, "r");
+ g_free(fullpath);
+ return f;
+ }
+ free(fullpath);
+ }
+
+ FILE* f = fopen(path, "rb");
+ if (f) return f;
+
+ return NULL;
+}
+
+#else
+
+FILE* OpenLocalFile(const char* path, const char* mode)
+{
+ bool relpath = false;
+ int pathlen = strlen(path);
+
+#ifdef __WIN32__
+ if (pathlen > 3)
+ {
+ if (path[1] == ':' && path[2] == '\\')
+ return OpenFile(path, mode);
+ }
+#else
+ if (pathlen > 1)
+ {
+ if (path[0] == '/')
+ return OpenFile(path, mode);
+ }
+#endif
+
+ if (pathlen >= 3)
+ {
+ if (path[0] == '.' && path[1] == '.' && (path[2] == '/' || path[2] == '\\'))
+ relpath = true;
+ }
+
+ int emudirlen = strlen(EmuDirectory);
+ char* emudirpath;
+ if (emudirlen)
+ {
+ int len = emudirlen + 1 + pathlen + 1;
+ emudirpath = new char[len];
+ strncpy(&emudirpath[0], EmuDirectory, emudirlen);
+ emudirpath[emudirlen] = '/';
+ strncpy(&emudirpath[emudirlen+1], path, pathlen);
+ emudirpath[emudirlen+1+pathlen] = '\0';
+ }
+ else
+ {
+ emudirpath = new char[pathlen+1];
+ strncpy(&emudirpath[0], path, pathlen);
+ emudirpath[pathlen] = '\0';
+ }
+
+ // Locations are application directory, and AppData/melonDS on Windows or XDG_CONFIG_HOME/melonDS on Linux
+
+ FILE* f;
+
+ // First check current working directory
+ f = OpenFile(path, mode, true);
+ if (f) { delete[] emudirpath; return f; }
+
+ // then emu directory
+ f = OpenFile(emudirpath, mode, true);
+ if (f) { delete[] emudirpath; return f; }
+
+#ifdef __WIN32__
+
+ // a path relative to AppData wouldn't make much sense
+ if (!relpath)
+ {
+ // Now check AppData
+ PWSTR appDataPath = NULL;
+ SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &appDataPath);
+ if (!appDataPath)
+ {
+ delete[] emudirpath;
+ return NULL;
+ }
+
+ // this will be more than enough
+ WCHAR fatperm[4];
+ fatperm[0] = mode[0];
+ fatperm[1] = mode[1];
+ fatperm[2] = mode[2];
+ fatperm[3] = 0;
+
+ int fnlen = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
+ if (fnlen < 1) { delete[] emudirpath; return NULL; }
+ WCHAR* wfileName = new WCHAR[fnlen];
+ int res = MultiByteToWideChar(CP_UTF8, 0, path, -1, wfileName, fnlen);
+ if (res != fnlen) { delete[] wfileName; delete[] emudirpath; return NULL; } // checkme?
+
+ const WCHAR* appdir = L"\\melonDS\\";
+
+ int pos = wcslen(appDataPath);
+ void* ptr = CoTaskMemRealloc(appDataPath, (pos+wcslen(appdir)+fnlen+1)*sizeof(WCHAR));
+ if (!ptr) { delete[] wfileName; delete[] emudirpath; return NULL; } // oh well
+ appDataPath = (PWSTR)ptr;
+
+ wcscpy(&appDataPath[pos], appdir); pos += wcslen(appdir);
+ wcscpy(&appDataPath[pos], wfileName);
+
+ f = _wfopen(appDataPath, L"rb");
+ if (f) f = _wfreopen(appDataPath, fatperm, f);
+ CoTaskMemFree(appDataPath);
+ delete[] wfileName;
+ if (f) { delete[] emudirpath; return f; }
+ }
+
+#else
+
+ if (!relpath)
+ {
+ // Now check XDG_CONFIG_HOME
+ // TODO: check for memory leak there
+ std::string fullpath = std::string(g_get_user_config_dir()) + "/melonDS/" + path;
+ f = OpenFile(fullpath.c_str(), mode, true);
+ if (f) { delete[] emudirpath; return f; }
+ }
+
+#endif
+
+ if (mode[0] != 'r')
+ {
+ f = OpenFile(emudirpath, mode);
+ if (f) { delete[] emudirpath; return f; }
+ }
+
+ delete[] emudirpath;
+ return NULL;
+}
+
+FILE* OpenDataFile(const char* path)
+{
+ return OpenLocalFile(path, "rb");
+}
+
+#endif
+
+
+void* Thread_Create(void (*func)())
+{
+ ThreadData* data = new ThreadData;
+ data->Func = func;
+ data->ID = SDL_CreateThread(ThreadEntry, "melonDS core thread", data);
+ return data;
+}
+
+void Thread_Free(void* thread)
+{
+ delete (ThreadData*)thread;
+}
+
+void Thread_Wait(void* thread)
+{
+ SDL_WaitThread((SDL_Thread*)((ThreadData*)thread)->ID, NULL);
+}
+
+
+void* Semaphore_Create()
+{
+ return SDL_CreateSemaphore(0);
+}
+
+void Semaphore_Free(void* sema)
+{
+ SDL_DestroySemaphore((SDL_sem*)sema);
+}
+
+void Semaphore_Reset(void* sema)
+{
+ while (SDL_SemTryWait((SDL_sem*)sema) == 0);
+}
+
+void Semaphore_Wait(void* sema)
+{
+ SDL_SemWait((SDL_sem*)sema);
+}
+
+void Semaphore_Post(void* sema)
+{
+ SDL_SemPost((SDL_sem*)sema);
+}
+
+
+void* GL_GetProcAddress(const char* proc)
+{
+ return NULL;//uiGLGetProcAddress(proc);
+}
+
+
+bool MP_Init()
+{
+ int opt_true = 1;
+ int res;
+
+#ifdef __WIN32__
+ WSADATA wsadata;
+ if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
+ {
+ return false;
+ }
+#endif // __WIN32__
+
+ MPSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ if (MPSocket < 0)
+ {
+ return false;
+ }
+
+ res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int));
+ if (res < 0)
+ {
+ closesocket(MPSocket);
+ MPSocket = INVALID_SOCKET;
+ return false;
+ }
+
+ sockaddr_t saddr;
+ saddr.sa_family = AF_INET;
+ *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK);
+ *(u16*)&saddr.sa_data[0] = htons(7064);
+ res = bind(MPSocket, &saddr, sizeof(sockaddr_t));
+ if (res < 0)
+ {
+ closesocket(MPSocket);
+ MPSocket = INVALID_SOCKET;
+ return false;
+ }
+
+ res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int));
+ if (res < 0)
+ {
+ closesocket(MPSocket);
+ MPSocket = INVALID_SOCKET;
+ return false;
+ }
+
+ MPSendAddr.sa_family = AF_INET;
+ *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST);
+ *(u16*)&MPSendAddr.sa_data[0] = htons(7064);
+
+ return true;
+}
+
+void MP_DeInit()
+{
+ if (MPSocket >= 0)
+ closesocket(MPSocket);
+
+#ifdef __WIN32__
+ WSACleanup();
+#endif // __WIN32__
+}
+
+int MP_SendPacket(u8* data, int len)
+{
+ if (MPSocket < 0)
+ return 0;
+
+ if (len > 2048-8)
+ {
+ printf("MP_SendPacket: error: packet too long (%d)\n", len);
+ return 0;
+ }
+
+ *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI
+ PacketBuffer[4] = NIFI_VER;
+ PacketBuffer[5] = 0;
+ *(u16*)&PacketBuffer[6] = htons(len);
+ memcpy(&PacketBuffer[8], data, len);
+
+ int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t));
+ if (slen < 8) return 0;
+ return slen - 8;
+}
+
+int MP_RecvPacket(u8* data, bool block)
+{
+ if (MPSocket < 0)
+ return 0;
+
+ fd_set fd;
+ struct timeval tv;
+
+ FD_ZERO(&fd);
+ FD_SET(MPSocket, &fd);
+ tv.tv_sec = 0;
+ tv.tv_usec = block ? 5000 : 0;
+
+ if (!select(MPSocket+1, &fd, 0, 0, &tv))
+ {
+ return 0;
+ }
+
+ sockaddr_t fromAddr;
+ socklen_t fromLen = sizeof(sockaddr_t);
+ int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen);
+ if (rlen < 8+24)
+ {
+ return 0;
+ }
+ rlen -= 8;
+
+ if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E)
+ {
+ return 0;
+ }
+
+ if (PacketBuffer[4] != NIFI_VER)
+ {
+ return 0;
+ }
+
+ if (ntohs(*(u16*)&PacketBuffer[6]) != rlen)
+ {
+ return 0;
+ }
+
+ memcpy(data, &PacketBuffer[8], rlen);
+ return rlen;
+}
+
+
+
+bool LAN_Init()
+{
+ /*if (Config::DirectLAN)
+ {
+ if (!LAN_PCap::Init(true))
+ return false;
+ }
+ else
+ {
+ if (!LAN_Socket::Init())
+ return false;
+ }*/
+
+ return true;
+}
+
+void LAN_DeInit()
+{
+ // checkme. blarg
+ //if (Config::DirectLAN)
+ // LAN_PCap::DeInit();
+ //else
+ // LAN_Socket::DeInit();
+ /*LAN_PCap::DeInit();
+ LAN_Socket::DeInit();*/
+}
+
+int LAN_SendPacket(u8* data, int len)
+{
+ /*if (Config::DirectLAN)
+ return LAN_PCap::SendPacket(data, len);
+ else
+ return LAN_Socket::SendPacket(data, len);*/
+ return 0;
+}
+
+int LAN_RecvPacket(u8* data)
+{
+ /*if (Config::DirectLAN)
+ return LAN_PCap::RecvPacket(data);
+ else
+ return LAN_Socket::RecvPacket(data);*/
+ return 0;
+}
+
+
+}
diff --git a/src/frontend/qt_sdl/PlatformConfig.cpp b/src/frontend/qt_sdl/PlatformConfig.cpp
new file mode 100644
index 0000000..f78b195
--- /dev/null
+++ b/src/frontend/qt_sdl/PlatformConfig.cpp
@@ -0,0 +1,151 @@
+/*
+ Copyright 2016-2020 Arisotura
+
+ This file is part of melonDS.
+
+ melonDS is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "PlatformConfig.h"
+
+namespace Config
+{
+
+int KeyMapping[12];
+int JoyMapping[12];
+
+int HKKeyMapping[HK_MAX];
+int HKJoyMapping[HK_MAX];
+
+int JoystickID;
+
+int WindowWidth;
+int WindowHeight;
+int WindowMaximized;
+
+int ScreenRotation;
+int ScreenGap;
+int ScreenLayout;
+int ScreenSizing;
+int ScreenFilter;
+
+int ScreenUseGL;
+int ScreenVSync;
+int ScreenRatio;
+
+int LimitFPS;
+int AudioSync;
+int ShowOSD;
+
+int DirectBoot;
+
+int SocketBindAnyAddr;
+char LANDevice[128];
+int DirectLAN;
+
+int SavestateRelocSRAM;
+
+int AudioVolume;
+int MicInputType;
+char MicWavPath[512];
+
+char LastROMFolder[512];
+
+
+ConfigEntry PlatformConfigFile[] =
+{
+ {"Key_A", 0, &KeyMapping[0], 32, NULL, 0},
+ {"Key_B", 0, &KeyMapping[1], 31, NULL, 0},
+ {"Key_Select", 0, &KeyMapping[2], 57, NULL, 0},
+ {"Key_Start", 0, &KeyMapping[3], 28, NULL, 0},
+ {"Key_Right", 0, &KeyMapping[4], 333, NULL, 0},
+ {"Key_Left", 0, &KeyMapping[5], 331, NULL, 0},
+ {"Key_Up", 0, &KeyMapping[6], 328, NULL, 0},
+ {"Key_Down", 0, &KeyMapping[7], 336, NULL, 0},
+ {"Key_R", 0, &KeyMapping[8], 54, NULL, 0},
+ {"Key_L", 0, &KeyMapping[9], 86, NULL, 0},
+ {"Key_X", 0, &KeyMapping[10], 17, NULL, 0},
+ {"Key_Y", 0, &KeyMapping[11], 30, NULL, 0},
+
+ {"Joy_A", 0, &JoyMapping[0], -1, NULL, 0},
+ {"Joy_B", 0, &JoyMapping[1], -1, NULL, 0},
+ {"Joy_Select", 0, &JoyMapping[2], -1, NULL, 0},
+ {"Joy_Start", 0, &JoyMapping[3], -1, NULL, 0},
+ {"Joy_Right", 0, &JoyMapping[4], -1, NULL, 0},
+ {"Joy_Left", 0, &JoyMapping[5], -1, NULL, 0},
+ {"Joy_Up", 0, &JoyMapping[6], -1, NULL, 0},
+ {"Joy_Down", 0, &JoyMapping[7], -1, NULL, 0},
+ {"Joy_R", 0, &JoyMapping[8], -1, NULL, 0},
+ {"Joy_L", 0, &JoyMapping[9], -1, NULL, 0},
+ {"Joy_X", 0, &JoyMapping[10], -1, NULL, 0},
+ {"Joy_Y", 0, &JoyMapping[11], -1, NULL, 0},
+
+ {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], 0x0D, NULL, 0},
+ {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], 0x35, NULL, 0},
+ {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, NULL, 0},
+ {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, NULL, 0},
+ {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], 0x0F, NULL, 0},
+ {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, NULL, 0},
+ {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], 0x4B, NULL, 0},
+ {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], 0x4D, NULL, 0},
+
+ {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, NULL, 0},
+ {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, NULL, 0},
+ {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, NULL, 0},
+ {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, NULL, 0},
+ {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, NULL, 0},
+ {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, NULL, 0},
+ {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, NULL, 0},
+ {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, NULL, 0},
+
+ {"JoystickID", 0, &JoystickID, 0, NULL, 0},
+
+ {"WindowWidth", 0, &WindowWidth, 256, NULL, 0},
+ {"WindowHeight", 0, &WindowHeight, 384, NULL, 0},
+ {"WindowMax", 0, &WindowMaximized, 0, NULL, 0},
+
+ {"ScreenRotation", 0, &ScreenRotation, 0, NULL, 0},
+ {"ScreenGap", 0, &ScreenGap, 0, NULL, 0},
+ {"ScreenLayout", 0, &ScreenLayout, 0, NULL, 0},
+ {"ScreenSizing", 0, &ScreenSizing, 0, NULL, 0},
+ {"ScreenFilter", 0, &ScreenFilter, 1, NULL, 0},
+
+ {"ScreenUseGL", 0, &ScreenUseGL, 1, NULL, 0},
+ {"ScreenVSync", 0, &ScreenVSync, 0, NULL, 0},
+ {"ScreenRatio", 0, &ScreenRatio, 0, NULL, 0},
+
+ {"LimitFPS", 0, &LimitFPS, 0, NULL, 0},
+ {"AudioSync", 0, &AudioSync, 1, NULL, 0},
+ {"ShowOSD", 0, &ShowOSD, 1, NULL, 0},
+
+ {"DirectBoot", 0, &DirectBoot, 1, NULL, 0},
+
+ {"SockBindAnyAddr", 0, &SocketBindAnyAddr, 0, NULL, 0},
+ {"LANDevice", 1, LANDevice, 0, "", 127},
+ {"DirectLAN", 0, &DirectLAN, 0, NULL, 0},
+
+ {"SavStaRelocSRAM", 0, &SavestateRelocSRAM, 0, NULL, 0},
+
+ {"AudioVolume", 0, &AudioVolume, 256, NULL, 0},
+ {"MicInputType", 0, &MicInputType, 1, NULL, 0},
+ {"MicWavPath", 1, MicWavPath, 0, "", 511},
+
+ {"LastROMFolder", 1, LastROMFolder, 0, "", 511},
+
+ {"", -1, NULL, 0, NULL, 0}
+};
+
+}
diff --git a/src/frontend/qt_sdl/PlatformConfig.h b/src/frontend/qt_sdl/PlatformConfig.h
new file mode 100644
index 0000000..d0f765b
--- /dev/null
+++ b/src/frontend/qt_sdl/PlatformConfig.h
@@ -0,0 +1,82 @@
+/*
+ Copyright 2016-2020 Arisotura
+
+ 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 PLATFORMCONFIG_H
+#define PLATFORMCONFIG_H
+
+#include "Config.h"
+
+enum
+{
+ HK_Lid = 0,
+ HK_Mic,
+ HK_Pause,
+ HK_Reset,
+ HK_FastForward,
+ HK_FastForwardToggle,
+ HK_SolarSensorDecrease,
+ HK_SolarSensorIncrease,
+ HK_MAX
+};
+
+namespace Config
+{
+
+extern int KeyMapping[12];
+extern int JoyMapping[12];
+
+extern int HKKeyMapping[HK_MAX];
+extern int HKJoyMapping[HK_MAX];
+
+extern int JoystickID;
+
+extern int WindowWidth;
+extern int WindowHeight;
+extern int WindowMaximized;
+
+extern int ScreenRotation;
+extern int ScreenGap;
+extern int ScreenLayout;
+extern int ScreenSizing;
+extern int ScreenFilter;
+
+extern int ScreenUseGL;
+extern int ScreenVSync;
+extern int ScreenRatio;
+
+extern int LimitFPS;
+extern int AudioSync;
+extern int ShowOSD;
+
+extern int DirectBoot;
+
+extern int SocketBindAnyAddr;
+extern char LANDevice[128];
+extern int DirectLAN;
+
+extern int SavestateRelocSRAM;
+
+extern int AudioVolume;
+extern int MicInputType;
+extern char MicWavPath[512];
+
+extern char LastROMFolder[512];
+
+}
+
+#endif // PLATFORMCONFIG_H
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index 0eb84a5..9192685 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -22,20 +22,364 @@
#include <string.h>
#include <QApplication>
-#include <QMainWindow>
+#include <QMessageBox>
+#include <QMenuBar>
+#include <QFileDialog>
+#include <QPaintEvent>
+#include <QPainter>
+
+#include <SDL2/SDL.h>
#include "main.h"
-#include "../../version.h"
+#include "types.h"
+#include "version.h"
+
+#include "NDS.h"
+#include "GBACart.h"
+#include "GPU.h"
+#include "SPU.h"
+#include "Wifi.h"
+#include "Platform.h"
+#include "Config.h"
+
+#include "Savestate.h"
+
+
+char* EmuDirectory;
+
+bool RunningSomething;
+char ROMPath[2][1024];
+char SRAMPath[2][1024];
+char PrevSRAMPath[2][1024]; // for savestate 'undo load'
+
+bool SavestateLoaded;
+
+MainWindow* mainWindow;
+EmuThread* emuThread;
+
+
+EmuThread::EmuThread(QObject* parent) : QThread(parent)
+{
+ EmuStatus = 0;
+ EmuRunning = 2;
+}
+
+void EmuThread::run()
+{
+ NDS::Init();
+
+ /*MainScreenPos[0] = 0;
+ MainScreenPos[1] = 0;
+ MainScreenPos[2] = 0;
+ AutoScreenSizing = 0;*/
+
+ /*if (Screen_UseGL)
+ {
+ uiGLMakeContextCurrent(GLContext);
+ GPU3D::InitRenderer(true);
+ uiGLMakeContextCurrent(NULL);
+ }
+ else*/
+ {
+ GPU3D::InitRenderer(false);
+ }
+
+ /*Touching = false;
+ KeyInputMask = 0xFFF;
+ JoyInputMask = 0xFFF;
+ KeyHotkeyMask = 0;
+ JoyHotkeyMask = 0;
+ HotkeyMask = 0;
+ LastHotkeyMask = 0;
+ LidStatus = false;*/
+
+ u32 nframes = 0;
+ u32 starttick = SDL_GetTicks();
+ u32 lasttick = starttick;
+ u32 lastmeasuretick = lasttick;
+ u32 fpslimitcount = 0;
+ u64 perfcount = SDL_GetPerformanceCounter();
+ u64 perffreq = SDL_GetPerformanceFrequency();
+ float samplesleft = 0;
+ u32 nsamples = 0;
+
+ char melontitle[100];
+ SDL_mutex* titlemutex = SDL_CreateMutex();
+ void* titledata[2] = {melontitle, titlemutex};
+printf("emu thread start: %d\n", EmuRunning);
+ while (EmuRunning != 0)
+ {
+ /*ProcessInput();
+
+ if (HotkeyPressed(HK_FastForwardToggle))
+ {
+ Config::LimitFPS = !Config::LimitFPS;
+ uiQueueMain(UpdateFPSLimit, NULL);
+ }
+ // TODO: similar hotkeys for video/audio sync?
+
+ if (HotkeyPressed(HK_Pause)) uiQueueMain(TogglePause, NULL);
+ if (HotkeyPressed(HK_Reset)) uiQueueMain(Reset, NULL);
+
+ if (GBACart::CartInserted && GBACart::HasSolarSensor)
+ {
+ if (HotkeyPressed(HK_SolarSensorDecrease))
+ {
+ if (GBACart_SolarSensor::LightLevel > 0) GBACart_SolarSensor::LightLevel--;
+ char msg[64];
+ sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
+ OSD::AddMessage(0, msg);
+ }
+ if (HotkeyPressed(HK_SolarSensorIncrease))
+ {
+ if (GBACart_SolarSensor::LightLevel < 10) GBACart_SolarSensor::LightLevel++;
+ char msg[64];
+ sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
+ OSD::AddMessage(0, msg);
+ }
+ }*/
+
+ if (EmuRunning == 1)
+ {
+ EmuStatus = 1;
+
+ // process input and hotkeys
+ NDS::SetKeyMask(0xFFF);
+ /*NDS::SetKeyMask(KeyInputMask & JoyInputMask);
+
+ if (HotkeyPressed(HK_Lid))
+ {
+ LidStatus = !LidStatus;
+ NDS::SetLidClosed(LidStatus);
+ OSD::AddMessage(0, LidStatus ? "Lid closed" : "Lid opened");
+ }*/
+
+ // microphone input
+ /*FeedMicInput();
+
+ if (Screen_UseGL)
+ {
+ uiGLBegin(GLContext);
+ uiGLMakeContextCurrent(GLContext);
+ }*/
+
+ // auto screen layout
+ /*{
+ MainScreenPos[2] = MainScreenPos[1];
+ MainScreenPos[1] = MainScreenPos[0];
+ MainScreenPos[0] = NDS::PowerControl9 >> 15;
+
+ int guess;
+ if (MainScreenPos[0] == MainScreenPos[2] &&
+ MainScreenPos[0] != MainScreenPos[1])
+ {
+ // constant flickering, likely displaying 3D on both screens
+ // TODO: when both screens are used for 2D only...???
+ guess = 0;
+ }
+ else
+ {
+ if (MainScreenPos[0] == 1)
+ guess = 1;
+ else
+ guess = 2;
+ }
+
+ if (guess != AutoScreenSizing)
+ {
+ AutoScreenSizing = guess;
+ SetupScreenRects(WindowWidth, WindowHeight);
+ }
+ }*/
+
+ // emulate
+ u32 nlines = NDS::RunFrame();
+
+#ifdef MELONCAP
+ MelonCap::Update();
+#endif // MELONCAP
+
+ if (EmuRunning == 0) break;
+
+ /*if (Screen_UseGL)
+ {
+ GLScreen_DrawScreen();
+ uiGLEnd(GLContext);
+ }
+ uiAreaQueueRedrawAll(MainDrawArea);*/
+
+ /*bool fastforward = HotkeyDown(HK_FastForward);
+
+ if (Config::AudioSync && !fastforward)
+ {
+ SDL_LockMutex(AudioSyncLock);
+ while (SPU::GetOutputSize() > 1024)
+ {
+ int ret = SDL_CondWaitTimeout(AudioSync, AudioSyncLock, 500);
+ if (ret == SDL_MUTEX_TIMEDOUT) break;
+ }
+ SDL_UnlockMutex(AudioSyncLock);
+ }
+
+ float framerate = (1000.0f * nlines) / (60.0f * 263.0f);
+
+ {
+ u32 curtick = SDL_GetTicks();
+ u32 delay = curtick - lasttick;
+
+ bool limitfps = Config::LimitFPS && !fastforward;
+ if (limitfps)
+ {
+ float wantedtickF = starttick + (framerate * (fpslimitcount+1));
+ u32 wantedtick = (u32)ceil(wantedtickF);
+ if (curtick < wantedtick) SDL_Delay(wantedtick - curtick);
+
+ lasttick = SDL_GetTicks();
+ fpslimitcount++;
+ if ((abs(wantedtickF - (float)wantedtick) < 0.001312) || (fpslimitcount > 60))
+ {
+ fpslimitcount = 0;
+ nsamples = 0;
+ starttick = lasttick;
+ }
+ }
+ else
+ {
+ if (delay < 1) SDL_Delay(1);
+ lasttick = SDL_GetTicks();
+ }
+ }
+
+ nframes++;
+ if (nframes >= 30)
+ {
+ u32 tick = SDL_GetTicks();
+ u32 diff = tick - lastmeasuretick;
+ lastmeasuretick = tick;
+
+ u32 fps;
+ if (diff < 1) fps = 77777;
+ else fps = (nframes * 1000) / diff;
+ nframes = 0;
+
+ float fpstarget;
+ if (framerate < 1) fpstarget = 999;
+ else fpstarget = 1000.0f/framerate;
+
+ SDL_LockMutex(titlemutex);
+ sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ SDL_UnlockMutex(titlemutex);
+ uiQueueMain(UpdateWindowTitle, titledata);
+ }*/
+ }
+ else
+ {
+ // paused
+ nframes = 0;
+ lasttick = SDL_GetTicks();
+ starttick = lasttick;
+ lastmeasuretick = lasttick;
+ fpslimitcount = 0;
+
+ if (EmuRunning == 2)
+ {
+ /*if (Screen_UseGL)
+ {
+ uiGLBegin(GLContext);
+ uiGLMakeContextCurrent(GLContext);
+ GLScreen_DrawScreen();
+ uiGLEnd(GLContext);
+ }
+ uiAreaQueueRedrawAll(MainDrawArea);*/
+ }
+
+ //if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+
+ EmuStatus = EmuRunning;
+
+ SDL_Delay(100);
+ }
+ printf("ran iteration: status=%d run=%d\n", EmuStatus, EmuRunning);
+ }
+
+ EmuStatus = 0;
+
+ SDL_DestroyMutex(titlemutex);
+
+ //if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
+
+ NDS::DeInit();
+ //Platform::LAN_DeInit();
+
+ /*if (Screen_UseGL)
+ {
+ OSD::DeInit(true);
+ GLScreen_DeInit();
+ }
+ else
+ OSD::DeInit(false);*/
+
+ //if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+}
+
+void EmuThread::emuRun()
+{
+ EmuRunning = 1;
+}
+
+void EmuThread::emuPause(bool refresh)
+{
+ int status = refresh ? 2:3;
+ PrevEmuStatus = EmuRunning;
+ EmuRunning = status;printf("emuPause %d -> %d %d\n", PrevEmuStatus, EmuRunning, EmuStatus);
+ while (EmuStatus != status);printf("wait done\n");
+}
+
+void EmuThread::emuUnpause()
+{
+ EmuRunning = PrevEmuStatus;
+}
+
+void EmuThread::emuStop()
+{
+ EmuRunning = 0;
+}
+
+
+MainWindowPanel::MainWindowPanel(QWidget* parent) : QWidget(parent)
+{
+}
+
+MainWindowPanel::~MainWindowPanel()
+{
+}
+
+void MainWindowPanel::paintEvent(QPaintEvent* event)
+{
+ QPainter painter(this);
+
+ //painter.
+}
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
setWindowTitle("melonDS - assfucking Qt version");
- // burp
- QWidget *centralWidget = new QWidget(this);
- setCentralWidget(centralWidget);
+ QMenuBar* menubar = new QMenuBar();
+ {
+ QMenu* menu = menubar->addMenu("File");
+ QAction* act;
+
+ act = menu->addAction("Open file...");
+ connect(act, &QAction::triggered, this, &MainWindow::onOpenFile);
+ }
+ setMenuBar(menubar);
+
+ panel = new MainWindowPanel(this);
+ setCentralWidget(panel);
+ panel->setMinimumSize(256, 384);
}
MainWindow::~MainWindow()
@@ -43,6 +387,13 @@ MainWindow::~MainWindow()
}
+void MainWindow::onOpenFile()
+{
+ QString filename = QFileDialog::getOpenFileName(this, "Open ROM", "", "DS ROMs (*.nds *.srl);;Any file (*.*)");
+ printf("fark: %p %d %s\n", filename, filename.isEmpty(), filename.toStdString().c_str());
+}
+
+
int main(int argc, char** argv)
{
srand(time(NULL));
@@ -50,12 +401,212 @@ int main(int argc, char** argv)
printf("melonDS " MELONDS_VERSION "\n");
printf(MELONDS_URL "\n");
+#if defined(__WIN32__) || defined(UNIX_PORTABLE)
+ if (argc > 0 && strlen(argv[0]) > 0)
+ {
+ int len = strlen(argv[0]);
+ while (len > 0)
+ {
+ if (argv[0][len] == '/') break;
+ if (argv[0][len] == '\\') break;
+ len--;
+ }
+ if (len > 0)
+ {
+ EmuDirectory = new char[len+1];
+ strncpy(EmuDirectory, argv[0], len);
+ EmuDirectory[len] = '\0';
+ }
+ else
+ {
+ EmuDirectory = new char[2];
+ strcpy(EmuDirectory, ".");
+ }
+ }
+ else
+ {
+ EmuDirectory = new char[2];
+ strcpy(EmuDirectory, ".");
+ }
+#else
+ const char* confdir = g_get_user_config_dir();
+ const char* confname = "/melonDS";
+ EmuDirectory = new char[strlen(confdir) + strlen(confname) + 1];
+ strcat(EmuDirectory, confdir);
+ strcat(EmuDirectory, confname);
+#endif
+
QApplication melon(argc, argv);
- MainWindow win;
- win.show();
+ // http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+
+ if (SDL_Init(SDL_INIT_HAPTIC) < 0)
+ {
+ printf("SDL couldn't init rumble\n");
+ }
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0)
+ {
+ QMessageBox::critical(NULL, "melonDS", "SDL shat itself :(");
+ return 1;
+ }
+
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ Config::Load();
+
+ //if (Config::AudioVolume < 0) Config::AudioVolume = 0;
+ //else if (Config::AudioVolume > 256) Config::AudioVolume = 256;
- return melon.exec();
+ // TODO: those should be checked before running anything
+ // (as to let the user specify their own BIOS/firmware path etc)
+#if 0
+ if (!Platform::LocalFileExists("bios7.bin") ||
+ !Platform::LocalFileExists("bios9.bin") ||
+ !Platform::LocalFileExists("firmware.bin"))
+ {
+#if defined(__WIN32__) || defined(UNIX_PORTABLE)
+ const char* locationName = "the directory you run melonDS from";
+#else
+ char* locationName = EmuDirectory;
+#endif
+ char msgboxtext[512];
+ sprintf(msgboxtext,
+ "One or more of the following required files don't exist or couldn't be accessed:\n\n"
+ "bios7.bin -- ARM7 BIOS\n"
+ "bios9.bin -- ARM9 BIOS\n"
+ "firmware.bin -- firmware image\n\n"
+ "Dump the files from your DS and place them in %s.\n"
+ "Make sure that the files can be accessed.",
+ locationName
+ );
+
+ uiMsgBoxError(NULL, "BIOS/Firmware not found", msgboxtext);
+
+ uiUninit();
+ SDL_Quit();
+ return 0;
+ }
+ if (!Platform::LocalFileExists("firmware.bin.bak"))
+ {
+ // verify the firmware
+ //
+ // there are dumps of an old hacked firmware floating around on the internet
+ // and those are problematic
+ // the hack predates WFC, and, due to this, any game that alters the WFC
+ // access point data will brick that firmware due to it having critical
+ // data in the same area. it has the same problem on hardware.
+ //
+ // but this should help stop users from reporting that issue over and over
+ // again, when the issue is not from melonDS but from their firmware dump.
+ //
+ // I don't know about all the firmware hacks in existence, but the one I
+ // looked at has 0x180 bytes from the header repeated at 0x3FC80, but
+ // bytes 0x0C-0x14 are different.
+
+ FILE* f = Platform::OpenLocalFile("firmware.bin", "rb");
+ u8 chk1[0x180], chk2[0x180];
+
+ fseek(f, 0, SEEK_SET);
+ fread(chk1, 1, 0x180, f);
+ fseek(f, -0x380, SEEK_END);
+ fread(chk2, 1, 0x180, f);
+
+ memset(&chk1[0x0C], 0, 8);
+ memset(&chk2[0x0C], 0, 8);
+
+ fclose(f);
+
+ if (!memcmp(chk1, chk2, 0x180))
+ {
+ uiMsgBoxError(NULL,
+ "Problematic firmware dump",
+ "You are using an old hacked firmware dump.\n"
+ "Firmware boot will stop working if you run any game that alters WFC settings.\n\n"
+ "Note that the issue is not from melonDS, it would also happen on an actual DS.");
+ }
+ }
+ {
+ const char* romlist_missing = "Save memory type detection will not work correctly.\n\n"
+ "You should use the latest version of romlist.bin (provided in melonDS release packages).";
+#if !defined(UNIX_PORTABLE) && !defined(__WIN32__)
+ std::string missingstr = std::string(romlist_missing) +
+ "\n\nThe ROM list should be placed in " + g_get_user_data_dir() + "/melonds/, otherwise "
+ "melonDS will search for it in the current working directory.";
+ const char* romlist_missing_text = missingstr.c_str();
+#else
+ const char* romlist_missing_text = romlist_missing;
+#endif
+
+ FILE* f = Platform::OpenDataFile("romlist.bin");
+ if (f)
+ {
+ u32 data;
+ fread(&data, 4, 1, f);
+ fclose(f);
+
+ if ((data >> 24) == 0) // old CRC-based list
+ {
+ uiMsgBoxError(NULL, "Your version of romlist.bin is outdated.", romlist_missing_text);
+ }
+ }
+ else
+ {
+ uiMsgBoxError(NULL, "romlist.bin not found.", romlist_missing_text);
+ }
+ }
+#endif
+
+ mainWindow = new MainWindow();
+ mainWindow->show();
+
+ emuThread = new EmuThread();
+ emuThread->start();
+ emuThread->emuPause(true);
+
+ if (argc > 1)
+ {
+ char* file = argv[1];
+ char* ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl"))
+ {
+ strncpy(ROMPath[0], file, 1023);
+ ROMPath[0][1023] = '\0';
+
+ //SetupSRAMPath(0);
+
+ //if (NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot))
+ // Run();
+ }
+
+ if (argc > 2)
+ {
+ file = argv[2];
+ ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "gba"))
+ {
+ strncpy(ROMPath[1], file, 1023);
+ ROMPath[1][1023] = '\0';
+
+ //SetupSRAMPath(1);
+
+ //NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]);
+ }
+ }
+ }
+
+ int ret = melon.exec();
+printf("melon over\n");
+ emuThread->emuStop();printf("STOP\n");
+ emuThread->wait();printf("farked\n");
+
+ Config::Save();
+
+ SDL_Quit();
+ delete[] EmuDirectory;
+ return ret;
}
#ifdef __WIN32__
@@ -71,6 +622,7 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho
char** argv = new char*[argc];
for (int i = 0; i < argc; i++)
{
+ if (!argv_w) { argv[i] = nullarg; continue; }
int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL);
if (len < 1) { argv[i] = nullarg; continue; }
argv[i] = new char[len];
@@ -78,6 +630,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho
if (res != len) { delete[] argv[i]; argv[i] = nullarg; }
}
+ if (argv_w) LocalFree(argv_w);
+
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
freopen("CONOUT$", "w", stdout);
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index ad795ef..92b1846 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -19,8 +19,45 @@
#ifndef MAIN_H
#define MAIN_H
+#include <QThread>
+#include <QWidget>
#include <QMainWindow>
+
+class EmuThread : public QThread
+{
+ Q_OBJECT
+ void run() override;
+
+public:
+ explicit EmuThread(QObject* parent = nullptr);
+
+ // to be called from the UI thread
+ void emuRun();
+ void emuPause(bool refresh);
+ void emuUnpause();
+ void emuStop();
+
+private:
+ volatile int EmuStatus;
+ int PrevEmuStatus;
+ int EmuRunning;
+};
+
+
+class MainWindowPanel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindowPanel(QWidget* parent);
+ ~MainWindowPanel();
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+};
+
+
class MainWindow : public QMainWindow
{
Q_OBJECT
@@ -29,8 +66,11 @@ public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
+private slots:
+ void onOpenFile();
+
private:
- // private shit goes here
+ MainWindowPanel* panel;
};
#endif // MAIN_H