Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/SDL/SDLMain.cpp
3185 views
1
#include <cstdlib>
2
#include <unistd.h>
3
#include <pwd.h>
4
5
#include "ppsspp_config.h"
6
#if PPSSPP_PLATFORM(MAC)
7
#include "SDL2/SDL.h"
8
#include "SDL2/SDL_syswm.h"
9
#include "SDL2/SDL_mouse.h"
10
#else
11
#include "SDL.h"
12
#include "SDL_syswm.h"
13
#include "SDL_mouse.h"
14
#endif
15
#include "SDL/SDLJoystick.h"
16
SDLJoystick *joystick = NULL;
17
18
#if PPSSPP_PLATFORM(RPI)
19
#include <bcm_host.h>
20
#endif
21
22
#include <atomic>
23
#include <algorithm>
24
#include <cmath>
25
#include <csignal>
26
#include <thread>
27
#include <locale>
28
29
#include "ext/portable-file-dialogs/portable-file-dialogs.h"
30
31
#include "ext/imgui/imgui.h"
32
#include "ext/imgui/imgui_impl_platform.h"
33
#include "Common/System/Display.h"
34
#include "Common/System/System.h"
35
#include "Common/System/Request.h"
36
#include "Common/System/NativeApp.h"
37
#include "Common/Audio/AudioBackend.h"
38
#include "ext/glslang/glslang/Public/ShaderLang.h"
39
#include "Common/Data/Format/PNGLoad.h"
40
#include "Common/Net/Resolve.h"
41
#include "Common/File/FileUtil.h"
42
#include "NKCodeFromSDL.h"
43
#include "Common/Math/math_util.h"
44
#include "Common/GPU/OpenGL/GLRenderManager.h"
45
#include "Common/Profiler/Profiler.h"
46
#include "Common/Log/LogManager.h"
47
48
#if defined(VK_USE_PLATFORM_XLIB_KHR)
49
#include <X11/Xlib.h>
50
#include <X11/Xutil.h>
51
#elif defined(VK_USE_PLATFORM_XCB_KHR)
52
#include <X11/Xlib.h>
53
#include <X11/Xutil.h>
54
#include <X11/Xlib-xcb.h>
55
#endif
56
57
#include "Common/GraphicsContext.h"
58
#include "Common/TimeUtil.h"
59
#include "Common/Input/InputState.h"
60
#include "Common/Input/KeyCodes.h"
61
#include "Common/Data/Collections/ConstMap.h"
62
#include "Common/Data/Encoding/Utf8.h"
63
#include "Common/Thread/ThreadUtil.h"
64
#include "Core/System.h"
65
#include "Core/Core.h"
66
#include "Core/Config.h"
67
#include "Core/ConfigValues.h"
68
#include "SDLGLGraphicsContext.h"
69
#include "SDLVulkanGraphicsContext.h"
70
71
#if PPSSPP_PLATFORM(MAC)
72
#include "SDL2/SDL_vulkan.h"
73
#else
74
#include "SDL_vulkan.h"
75
#endif
76
77
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
78
#include "UI/DarwinFileSystemServices.h"
79
#endif
80
81
#if PPSSPP_PLATFORM(MAC)
82
#include "CocoaBarItems.h"
83
#endif
84
85
#if PPSSPP_PLATFORM(SWITCH)
86
#define LIBNX_SWKBD_LIMIT 500 // enforced by HOS
87
extern u32 __nx_applet_type; // Not exposed through a header?
88
#endif
89
90
GlobalUIState lastUIState = UISTATE_MENU;
91
GlobalUIState GetUIState();
92
93
static bool g_QuitRequested = false;
94
static bool g_RestartRequested = false;
95
96
static int g_DesktopWidth = 0;
97
static int g_DesktopHeight = 0;
98
static float g_DesktopDPI = 1.0f;
99
static float g_ForcedDPI = 0.0f; // if this is 0.0f, use g_DesktopDPI
100
static float g_RefreshRate = 60.f;
101
static int g_sampleRate = 44100;
102
103
static bool g_rebootEmuThread = false;
104
105
static SDL_AudioSpec g_retFmt;
106
107
static bool g_textFocusChanged;
108
static bool g_textFocus;
109
double g_audioStartTime = 0.0;
110
111
// Window state to be transferred to the main SDL thread.
112
static std::mutex g_mutexWindow;
113
struct WindowState {
114
std::string title;
115
bool toggleFullScreenNextFrame;
116
int toggleFullScreenType;
117
bool clipboardDataAvailable;
118
std::string clipboardString;
119
bool update;
120
};
121
static WindowState g_windowState;
122
123
int getDisplayNumber(void) {
124
int displayNumber = 0;
125
char * displayNumberStr;
126
127
//get environment
128
displayNumberStr=getenv("SDL_VIDEO_FULLSCREEN_HEAD");
129
130
if (displayNumberStr) {
131
displayNumber = atoi(displayNumberStr);
132
}
133
134
return displayNumber;
135
}
136
137
void sdl_mixaudio_callback(void *userdata, Uint8 *stream, int len) {
138
NativeMix((short *)stream, len / (2 * 2), g_sampleRate, userdata);
139
}
140
141
static SDL_AudioDeviceID audioDev = 0;
142
143
// Must be called after NativeInit().
144
static void InitSDLAudioDevice(const std::string &name = "") {
145
SDL_AudioSpec fmt;
146
memset(&fmt, 0, sizeof(fmt));
147
fmt.freq = g_sampleRate;
148
fmt.format = AUDIO_S16;
149
fmt.channels = 2;
150
fmt.samples = std::max(g_Config.iSDLAudioBufferSize, 128);
151
fmt.callback = &sdl_mixaudio_callback;
152
fmt.userdata = nullptr;
153
154
std::string startDevice = name;
155
if (startDevice.empty()) {
156
startDevice = g_Config.sAudioDevice;
157
}
158
159
// List available audio devices before trying to open, for debugging purposes.
160
const int deviceCount = SDL_GetNumAudioDevices(0);
161
if (deviceCount > 0) {
162
INFO_LOG(Log::Audio, "Available audio devices:");
163
for (int i = 0; i < deviceCount; i++) {
164
const char *deviceName = SDL_GetAudioDeviceName(i, 0);
165
INFO_LOG(Log::Audio, " * '%s'", deviceName);
166
}
167
} else {
168
INFO_LOG(Log::Audio, "Failed to list audio devices: retval=%d", deviceCount);
169
}
170
171
audioDev = 0;
172
if (!startDevice.empty()) {
173
INFO_LOG(Log::Audio, "Opening audio device: '%s'", startDevice.c_str());
174
audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
175
if (audioDev <= 0) {
176
WARN_LOG(Log::Audio, "Failed to open audio device '%s'", startDevice.c_str());
177
}
178
}
179
if (audioDev <= 0) {
180
if (audioDev < 0) {
181
WARN_LOG(Log::Audio, "SDL: Error: '%s'. Trying the default audio device", SDL_GetError());
182
} else {
183
INFO_LOG(Log::Audio, "Opening default audio device");
184
}
185
audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
186
}
187
if (audioDev <= 0) {
188
ERROR_LOG(Log::Audio, "Failed to open audio device '%s', second try. Giving up.", SDL_GetError());
189
} else {
190
if (g_retFmt.samples != fmt.samples) // Notify, but still use it
191
ERROR_LOG(Log::Audio, "Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples);
192
if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) {
193
ERROR_LOG(Log::Audio, "Sound buffer format does not match requested format.");
194
ERROR_LOG(Log::Audio, "Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq);
195
ERROR_LOG(Log::Audio, "Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format);
196
ERROR_LOG(Log::Audio, "Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels);
197
ERROR_LOG(Log::Audio, "Provided output format does not match requirement, turning audio off");
198
SDL_CloseAudioDevice(audioDev);
199
}
200
SDL_PauseAudioDevice(audioDev, 0);
201
}
202
}
203
204
static void StopSDLAudioDevice() {
205
if (audioDev > 0) {
206
SDL_PauseAudioDevice(audioDev, 1);
207
SDL_CloseAudioDevice(audioDev);
208
}
209
}
210
211
static void UpdateScreenDPI(SDL_Window *window) {
212
int drawable_width, window_width, window_height;
213
SDL_GetWindowSize(window, &window_width, &window_height);
214
215
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL)
216
SDL_GL_GetDrawableSize(window, &drawable_width, NULL);
217
else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN)
218
SDL_Vulkan_GetDrawableSize(window, &drawable_width, NULL);
219
else {
220
// If we add SDL support for more platforms, we'll end up here.
221
g_DesktopDPI = 1.0f;
222
return;
223
}
224
// Round up a little otherwise there would be a gap sometimes
225
// in fractional scaling
226
g_DesktopDPI = ((float) drawable_width + 1.0f) / window_width;
227
}
228
229
// Simple implementations of System functions
230
231
void System_Toast(std::string_view text) {
232
#ifdef _WIN32
233
std::wstring str = ConvertUTF8ToWString(text);
234
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
235
#else
236
printf("%*.s", (int)text.length(), text.data());
237
#endif
238
}
239
240
void System_ShowKeyboard() {
241
// Irrelevant on PC
242
}
243
244
void System_Vibrate(int length_ms) {
245
// Ignore on PC
246
}
247
248
AudioBackend *System_CreateAudioBackend() {
249
// Use legacy mechanisms.
250
return nullptr;
251
}
252
253
static void InitializeFilters(std::vector<std::string> &filters, BrowseFileType type) {
254
switch (type) {
255
case BrowseFileType::BOOTABLE:
256
filters.push_back("All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)");
257
filters.push_back("*.pbp *.elf *.iso *.cso *.chd *.prx *.zip *.ppdmp");
258
break;
259
case BrowseFileType::INI:
260
filters.push_back("Ini files");
261
filters.push_back("*.ini");
262
break;
263
case BrowseFileType::ZIP:
264
filters.push_back("ZIP files");
265
filters.push_back("*.zip");
266
break;
267
case BrowseFileType::DB:
268
filters.push_back("Cheat db files");
269
filters.push_back("*.db");
270
break;
271
case BrowseFileType::SOUND_EFFECT:
272
filters.push_back("Sound effect files (wav, mp3)");
273
filters.push_back("*.wav *.mp3");
274
break;
275
case BrowseFileType::SYMBOL_MAP:
276
filters.push_back("PPSSPP Symbol Map files (ppmap)");
277
filters.push_back("*.ppmap");
278
break;
279
case BrowseFileType::SYMBOL_MAP_NOCASH:
280
filters.push_back("No$ symbol Map files (sym)");
281
filters.push_back("*.sym");
282
break;
283
case BrowseFileType::ATRAC3:
284
filters.push_back("Atrac3 files (at3)");
285
filters.push_back("*.at3");
286
break;
287
case BrowseFileType::IMAGE:
288
filters.push_back("Pictures (jpg, png)");
289
filters.push_back("*.jpg *.png");
290
break;
291
case BrowseFileType::ANY:
292
break;
293
}
294
filters.push_back("All files (*.*)");
295
filters.push_back("*");
296
}
297
298
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
299
switch (type) {
300
case SystemRequestType::RESTART_APP:
301
g_RestartRequested = true;
302
// TODO: Also save param1 and then split it into an argv.
303
return true;
304
case SystemRequestType::EXIT_APP:
305
// Do a clean exit
306
g_QuitRequested = true;
307
return true;
308
#if PPSSPP_PLATFORM(SWITCH)
309
case SystemRequestType::INPUT_TEXT_MODAL:
310
{
311
// swkbd only works on "real" titles
312
if (__nx_applet_type != AppletType_Application && __nx_applet_type != AppletType_SystemApplication) {
313
g_requestManager.PostSystemFailure(requestId);
314
return true;
315
}
316
317
SwkbdConfig kbd;
318
Result rc = swkbdCreate(&kbd, 0);
319
320
if (R_SUCCEEDED(rc)) {
321
char buf[LIBNX_SWKBD_LIMIT] = {'\0'};
322
swkbdConfigMakePresetDefault(&kbd);
323
324
swkbdConfigSetHeaderText(&kbd, param1.c_str());
325
swkbdConfigSetInitialText(&kbd, param2.c_str());
326
327
rc = swkbdShow(&kbd, buf, sizeof(buf));
328
329
swkbdClose(&kbd);
330
331
g_requestManager.PostSystemSuccess(requestId, buf);
332
return true;
333
}
334
335
g_requestManager.PostSystemFailure(requestId);
336
return true;
337
}
338
#endif // PPSSPP_PLATFORM(SWITCH)
339
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
340
case SystemRequestType::BROWSE_FOR_FILE:
341
{
342
DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {
343
if (success) {
344
g_requestManager.PostSystemSuccess(requestId, path.c_str());
345
} else {
346
g_requestManager.PostSystemFailure(requestId);
347
}
348
};
349
BrowseFileType fileType = (BrowseFileType)param3;
350
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false, fileType);
351
return true;
352
}
353
case SystemRequestType::BROWSE_FOR_IMAGE:
354
{
355
DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {
356
if (success) {
357
g_requestManager.PostSystemSuccess(requestId, path.c_str());
358
} else {
359
g_requestManager.PostSystemFailure(requestId);
360
}
361
};
362
BrowseFileType fileType = BrowseFileType::IMAGE;
363
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false, fileType);
364
return true;
365
}
366
case SystemRequestType::BROWSE_FOR_FOLDER:
367
{
368
DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {
369
if (success) {
370
g_requestManager.PostSystemSuccess(requestId, path.c_str());
371
} else {
372
g_requestManager.PostSystemFailure(requestId);
373
}
374
};
375
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true);
376
return true;
377
}
378
#else
379
case SystemRequestType::BROWSE_FOR_IMAGE:
380
{
381
// TODO: Add non-blocking support.
382
const std::string &title = param1;
383
std::vector<std::string> filters;
384
InitializeFilters(filters, BrowseFileType::IMAGE);
385
std::vector<std::string> result = pfd::open_file(title, "", filters).result();
386
if (!result.empty()) {
387
g_requestManager.PostSystemSuccess(requestId, result[0]);
388
} else {
389
g_requestManager.PostSystemFailure(requestId);
390
}
391
return true;
392
}
393
case SystemRequestType::BROWSE_FOR_FILE:
394
case SystemRequestType::BROWSE_FOR_FILE_SAVE:
395
{
396
// TODO: Add non-blocking support.
397
const BrowseFileType browseType = (BrowseFileType)param3;
398
std::string initialFilename = param2;
399
const std::string &title = param1;
400
std::vector<std::string> filters;
401
InitializeFilters(filters, browseType);
402
if (type == SystemRequestType::BROWSE_FOR_FILE) {
403
std::vector<std::string> result = pfd::open_file(title, initialFilename, filters).result();
404
if (!result.empty()) {
405
g_requestManager.PostSystemSuccess(requestId, result[0]);
406
} else {
407
g_requestManager.PostSystemFailure(requestId);
408
}
409
} else {
410
std::string result = pfd::save_file(title, initialFilename, filters).result();
411
if (!result.empty()) {
412
g_requestManager.PostSystemSuccess(requestId, result);
413
} else {
414
g_requestManager.PostSystemFailure(requestId);
415
}
416
}
417
return true;
418
}
419
case SystemRequestType::BROWSE_FOR_FOLDER:
420
{
421
// TODO: Add non-blocking support.
422
std::string result = pfd::select_folder(param1, param2).result();
423
if (!result.empty()) {
424
g_requestManager.PostSystemSuccess(requestId, result);
425
} else {
426
g_requestManager.PostSystemFailure(requestId);
427
}
428
return true;
429
}
430
#endif
431
case SystemRequestType::TOGGLE_FULLSCREEN_STATE:
432
{
433
std::lock_guard<std::mutex> guard(g_mutexWindow);
434
g_windowState.update = true;
435
g_windowState.toggleFullScreenNextFrame = true;
436
if (param1 == "1") {
437
g_windowState.toggleFullScreenType = 1;
438
} else if (param1 == "0") {
439
g_windowState.toggleFullScreenType = 0;
440
} else {
441
// Just toggle.
442
g_windowState.toggleFullScreenType = -1;
443
}
444
return true;
445
}
446
case SystemRequestType::SET_WINDOW_TITLE:
447
{
448
std::lock_guard<std::mutex> guard(g_mutexWindow);
449
const char *app_name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold" : "PPSSPP";
450
g_windowState.title = param1.empty() ? app_name : param1;
451
g_windowState.update = true;
452
return true;
453
}
454
case SystemRequestType::COPY_TO_CLIPBOARD:
455
{
456
std::lock_guard<std::mutex> guard(g_mutexWindow);
457
g_windowState.clipboardString = param1;
458
g_windowState.clipboardDataAvailable = true;
459
g_windowState.update = true;
460
return true;
461
}
462
case SystemRequestType::SHOW_FILE_IN_FOLDER:
463
{
464
#if PPSSPP_PLATFORM(WINDOWS)
465
SFGAOF flags;
466
PIDLIST_ABSOLUTE pidl = nullptr;
467
HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str(), nullptr, &pidl, 0, &flags);
468
if (pidl) {
469
if (SUCCEEDED(hr))
470
SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
471
CoTaskMemFree(pidl);
472
}
473
#elif PPSSPP_PLATFORM(MAC)
474
OSXShowInFinder(param1.c_str());
475
#elif (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
476
pid_t pid = fork();
477
if (pid < 0)
478
return true;
479
480
if (pid == 0) {
481
execlp("xdg-open", "xdg-open", param1.c_str(), nullptr);
482
exit(1);
483
}
484
#endif /* PPSSPP_PLATFORM(WINDOWS) */
485
return true;
486
}
487
case SystemRequestType::NOTIFY_UI_EVENT:
488
{
489
switch ((UIEventNotification)param3) {
490
case UIEventNotification::TEXT_GOTFOCUS:
491
g_textFocus = true;
492
g_textFocusChanged = true;
493
break;
494
case UIEventNotification::POPUP_CLOSED:
495
case UIEventNotification::TEXT_LOSTFOCUS:
496
g_textFocus = false;
497
g_textFocusChanged = true;
498
break;
499
default:
500
break;
501
}
502
return true;
503
}
504
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
505
INFO_LOG(Log::UI, "SET_KEEP_SCREEN_BRIGHT not implemented.");
506
return true;
507
default:
508
INFO_LOG(Log::UI, "Unhandled system request %s", RequestTypeAsString(type));
509
return false;
510
}
511
}
512
513
void System_AskForPermission(SystemPermission permission) {}
514
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
515
516
void System_LaunchUrl(LaunchUrlType urlType, const char *url) {
517
switch (urlType) {
518
case LaunchUrlType::BROWSER_URL:
519
case LaunchUrlType::MARKET_URL:
520
{
521
#if PPSSPP_PLATFORM(SWITCH)
522
Uuid uuid = { 0 };
523
WebWifiConfig conf;
524
webWifiCreate(&conf, NULL, url, uuid, 0);
525
webWifiShow(&conf, NULL);
526
#elif defined(MOBILE_DEVICE)
527
INFO_LOG(Log::System, "Would have gone to %s but LaunchBrowser is not implemented on this platform", url);
528
#elif defined(_WIN32)
529
std::wstring wurl = ConvertUTF8ToWString(url);
530
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
531
#elif defined(__APPLE__)
532
OSXOpenURL(url);
533
#else
534
std::string command = std::string("xdg-open ") + url;
535
int err = system(command.c_str());
536
if (err) {
537
INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);
538
}
539
#endif
540
break;
541
}
542
case LaunchUrlType::EMAIL_ADDRESS:
543
{
544
#if defined(MOBILE_DEVICE)
545
INFO_LOG(Log::System, "Would have opened your email client for %s but LaunchEmail is not implemented on this platform", url);
546
#elif defined(_WIN32)
547
std::wstring mailto = std::wstring(L"mailto:") + ConvertUTF8ToWString(url);
548
ShellExecute(NULL, L"open", mailto.c_str(), NULL, NULL, SW_SHOWNORMAL);
549
#elif defined(__APPLE__)
550
std::string mailToURL = std::string("mailto:") + url;
551
OSXOpenURL(mailToURL.c_str());
552
#else
553
std::string command = std::string("xdg-email ") + url;
554
int err = system(command.c_str());
555
if (err) {
556
INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);
557
}
558
#endif
559
break;
560
}
561
}
562
}
563
564
std::string System_GetProperty(SystemProperty prop) {
565
switch (prop) {
566
case SYSPROP_NAME:
567
#ifdef _WIN32
568
return "SDL:Windows";
569
#elif __linux__
570
return "SDL:Linux";
571
#elif __APPLE__
572
return "SDL:macOS";
573
#elif PPSSPP_PLATFORM(SWITCH)
574
return "SDL:Horizon";
575
#else
576
return "SDL:";
577
#endif
578
case SYSPROP_LANGREGION: {
579
// Get user-preferred locale from OS
580
setlocale(LC_ALL, "");
581
std::string locale(setlocale(LC_ALL, NULL));
582
// Set c and c++ strings back to POSIX
583
std::locale::global(std::locale("POSIX"));
584
if (!locale.empty()) {
585
// Technically, this is an opaque string, but try to find the locale code.
586
size_t messagesPos = locale.find("LC_MESSAGES=");
587
if (messagesPos != std::string::npos) {
588
messagesPos += strlen("LC_MESSAGES=");
589
size_t semi = locale.find(';', messagesPos);
590
locale = locale.substr(messagesPos, semi - messagesPos);
591
}
592
593
if (locale.find("_", 0) != std::string::npos) {
594
if (locale.find(".", 0) != std::string::npos) {
595
return locale.substr(0, locale.find(".",0));
596
}
597
return locale;
598
}
599
}
600
return "en_US";
601
}
602
case SYSPROP_CLIPBOARD_TEXT:
603
return SDL_HasClipboardText() ? SDL_GetClipboardText() : "";
604
case SYSPROP_AUDIO_DEVICE_LIST:
605
{
606
std::string result;
607
for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {
608
const char *name = SDL_GetAudioDeviceName(i, 0);
609
if (!name) {
610
continue;
611
}
612
613
if (i == 0) {
614
result = name;
615
} else {
616
result.append(1, '\0');
617
result.append(name);
618
}
619
}
620
return result;
621
}
622
case SYSPROP_BUILD_VERSION:
623
return PPSSPP_GIT_VERSION;
624
case SYSPROP_USER_DOCUMENTS_DIR:
625
{
626
const char *home = getenv("HOME");
627
return home ? std::string(home) : "/";
628
}
629
default:
630
return "";
631
}
632
}
633
634
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
635
std::vector<std::string> result;
636
637
switch (prop) {
638
case SYSPROP_TEMP_DIRS:
639
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
640
result.push_back(getenv("TMPDIR"));
641
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
642
result.push_back(getenv("TMP"));
643
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
644
result.push_back(getenv("TEMP"));
645
return result;
646
647
default:
648
return result;
649
}
650
}
651
652
#if PPSSPP_PLATFORM(MAC)
653
extern "C" {
654
int Apple_GetCurrentBatteryCapacity();
655
}
656
#endif
657
658
int64_t System_GetPropertyInt(SystemProperty prop) {
659
switch (prop) {
660
case SYSPROP_AUDIO_SAMPLE_RATE:
661
return g_retFmt.freq;
662
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
663
return g_retFmt.samples;
664
case SYSPROP_DEVICE_TYPE:
665
#if defined(MOBILE_DEVICE)
666
return DEVICE_TYPE_MOBILE;
667
#else
668
return DEVICE_TYPE_DESKTOP;
669
#endif
670
case SYSPROP_DISPLAY_COUNT:
671
return SDL_GetNumVideoDisplays();
672
case SYSPROP_KEYBOARD_LAYOUT:
673
{
674
char q, w, y;
675
q = SDL_GetKeyFromScancode(SDL_SCANCODE_Q);
676
w = SDL_GetKeyFromScancode(SDL_SCANCODE_W);
677
y = SDL_GetKeyFromScancode(SDL_SCANCODE_Y);
678
if (q == 'a' && w == 'z' && y == 'y')
679
return KEYBOARD_LAYOUT_AZERTY;
680
else if (q == 'q' && w == 'w' && y == 'z')
681
return KEYBOARD_LAYOUT_QWERTZ;
682
return KEYBOARD_LAYOUT_QWERTY;
683
}
684
case SYSPROP_DISPLAY_XRES:
685
return g_DesktopWidth;
686
case SYSPROP_DISPLAY_YRES:
687
return g_DesktopHeight;
688
case SYSPROP_BATTERY_PERCENTAGE:
689
#if PPSSPP_PLATFORM(MAC)
690
// Let's keep using the old code on Mac for safety. Evaluate later if to be deleted.
691
return Apple_GetCurrentBatteryCapacity();
692
#else
693
{
694
int seconds = 0;
695
int percentage = 0;
696
SDL_GetPowerInfo(&seconds, &percentage);
697
return percentage;
698
}
699
#endif
700
default:
701
return -1;
702
}
703
}
704
705
float System_GetPropertyFloat(SystemProperty prop) {
706
switch (prop) {
707
case SYSPROP_DISPLAY_REFRESH_RATE:
708
return g_RefreshRate;
709
case SYSPROP_DISPLAY_DPI:
710
return (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI) * 96.0;
711
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
712
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
713
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
714
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
715
return 0.0f;
716
default:
717
return -1;
718
}
719
}
720
721
bool System_GetPropertyBool(SystemProperty prop) {
722
switch (prop) {
723
case SYSPROP_HAS_TEXT_CLIPBOARD:
724
case SYSPROP_CAN_SHOW_FILE:
725
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
726
return true;
727
#else
728
return false;
729
#endif
730
case SYSPROP_HAS_OPEN_DIRECTORY:
731
#if PPSSPP_PLATFORM(WINDOWS)
732
return true;
733
#elif PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
734
return true;
735
#endif
736
case SYSPROP_HAS_BACK_BUTTON:
737
return true;
738
#if PPSSPP_PLATFORM(SWITCH)
739
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
740
return __nx_applet_type == AppletType_Application || __nx_applet_type != AppletType_SystemApplication;
741
#endif
742
case SYSPROP_HAS_KEYBOARD:
743
return true;
744
case SYSPROP_APP_GOLD:
745
#ifdef GOLD
746
return true;
747
#else
748
return false;
749
#endif
750
case SYSPROP_CAN_JIT:
751
return true;
752
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
753
return true; // FileUtil.cpp: OpenFileInEditor
754
#ifndef HTTPS_NOT_AVAILABLE
755
case SYSPROP_SUPPORTS_HTTPS:
756
return !g_Config.bDisableHTTPS;
757
#endif
758
case SYSPROP_HAS_FOLDER_BROWSER:
759
case SYSPROP_HAS_FILE_BROWSER:
760
#if PPSSPP_PLATFORM(MAC)
761
return true;
762
#else
763
return pfd::settings::available();
764
#endif
765
case SYSPROP_HAS_ACCELEROMETER:
766
#if defined(MOBILE_DEVICE)
767
return true;
768
#else
769
return false;
770
#endif
771
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
772
return true;
773
case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO:
774
#if PPSSPP_ARCH(64BIT) && !defined(MOBILE_DEVICE)
775
return true;
776
#else
777
return false;
778
#endif
779
// hack for testing - do not commit
780
case SYSPROP_USE_IAP:
781
return false;
782
default:
783
return false;
784
}
785
}
786
787
void System_Notify(SystemNotification notification) {
788
switch (notification) {
789
case SystemNotification::AUDIO_RESET_DEVICE:
790
StopSDLAudioDevice();
791
InitSDLAudioDevice();
792
break;
793
794
default:
795
break;
796
}
797
}
798
799
// returns -1 on failure
800
static int parseInt(const char *str) {
801
int val;
802
int retval = sscanf(str, "%d", &val);
803
printf("%i = scanf %s\n", retval, str);
804
if (retval != 1) {
805
return -1;
806
} else {
807
return val;
808
}
809
}
810
811
static float parseFloat(const char *str) {
812
float val;
813
int retval = sscanf(str, "%f", &val);
814
printf("%i = sscanf %s\n", retval, str);
815
if (retval != 1) {
816
return -1.0f;
817
} else {
818
return val;
819
}
820
}
821
822
void UpdateWindowState(SDL_Window *window) {
823
SDL_SetWindowTitle(window, g_windowState.title.c_str());
824
if (g_windowState.toggleFullScreenNextFrame) {
825
g_windowState.toggleFullScreenNextFrame = false;
826
827
Uint32 window_flags = SDL_GetWindowFlags(window);
828
if (g_windowState.toggleFullScreenType == -1) {
829
window_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
830
} else if (g_windowState.toggleFullScreenType == 1) {
831
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
832
} else {
833
window_flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP;
834
}
835
SDL_SetWindowFullscreen(window, window_flags);
836
}
837
if (g_windowState.clipboardDataAvailable) {
838
SDL_SetClipboardText(g_windowState.clipboardString.c_str());
839
g_windowState.clipboardDataAvailable = false;
840
g_windowState.clipboardString.clear();
841
}
842
g_windowState.update = false;
843
}
844
845
enum class EmuThreadState {
846
DISABLED,
847
START_REQUESTED,
848
RUNNING,
849
QUIT_REQUESTED,
850
STOPPED,
851
};
852
853
static std::thread emuThread;
854
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
855
856
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
857
SetCurrentThreadName("EmuThread");
858
859
// There's no real requirement that NativeInit happen on this thread.
860
// We just call the update/render loop here.
861
emuThreadState = (int)EmuThreadState::RUNNING;
862
863
NativeInitGraphics(graphicsContext);
864
865
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
866
NativeFrame(graphicsContext);
867
}
868
emuThreadState = (int)EmuThreadState::STOPPED;
869
graphicsContext->StopThread();
870
871
NativeShutdownGraphics();
872
}
873
874
static void EmuThreadStart(GraphicsContext *context) {
875
emuThreadState = (int)EmuThreadState::START_REQUESTED;
876
emuThread = std::thread(&EmuThreadFunc, context);
877
}
878
879
static void EmuThreadStop(const char *reason) {
880
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
881
}
882
883
static void EmuThreadJoin() {
884
emuThread.join();
885
emuThread = std::thread();
886
}
887
888
struct InputStateTracker {
889
void MouseCaptureControl() {
890
bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_IsMappingMouseInput);
891
if (mouseCaptured != captureMouseCondition) {
892
mouseCaptured = captureMouseCondition;
893
if (captureMouseCondition)
894
SDL_SetRelativeMouseMode(SDL_TRUE);
895
else
896
SDL_SetRelativeMouseMode(SDL_FALSE);
897
}
898
}
899
900
int mouseDown; // bitflags
901
bool mouseCaptured;
902
};
903
904
SDL_Cursor *g_builtinCursors[SDL_NUM_SYSTEM_CURSORS];
905
906
static SDL_SystemCursor GetSDLCursorFromImgui(ImGuiMouseCursor cursor) {
907
switch (cursor) {
908
case ImGuiMouseCursor_Arrow: return SDL_SYSTEM_CURSOR_ARROW; break;
909
case ImGuiMouseCursor_TextInput: return SDL_SYSTEM_CURSOR_IBEAM; break;
910
case ImGuiMouseCursor_ResizeAll: return SDL_SYSTEM_CURSOR_SIZEALL; break;
911
case ImGuiMouseCursor_ResizeEW: return SDL_SYSTEM_CURSOR_SIZEWE; break;
912
case ImGuiMouseCursor_ResizeNS: return SDL_SYSTEM_CURSOR_SIZENS; break;
913
case ImGuiMouseCursor_ResizeNESW: return SDL_SYSTEM_CURSOR_SIZENESW; break;
914
case ImGuiMouseCursor_ResizeNWSE: return SDL_SYSTEM_CURSOR_SIZENWSE; break;
915
case ImGuiMouseCursor_Hand: return SDL_SYSTEM_CURSOR_HAND; break;
916
case ImGuiMouseCursor_NotAllowed: return SDL_SYSTEM_CURSOR_NO; break;
917
default: return SDL_SYSTEM_CURSOR_ARROW; break;
918
}
919
}
920
921
void UpdateCursor() {
922
static SDL_SystemCursor curCursor = SDL_SYSTEM_CURSOR_ARROW;
923
auto cursor = ImGui_ImplPlatform_GetCursor();
924
SDL_SystemCursor sysCursor = GetSDLCursorFromImgui(cursor);
925
if (sysCursor != curCursor) {
926
curCursor = sysCursor;
927
if (!g_builtinCursors[(int)curCursor]) {
928
g_builtinCursors[(int)curCursor] = SDL_CreateSystemCursor(curCursor);
929
}
930
}
931
SDL_SetCursor(g_builtinCursors[(int)curCursor]);
932
}
933
934
static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputStateTracker *inputTracker) {
935
// We have to juggle around 3 kinds of "DPI spaces" if a logical DPI is
936
// provided (through --dpi, it is equal to system DPI if unspecified):
937
// - SDL gives us motion events in "system DPI" points
938
// - Native_UpdateScreenScale expects pixels, so in a way "96 DPI" points
939
// - The UI code expects motion events in "logical DPI" points
940
float mx = event.motion.x * g_DesktopDPI * g_display.dpi_scale_x;
941
float my = event.motion.y * g_DesktopDPI * g_display.dpi_scale_x;
942
943
switch (event.type) {
944
case SDL_QUIT:
945
g_QuitRequested = 1;
946
break;
947
948
#if !defined(MOBILE_DEVICE)
949
case SDL_WINDOWEVENT:
950
switch (event.window.event) {
951
case SDL_WINDOWEVENT_SIZE_CHANGED: // better than RESIZED, more general
952
{
953
int new_width = event.window.data1;
954
int new_height = event.window.data2;
955
956
// The size given by SDL is in point-units, convert these to
957
// pixels before passing to Native_UpdateScreenScale()
958
int new_width_px = new_width * g_DesktopDPI;
959
int new_height_px = new_height * g_DesktopDPI;
960
961
Native_NotifyWindowHidden(false);
962
963
Uint32 window_flags = SDL_GetWindowFlags(window);
964
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
965
966
// This one calls NativeResized if the size changed.
967
Native_UpdateScreenScale(new_width_px, new_height_px, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
968
969
// Set variable here in case fullscreen was toggled by hotkey
970
if (g_Config.UseFullScreen() != fullscreen) {
971
g_Config.bFullScreen = fullscreen;
972
g_Config.iForceFullScreen = -1;
973
} else {
974
// It is possible for the monitor to change DPI, so recalculate
975
// DPI on each resize event.
976
UpdateScreenDPI(window);
977
}
978
979
if (!g_Config.bFullScreen) {
980
g_Config.iWindowWidth = new_width;
981
g_Config.iWindowHeight = new_height;
982
}
983
// Hide/Show cursor correctly toggling fullscreen
984
if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) {
985
SDL_ShowCursor(SDL_DISABLE);
986
} else if (lastUIState != UISTATE_INGAME || !fullscreen) {
987
SDL_ShowCursor(SDL_ENABLE);
988
}
989
break;
990
}
991
992
case SDL_WINDOWEVENT_MOVED:
993
{
994
Uint32 window_flags = SDL_GetWindowFlags(window);
995
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
996
if (!fullscreen) {
997
g_Config.iWindowX = (int)event.window.data1;
998
g_Config.iWindowY = (int)event.window.data2;
999
}
1000
break;
1001
}
1002
1003
case SDL_WINDOWEVENT_MINIMIZED:
1004
case SDL_WINDOWEVENT_HIDDEN:
1005
Native_NotifyWindowHidden(true);
1006
break;
1007
case SDL_WINDOWEVENT_EXPOSED:
1008
case SDL_WINDOWEVENT_SHOWN:
1009
Native_NotifyWindowHidden(false);
1010
break;
1011
default:
1012
break;
1013
}
1014
break;
1015
#endif
1016
case SDL_KEYDOWN:
1017
{
1018
if (event.key.repeat > 0) { break;}
1019
int k = event.key.keysym.sym;
1020
KeyInput key;
1021
key.flags = KEY_DOWN;
1022
auto mapped = KeyMapRawSDLtoNative.find(k);
1023
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
1024
break;
1025
}
1026
key.keyCode = mapped->second;
1027
key.deviceId = DEVICE_ID_KEYBOARD;
1028
NativeKey(key);
1029
1030
#ifdef _DEBUG
1031
if (k == SDLK_F7) {
1032
printf("f7 pressed - rebooting emuthread\n");
1033
g_rebootEmuThread = true;
1034
}
1035
#endif
1036
// Convenience subset of what
1037
// "Enable standard shortcut keys"
1038
// does on Windows.
1039
if(g_Config.bSystemControls) {
1040
bool ctrl = bool(event.key.keysym.mod & KMOD_CTRL);
1041
if (ctrl && (k == SDLK_w))
1042
{
1043
if (Core_IsStepping())
1044
Core_Resume();
1045
Core_Stop();
1046
System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);
1047
// NOTE: Unlike Windows version, this
1048
// does not need Core_WaitInactive();
1049
// since SDL does not have a separate
1050
// UI thread.
1051
}
1052
1053
/*
1054
// TODO: Enable this?
1055
if (k == SDLK_F11) {
1056
#if !defined(MOBILE_DEVICE)
1057
g_Config.bFullScreen = !g_Config.bFullScreen;
1058
System_ToggleFullscreenState("");
1059
#endif
1060
}
1061
*/
1062
}
1063
break;
1064
}
1065
case SDL_KEYUP:
1066
{
1067
if (event.key.repeat > 0) { break;}
1068
int k = event.key.keysym.sym;
1069
KeyInput key;
1070
key.flags = KEY_UP;
1071
auto mapped = KeyMapRawSDLtoNative.find(k);
1072
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
1073
break;
1074
}
1075
key.keyCode = mapped->second;
1076
key.deviceId = DEVICE_ID_KEYBOARD;
1077
NativeKey(key);
1078
break;
1079
}
1080
case SDL_TEXTINPUT:
1081
{
1082
int pos = 0;
1083
int c = u8_nextchar(event.text.text, &pos, strlen(event.text.text));
1084
KeyInput key;
1085
key.flags = KEY_CHAR;
1086
key.unicodeChar = c;
1087
key.deviceId = DEVICE_ID_KEYBOARD;
1088
NativeKey(key);
1089
break;
1090
}
1091
// This behavior doesn't feel right on a macbook with a touchpad.
1092
#if !PPSSPP_PLATFORM(MAC)
1093
case SDL_FINGERMOTION:
1094
{
1095
int w, h;
1096
SDL_GetWindowSize(window, &w, &h);
1097
TouchInput input{};
1098
input.id = event.tfinger.fingerId;
1099
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1100
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1101
input.flags = TOUCH_MOVE;
1102
input.timestamp = event.tfinger.timestamp;
1103
NativeTouch(input);
1104
break;
1105
}
1106
case SDL_FINGERDOWN:
1107
{
1108
int w, h;
1109
SDL_GetWindowSize(window, &w, &h);
1110
TouchInput input{};
1111
input.id = event.tfinger.fingerId;
1112
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1113
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1114
input.flags = TOUCH_DOWN;
1115
input.timestamp = event.tfinger.timestamp;
1116
NativeTouch(input);
1117
1118
KeyInput key{};
1119
key.deviceId = DEVICE_ID_MOUSE;
1120
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
1121
key.flags = KEY_DOWN;
1122
NativeKey(key);
1123
break;
1124
}
1125
case SDL_FINGERUP:
1126
{
1127
int w, h;
1128
SDL_GetWindowSize(window, &w, &h);
1129
TouchInput input{};
1130
input.id = event.tfinger.fingerId;
1131
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1132
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1133
input.flags = TOUCH_UP;
1134
input.timestamp = event.tfinger.timestamp;
1135
NativeTouch(input);
1136
1137
KeyInput key;
1138
key.deviceId = DEVICE_ID_MOUSE;
1139
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
1140
key.flags = KEY_UP;
1141
NativeKey(key);
1142
break;
1143
}
1144
#endif
1145
case SDL_MOUSEBUTTONDOWN:
1146
switch (event.button.button) {
1147
case SDL_BUTTON_LEFT:
1148
{
1149
inputTracker->mouseDown |= 1;
1150
TouchInput input{};
1151
input.x = mx;
1152
input.y = my;
1153
input.flags = TOUCH_DOWN | TOUCH_MOUSE;
1154
input.buttons = 1;
1155
input.id = 0;
1156
NativeTouch(input);
1157
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN);
1158
NativeKey(key);
1159
}
1160
break;
1161
case SDL_BUTTON_RIGHT:
1162
{
1163
inputTracker->mouseDown |= 2;
1164
TouchInput input{};
1165
input.x = mx;
1166
input.y = my;
1167
input.flags = TOUCH_DOWN | TOUCH_MOUSE;
1168
input.buttons = 2;
1169
input.id = 0;
1170
NativeTouch(input);
1171
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN);
1172
NativeKey(key);
1173
}
1174
break;
1175
case SDL_BUTTON_MIDDLE:
1176
{
1177
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_DOWN);
1178
NativeKey(key);
1179
}
1180
break;
1181
case SDL_BUTTON_X1:
1182
{
1183
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_DOWN);
1184
NativeKey(key);
1185
}
1186
break;
1187
case SDL_BUTTON_X2:
1188
{
1189
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_DOWN);
1190
NativeKey(key);
1191
}
1192
break;
1193
}
1194
break;
1195
case SDL_MOUSEWHEEL:
1196
{
1197
KeyInput key{};
1198
key.deviceId = DEVICE_ID_MOUSE;
1199
key.flags = KEY_DOWN;
1200
#if SDL_VERSION_ATLEAST(2, 0, 18)
1201
if (event.wheel.preciseY != 0.0f) {
1202
// Should the scale be DPI-driven?
1203
const float scale = 30.0f;
1204
key.keyCode = event.wheel.preciseY > 0 ? NKCODE_EXT_MOUSEWHEEL_UP : NKCODE_EXT_MOUSEWHEEL_DOWN;
1205
key.flags |= KEY_HASWHEELDELTA;
1206
int wheelDelta = event.wheel.preciseY * scale;
1207
if (event.wheel.preciseY < 0) {
1208
wheelDelta = -wheelDelta;
1209
}
1210
key.flags |= wheelDelta << 16;
1211
NativeKey(key);
1212
break;
1213
}
1214
#endif
1215
if (event.wheel.y > 0) {
1216
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
1217
NativeKey(key);
1218
} else if (event.wheel.y < 0) {
1219
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
1220
NativeKey(key);
1221
}
1222
break;
1223
}
1224
case SDL_MOUSEMOTION:
1225
{
1226
TouchInput input{};
1227
input.x = mx;
1228
input.y = my;
1229
input.flags = TOUCH_MOVE | TOUCH_MOUSE;
1230
input.buttons = inputTracker->mouseDown;
1231
input.id = 0;
1232
NativeTouch(input);
1233
NativeMouseDelta(event.motion.xrel, event.motion.yrel);
1234
1235
UpdateCursor();
1236
break;
1237
}
1238
case SDL_MOUSEBUTTONUP:
1239
switch (event.button.button) {
1240
case SDL_BUTTON_LEFT:
1241
{
1242
inputTracker->mouseDown &= ~1;
1243
TouchInput input{};
1244
input.x = mx;
1245
input.y = my;
1246
input.flags = TOUCH_UP | TOUCH_MOUSE;
1247
input.buttons = 1;
1248
NativeTouch(input);
1249
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP);
1250
NativeKey(key);
1251
}
1252
break;
1253
case SDL_BUTTON_RIGHT:
1254
{
1255
inputTracker->mouseDown &= ~2;
1256
// Right button only emits mouse move events. This is weird,
1257
// but consistent with Windows. Needs cleanup.
1258
TouchInput input{};
1259
input.x = mx;
1260
input.y = my;
1261
input.flags = TOUCH_UP | TOUCH_MOUSE;
1262
input.buttons = 2;
1263
NativeTouch(input);
1264
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP);
1265
NativeKey(key);
1266
}
1267
break;
1268
case SDL_BUTTON_MIDDLE:
1269
{
1270
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_UP);
1271
NativeKey(key);
1272
}
1273
break;
1274
case SDL_BUTTON_X1:
1275
{
1276
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_UP);
1277
NativeKey(key);
1278
}
1279
break;
1280
case SDL_BUTTON_X2:
1281
{
1282
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_UP);
1283
NativeKey(key);
1284
}
1285
break;
1286
}
1287
break;
1288
1289
#if SDL_VERSION_ATLEAST(2, 0, 4)
1290
case SDL_AUDIODEVICEADDED:
1291
// Automatically switch to the new device.
1292
if (event.adevice.iscapture == 0) {
1293
const char *name = SDL_GetAudioDeviceName(event.adevice.which, 0);
1294
if (!name) {
1295
INFO_LOG(Log::Audio, "Got bogus new audio device notification");
1296
break;
1297
}
1298
// Don't start auto switching for a couple of seconds, because some devices init on start.
1299
bool doAutoSwitch = g_Config.bAutoAudioDevice;
1300
if ((time_now_d() - g_audioStartTime) < 3.0) {
1301
INFO_LOG(Log::Audio, "Ignoring new audio device: %s (current: %s)", name, g_Config.sAudioDevice.c_str());
1302
doAutoSwitch = false;
1303
}
1304
if (doAutoSwitch || g_Config.sAudioDevice == name) {
1305
StopSDLAudioDevice();
1306
1307
INFO_LOG(Log::Audio, "!!! Auto-switching to new audio device: '%s'", name);
1308
1309
InitSDLAudioDevice(name ? name : "");
1310
}
1311
}
1312
break;
1313
case SDL_AUDIODEVICEREMOVED:
1314
if (event.adevice.iscapture == 0 && event.adevice.which == audioDev) {
1315
StopSDLAudioDevice();
1316
INFO_LOG(Log::Audio, "Audio device removed, reselecting");
1317
InitSDLAudioDevice();
1318
}
1319
break;
1320
#endif
1321
1322
default:
1323
if (joystick) {
1324
joystick->ProcessInput(event);
1325
}
1326
break;
1327
}
1328
}
1329
1330
void UpdateTextFocus() {
1331
if (g_textFocusChanged) {
1332
INFO_LOG(Log::System, "Updating text focus: %d", g_textFocus);
1333
if (g_textFocus) {
1334
SDL_StartTextInput();
1335
} else {
1336
SDL_StopTextInput();
1337
}
1338
g_textFocusChanged = false;
1339
}
1340
}
1341
1342
void UpdateSDLCursor() {
1343
#if !defined(MOBILE_DEVICE)
1344
if (lastUIState != GetUIState()) {
1345
lastUIState = GetUIState();
1346
if (lastUIState == UISTATE_INGAME && g_Config.UseFullScreen() && !g_Config.bShowTouchControls)
1347
SDL_ShowCursor(SDL_DISABLE);
1348
if (lastUIState != UISTATE_INGAME || !g_Config.UseFullScreen())
1349
SDL_ShowCursor(SDL_ENABLE);
1350
}
1351
#endif
1352
}
1353
1354
#ifdef _WIN32
1355
#undef main
1356
#endif
1357
int main(int argc, char *argv[]) {
1358
for (int i = 1; i < argc; i++) {
1359
if (!strcmp(argv[i], "--version")) {
1360
printf("%s\n", PPSSPP_GIT_VERSION);
1361
return 0;
1362
}
1363
}
1364
1365
TimeInit();
1366
1367
g_logManager.EnableOutput(LogOutput::Stdio);
1368
1369
#ifdef HAVE_LIBNX
1370
socketInitializeDefault();
1371
nxlinkStdio();
1372
#else // HAVE_LIBNX
1373
// Ignore sigpipe.
1374
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
1375
perror("Unable to ignore SIGPIPE");
1376
}
1377
#endif // HAVE_LIBNX
1378
1379
PROFILE_INIT();
1380
glslang::InitializeProcess();
1381
1382
#if PPSSPP_PLATFORM(RPI)
1383
bcm_host_init();
1384
#endif
1385
putenv((char*)"SDL_VIDEO_CENTERED=1");
1386
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
1387
1388
#ifdef SDL_HINT_TOUCH_MOUSE_EVENTS
1389
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
1390
#endif
1391
1392
bool vulkanMayBeAvailable = false;
1393
if (VulkanMayBeAvailable()) {
1394
printf("DEBUG: Vulkan might be available.\n");
1395
vulkanMayBeAvailable = true;
1396
} else {
1397
printf("DEBUG: Vulkan is not available, not using Vulkan.\n");
1398
}
1399
1400
SDL_version compiled;
1401
SDL_version linked;
1402
int set_xres = -1;
1403
int set_yres = -1;
1404
bool portrait = false;
1405
bool set_ipad = false;
1406
float set_dpi = 0.0f;
1407
float set_scale = 1.0f;
1408
1409
// Produce a new set of arguments with the ones we skip.
1410
int remain_argc = 1;
1411
const char *remain_argv[256] = { argv[0] };
1412
1413
Uint32 mode = 0;
1414
for (int i = 1; i < argc; i++) {
1415
if (!strcmp(argv[i],"--fullscreen")) {
1416
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1417
g_Config.iForceFullScreen = 1;
1418
} else if (set_xres == -2)
1419
set_xres = parseInt(argv[i]);
1420
else if (set_yres == -2)
1421
set_yres = parseInt(argv[i]);
1422
else if (set_dpi == -2)
1423
set_dpi = parseFloat(argv[i]);
1424
else if (set_scale == -2)
1425
set_scale = parseFloat(argv[i]);
1426
else if (!strcmp(argv[i],"--xres"))
1427
set_xres = -2;
1428
else if (!strcmp(argv[i],"--yres"))
1429
set_yres = -2;
1430
else if (!strcmp(argv[i],"--dpi"))
1431
set_dpi = -2;
1432
else if (!strcmp(argv[i],"--scale"))
1433
set_scale = -2;
1434
else if (!strcmp(argv[i],"--ipad"))
1435
set_ipad = true;
1436
else if (!strcmp(argv[i],"--portrait"))
1437
portrait = true;
1438
else {
1439
remain_argv[remain_argc++] = argv[i];
1440
}
1441
}
1442
1443
std::string app_name;
1444
std::string app_name_nice;
1445
std::string version;
1446
bool landscape;
1447
NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version);
1448
1449
bool joystick_enabled = true;
1450
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {
1451
fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");
1452
joystick_enabled = false;
1453
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
1454
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
1455
return 1;
1456
}
1457
}
1458
1459
SDL_VERSION(&compiled);
1460
SDL_GetVersion(&linked);
1461
printf("Info: We compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch);
1462
if (compiled.minor != linked.minor || compiled.patch != linked.patch) {
1463
printf(", but we are linking against SDL version %d.%d.%d., be aware that this can lead to unexpected behaviors\n", linked.major, linked.minor, linked.patch);
1464
} else {
1465
printf(" and we are linking against SDL version %d.%d.%d. :)\n", linked.major, linked.minor, linked.patch);
1466
}
1467
1468
// Get the video info before doing anything else, so we don't get skewed resolution results.
1469
// TODO: support multiple displays correctly
1470
SDL_DisplayMode displayMode;
1471
int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode);
1472
if (should_be_zero != 0) {
1473
fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError());
1474
return 1;
1475
}
1476
g_DesktopWidth = displayMode.w;
1477
g_DesktopHeight = displayMode.h;
1478
g_RefreshRate = displayMode.refresh_rate;
1479
1480
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
1481
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
1482
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
1483
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
1484
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
1485
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
1486
1487
// Force fullscreen if the resolution is too low to run windowed.
1488
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
1489
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1490
}
1491
1492
// If we're on mobile, don't try for windowed either.
1493
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
1494
mode |= SDL_WINDOW_FULLSCREEN;
1495
#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
1496
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1497
#else
1498
mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
1499
#endif
1500
1501
if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) {
1502
g_display.pixel_xres = g_DesktopWidth;
1503
g_display.pixel_yres = g_DesktopHeight;
1504
if (g_Config.iForceFullScreen == -1)
1505
g_Config.bFullScreen = true;
1506
} else {
1507
// set a sensible default resolution (2x)
1508
g_display.pixel_xres = 480 * 2 * set_scale;
1509
g_display.pixel_yres = 272 * 2 * set_scale;
1510
if (portrait) {
1511
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1512
}
1513
if (g_Config.iForceFullScreen == -1)
1514
g_Config.bFullScreen = false;
1515
}
1516
1517
if (set_ipad) {
1518
g_display.pixel_xres = 1024;
1519
g_display.pixel_yres = 768;
1520
}
1521
if (!landscape) {
1522
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1523
}
1524
1525
if (set_xres > 0) {
1526
g_display.pixel_xres = set_xres;
1527
}
1528
if (set_yres > 0) {
1529
g_display.pixel_yres = set_yres;
1530
}
1531
if (set_dpi > 0) {
1532
g_ForcedDPI = set_dpi;
1533
}
1534
1535
// Mac / Linux
1536
char path[2048];
1537
#if PPSSPP_PLATFORM(SWITCH)
1538
strcpy(path, "/switch/ppsspp/");
1539
#else
1540
const char *the_path = getenv("HOME");
1541
if (!the_path) {
1542
struct passwd *pwd = getpwuid(getuid());
1543
if (pwd)
1544
the_path = pwd->pw_dir;
1545
}
1546
if (the_path)
1547
strcpy(path, the_path);
1548
#endif
1549
if (strlen(path) > 0 && path[strlen(path) - 1] != '/')
1550
strcat(path, "/");
1551
1552
#if PPSSPP_PLATFORM(MAC)
1553
std::string external_dir_str;
1554
if (SDL_GetBasePath())
1555
external_dir_str = std::string(SDL_GetBasePath()) + "/assets";
1556
else
1557
external_dir_str = "/tmp";
1558
const char *external_dir = external_dir_str.c_str();
1559
#else
1560
const char *external_dir = "/tmp";
1561
#endif
1562
NativeInit(remain_argc, (const char **)remain_argv, path, external_dir, nullptr);
1563
1564
// Use the setting from the config when initing the window.
1565
if (g_Config.UseFullScreen())
1566
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1567
1568
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());
1569
int y = SDL_WINDOWPOS_UNDEFINED;
1570
int w = g_display.pixel_xres;
1571
int h = g_display.pixel_yres;
1572
1573
if (!g_Config.bFullScreen) {
1574
if (g_Config.iWindowX != -1)
1575
x = g_Config.iWindowX;
1576
if (g_Config.iWindowY != -1)
1577
y = g_Config.iWindowY;
1578
if (g_Config.iWindowWidth > 0 && set_xres <= 0)
1579
w = g_Config.iWindowWidth;
1580
if (g_Config.iWindowHeight > 0 && set_yres <= 0)
1581
h = g_Config.iWindowHeight;
1582
}
1583
1584
GraphicsContext *graphicsContext = nullptr;
1585
SDL_Window *window = nullptr;
1586
1587
// Switch away from Vulkan if not available.
1588
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && !vulkanMayBeAvailable) {
1589
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1590
}
1591
1592
std::string error_message;
1593
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1594
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1595
if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {
1596
// Let's try the fallback once per process run.
1597
printf("GL init error '%s' - falling back to Vulkan\n", error_message.c_str());
1598
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
1599
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1600
delete glctx;
1601
1602
// NOTE : This should match the lines below in the Vulkan case.
1603
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1604
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1605
printf("Vulkan fallback failed: %s\n", error_message.c_str());
1606
return 1;
1607
}
1608
graphicsContext = vkctx;
1609
} else {
1610
graphicsContext = glctx;
1611
}
1612
#if !PPSSPP_PLATFORM(SWITCH)
1613
} else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1614
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1615
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1616
// Let's try the fallback once per process run.
1617
1618
printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str());
1619
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1620
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1621
delete vkctx;
1622
1623
// NOTE : This should match the three lines above in the OpenGL case.
1624
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1625
if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {
1626
printf("GL fallback failed: %s\n", error_message.c_str());
1627
return 1;
1628
}
1629
graphicsContext = glctx;
1630
} else {
1631
graphicsContext = vkctx;
1632
}
1633
#endif
1634
}
1635
1636
UpdateScreenDPI(window);
1637
1638
float dpi_scale = 1.0f / (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI);
1639
1640
Native_UpdateScreenScale(w * g_DesktopDPI, h * g_DesktopDPI, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
1641
1642
bool mainThreadIsRender = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1643
1644
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
1645
1646
char iconPath[PATH_MAX];
1647
#if defined(ASSETS_DIR)
1648
snprintf(iconPath, PATH_MAX, "%sicon_regular_72.png", ASSETS_DIR);
1649
if (access(iconPath, F_OK) != 0)
1650
snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1651
#else
1652
snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1653
#endif
1654
int width = 0, height = 0;
1655
unsigned char *imageData;
1656
if (pngLoad(iconPath, &width, &height, &imageData) == 1) {
1657
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,
1658
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
1659
memcpy(surface->pixels, imageData, width*height*4);
1660
SDL_SetWindowIcon(window, surface);
1661
SDL_FreeSurface(surface);
1662
free(imageData);
1663
imageData = NULL;
1664
}
1665
1666
// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
1667
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1668
printf("Init from thread error: '%s'\n", error_message.c_str());
1669
return 1;
1670
}
1671
1672
// OK, we have a valid graphics backend selected. Let's clear the failures.
1673
g_Config.sFailedGPUBackends.clear();
1674
1675
#ifdef MOBILE_DEVICE
1676
SDL_ShowCursor(SDL_DISABLE);
1677
#endif
1678
1679
// Avoid the IME popup when holding keys. This doesn't affect all versions of SDL.
1680
// Note: We re-enable it in text input fields! This is necessary otherwise we don't receive
1681
// KEY_CHAR events.
1682
SDL_StopTextInput();
1683
1684
InitSDLAudioDevice();
1685
g_audioStartTime = time_now_d();
1686
1687
if (joystick_enabled) {
1688
joystick = new SDLJoystick();
1689
} else {
1690
joystick = nullptr;
1691
}
1692
EnableFZ();
1693
1694
EmuThreadStart(graphicsContext);
1695
1696
graphicsContext->ThreadStart();
1697
1698
InputStateTracker inputTracker{};
1699
1700
#if PPSSPP_PLATFORM(MAC)
1701
// setup menu items for macOS
1702
initializeOSXExtras();
1703
#endif
1704
1705
bool waitOnExit = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1706
1707
// Check if the path to a directory containing an unpacked ISO is passed as a command line argument
1708
for (int i = 1; i < argc; i++) {
1709
if (File::IsDirectory(Path(argv[i]))) {
1710
// Display the toast warning
1711
System_Toast("Warning: Playing unpacked games may cause issues.");
1712
break;
1713
}
1714
}
1715
1716
if (!mainThreadIsRender) {
1717
// Vulkan mode uses this.
1718
// We should only be a message pump. This allows for lower latency
1719
// input events, and so on.
1720
while (true) {
1721
SDL_Event event;
1722
while (SDL_WaitEventTimeout(&event, 100)) {
1723
ProcessSDLEvent(window, event, &inputTracker);
1724
}
1725
if (g_QuitRequested || g_RestartRequested)
1726
break;
1727
1728
UpdateTextFocus();
1729
UpdateSDLCursor();
1730
1731
inputTracker.MouseCaptureControl();
1732
1733
{
1734
std::lock_guard<std::mutex> guard(g_mutexWindow);
1735
if (g_windowState.update) {
1736
UpdateWindowState(window);
1737
}
1738
}
1739
}
1740
} else while (true) {
1741
{
1742
SDL_Event event;
1743
while (SDL_PollEvent(&event)) {
1744
ProcessSDLEvent(window, event, &inputTracker);
1745
}
1746
}
1747
if (g_QuitRequested || g_RestartRequested)
1748
break;
1749
if (emuThreadState == (int)EmuThreadState::DISABLED) {
1750
NativeFrame(graphicsContext);
1751
}
1752
if (g_QuitRequested || g_RestartRequested)
1753
break;
1754
1755
UpdateTextFocus();
1756
UpdateSDLCursor();
1757
1758
inputTracker.MouseCaptureControl();
1759
1760
bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;
1761
if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {
1762
if (!graphicsContext->ThreadFrame())
1763
break;
1764
}
1765
1766
{
1767
std::lock_guard<std::mutex> guard(g_mutexWindow);
1768
if (g_windowState.update) {
1769
UpdateWindowState(window);
1770
}
1771
}
1772
1773
if (g_rebootEmuThread) {
1774
printf("rebooting emu thread");
1775
g_rebootEmuThread = false;
1776
EmuThreadStop("shutdown");
1777
// Skipping GL calls, the old context is gone.
1778
while (graphicsContext->ThreadFrame()) {
1779
INFO_LOG(Log::System, "graphicsContext->ThreadFrame executed to clear buffers");
1780
}
1781
EmuThreadJoin();
1782
graphicsContext->ThreadEnd();
1783
graphicsContext->ShutdownFromRenderThread();
1784
1785
printf("OK, shutdown complete. starting up graphics again.\n");
1786
1787
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1788
SDLGLGraphicsContext *ctx = (SDLGLGraphicsContext *)graphicsContext;
1789
if (!ctx->Init(window, x, y, w, h, mode, &error_message)) {
1790
printf("Failed to reinit graphics.\n");
1791
}
1792
}
1793
1794
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1795
System_Toast("Graphics initialization failed. Quitting.");
1796
return 1;
1797
}
1798
1799
EmuThreadStart(graphicsContext);
1800
graphicsContext->ThreadStart();
1801
}
1802
}
1803
1804
EmuThreadStop("shutdown");
1805
1806
if (waitOnExit) {
1807
while (graphicsContext->ThreadFrame()) {
1808
// Need to keep eating frames to allow the EmuThread to exit correctly.
1809
continue;
1810
}
1811
}
1812
1813
EmuThreadJoin();
1814
1815
delete joystick;
1816
1817
graphicsContext->ThreadEnd();
1818
1819
NativeShutdown();
1820
1821
// Destroys Draw, which is used in NativeShutdown to shutdown.
1822
graphicsContext->ShutdownFromRenderThread();
1823
graphicsContext->Shutdown();
1824
delete graphicsContext;
1825
1826
if (audioDev > 0) {
1827
SDL_PauseAudioDevice(audioDev, 1);
1828
SDL_CloseAudioDevice(audioDev);
1829
}
1830
SDL_Quit();
1831
#if PPSSPP_PLATFORM(RPI)
1832
bcm_host_deinit();
1833
#endif
1834
1835
glslang::FinalizeProcess();
1836
printf("Leaving main\n");
1837
#ifdef HAVE_LIBNX
1838
socketExit();
1839
#endif
1840
1841
// If a restart was requested (and supported on this platform), respawn the executable.
1842
if (g_RestartRequested) {
1843
#if PPSSPP_PLATFORM(MAC)
1844
RestartMacApp();
1845
#elif PPSSPP_PLATFORM(LINUX)
1846
// Hackery from https://unix.stackexchange.com/questions/207935/how-to-restart-or-reset-a-running-process-in-linux,
1847
char *exec_argv[] = { argv[0], nullptr };
1848
execv("/proc/self/exe", exec_argv);
1849
#endif
1850
}
1851
return 0;
1852
}
1853
1854