2012-12-13 06:38:33 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2012 Sacha Refshauge
|
|
|
|
*
|
|
|
|
*/
|
2013-10-29 01:23:42 +00:00
|
|
|
// Qt 4.7+ / 5.0+ implementation of the framework.
|
2016-10-12 10:32:20 +00:00
|
|
|
// Currently supports: Android, Linux, Windows, Mac OSX
|
2012-12-13 06:38:33 +00:00
|
|
|
|
2013-10-18 19:21:44 +00:00
|
|
|
#include <QApplication>
|
2019-05-15 21:31:05 +00:00
|
|
|
#include <QClipboard>
|
2012-12-13 06:38:33 +00:00
|
|
|
#include <QDesktopWidget>
|
|
|
|
#include <QDesktopServices>
|
2019-02-17 15:30:40 +00:00
|
|
|
#include <QDir>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QFileDialog>
|
2013-10-18 19:21:44 +00:00
|
|
|
#include <QLocale>
|
2018-06-09 23:21:28 +00:00
|
|
|
#include <QScreen>
|
2013-10-22 04:52:14 +00:00
|
|
|
#include <QThread>
|
2019-02-17 15:30:40 +00:00
|
|
|
#include <QUrl>
|
2012-12-13 06:38:33 +00:00
|
|
|
|
2017-12-07 16:02:00 +00:00
|
|
|
#include "ext/glslang/glslang/Public/ShaderLang.h"
|
|
|
|
|
2014-06-25 05:40:28 +00:00
|
|
|
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
|
2014-06-03 00:02:24 +00:00
|
|
|
#include <QStandardPaths>
|
2014-07-25 04:41:23 +00:00
|
|
|
#ifdef QT_HAS_SYSTEMINFO
|
2014-07-22 06:03:42 +00:00
|
|
|
#include <QScreenSaver>
|
2014-06-03 00:02:24 +00:00
|
|
|
#endif
|
2014-07-25 04:41:23 +00:00
|
|
|
#endif
|
2014-06-03 00:02:24 +00:00
|
|
|
|
2014-12-18 22:57:59 +00:00
|
|
|
#ifdef SDL
|
2013-11-22 00:59:18 +00:00
|
|
|
#include "SDL/SDLJoystick.h"
|
2014-06-25 03:00:42 +00:00
|
|
|
#include "SDL_audio.h"
|
2013-11-22 00:59:18 +00:00
|
|
|
#endif
|
2012-12-13 06:38:33 +00:00
|
|
|
#include "QtMain.h"
|
2016-08-07 03:19:50 +00:00
|
|
|
#include "gfx_es2/gpu_features.h"
|
2019-02-17 15:48:32 +00:00
|
|
|
#include "i18n/i18n.h"
|
2014-07-16 13:50:49 +00:00
|
|
|
#include "math/math_util.h"
|
2018-01-31 11:05:18 +00:00
|
|
|
#include "thread/threadutil.h"
|
2019-01-06 22:46:43 +00:00
|
|
|
#include "util/text/utf8.h"
|
2018-06-17 01:42:31 +00:00
|
|
|
#include "Core/Config.h"
|
|
|
|
#include "Core/ConfigValues.h"
|
2020-01-25 08:06:00 +00:00
|
|
|
#include "Core/HW/Camera.h"
|
2012-12-13 06:38:33 +00:00
|
|
|
|
2014-09-23 20:30:50 +00:00
|
|
|
#include <string.h>
|
2014-02-12 10:54:53 +00:00
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
MainUI *emugl = nullptr;
|
2020-01-05 17:04:07 +00:00
|
|
|
static float refreshRate = 60.f;
|
2019-02-17 15:30:40 +00:00
|
|
|
static int browseFileEvent = -1;
|
2019-02-17 15:48:32 +00:00
|
|
|
static int browseFolderEvent = -1;
|
2020-01-25 08:06:00 +00:00
|
|
|
QTCamera *qtcamera = nullptr;
|
2013-04-01 12:42:40 +00:00
|
|
|
|
2014-12-18 22:57:59 +00:00
|
|
|
#ifdef SDL
|
2019-09-15 20:23:48 +00:00
|
|
|
static SDL_AudioDeviceID audioDev = 0;
|
|
|
|
|
2014-06-25 03:00:42 +00:00
|
|
|
extern void mixaudio(void *userdata, Uint8 *stream, int len) {
|
|
|
|
NativeMix((short *)stream, len / 4);
|
|
|
|
}
|
2019-09-15 20:23:48 +00:00
|
|
|
|
|
|
|
static void InitSDLAudioDevice() {
|
|
|
|
SDL_AudioSpec fmt, ret_fmt;
|
|
|
|
memset(&fmt, 0, sizeof(fmt));
|
|
|
|
fmt.freq = 44100;
|
|
|
|
fmt.format = AUDIO_S16;
|
|
|
|
fmt.channels = 2;
|
|
|
|
fmt.samples = 2048;
|
|
|
|
fmt.callback = &mixaudio;
|
|
|
|
fmt.userdata = nullptr;
|
|
|
|
|
|
|
|
audioDev = 0;
|
|
|
|
if (!g_Config.sAudioDevice.empty()) {
|
|
|
|
audioDev = SDL_OpenAudioDevice(g_Config.sAudioDevice.c_str(), 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
|
|
|
if (audioDev <= 0) {
|
|
|
|
WLOG("Failed to open preferred audio device %s", g_Config.sAudioDevice.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (audioDev <= 0) {
|
|
|
|
audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
|
|
|
}
|
|
|
|
if (audioDev <= 0) {
|
|
|
|
ELOG("Failed to open audio: %s", SDL_GetError());
|
|
|
|
} else {
|
|
|
|
if (ret_fmt.samples != fmt.samples) // Notify, but still use it
|
|
|
|
ELOG("Output audio samples: %d (requested: %d)", ret_fmt.samples, fmt.samples);
|
|
|
|
if (ret_fmt.freq != fmt.freq || ret_fmt.format != fmt.format || ret_fmt.channels != fmt.channels) {
|
|
|
|
ELOG("Sound buffer format does not match requested format.");
|
|
|
|
ELOG("Output audio freq: %d (requested: %d)", ret_fmt.freq, fmt.freq);
|
|
|
|
ELOG("Output audio format: %d (requested: %d)", ret_fmt.format, fmt.format);
|
|
|
|
ELOG("Output audio channels: %d (requested: %d)", ret_fmt.channels, fmt.channels);
|
|
|
|
ELOG("Provided output format does not match requirement, turning audio off");
|
|
|
|
SDL_CloseAudioDevice(audioDev);
|
|
|
|
}
|
|
|
|
SDL_PauseAudioDevice(audioDev, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void StopSDLAudioDevice() {
|
|
|
|
if (audioDev > 0) {
|
|
|
|
SDL_PauseAudioDevice(audioDev, 1);
|
|
|
|
SDL_CloseAudioDevice(audioDev);
|
|
|
|
}
|
|
|
|
}
|
2014-06-25 03:00:42 +00:00
|
|
|
#endif
|
|
|
|
|
2013-09-04 09:28:19 +00:00
|
|
|
std::string System_GetProperty(SystemProperty prop) {
|
|
|
|
switch (prop) {
|
|
|
|
case SYSPROP_NAME:
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(__ANDROID__)
|
2014-06-03 00:02:24 +00:00
|
|
|
return "Qt:Android";
|
2013-10-19 19:03:31 +00:00
|
|
|
#elif defined(Q_OS_LINUX)
|
2013-09-04 09:28:19 +00:00
|
|
|
return "Qt:Linux";
|
2013-08-18 07:28:34 +00:00
|
|
|
#elif defined(_WIN32)
|
2013-09-04 09:28:19 +00:00
|
|
|
return "Qt:Windows";
|
2013-12-08 10:14:49 +00:00
|
|
|
#elif defined(Q_OS_MAC)
|
2020-01-24 17:39:37 +00:00
|
|
|
return "Qt:macOS";
|
2013-08-18 07:28:34 +00:00
|
|
|
#else
|
2013-09-04 09:28:19 +00:00
|
|
|
return "Qt";
|
2013-08-18 07:28:34 +00:00
|
|
|
#endif
|
2013-10-17 08:41:00 +00:00
|
|
|
case SYSPROP_LANGREGION:
|
|
|
|
return QLocale::system().name().toStdString();
|
2019-05-15 21:31:05 +00:00
|
|
|
case SYSPROP_CLIPBOARD_TEXT:
|
|
|
|
return QApplication::clipboard()->text().toStdString();
|
2019-09-15 20:23:48 +00:00
|
|
|
#if defined(SDL)
|
|
|
|
case SYSPROP_AUDIO_DEVICE_LIST:
|
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {
|
|
|
|
const char *name = SDL_GetAudioDeviceName(i, 0);
|
|
|
|
if (!name) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
result = name;
|
|
|
|
} else {
|
|
|
|
result.append(1, '\0');
|
|
|
|
result.append(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
#endif
|
2013-09-04 09:28:19 +00:00
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
2013-08-18 07:28:34 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 13:17:23 +00:00
|
|
|
int System_GetPropertyInt(SystemProperty prop) {
|
2017-04-30 00:35:12 +00:00
|
|
|
switch (prop) {
|
|
|
|
case SYSPROP_AUDIO_SAMPLE_RATE:
|
|
|
|
return 44100;
|
2015-04-03 09:13:38 +00:00
|
|
|
case SYSPROP_DEVICE_TYPE:
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(__ANDROID__)
|
2015-04-03 09:13:38 +00:00
|
|
|
return DEVICE_TYPE_MOBILE;
|
|
|
|
#elif defined(Q_OS_LINUX)
|
|
|
|
return DEVICE_TYPE_DESKTOP;
|
|
|
|
#elif defined(_WIN32)
|
|
|
|
return DEVICE_TYPE_DESKTOP;
|
|
|
|
#elif defined(Q_OS_MAC)
|
|
|
|
return DEVICE_TYPE_DESKTOP;
|
|
|
|
#else
|
|
|
|
return DEVICE_TYPE_DESKTOP;
|
|
|
|
#endif
|
2020-01-05 07:46:27 +00:00
|
|
|
case SYSPROP_DISPLAY_COUNT:
|
|
|
|
return QApplication::screens().size();
|
2017-04-30 00:35:12 +00:00
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 17:04:07 +00:00
|
|
|
float System_GetPropertyFloat(SystemProperty prop) {
|
2020-01-05 07:46:27 +00:00
|
|
|
switch (prop) {
|
2020-01-05 17:04:07 +00:00
|
|
|
case SYSPROP_DISPLAY_REFRESH_RATE:
|
|
|
|
return refreshRate;
|
2020-01-05 07:46:27 +00:00
|
|
|
case SYSPROP_DISPLAY_LOGICAL_DPI:
|
|
|
|
return QApplication::primaryScreen()->logicalDotsPerInch();
|
|
|
|
case SYSPROP_DISPLAY_DPI:
|
|
|
|
return QApplication::primaryScreen()->physicalDotsPerInch();
|
|
|
|
default:
|
2020-01-05 17:04:07 +00:00
|
|
|
return -1;
|
2020-01-05 07:46:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-30 00:35:12 +00:00
|
|
|
bool System_GetPropertyBool(SystemProperty prop) {
|
|
|
|
switch (prop) {
|
2017-03-07 09:33:53 +00:00
|
|
|
case SYSPROP_HAS_BACK_BUTTON:
|
2017-04-30 00:35:12 +00:00
|
|
|
return true;
|
2019-02-17 15:30:40 +00:00
|
|
|
case SYSPROP_HAS_FILE_BROWSER:
|
|
|
|
return true;
|
2017-04-05 14:21:08 +00:00
|
|
|
case SYSPROP_APP_GOLD:
|
|
|
|
#ifdef GOLD
|
2017-04-30 00:35:12 +00:00
|
|
|
return true;
|
2017-04-05 14:21:08 +00:00
|
|
|
#else
|
2017-04-30 00:35:12 +00:00
|
|
|
return false;
|
2017-04-05 14:21:08 +00:00
|
|
|
#endif
|
|
|
|
default:
|
2017-04-30 00:35:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-01-11 13:17:23 +00:00
|
|
|
}
|
2014-07-20 10:04:22 +00:00
|
|
|
|
2013-12-04 17:12:57 +00:00
|
|
|
void System_SendMessage(const char *command, const char *parameter) {
|
2014-02-15 09:42:48 +00:00
|
|
|
if (!strcmp(command, "finish")) {
|
2014-05-15 07:47:33 +00:00
|
|
|
qApp->exit(0);
|
2019-02-17 15:30:40 +00:00
|
|
|
} else if (!strcmp(command, "browse_file")) {
|
|
|
|
QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFileEvent));
|
2019-02-17 15:48:32 +00:00
|
|
|
} else if (!strcmp(command, "browse_folder")) {
|
|
|
|
QCoreApplication::postEvent(emugl, new QEvent((QEvent::Type)browseFolderEvent));
|
2019-02-23 09:55:28 +00:00
|
|
|
} else if (!strcmp(command, "graphics_restart")) {
|
|
|
|
// Should find a way to properly restart the app.
|
|
|
|
qApp->exit(0);
|
2020-01-25 08:06:00 +00:00
|
|
|
} else if (!strcmp(command, "camera_command")) {
|
|
|
|
if (!strncmp(parameter, "startVideo", 10)) {
|
|
|
|
int width = 0, height = 0;
|
|
|
|
sscanf(parameter, "startVideo_%dx%d", &width, &height);
|
|
|
|
emit(qtcamera->onStartCamera(width, height));
|
|
|
|
} else if (!strcmp(parameter, "stopVideo")) {
|
|
|
|
emit(qtcamera->onStopCamera());
|
|
|
|
}
|
2019-05-15 21:31:05 +00:00
|
|
|
} else if (!strcmp(command, "setclipboardtext")) {
|
|
|
|
QApplication::clipboard()->setText(parameter);
|
2019-09-15 20:23:48 +00:00
|
|
|
#if defined(SDL)
|
|
|
|
} else if (!strcmp(command, "audio_resetDevice")) {
|
|
|
|
StopSDLAudioDevice();
|
|
|
|
InitSDLAudioDevice();
|
|
|
|
#endif
|
2014-02-15 09:42:48 +00:00
|
|
|
}
|
2013-12-04 17:12:57 +00:00
|
|
|
}
|
|
|
|
|
2015-12-17 21:41:50 +00:00
|
|
|
void System_AskForPermission(SystemPermission permission) {}
|
|
|
|
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
|
|
|
|
|
2020-03-09 01:59:17 +00:00
|
|
|
void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) {
|
|
|
|
QString text = emugl->InputBoxGetQString(QString::fromStdString(title), QString::fromStdString(defaultValue));
|
|
|
|
if (text.isEmpty()) {
|
2020-03-10 00:36:52 +00:00
|
|
|
NativeInputBoxReceived(cb, false, "");
|
2020-03-09 01:59:17 +00:00
|
|
|
} else {
|
2020-03-10 00:36:52 +00:00
|
|
|
NativeInputBoxReceived(cb, true, text.toStdString());
|
2020-03-09 01:59:17 +00:00
|
|
|
}
|
2013-12-08 10:14:49 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 16:49:09 +00:00
|
|
|
void Vibrate(int length_ms) {
|
|
|
|
if (length_ms == -1 || length_ms == -3)
|
|
|
|
length_ms = 50;
|
|
|
|
else if (length_ms == -2)
|
|
|
|
length_ms = 25;
|
|
|
|
}
|
|
|
|
|
2019-05-21 21:22:56 +00:00
|
|
|
void OpenDirectory(const char *path) {
|
|
|
|
// Unsupported
|
|
|
|
}
|
|
|
|
|
2012-12-13 06:38:33 +00:00
|
|
|
void LaunchBrowser(const char *url)
|
|
|
|
{
|
|
|
|
QDesktopServices::openUrl(QUrl(url));
|
|
|
|
}
|
|
|
|
|
2019-09-15 20:23:48 +00:00
|
|
|
static int mainInternal(QApplication &a) {
|
2014-06-17 13:45:04 +00:00
|
|
|
#ifdef MOBILE_DEVICE
|
2014-02-15 09:42:48 +00:00
|
|
|
emugl = new MainUI();
|
|
|
|
emugl->resize(pixel_xres, pixel_yres);
|
|
|
|
emugl->showFullScreen();
|
|
|
|
#endif
|
2014-07-16 13:50:49 +00:00
|
|
|
EnableFZ();
|
2014-02-15 09:42:48 +00:00
|
|
|
// Disable screensaver
|
2016-10-11 16:48:49 +00:00
|
|
|
#if defined(QT_HAS_SYSTEMINFO)
|
2014-07-22 06:03:42 +00:00
|
|
|
QScreenSaver ssObject(emugl);
|
|
|
|
ssObject.setScreenSaverEnabled(false);
|
2014-02-15 09:42:48 +00:00
|
|
|
#endif
|
|
|
|
|
2014-12-18 22:57:59 +00:00
|
|
|
#ifdef SDL
|
2014-06-25 03:00:42 +00:00
|
|
|
SDLJoystick joy(true);
|
2017-02-01 06:19:18 +00:00
|
|
|
joy.registerEventHandler();
|
2014-06-25 03:00:42 +00:00
|
|
|
SDL_Init(SDL_INIT_AUDIO);
|
2019-09-15 20:23:48 +00:00
|
|
|
InitSDLAudioDevice();
|
2014-06-25 03:00:42 +00:00
|
|
|
#else
|
2014-02-15 09:42:48 +00:00
|
|
|
QScopedPointer<MainAudio> audio(new MainAudio());
|
2014-08-20 17:46:00 +00:00
|
|
|
audio->run();
|
2014-06-25 03:00:42 +00:00
|
|
|
#endif
|
2019-02-17 15:30:40 +00:00
|
|
|
|
|
|
|
browseFileEvent = QEvent::registerEventType();
|
2019-02-17 15:48:32 +00:00
|
|
|
browseFolderEvent = QEvent::registerEventType();
|
2019-02-17 15:30:40 +00:00
|
|
|
|
2018-02-04 12:54:10 +00:00
|
|
|
int retval = a.exec();
|
|
|
|
delete emugl;
|
|
|
|
return retval;
|
2014-02-15 09:42:48 +00:00
|
|
|
}
|
|
|
|
|
2018-01-31 11:05:18 +00:00
|
|
|
void MainUI::EmuThreadFunc() {
|
|
|
|
setCurrentThreadName("Emu");
|
|
|
|
|
2018-02-04 12:54:10 +00:00
|
|
|
// There's no real requirement that NativeInit happen on this thread, though it can't hurt...
|
|
|
|
// We just call the update/render loop here. NativeInitGraphics should be here though.
|
|
|
|
NativeInitGraphics(graphicsContext);
|
|
|
|
|
2018-01-31 11:05:18 +00:00
|
|
|
emuThreadState = (int)EmuThreadState::RUNNING;
|
|
|
|
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
|
|
|
|
updateAccelerometer();
|
|
|
|
UpdateRunLoop();
|
|
|
|
}
|
|
|
|
emuThreadState = (int)EmuThreadState::STOPPED;
|
2018-02-07 15:43:49 +00:00
|
|
|
|
|
|
|
NativeShutdownGraphics();
|
2018-02-11 00:23:05 +00:00
|
|
|
graphicsContext->StopThread();
|
2018-01-31 11:05:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainUI::EmuThreadStart() {
|
|
|
|
emuThreadState = (int)EmuThreadState::START_REQUESTED;
|
|
|
|
emuThread = std::thread([&]() { this->EmuThreadFunc(); } );
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainUI::EmuThreadStop() {
|
|
|
|
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
|
2018-02-07 15:43:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainUI::EmuThreadJoin() {
|
2018-01-31 11:05:18 +00:00
|
|
|
emuThread.join();
|
|
|
|
emuThread = std::thread();
|
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
MainUI::MainUI(QWidget *parent)
|
|
|
|
: QGLWidget(parent) {
|
2018-01-31 11:05:18 +00:00
|
|
|
emuThreadState = (int)EmuThreadState::DISABLED;
|
|
|
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
2016-02-10 11:37:47 +00:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
2018-01-31 11:05:18 +00:00
|
|
|
setAttribute(Qt::WA_LockLandscapeOrientation);
|
2016-02-10 11:37:47 +00:00
|
|
|
#endif
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(MOBILE_DEVICE)
|
2018-01-31 11:05:18 +00:00
|
|
|
acc = new QAccelerometer(this);
|
|
|
|
acc->start();
|
2016-02-10 11:37:47 +00:00
|
|
|
#endif
|
2018-01-31 11:05:18 +00:00
|
|
|
setFocus();
|
|
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
|
|
startTimer(16);
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
MainUI::~MainUI() {
|
2018-02-04 12:54:10 +00:00
|
|
|
ILOG("MainUI::Destructor");
|
2018-01-31 17:35:48 +00:00
|
|
|
if (emuThreadState != (int)EmuThreadState::DISABLED) {
|
2018-02-04 12:54:10 +00:00
|
|
|
ILOG("EmuThreadStop");
|
2018-01-31 11:05:18 +00:00
|
|
|
EmuThreadStop();
|
2018-02-11 19:40:11 +00:00
|
|
|
while (graphicsContext->ThreadFrame()) {
|
2018-02-07 15:43:49 +00:00
|
|
|
// Need to keep eating frames to allow the EmuThread to exit correctly.
|
2018-02-11 19:40:11 +00:00
|
|
|
continue;
|
2018-02-07 15:43:49 +00:00
|
|
|
}
|
|
|
|
EmuThreadJoin();
|
2018-01-31 11:05:18 +00:00
|
|
|
}
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(MOBILE_DEVICE)
|
2018-01-31 11:05:18 +00:00
|
|
|
delete acc;
|
2016-02-10 11:37:47 +00:00
|
|
|
#endif
|
2018-01-31 11:05:18 +00:00
|
|
|
graphicsContext->Shutdown();
|
|
|
|
delete graphicsContext;
|
|
|
|
graphicsContext = nullptr;
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
QString MainUI::InputBoxGetQString(QString title, QString defaultValue) {
|
|
|
|
bool ok;
|
|
|
|
QString text = QInputDialog::getText(this, title, title, QLineEdit::Normal, defaultValue, &ok);
|
|
|
|
if (!ok)
|
|
|
|
text = QString();
|
|
|
|
return text;
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainUI::resizeGL(int w, int h) {
|
|
|
|
if (UpdateScreenScale(w, h)) {
|
|
|
|
NativeMessageReceived("gpu_resized", "");
|
|
|
|
}
|
|
|
|
xscale = w / this->width();
|
|
|
|
yscale = h / this->height();
|
|
|
|
|
|
|
|
PSP_CoreParameter().pixelWidth = pixel_xres;
|
|
|
|
PSP_CoreParameter().pixelHeight = pixel_yres;
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainUI::timerEvent(QTimerEvent *) {
|
|
|
|
updateGL();
|
|
|
|
emit newFrame();
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainUI::changeEvent(QEvent *e) {
|
|
|
|
QGLWidget::changeEvent(e);
|
|
|
|
if (e->type() == QEvent::WindowStateChange)
|
|
|
|
Core_NotifyWindowHidden(isMinimized());
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
bool MainUI::event(QEvent *e) {
|
|
|
|
TouchInput input;
|
|
|
|
QList<QTouchEvent::TouchPoint> touchPoints;
|
|
|
|
|
|
|
|
switch (e->type()) {
|
|
|
|
case QEvent::TouchBegin:
|
|
|
|
case QEvent::TouchUpdate:
|
|
|
|
case QEvent::TouchEnd:
|
|
|
|
touchPoints = static_cast<QTouchEvent *>(e)->touchPoints();
|
|
|
|
foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) {
|
|
|
|
switch (touchPoint.state()) {
|
|
|
|
case Qt::TouchPointStationary:
|
|
|
|
break;
|
|
|
|
case Qt::TouchPointPressed:
|
|
|
|
case Qt::TouchPointReleased:
|
|
|
|
input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale;
|
|
|
|
input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale;
|
|
|
|
input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TOUCH_DOWN : TOUCH_UP;
|
|
|
|
input.id = touchPoint.id();
|
|
|
|
NativeTouch(input);
|
|
|
|
break;
|
|
|
|
case Qt::TouchPointMoved:
|
|
|
|
input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale;
|
|
|
|
input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale;
|
|
|
|
input.flags = TOUCH_MOVE;
|
|
|
|
input.id = touchPoint.id();
|
|
|
|
NativeTouch(input);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case QEvent::MouseButtonDblClick:
|
|
|
|
if (!g_Config.bShowTouchControls || GetUIState() != UISTATE_INGAME)
|
|
|
|
emit doubleClick();
|
|
|
|
break;
|
|
|
|
case QEvent::MouseButtonPress:
|
|
|
|
case QEvent::MouseButtonRelease:
|
|
|
|
input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale;
|
|
|
|
input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale;
|
|
|
|
input.flags = (e->type() == QEvent::MouseButtonPress) ? TOUCH_DOWN : TOUCH_UP;
|
|
|
|
input.id = 0;
|
|
|
|
NativeTouch(input);
|
|
|
|
break;
|
|
|
|
case QEvent::MouseMove:
|
|
|
|
input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale;
|
|
|
|
input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale;
|
|
|
|
input.flags = TOUCH_MOVE;
|
|
|
|
input.id = 0;
|
|
|
|
NativeTouch(input);
|
|
|
|
break;
|
|
|
|
case QEvent::Wheel:
|
|
|
|
NativeKey(KeyInput(DEVICE_ID_MOUSE, ((QWheelEvent*)e)->delta()<0 ? NKCODE_EXT_MOUSEWHEEL_DOWN : NKCODE_EXT_MOUSEWHEEL_UP, KEY_DOWN));
|
|
|
|
break;
|
|
|
|
case QEvent::KeyPress:
|
|
|
|
{
|
|
|
|
auto qtKeycode = ((QKeyEvent*)e)->key();
|
|
|
|
auto iter = KeyMapRawQttoNative.find(qtKeycode);
|
|
|
|
int nativeKeycode = 0;
|
|
|
|
if (iter != KeyMapRawQttoNative.end()) {
|
|
|
|
nativeKeycode = iter->second;
|
|
|
|
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, nativeKeycode, KEY_DOWN));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also get the unicode value.
|
|
|
|
QString text = ((QKeyEvent*)e)->text();
|
|
|
|
std::string str = text.toStdString();
|
|
|
|
// Now, we don't want CHAR events for non-printable characters. Not quite sure how we'll best
|
|
|
|
// do that, but here's one attempt....
|
|
|
|
switch (nativeKeycode) {
|
|
|
|
case NKCODE_DEL:
|
|
|
|
case NKCODE_FORWARD_DEL:
|
|
|
|
case NKCODE_TAB:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (str.size()) {
|
|
|
|
int pos = 0;
|
|
|
|
int code = u8_nextchar(str.c_str(), &pos);
|
|
|
|
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, code, KEY_CHAR));
|
2019-01-06 22:46:43 +00:00
|
|
|
}
|
2019-02-18 13:00:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case QEvent::KeyRelease:
|
|
|
|
NativeKey(KeyInput(DEVICE_ID_KEYBOARD, KeyMapRawQttoNative.find(((QKeyEvent*)e)->key())->second, KEY_UP));
|
|
|
|
break;
|
2019-02-17 15:30:40 +00:00
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
default:
|
2019-02-17 15:30:40 +00:00
|
|
|
if (e->type() == browseFileEvent) {
|
2019-02-18 13:00:28 +00:00
|
|
|
QString fileName = QFileDialog::getOpenFileName(nullptr, "Load ROM", g_Config.currentDirectory.c_str(), "PSP ROMs (*.iso *.cso *.pbp *.elf *.zip *.ppdmp)");
|
2019-02-17 15:30:40 +00:00
|
|
|
if (QFile::exists(fileName)) {
|
|
|
|
QDir newPath;
|
|
|
|
g_Config.currentDirectory = newPath.filePath(fileName).toStdString();
|
2019-02-23 09:49:49 +00:00
|
|
|
g_Config.Save("browseFileEvent");
|
2019-02-17 15:30:40 +00:00
|
|
|
|
|
|
|
NativeMessageReceived("boot", fileName.toStdString().c_str());
|
|
|
|
}
|
|
|
|
break;
|
2019-02-17 15:48:32 +00:00
|
|
|
} else if (e->type() == browseFolderEvent) {
|
2020-01-26 18:43:18 +00:00
|
|
|
auto mm = GetI18NCategory("MainMenu");
|
2019-02-17 15:48:32 +00:00
|
|
|
QString fileName = QFileDialog::getExistingDirectory(nullptr, mm->T("Choose folder"), g_Config.currentDirectory.c_str());
|
|
|
|
if (QDir(fileName).exists()) {
|
|
|
|
NativeMessageReceived("browse_folderSelect", fileName.toStdString().c_str());
|
|
|
|
}
|
2019-02-17 15:30:40 +00:00
|
|
|
} else {
|
2019-02-18 13:00:28 +00:00
|
|
|
return QWidget::event(e);
|
2019-02-17 15:30:40 +00:00
|
|
|
}
|
2019-02-18 13:00:28 +00:00
|
|
|
}
|
|
|
|
e->accept();
|
|
|
|
return true;
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2018-02-08 10:19:48 +00:00
|
|
|
void MainUI::initializeGL() {
|
|
|
|
if (g_Config.iGPUBackend != (int)GPUBackend::OPENGL) {
|
|
|
|
ILOG("Only GL supported under Qt - switching.");
|
|
|
|
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
|
|
|
}
|
|
|
|
|
2019-08-18 04:15:29 +00:00
|
|
|
SetGLCoreContext(format().profile() == QGLFormat::CoreProfile);
|
|
|
|
|
2016-02-10 11:37:47 +00:00
|
|
|
#ifndef USING_GLES2
|
2016-08-07 03:19:50 +00:00
|
|
|
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
|
|
|
|
// glewExperimental allows us to force GLEW to search for the pointers anyway.
|
2018-02-08 10:19:48 +00:00
|
|
|
if (gl_extensions.IsCoreContext) {
|
2016-08-07 03:19:50 +00:00
|
|
|
glewExperimental = true;
|
2018-02-08 10:19:48 +00:00
|
|
|
}
|
2016-08-07 03:19:50 +00:00
|
|
|
glewInit();
|
|
|
|
// Unfortunately, glew will generate an invalid enum error, ignore.
|
2018-02-08 10:19:48 +00:00
|
|
|
if (gl_extensions.IsCoreContext) {
|
2016-08-07 03:19:50 +00:00
|
|
|
glGetError();
|
2018-02-08 10:19:48 +00:00
|
|
|
}
|
2016-02-10 11:37:47 +00:00
|
|
|
#endif
|
2018-01-31 17:35:48 +00:00
|
|
|
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
|
2018-02-08 10:19:48 +00:00
|
|
|
// OpenGL uses a background thread to do the main processing and only renders on the gl thread.
|
|
|
|
ILOG("Initializing GL graphics context");
|
|
|
|
graphicsContext = new QtGLGraphicsContext();
|
2018-01-31 11:05:18 +00:00
|
|
|
ILOG("Using thread, starting emu thread");
|
|
|
|
EmuThreadStart();
|
|
|
|
} else {
|
|
|
|
ILOG("Not using thread, backend=%d", (int)g_Config.iGPUBackend);
|
|
|
|
}
|
2018-01-31 17:35:48 +00:00
|
|
|
graphicsContext->ThreadStart();
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2018-02-08 10:19:48 +00:00
|
|
|
void MainUI::paintGL() {
|
2019-02-18 13:00:28 +00:00
|
|
|
#ifdef SDL
|
2018-01-31 17:35:48 +00:00
|
|
|
SDL_PumpEvents();
|
2019-02-18 13:00:28 +00:00
|
|
|
#endif
|
2018-01-31 17:35:48 +00:00
|
|
|
updateAccelerometer();
|
|
|
|
if (emuThreadState == (int)EmuThreadState::DISABLED) {
|
2018-01-31 11:05:18 +00:00
|
|
|
UpdateRunLoop();
|
2018-01-31 17:35:48 +00:00
|
|
|
} else {
|
|
|
|
graphicsContext->ThreadFrame();
|
|
|
|
// Do the rest in EmuThreadFunc
|
2018-01-31 11:05:18 +00:00
|
|
|
}
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainUI::updateAccelerometer() {
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(MOBILE_DEVICE)
|
2019-02-18 13:00:28 +00:00
|
|
|
// TODO: Toggle it depending on whether it is enabled
|
|
|
|
QAccelerometerReading *reading = acc->reading();
|
|
|
|
if (reading) {
|
|
|
|
AxisInput axis;
|
|
|
|
axis.deviceId = DEVICE_ID_ACCELEROMETER;
|
|
|
|
axis.flags = 0;
|
|
|
|
|
|
|
|
axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X;
|
|
|
|
axis.value = reading->x();
|
|
|
|
NativeAxis(axis);
|
|
|
|
|
|
|
|
axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y;
|
|
|
|
axis.value = reading->y();
|
|
|
|
NativeAxis(axis);
|
|
|
|
|
|
|
|
axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z;
|
|
|
|
axis.value = reading->z();
|
|
|
|
NativeAxis(axis);
|
|
|
|
}
|
2016-02-10 11:37:47 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef SDL
|
|
|
|
// Audio
|
|
|
|
#define AUDIO_FREQ 44100
|
|
|
|
#define AUDIO_CHANNELS 2
|
|
|
|
#define AUDIO_SAMPLES 2048
|
|
|
|
#define AUDIO_SAMPLESIZE 16
|
|
|
|
#define AUDIO_BUFFERS 5
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
MainAudio::~MainAudio() {
|
|
|
|
if (feed != nullptr) {
|
|
|
|
killTimer(timer);
|
|
|
|
feed->close();
|
|
|
|
}
|
|
|
|
if (output) {
|
|
|
|
output->stop();
|
|
|
|
delete output;
|
|
|
|
}
|
|
|
|
if (mixbuf)
|
|
|
|
free(mixbuf);
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainAudio::run() {
|
|
|
|
QAudioFormat fmt;
|
|
|
|
fmt.setSampleRate(AUDIO_FREQ);
|
|
|
|
fmt.setCodec("audio/pcm");
|
|
|
|
fmt.setChannelCount(AUDIO_CHANNELS);
|
|
|
|
fmt.setSampleSize(AUDIO_SAMPLESIZE);
|
|
|
|
fmt.setByteOrder(QAudioFormat::LittleEndian);
|
|
|
|
fmt.setSampleType(QAudioFormat::SignedInt);
|
|
|
|
mixlen = sizeof(short)*AUDIO_BUFFERS*AUDIO_CHANNELS*AUDIO_SAMPLES;
|
|
|
|
mixbuf = (char*)malloc(mixlen);
|
|
|
|
output = new QAudioOutput(fmt);
|
|
|
|
output->setBufferSize(mixlen);
|
|
|
|
feed = output->start();
|
|
|
|
if (feed != nullptr) {
|
|
|
|
// buffering has already done in the internal mixed buffer
|
|
|
|
// use a small interval to copy mixed audio stream from
|
|
|
|
// internal buffer to audio output buffer as soon as possible
|
|
|
|
// use 1 instead of 0 to prevent CPU exhausting
|
|
|
|
timer = startTimer(1);
|
|
|
|
}
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 13:00:28 +00:00
|
|
|
void MainAudio::timerEvent(QTimerEvent *) {
|
|
|
|
memset(mixbuf, 0, mixlen);
|
|
|
|
size_t frames = NativeMix((short *)mixbuf, AUDIO_BUFFERS*AUDIO_SAMPLES);
|
|
|
|
if (frames > 0)
|
|
|
|
feed->write(mixbuf, sizeof(short) * AUDIO_CHANNELS * frames);
|
2016-02-10 11:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2020-01-25 08:06:00 +00:00
|
|
|
void QTCamera::startCamera(int width, int height) {
|
|
|
|
__qt_startCapture(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTCamera::stopCamera() {
|
|
|
|
__qt_stopCapture();
|
|
|
|
}
|
|
|
|
|
2014-12-18 22:57:59 +00:00
|
|
|
#ifndef SDL
|
2013-11-22 00:59:18 +00:00
|
|
|
Q_DECL_EXPORT
|
|
|
|
#endif
|
|
|
|
int main(int argc, char *argv[])
|
2012-12-13 06:38:33 +00:00
|
|
|
{
|
2018-05-09 19:23:04 +00:00
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
if (!strcmp(argv[i], "--version")) {
|
|
|
|
printf("%s\n", PPSSPP_GIT_VERSION);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-07 16:02:00 +00:00
|
|
|
glslang::InitializeProcess();
|
2016-10-12 10:32:20 +00:00
|
|
|
#if defined(Q_OS_LINUX)
|
2013-01-14 09:20:28 +00:00
|
|
|
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
|
|
|
|
#endif
|
2019-08-18 04:15:29 +00:00
|
|
|
|
|
|
|
// Qt would otherwise default to a 3.0 compatibility profile
|
|
|
|
// except on Nvidia, where Nvidia gives us the highest supported anyway
|
|
|
|
QGLFormat format;
|
|
|
|
format.setVersion(4, 6);
|
|
|
|
format.setProfile(QGLFormat::CoreProfile);
|
|
|
|
QGLFormat::setDefaultFormat(format);
|
|
|
|
|
2012-12-13 06:38:33 +00:00
|
|
|
QApplication a(argc, argv);
|
2020-01-05 07:46:27 +00:00
|
|
|
QScreen* screen = a.primaryScreen();
|
|
|
|
QSizeF res = screen->physicalSize();
|
2020-01-25 08:06:00 +00:00
|
|
|
|
2012-12-13 06:38:33 +00:00
|
|
|
if (res.width() < res.height())
|
|
|
|
res.transpose();
|
|
|
|
pixel_xres = res.width();
|
|
|
|
pixel_yres = res.height();
|
2020-01-05 07:46:27 +00:00
|
|
|
|
|
|
|
g_dpi_scale_x = screen->logicalDotsPerInchX() / screen->physicalDotsPerInchX();
|
|
|
|
g_dpi_scale_y = screen->logicalDotsPerInchY() / screen->physicalDotsPerInchY();
|
2017-08-07 11:18:19 +00:00
|
|
|
g_dpi_scale_real_x = g_dpi_scale_x;
|
|
|
|
g_dpi_scale_real_y = g_dpi_scale_y;
|
2017-08-07 18:10:13 +00:00
|
|
|
dp_xres = (int)(pixel_xres * g_dpi_scale_x);
|
|
|
|
dp_yres = (int)(pixel_yres * g_dpi_scale_y);
|
2018-06-09 23:21:28 +00:00
|
|
|
|
2020-01-05 17:04:07 +00:00
|
|
|
refreshRate = screen->refreshRate();
|
2018-06-09 23:21:28 +00:00
|
|
|
|
2020-01-25 08:06:00 +00:00
|
|
|
qtcamera = new QTCamera;
|
|
|
|
QObject::connect(qtcamera, SIGNAL(onStartCamera(int, int)), qtcamera, SLOT(startCamera(int, int)));
|
|
|
|
QObject::connect(qtcamera, SIGNAL(onStopCamera()), qtcamera, SLOT(stopCamera()));
|
|
|
|
|
2014-06-25 05:40:28 +00:00
|
|
|
std::string savegame_dir = ".";
|
2017-04-07 07:56:57 +00:00
|
|
|
std::string external_dir = ".";
|
2014-06-25 05:40:28 +00:00
|
|
|
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
|
|
|
|
savegame_dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString();
|
2017-04-07 07:56:57 +00:00
|
|
|
external_dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation).toStdString();
|
2012-12-13 06:38:33 +00:00
|
|
|
#endif
|
2014-06-25 05:40:28 +00:00
|
|
|
savegame_dir += "/";
|
2017-04-07 07:56:57 +00:00
|
|
|
external_dir += "/";
|
2018-01-31 11:05:18 +00:00
|
|
|
|
2018-06-09 23:28:15 +00:00
|
|
|
NativeInit(argc, (const char **)argv, savegame_dir.c_str(), external_dir.c_str(), nullptr);
|
2018-01-31 11:05:18 +00:00
|
|
|
|
|
|
|
// TODO: Support other backends than GL, like Vulkan, in the Qt backend.
|
|
|
|
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
|
|
|
|
2019-09-15 20:23:48 +00:00
|
|
|
int ret = mainInternal(a);
|
2018-02-04 12:54:10 +00:00
|
|
|
ILOG("Left mainInternal here.");
|
2012-12-16 09:47:51 +00:00
|
|
|
|
2014-12-18 22:57:59 +00:00
|
|
|
#ifdef SDL
|
2019-09-15 19:42:49 +00:00
|
|
|
if (audioDev > 0) {
|
|
|
|
SDL_PauseAudioDevice(audioDev, 1);
|
|
|
|
SDL_CloseAudioDevice(audioDev);
|
|
|
|
}
|
2014-06-25 03:26:43 +00:00
|
|
|
#endif
|
2012-12-13 06:38:33 +00:00
|
|
|
NativeShutdown();
|
2017-12-07 16:02:00 +00:00
|
|
|
glslang::FinalizeProcess();
|
2012-12-13 06:38:33 +00:00
|
|
|
return ret;
|
|
|
|
}
|