ppsspp/SDL/SDLMain.cpp
2020-01-24 15:27:10 +01:00

1034 lines
28 KiB
C++

// SDL/EGL implementation of the framework.
// This is quite messy due to platform-specific implementations and #ifdef's.
// If your platform is not supported, it is suggested to use Qt instead.
#include <unistd.h>
#include <pwd.h>
#include "SDL.h"
#include "SDL/SDLJoystick.h"
SDLJoystick *joystick = NULL;
#if PPSSPP_PLATFORM(RPI)
#include <bcm_host.h>
#endif
#include <atomic>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <thread>
#include <locale>
#include "base/display.h"
#include "base/logging.h"
#include "base/timeutil.h"
#include "ext/glslang/glslang/Public/ShaderLang.h"
#include "image/png_load.h"
#include "input/input_state.h"
#include "input/keycodes.h"
#include "net/resolve.h"
#include "NKCodeFromSDL.h"
#include "util/const_map.h"
#include "util/text/utf8.h"
#include "math/math_util.h"
#include "thin3d/GLRenderManager.h"
#include "thread/threadutil.h"
#include "math.h"
#include "SDL_syswm.h"
#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#elif defined(VK_USE_PLATFORM_XCB_KHR)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlib-xcb.h>
#endif
#include "Core/System.h"
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Common/GraphicsContext.h"
#include "SDLGLGraphicsContext.h"
#include "SDLVulkanGraphicsContext.h"
GlobalUIState lastUIState = UISTATE_MENU;
GlobalUIState GetUIState();
static bool g_ToggleFullScreenNextFrame = false;
static int g_ToggleFullScreenType;
static int g_QuitRequested = 0;
static int g_DesktopWidth = 0;
static int g_DesktopHeight = 0;
static int g_RefreshRate = 60000;
int getDisplayNumber(void) {
int displayNumber = 0;
char * displayNumberStr;
//get environment
displayNumberStr=getenv("SDL_VIDEO_FULLSCREEN_HEAD");
if (displayNumberStr) {
displayNumber = atoi(displayNumberStr);
}
return displayNumber;
}
extern void mixaudio(void *userdata, Uint8 *stream, int len) {
NativeMix((short *)stream, len / 4);
}
static SDL_AudioDeviceID audioDev = 0;
// Must be called after NativeInit().
static void InitSDLAudioDevice(const std::string &name = "") {
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;
std::string startDevice = name;
if (startDevice.empty()) {
startDevice = g_Config.sAudioDevice;
}
audioDev = 0;
if (!startDevice.empty()) {
audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (audioDev <= 0) {
WLOG("Failed to open audio device: %s", startDevice.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);
}
}
// Simple implementations of System functions
void SystemToast(const char *text) {
#ifdef _WIN32
std::wstring str = ConvertUTF8ToWString(text);
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
#else
puts(text);
#endif
}
void ShowKeyboard() {
// Irrelevant on PC
}
void Vibrate(int length_ms) {
// Ignore on PC
}
void System_SendMessage(const char *command, const char *parameter) {
if (!strcmp(command, "toggle_fullscreen")) {
g_ToggleFullScreenNextFrame = true;
if (strcmp(parameter, "1") == 0) {
g_ToggleFullScreenType = 1;
} else if (strcmp(parameter, "0") == 0) {
g_ToggleFullScreenType = 0;
} else {
// Just toggle.
g_ToggleFullScreenType = -1;
}
} else if (!strcmp(command, "finish")) {
// Do a clean exit
g_QuitRequested = true;
} else if (!strcmp(command, "graphics_restart")) {
// Not sure how we best do this, but do a clean exit, better than being stuck in a bad state.
g_QuitRequested = true;
} else if (!strcmp(command, "setclipboardtext")) {
SDL_SetClipboardText(parameter);
} else if (!strcmp(command, "audio_resetDevice")) {
StopSDLAudioDevice();
InitSDLAudioDevice();
}
}
void System_AskForPermission(SystemPermission permission) {}
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
void OpenDirectory(const char *path) {
#if defined(_WIN32)
PIDLIST_ABSOLUTE pidl = ILCreateFromPath(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str());
if (pidl) {
SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
ILFree(pidl);
}
#endif
}
void LaunchBrowser(const char *url) {
#if defined(MOBILE_DEVICE)
ILOG("Would have gone to %s but LaunchBrowser is not implemented on this platform", url);
#elif defined(_WIN32)
std::wstring wurl = ConvertUTF8ToWString(url);
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
#elif defined(__APPLE__)
std::string command = std::string("open ") + url;
system(command.c_str());
#else
std::string command = std::string("xdg-open ") + url;
int err = system(command.c_str());
if (err) {
ILOG("Would have gone to %s but xdg-utils seems not to be installed", url)
}
#endif
}
void LaunchMarket(const char *url) {
#if defined(MOBILE_DEVICE)
ILOG("Would have gone to %s but LaunchMarket is not implemented on this platform", url);
#elif defined(_WIN32)
std::wstring wurl = ConvertUTF8ToWString(url);
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
#elif defined(__APPLE__)
std::string command = std::string("open ") + url;
system(command.c_str());
#else
std::string command = std::string("xdg-open ") + url;
int err = system(command.c_str());
if (err) {
ILOG("Would have gone to %s but xdg-utils seems not to be installed", url)
}
#endif
}
void LaunchEmail(const char *email_address) {
#if defined(MOBILE_DEVICE)
ILOG("Would have opened your email client for %s but LaunchEmail is not implemented on this platform", email_address);
#elif defined(_WIN32)
std::wstring mailto = std::wstring(L"mailto:") + ConvertUTF8ToWString(email_address);
ShellExecute(NULL, L"open", mailto.c_str(), NULL, NULL, SW_SHOWNORMAL);
#elif defined(__APPLE__)
std::string command = std::string("open mailto:") + email_address;
system(command.c_str());
#else
std::string command = std::string("xdg-email ") + email_address;
int err = system(command.c_str());
if (err) {
ILOG("Would have gone to %s but xdg-utils seems not to be installed", email_address)
}
#endif
}
std::string System_GetProperty(SystemProperty prop) {
switch (prop) {
case SYSPROP_NAME:
#ifdef _WIN32
return "SDL:Windows";
#elif __linux__
return "SDL:Linux";
#elif __APPLE__
return "SDL:OSX";
#else
return "SDL:";
#endif
case SYSPROP_LANGREGION: {
// Get user-preferred locale from OS
setlocale(LC_ALL, "");
std::string locale(setlocale(LC_ALL, NULL));
// Set c and c++ strings back to POSIX
std::locale::global(std::locale("POSIX"));
if (!locale.empty()) {
if (locale.find("_", 0) != std::string::npos) {
if (locale.find(".", 0) != std::string::npos) {
return locale.substr(0, locale.find(".",0));
} else {
return locale;
}
}
}
return "en_US";
}
case SYSPROP_CLIPBOARD_TEXT:
return SDL_HasClipboardText() ? SDL_GetClipboardText() : "";
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;
}
default:
return "";
}
}
int System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
case SYSPROP_AUDIO_SAMPLE_RATE:
return 44100;
case SYSPROP_DISPLAY_REFRESH_RATE:
return g_RefreshRate;
case SYSPROP_DEVICE_TYPE:
#if defined(MOBILE_DEVICE)
return DEVICE_TYPE_MOBILE;
#else
return DEVICE_TYPE_DESKTOP;
#endif
case SYSPROP_DISPLAY_COUNT:
return SDL_GetNumVideoDisplays();
default:
return -1;
}
}
bool System_GetPropertyBool(SystemProperty prop) {
switch (prop) {
case SYSPROP_HAS_BACK_BUTTON:
return true;
case SYSPROP_APP_GOLD:
#ifdef GOLD
return true;
#else
return false;
#endif
default:
return false;
}
}
// returns -1 on failure
static int parseInt(const char *str) {
int val;
int retval = sscanf(str, "%d", &val);
printf("%i = scanf %s\n", retval, str);
if (retval != 1) {
return -1;
} else {
return val;
}
}
static float parseFloat(const char *str) {
float val;
int retval = sscanf(str, "%f", &val);
printf("%i = sscanf %s\n", retval, str);
if (retval != 1) {
return -1.0f;
} else {
return val;
}
}
void ToggleFullScreenIfFlagSet(SDL_Window *window) {
if (g_ToggleFullScreenNextFrame) {
g_ToggleFullScreenNextFrame = false;
Uint32 window_flags = SDL_GetWindowFlags(window);
if (g_ToggleFullScreenType == -1) {
window_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if (g_ToggleFullScreenType == 1) {
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else {
window_flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP;
}
SDL_SetWindowFullscreen(window, window_flags);
}
}
enum class EmuThreadState {
DISABLED,
START_REQUESTED,
RUNNING,
QUIT_REQUESTED,
STOPPED,
};
static std::thread emuThread;
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
setCurrentThreadName("Emu");
// There's no real requirement that NativeInit happen on this thread.
// We just call the update/render loop here.
emuThreadState = (int)EmuThreadState::RUNNING;
NativeInitGraphics(graphicsContext);
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
UpdateRunLoop();
}
emuThreadState = (int)EmuThreadState::STOPPED;
NativeShutdownGraphics();
graphicsContext->StopThread();
}
static void EmuThreadStart(GraphicsContext *context) {
emuThreadState = (int)EmuThreadState::START_REQUESTED;
emuThread = std::thread(&EmuThreadFunc, context);
}
static void EmuThreadStop() {
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
}
static void EmuThreadJoin() {
emuThread.join();
emuThread = std::thread();
}
#ifdef _WIN32
#undef main
#endif
int main(int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--version")) {
printf("%s\n", PPSSPP_GIT_VERSION);
return 0;
}
}
glslang::InitializeProcess();
#if PPSSPP_PLATFORM(RPI)
bcm_host_init();
#endif
putenv((char*)"SDL_VIDEO_CENTERED=1");
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
if (VulkanMayBeAvailable()) {
printf("DEBUG: Vulkan might be available.\n");
} else {
printf("DEBUG: Vulkan is not available, not using Vulkan.\n");
}
int set_xres = -1;
int set_yres = -1;
int w = 0, h = 0;
bool portrait = false;
bool set_ipad = false;
float set_dpi = 1.0f;
float set_scale = 1.0f;
// Produce a new set of arguments with the ones we skip.
int remain_argc = 1;
const char *remain_argv[256] = { argv[0] };
Uint32 mode = 0;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i],"--fullscreen"))
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
else if (set_xres == -2)
set_xres = parseInt(argv[i]);
else if (set_yres == -2)
set_yres = parseInt(argv[i]);
else if (set_dpi == -2)
set_dpi = parseFloat(argv[i]);
else if (set_scale == -2)
set_scale = parseFloat(argv[i]);
else if (!strcmp(argv[i],"--xres"))
set_xres = -2;
else if (!strcmp(argv[i],"--yres"))
set_yres = -2;
else if (!strcmp(argv[i],"--dpi"))
set_dpi = -2;
else if (!strcmp(argv[i],"--scale"))
set_scale = -2;
else if (!strcmp(argv[i],"--ipad"))
set_ipad = true;
else if (!strcmp(argv[i],"--portrait"))
portrait = true;
else {
remain_argv[remain_argc++] = argv[i];
}
}
std::string app_name;
std::string app_name_nice;
std::string version;
bool landscape;
NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version);
bool joystick_enabled = true;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");
joystick_enabled = false;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
return 1;
}
}
// Get the video info before doing anything else, so we don't get skewed resolution results.
// TODO: support multiple displays correctly
SDL_DisplayMode displayMode;
int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode);
if (should_be_zero != 0) {
fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError());
return 1;
}
g_DesktopWidth = displayMode.w;
g_DesktopHeight = displayMode.h;
g_RefreshRate = (int)(displayMode.refresh_rate * 1000);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Force fullscreen if the resolution is too low to run windowed.
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
// If we're on mobile, don't try for windowed either.
#if defined(MOBILE_DEVICE)
mode |= SDL_WINDOW_FULLSCREEN;
#elif defined(USING_FBDEV)
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
mode |= SDL_WINDOW_RESIZABLE;
#endif
if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) {
pixel_xres = g_DesktopWidth;
pixel_yres = g_DesktopHeight;
g_Config.bFullScreen = true;
} else {
// set a sensible default resolution (2x)
pixel_xres = 480 * 2 * set_scale;
pixel_yres = 272 * 2 * set_scale;
if (portrait) {
std::swap(pixel_xres, pixel_yres);
}
g_Config.bFullScreen = false;
}
set_dpi = 1.0f / set_dpi;
if (set_ipad) {
pixel_xres = 1024;
pixel_yres = 768;
}
if (!landscape) {
std::swap(pixel_xres, pixel_yres);
}
if (set_xres > 0) {
pixel_xres = set_xres;
}
if (set_yres > 0) {
pixel_yres = set_yres;
}
float dpi_scale = 1.0f;
if (set_dpi > 0) {
dpi_scale = set_dpi;
}
dp_xres = (float)pixel_xres * dpi_scale;
dp_yres = (float)pixel_yres * dpi_scale;
// Mac / Linux
char path[2048];
const char *the_path = getenv("HOME");
if (!the_path) {
struct passwd* pwd = getpwuid(getuid());
if (pwd)
the_path = pwd->pw_dir;
}
strcpy(path, the_path);
if (path[strlen(path)-1] != '/')
strcat(path, "/");
NativeInit(remain_argc, (const char **)remain_argv, path, "/tmp", nullptr);
// Use the setting from the config when initing the window.
if (g_Config.bFullScreen)
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());
int y = SDL_WINDOWPOS_UNDEFINED;
pixel_in_dps_x = (float)pixel_xres / dp_xres;
pixel_in_dps_y = (float)pixel_yres / dp_yres;
g_dpi_scale_x = dp_xres / (float)pixel_xres;
g_dpi_scale_y = dp_yres / (float)pixel_yres;
g_dpi_scale_real_x = g_dpi_scale_x;
g_dpi_scale_real_y = g_dpi_scale_y;
printf("Pixels: %i x %i\n", pixel_xres, pixel_yres);
printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres);
GraphicsContext *graphicsContext = nullptr;
SDL_Window *window = nullptr;
std::string error_message;
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
SDLGLGraphicsContext *ctx = new SDLGLGraphicsContext();
if (ctx->Init(window, x, y, mode, &error_message) != 0) {
printf("GL init error '%s'\n", error_message.c_str());
}
graphicsContext = ctx;
} else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
SDLVulkanGraphicsContext *ctx = new SDLVulkanGraphicsContext();
if (!ctx->Init(window, x, y, mode, &error_message)) {
printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str());
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
delete ctx;
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
glctx->Init(window, x, y, mode, &error_message);
graphicsContext = glctx;
} else {
graphicsContext = ctx;
}
}
bool useEmuThread = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
char iconPath[PATH_MAX];
snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
int width = 0, height = 0;
unsigned char *imageData;
if (pngLoad(iconPath, &width, &height, &imageData, false) == 1) {
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
memcpy(surface->pixels, imageData, width*height*4);
SDL_SetWindowIcon(window, surface);
SDL_FreeSurface(surface);
free(imageData);
imageData = NULL;
}
// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
if (!graphicsContext->InitFromRenderThread(&error_message)) {
printf("Init from thread error: '%s'\n", error_message.c_str());
}
#ifdef MOBILE_DEVICE
SDL_ShowCursor(SDL_DISABLE);
#endif
if (!useEmuThread) {
NativeInitGraphics(graphicsContext);
NativeResized();
}
// Ensure that the swap interval is set after context creation (needed for kmsdrm)
SDL_GL_SetSwapInterval(1);
InitSDLAudioDevice();
if (joystick_enabled) {
joystick = new SDLJoystick();
} else {
joystick = nullptr;
}
EnableFZ();
int framecount = 0;
bool mouseDown = false;
if (useEmuThread) {
EmuThreadStart(graphicsContext);
}
graphicsContext->ThreadStart();
bool windowHidden = false;
while (true) {
double startTime = time_now_d();
SDL_Event event, touchEvent;
while (SDL_PollEvent(&event)) {
float mx = event.motion.x * g_dpi_scale_x;
float my = event.motion.y * g_dpi_scale_y;
switch (event.type) {
case SDL_QUIT:
g_QuitRequested = 1;
break;
#if !defined(MOBILE_DEVICE)
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: // better than RESIZED, more general
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
{
windowHidden = false;
Core_NotifyWindowHidden(windowHidden);
Uint32 window_flags = SDL_GetWindowFlags(window);
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
UpdateScreenScale(event.window.data1, event.window.data2);
}
NativeMessageReceived("gpu_resized", "");
// Set variable here in case fullscreen was toggled by hotkey
g_Config.bFullScreen = fullscreen;
// Hide/Show cursor correctly toggling fullscreen
if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) {
SDL_ShowCursor(SDL_DISABLE);
} else if (lastUIState != UISTATE_INGAME || !fullscreen) {
SDL_ShowCursor(SDL_ENABLE);
}
break;
}
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_HIDDEN:
windowHidden = true;
Core_NotifyWindowHidden(windowHidden);
break;
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SHOWN:
windowHidden = false;
Core_NotifyWindowHidden(windowHidden);
break;
default:
break;
}
break;
#endif
case SDL_KEYDOWN:
{
if (event.key.repeat > 0) { break;}
int k = event.key.keysym.sym;
KeyInput key;
key.flags = KEY_DOWN;
auto mapped = KeyMapRawSDLtoNative.find(k);
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
break;
}
key.keyCode = mapped->second;
key.deviceId = DEVICE_ID_KEYBOARD;
NativeKey(key);
break;
}
case SDL_KEYUP:
{
if (event.key.repeat > 0) { break;}
int k = event.key.keysym.sym;
KeyInput key;
key.flags = KEY_UP;
auto mapped = KeyMapRawSDLtoNative.find(k);
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
break;
}
key.keyCode = mapped->second;
key.deviceId = DEVICE_ID_KEYBOARD;
NativeKey(key);
break;
}
case SDL_TEXTINPUT:
{
int pos = 0;
int c = u8_nextchar(event.text.text, &pos);
KeyInput key;
key.flags = KEY_CHAR;
key.keyCode = c;
key.deviceId = DEVICE_ID_KEYBOARD;
NativeKey(key);
break;
}
// This behavior doesn't feel right on a macbook with a touchpad.
#if !PPSSPP_PLATFORM(MAC)
case SDL_FINGERMOTION:
{
SDL_GetWindowSize(window, &w, &h);
touchEvent.type = SDL_MOUSEMOTION;
touchEvent.motion.type = SDL_MOUSEMOTION;
touchEvent.motion.timestamp = event.tfinger.timestamp;
touchEvent.motion.windowID = SDL_GetWindowID(window);
touchEvent.motion.which = SDL_TOUCH_MOUSEID;
touchEvent.motion.state = SDL_GetMouseState(NULL, NULL);
touchEvent.motion.x = event.tfinger.x * w;
touchEvent.motion.y = event.tfinger.y * h;
SDL_WarpMouseInWindow(window, event.tfinger.x * w, event.tfinger.y * h);
SDL_PushEvent(&touchEvent);
break;
}
case SDL_FINGERDOWN:
{
SDL_GetWindowSize(window, &w, &h);
touchEvent.type = SDL_MOUSEBUTTONDOWN;
touchEvent.button.type = SDL_MOUSEBUTTONDOWN;
touchEvent.button.timestamp = SDL_GetTicks();
touchEvent.button.windowID = SDL_GetWindowID(window);
touchEvent.button.which = SDL_TOUCH_MOUSEID;
touchEvent.button.button = SDL_BUTTON_LEFT;
touchEvent.button.state = SDL_PRESSED;
touchEvent.button.clicks = 1;
touchEvent.button.x = event.tfinger.x * w;
touchEvent.button.y = event.tfinger.y * h;
touchEvent.motion.type = SDL_MOUSEMOTION;
touchEvent.motion.timestamp = SDL_GetTicks();
touchEvent.motion.windowID = SDL_GetWindowID(window);
touchEvent.motion.which = SDL_TOUCH_MOUSEID;
touchEvent.motion.x = event.tfinger.x * w;
touchEvent.motion.y = event.tfinger.y * h;
// Any real mouse cursor should also move
SDL_WarpMouseInWindow(window, event.tfinger.x * w, event.tfinger.y * h);
// First finger down event also has to be a motion to that position
SDL_PushEvent(&touchEvent);
touchEvent.motion.type = SDL_MOUSEBUTTONDOWN;
// Now we push the mouse button event
SDL_PushEvent(&touchEvent);
break;
}
case SDL_FINGERUP:
{
SDL_GetWindowSize(window, &w, &h);
touchEvent.type = SDL_MOUSEBUTTONUP;
touchEvent.button.type = SDL_MOUSEBUTTONUP;
touchEvent.button.timestamp = SDL_GetTicks();
touchEvent.button.windowID = SDL_GetWindowID(window);
touchEvent.button.which = SDL_TOUCH_MOUSEID;
touchEvent.button.button = SDL_BUTTON_LEFT;
touchEvent.button.state = SDL_RELEASED;
touchEvent.button.clicks = 1;
touchEvent.button.x = event.tfinger.x * w;
touchEvent.button.y = event.tfinger.y * h;
SDL_PushEvent(&touchEvent);
break;
}
#endif
case SDL_MOUSEBUTTONDOWN:
switch (event.button.button) {
case SDL_BUTTON_LEFT:
{
mouseDown = true;
TouchInput input;
input.x = mx;
input.y = my;
input.flags = TOUCH_DOWN | TOUCH_MOUSE;
input.id = 0;
NativeTouch(input);
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN);
NativeKey(key);
}
break;
case SDL_BUTTON_RIGHT:
{
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN);
NativeKey(key);
}
break;
}
break;
case SDL_MOUSEWHEEL:
{
KeyInput key;
key.deviceId = DEVICE_ID_MOUSE;
if (event.wheel.y > 0) {
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
} else {
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
}
key.flags = KEY_DOWN;
NativeKey(key);
// SDL2 doesn't consider the mousewheel a button anymore
// so let's send the KEY_UP right away.
// Maybe KEY_UP alone will suffice?
key.flags = KEY_UP;
NativeKey(key);
}
case SDL_MOUSEMOTION:
if (mouseDown) {
TouchInput input;
input.x = mx;
input.y = my;
input.flags = TOUCH_MOVE | TOUCH_MOUSE;
input.id = 0;
NativeTouch(input);
}
break;
case SDL_MOUSEBUTTONUP:
switch (event.button.button) {
case SDL_BUTTON_LEFT:
{
mouseDown = false;
TouchInput input;
input.x = mx;
input.y = my;
input.flags = TOUCH_UP | TOUCH_MOUSE;
input.id = 0;
NativeTouch(input);
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP);
NativeKey(key);
}
break;
case SDL_BUTTON_RIGHT:
{
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP);
NativeKey(key);
}
break;
}
break;
#if SDL_VERSION_ATLEAST(2, 0, 4)
case SDL_AUDIODEVICEADDED:
// Automatically switch to the new device.
if (event.adevice.iscapture == 0) {
const char *name = SDL_GetAudioDeviceName(event.adevice.which, 0);
if (!name) {
break;
}
// Don't start auto switching for a second, because some devices init on start.
bool doAutoSwitch = g_Config.bAutoAudioDevice && framecount > 60;
if (doAutoSwitch || g_Config.sAudioDevice == name) {
StopSDLAudioDevice();
InitSDLAudioDevice(name ? name : "");
}
}
break;
case SDL_AUDIODEVICEREMOVED:
if (event.adevice.iscapture == 0 && event.adevice.which == audioDev) {
StopSDLAudioDevice();
InitSDLAudioDevice();
}
break;
#endif
default:
if (joystick) {
joystick->ProcessInput(event);
}
break;
}
}
if (g_QuitRequested)
break;
const uint8_t *keys = SDL_GetKeyboardState(NULL);
if (emuThreadState == (int)EmuThreadState::DISABLED) {
UpdateRunLoop();
}
if (g_QuitRequested)
break;
#if !defined(MOBILE_DEVICE)
if (lastUIState != GetUIState()) {
lastUIState = GetUIState();
if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !g_Config.bShowTouchControls)
SDL_ShowCursor(SDL_DISABLE);
if (lastUIState != UISTATE_INGAME || !g_Config.bFullScreen)
SDL_ShowCursor(SDL_ENABLE);
}
#endif
if (framecount % 60 == 0) {
// glsl_refresh(); // auto-reloads modified GLSL shaders once per second.
}
bool renderThreadPaused = windowHidden && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;
if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {
if (!graphicsContext->ThreadFrame())
break;
}
graphicsContext->SwapBuffers();
ToggleFullScreenIfFlagSet(window);
// Simple throttling to not burn the GPU in the menu.
time_update();
if (GetUIState() != UISTATE_INGAME || !PSP_IsInited() || renderThreadPaused) {
double diffTime = time_now_d() - startTime;
int sleepTime = (int)(1000.0 / 60.0) - (int)(diffTime * 1000.0);
if (sleepTime > 0)
sleep_ms(sleepTime);
}
time_update();
framecount++;
}
if (useEmuThread) {
EmuThreadStop();
while (graphicsContext->ThreadFrame()) {
// Need to keep eating frames to allow the EmuThread to exit correctly.
continue;
}
EmuThreadJoin();
}
delete joystick;
if (!useEmuThread) {
NativeShutdownGraphics();
}
graphicsContext->ThreadEnd();
NativeShutdown();
// Destroys Draw, which is used in NativeShutdown to shutdown.
graphicsContext->ShutdownFromRenderThread();
delete graphicsContext;
if (audioDev > 0) {
SDL_PauseAudioDevice(audioDev, 1);
SDL_CloseAudioDevice(audioDev);
}
SDL_Quit();
#if PPSSPP_PLATFORM(RPI)
bcm_host_deinit();
#endif
glslang::FinalizeProcess();
ILOG("Leaving main");
return 0;
}