Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/SDL/SDLMain.cpp
5669 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 "Common/StringUtils.h"
65
#include "Core/System.h"
66
#include "Core/Core.h"
67
#include "Core/Config.h"
68
#include "Core/ConfigValues.h"
69
#include "SDLGLGraphicsContext.h"
70
#include "SDLVulkanGraphicsContext.h"
71
72
#if PPSSPP_PLATFORM(MAC)
73
#include "SDL2/SDL_vulkan.h"
74
#else
75
#include "SDL_vulkan.h"
76
#endif
77
78
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
79
#include "UI/DarwinFileSystemServices.h"
80
#endif
81
82
#if PPSSPP_PLATFORM(MAC)
83
#include "CocoaBarItems.h"
84
#endif
85
86
#if PPSSPP_PLATFORM(SWITCH)
87
#define LIBNX_SWKBD_LIMIT 500 // enforced by HOS
88
extern u32 __nx_applet_type; // Not exposed through a header?
89
#endif
90
91
GlobalUIState lastUIState = UISTATE_MENU;
92
GlobalUIState GetUIState();
93
94
static bool g_QuitRequested = false;
95
static bool g_RestartRequested = false;
96
97
static int g_DesktopWidth = 0;
98
static int g_DesktopHeight = 0;
99
static float g_DesktopDPI = 1.0f;
100
static float g_ForcedDPI = 0.0f; // if this is 0.0f, use g_DesktopDPI
101
static float g_RefreshRate = 60.f;
102
static int g_sampleRate = 44100;
103
104
static bool g_rebootEmuThread = false;
105
106
static SDL_AudioSpec g_retFmt;
107
108
static bool g_textFocusChanged;
109
static bool g_textFocus;
110
double g_audioStartTime = 0.0;
111
112
// Window state to be transferred to the main SDL thread.
113
static std::mutex g_mutexWindow;
114
struct WindowState {
115
std::string title;
116
bool applyFullScreenNextFrame;
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
fprintf(stderr, "%*.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::APPLY_FULLSCREEN_STATE:
432
{
433
std::lock_guard<std::mutex> guard(g_mutexWindow);
434
g_windowState.update = true;
435
g_windowState.applyFullScreenNextFrame = true;
436
return true;
437
}
438
case SystemRequestType::SET_WINDOW_TITLE:
439
{
440
std::lock_guard<std::mutex> guard(g_mutexWindow);
441
const char *app_name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold" : "PPSSPP";
442
g_windowState.title = param1.empty() ? app_name : param1;
443
g_windowState.update = true;
444
return true;
445
}
446
case SystemRequestType::COPY_TO_CLIPBOARD:
447
{
448
std::lock_guard<std::mutex> guard(g_mutexWindow);
449
g_windowState.clipboardString = param1;
450
g_windowState.clipboardDataAvailable = true;
451
g_windowState.update = true;
452
return true;
453
}
454
case SystemRequestType::SHOW_FILE_IN_FOLDER:
455
{
456
#if PPSSPP_PLATFORM(WINDOWS)
457
SFGAOF flags;
458
PIDLIST_ABSOLUTE pidl = nullptr;
459
HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str(), nullptr, &pidl, 0, &flags);
460
if (pidl) {
461
if (SUCCEEDED(hr))
462
SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
463
CoTaskMemFree(pidl);
464
}
465
#elif PPSSPP_PLATFORM(MAC)
466
OSXShowInFinder(param1.c_str());
467
#elif (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
468
pid_t pid = fork();
469
if (pid < 0)
470
return true;
471
472
if (pid == 0) {
473
execlp("xdg-open", "xdg-open", param1.c_str(), nullptr);
474
exit(1);
475
}
476
#endif /* PPSSPP_PLATFORM(WINDOWS) */
477
return true;
478
}
479
case SystemRequestType::NOTIFY_UI_EVENT:
480
{
481
switch ((UIEventNotification)param3) {
482
case UIEventNotification::TEXT_GOTFOCUS:
483
g_textFocus = true;
484
g_textFocusChanged = true;
485
break;
486
case UIEventNotification::POPUP_CLOSED:
487
case UIEventNotification::TEXT_LOSTFOCUS:
488
g_textFocus = false;
489
g_textFocusChanged = true;
490
break;
491
default:
492
break;
493
}
494
return true;
495
}
496
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
497
INFO_LOG(Log::UI, "SET_KEEP_SCREEN_BRIGHT not implemented.");
498
return true;
499
default:
500
INFO_LOG(Log::UI, "Unhandled system request %s", RequestTypeAsString(type));
501
return false;
502
}
503
}
504
505
void System_AskForPermission(SystemPermission permission) {}
506
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
507
508
void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
509
switch (urlType) {
510
case LaunchUrlType::BROWSER_URL:
511
case LaunchUrlType::MARKET_URL:
512
{
513
#if PPSSPP_PLATFORM(SWITCH)
514
Uuid uuid = { 0 };
515
WebWifiConfig conf;
516
webWifiCreate(&conf, NULL, std::string(url).c_str(), uuid, 0);
517
webWifiShow(&conf, NULL);
518
#elif defined(MOBILE_DEVICE)
519
INFO_LOG(Log::System, "Would have gone to %.*s but LaunchBrowser is not implemented on this platform", STR_VIEW(url));
520
#elif defined(_WIN32)
521
std::wstring wurl = ConvertUTF8ToWString(url);
522
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
523
#elif defined(__APPLE__)
524
OSXOpenURL(std::string(url).c_str());
525
#else
526
std::string command = join("xdg-open ", url);
527
int err = system(command.c_str());
528
if (err) {
529
INFO_LOG(Log::System, "Would have gone to %.*s but xdg-utils seems not to be installed", STR_VIEW(url));
530
}
531
#endif
532
break;
533
}
534
case LaunchUrlType::EMAIL_ADDRESS:
535
{
536
#if defined(MOBILE_DEVICE)
537
INFO_LOG(Log::System, "Would have opened your email client for %.*s but LaunchEmail is not implemented on this platform", STR_VIEW(url));
538
#elif defined(_WIN32)
539
std::wstring mailto = std::wstring(L"mailto:") + ConvertUTF8ToWString(url);
540
ShellExecute(NULL, L"open", mailto.c_str(), NULL, NULL, SW_SHOWNORMAL);
541
#elif defined(__APPLE__)
542
OSXOpenURL(join("mailto:", url).c_str());
543
#else
544
int err = system(join("xdg-email ", url).c_str());
545
if (err) {
546
INFO_LOG(Log::System, "Would have gone to %.*s but xdg-utils seems not to be installed", STR_VIEW(url));
547
}
548
#endif
549
break;
550
}
551
case LaunchUrlType::LOCAL_FILE:
552
case LaunchUrlType::LOCAL_FOLDER:
553
#if defined(__APPLE__)
554
// If it's a folder and we're on a mac, open it in finder.
555
OSXShowInFinder(std::string(url).c_str());
556
#endif
557
// INFO_LOG(Log::System, "LaunchUrlType::LOCAL_FILE not implemented on this platform");
558
break;
559
}
560
}
561
562
std::string System_GetProperty(SystemProperty prop) {
563
switch (prop) {
564
case SYSPROP_NAME:
565
#ifdef _WIN32
566
return "SDL:Windows";
567
#elif __linux__
568
return "SDL:Linux";
569
#elif __APPLE__
570
return "SDL:macOS";
571
#elif PPSSPP_PLATFORM(SWITCH)
572
return "SDL:Horizon";
573
#else
574
return "SDL:";
575
#endif
576
case SYSPROP_LANGREGION: {
577
// Get user-preferred locale from OS
578
setlocale(LC_ALL, "");
579
std::string locale(setlocale(LC_ALL, NULL));
580
// Set c and c++ strings back to POSIX
581
std::locale::global(std::locale("POSIX"));
582
if (!locale.empty()) {
583
// Technically, this is an opaque string, but try to find the locale code.
584
size_t messagesPos = locale.find("LC_MESSAGES=");
585
if (messagesPos != std::string::npos) {
586
messagesPos += strlen("LC_MESSAGES=");
587
size_t semi = locale.find(';', messagesPos);
588
locale = locale.substr(messagesPos, semi - messagesPos);
589
}
590
591
if (locale.find("_", 0) != std::string::npos) {
592
if (locale.find(".", 0) != std::string::npos) {
593
return locale.substr(0, locale.find(".",0));
594
}
595
return locale;
596
}
597
}
598
return "en_US";
599
}
600
case SYSPROP_CLIPBOARD_TEXT:
601
return SDL_HasClipboardText() ? SDL_GetClipboardText() : "";
602
case SYSPROP_AUDIO_DEVICE_LIST:
603
{
604
std::string result;
605
for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {
606
const char *name = SDL_GetAudioDeviceName(i, 0);
607
if (!name) {
608
continue;
609
}
610
611
if (i == 0) {
612
result = name;
613
} else {
614
result.append(1, '\0');
615
result.append(name);
616
}
617
}
618
return result;
619
}
620
case SYSPROP_BUILD_VERSION:
621
return PPSSPP_GIT_VERSION;
622
case SYSPROP_USER_DOCUMENTS_DIR:
623
{
624
const char *home = getenv("HOME");
625
return home ? std::string(home) : "/";
626
}
627
default:
628
return "";
629
}
630
}
631
632
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
633
std::vector<std::string> result;
634
635
switch (prop) {
636
case SYSPROP_TEMP_DIRS:
637
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
638
result.push_back(getenv("TMPDIR"));
639
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
640
result.push_back(getenv("TMP"));
641
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
642
result.push_back(getenv("TEMP"));
643
return result;
644
645
default:
646
return result;
647
}
648
}
649
650
#if PPSSPP_PLATFORM(MAC)
651
extern "C" {
652
int Apple_GetCurrentBatteryCapacity();
653
}
654
#endif
655
656
int64_t System_GetPropertyInt(SystemProperty prop) {
657
switch (prop) {
658
case SYSPROP_AUDIO_SAMPLE_RATE:
659
return g_retFmt.freq;
660
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
661
return g_retFmt.samples;
662
case SYSPROP_DEVICE_TYPE:
663
#if defined(MOBILE_DEVICE)
664
return DEVICE_TYPE_MOBILE;
665
#else
666
return DEVICE_TYPE_DESKTOP;
667
#endif
668
case SYSPROP_DISPLAY_COUNT:
669
return SDL_GetNumVideoDisplays();
670
case SYSPROP_KEYBOARD_LAYOUT:
671
{
672
char q, w, y;
673
q = SDL_GetKeyFromScancode(SDL_SCANCODE_Q);
674
w = SDL_GetKeyFromScancode(SDL_SCANCODE_W);
675
y = SDL_GetKeyFromScancode(SDL_SCANCODE_Y);
676
if (q == 'a' && w == 'z' && y == 'y')
677
return KEYBOARD_LAYOUT_AZERTY;
678
else if (q == 'q' && w == 'w' && y == 'z')
679
return KEYBOARD_LAYOUT_QWERTZ;
680
return KEYBOARD_LAYOUT_QWERTY;
681
}
682
case SYSPROP_DISPLAY_XRES:
683
return g_DesktopWidth;
684
case SYSPROP_DISPLAY_YRES:
685
return g_DesktopHeight;
686
case SYSPROP_BATTERY_PERCENTAGE:
687
#if PPSSPP_PLATFORM(MAC)
688
// Let's keep using the old code on Mac for safety. Evaluate later if to be deleted.
689
return Apple_GetCurrentBatteryCapacity();
690
#else
691
{
692
int seconds = 0;
693
int percentage = 0;
694
SDL_GetPowerInfo(&seconds, &percentage);
695
return percentage;
696
}
697
#endif
698
default:
699
return -1;
700
}
701
}
702
703
float System_GetPropertyFloat(SystemProperty prop) {
704
switch (prop) {
705
case SYSPROP_DISPLAY_REFRESH_RATE:
706
return g_RefreshRate;
707
case SYSPROP_DISPLAY_DPI:
708
return (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI) * 96.0;
709
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
710
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
711
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
712
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
713
return 0.0f;
714
default:
715
return -1;
716
}
717
}
718
719
bool System_GetPropertyBool(SystemProperty prop) {
720
switch (prop) {
721
case SYSPROP_HAS_TEXT_CLIPBOARD:
722
case SYSPROP_CAN_SHOW_FILE:
723
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
724
return true;
725
#else
726
return false;
727
#endif
728
case SYSPROP_HAS_OPEN_DIRECTORY:
729
#if PPSSPP_PLATFORM(WINDOWS)
730
return true;
731
#elif PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
732
return true;
733
#endif
734
case SYSPROP_HAS_BACK_BUTTON:
735
return true;
736
#if PPSSPP_PLATFORM(SWITCH)
737
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
738
return __nx_applet_type == AppletType_Application || __nx_applet_type != AppletType_SystemApplication;
739
#endif
740
case SYSPROP_HAS_KEYBOARD:
741
return true;
742
case SYSPROP_APP_GOLD:
743
#ifdef GOLD
744
return true;
745
#else
746
return false;
747
#endif
748
case SYSPROP_CAN_JIT:
749
return true;
750
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
751
return true; // FileUtil.cpp: OpenFileInEditor
752
#ifndef HTTPS_NOT_AVAILABLE
753
case SYSPROP_SUPPORTS_HTTPS:
754
return !g_Config.bDisableHTTPS;
755
#endif
756
case SYSPROP_HAS_FOLDER_BROWSER:
757
case SYSPROP_HAS_FILE_BROWSER:
758
#if PPSSPP_PLATFORM(MAC)
759
return true;
760
#else
761
return pfd::settings::available();
762
#endif
763
case SYSPROP_HAS_ACCELEROMETER:
764
#if defined(MOBILE_DEVICE)
765
return true;
766
#else
767
return false;
768
#endif
769
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
770
return true;
771
case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO:
772
#if PPSSPP_ARCH(64BIT) && !defined(MOBILE_DEVICE)
773
return true;
774
#else
775
return false;
776
#endif
777
// hack for testing - do not commit
778
case SYSPROP_USE_IAP:
779
return false;
780
default:
781
return false;
782
}
783
}
784
785
void System_Notify(SystemNotification notification) {
786
switch (notification) {
787
case SystemNotification::AUDIO_RESET_DEVICE:
788
StopSDLAudioDevice();
789
InitSDLAudioDevice();
790
break;
791
792
default:
793
break;
794
}
795
}
796
797
// returns -1 on failure
798
static int parseInt(const char *str) {
799
int val;
800
int retval = sscanf(str, "%d", &val);
801
fprintf(stderr, "%i = scanf %s\n", retval, str);
802
if (retval != 1) {
803
return -1;
804
} else {
805
return val;
806
}
807
}
808
809
static float parseFloat(const char *str) {
810
float val;
811
int retval = sscanf(str, "%f", &val);
812
fprintf(stderr, "%i = sscanf %s\n", retval, str);
813
if (retval != 1) {
814
return -1.0f;
815
} else {
816
return val;
817
}
818
}
819
820
void UpdateWindowState(SDL_Window *window) {
821
SDL_SetWindowTitle(window, g_windowState.title.c_str());
822
if (g_windowState.applyFullScreenNextFrame) {
823
g_windowState.applyFullScreenNextFrame = false;
824
825
Uint32 window_flags = SDL_GetWindowFlags(window);
826
if (g_Config.bFullScreen) {
827
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
828
} else {
829
window_flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP;
830
}
831
SDL_SetWindowFullscreen(window, window_flags);
832
}
833
if (g_windowState.clipboardDataAvailable) {
834
SDL_SetClipboardText(g_windowState.clipboardString.c_str());
835
g_windowState.clipboardDataAvailable = false;
836
g_windowState.clipboardString.clear();
837
}
838
g_windowState.update = false;
839
}
840
841
enum class EmuThreadState {
842
DISABLED,
843
START_REQUESTED,
844
RUNNING,
845
QUIT_REQUESTED,
846
STOPPED,
847
};
848
849
static std::thread emuThread;
850
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
851
852
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
853
SetCurrentThreadName("EmuThread");
854
855
// There's no real requirement that NativeInit happen on this thread.
856
// We just call the update/render loop here.
857
emuThreadState = (int)EmuThreadState::RUNNING;
858
859
NativeInitGraphics(graphicsContext);
860
861
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
862
NativeFrame(graphicsContext);
863
}
864
emuThreadState = (int)EmuThreadState::STOPPED;
865
866
NativeShutdownGraphics();
867
}
868
869
static void EmuThreadStart(GraphicsContext *context) {
870
emuThreadState = (int)EmuThreadState::START_REQUESTED;
871
emuThread = std::thread(&EmuThreadFunc, context);
872
}
873
874
static void EmuThreadStop(const char *reason) {
875
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
876
}
877
878
static void EmuThreadJoin() {
879
emuThread.join();
880
emuThread = std::thread();
881
}
882
883
struct InputStateTracker {
884
void MouseCaptureControl() {
885
bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_IsMappingMouseInput);
886
if (mouseCaptured != captureMouseCondition) {
887
mouseCaptured = captureMouseCondition;
888
if (captureMouseCondition)
889
SDL_SetRelativeMouseMode(SDL_TRUE);
890
else
891
SDL_SetRelativeMouseMode(SDL_FALSE);
892
}
893
}
894
895
int mouseDown; // bitflags
896
bool mouseCaptured;
897
};
898
899
SDL_Cursor *g_builtinCursors[SDL_NUM_SYSTEM_CURSORS];
900
901
static SDL_SystemCursor GetSDLCursorFromImgui(ImGuiMouseCursor cursor) {
902
switch (cursor) {
903
case ImGuiMouseCursor_Arrow: return SDL_SYSTEM_CURSOR_ARROW; break;
904
case ImGuiMouseCursor_TextInput: return SDL_SYSTEM_CURSOR_IBEAM; break;
905
case ImGuiMouseCursor_ResizeAll: return SDL_SYSTEM_CURSOR_SIZEALL; break;
906
case ImGuiMouseCursor_ResizeEW: return SDL_SYSTEM_CURSOR_SIZEWE; break;
907
case ImGuiMouseCursor_ResizeNS: return SDL_SYSTEM_CURSOR_SIZENS; break;
908
case ImGuiMouseCursor_ResizeNESW: return SDL_SYSTEM_CURSOR_SIZENESW; break;
909
case ImGuiMouseCursor_ResizeNWSE: return SDL_SYSTEM_CURSOR_SIZENWSE; break;
910
case ImGuiMouseCursor_Hand: return SDL_SYSTEM_CURSOR_HAND; break;
911
case ImGuiMouseCursor_NotAllowed: return SDL_SYSTEM_CURSOR_NO; break;
912
default: return SDL_SYSTEM_CURSOR_ARROW; break;
913
}
914
}
915
916
void UpdateCursor() {
917
static SDL_SystemCursor curCursor = SDL_SYSTEM_CURSOR_ARROW;
918
auto cursor = ImGui_ImplPlatform_GetCursor();
919
SDL_SystemCursor sysCursor = GetSDLCursorFromImgui(cursor);
920
if (sysCursor != curCursor) {
921
curCursor = sysCursor;
922
if (!g_builtinCursors[(int)curCursor]) {
923
g_builtinCursors[(int)curCursor] = SDL_CreateSystemCursor(curCursor);
924
}
925
}
926
SDL_SetCursor(g_builtinCursors[(int)curCursor]);
927
}
928
929
static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputStateTracker *inputTracker) {
930
// We have to juggle around 3 kinds of "DPI spaces" if a logical DPI is
931
// provided (through --dpi, it is equal to system DPI if unspecified):
932
// - SDL gives us motion events in "system DPI" points
933
// - Native_UpdateScreenScale expects pixels, so in a way "96 DPI" points
934
// - The UI code expects motion events in "logical DPI" points
935
float mx = event.motion.x * g_DesktopDPI * g_display.dpi_scale_x;
936
float my = event.motion.y * g_DesktopDPI * g_display.dpi_scale_x;
937
938
switch (event.type) {
939
case SDL_QUIT:
940
g_QuitRequested = 1;
941
break;
942
943
#if !defined(MOBILE_DEVICE)
944
case SDL_WINDOWEVENT:
945
switch (event.window.event) {
946
case SDL_WINDOWEVENT_SIZE_CHANGED: // better than RESIZED, more general
947
{
948
int new_width = event.window.data1;
949
int new_height = event.window.data2;
950
951
// The size given by SDL is in point-units, convert these to
952
// pixels before passing to Native_UpdateScreenScale()
953
int new_width_px = new_width * g_DesktopDPI;
954
int new_height_px = new_height * g_DesktopDPI;
955
956
Native_NotifyWindowHidden(false);
957
958
Uint32 window_flags = SDL_GetWindowFlags(window);
959
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
960
961
// This one calls NativeResized if the size changed.
962
Native_UpdateScreenScale(new_width_px, new_height_px, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
963
964
// Set variable here in case fullscreen was toggled by hotkey
965
if (g_Config.bFullScreen != fullscreen) {
966
g_Config.bFullScreen = fullscreen;
967
} else {
968
// It is possible for the monitor to change DPI, so recalculate
969
// DPI on each resize event.
970
UpdateScreenDPI(window);
971
}
972
973
if (!g_Config.bFullScreen) {
974
g_Config.iWindowWidth = new_width;
975
g_Config.iWindowHeight = new_height;
976
}
977
// Hide/Show cursor correctly toggling fullscreen
978
if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) {
979
SDL_ShowCursor(SDL_DISABLE);
980
} else if (lastUIState != UISTATE_INGAME || !fullscreen) {
981
SDL_ShowCursor(SDL_ENABLE);
982
}
983
break;
984
}
985
986
case SDL_WINDOWEVENT_MOVED:
987
{
988
Uint32 window_flags = SDL_GetWindowFlags(window);
989
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
990
if (!fullscreen) {
991
g_Config.iWindowX = (int)event.window.data1;
992
g_Config.iWindowY = (int)event.window.data2;
993
}
994
break;
995
}
996
997
case SDL_WINDOWEVENT_MINIMIZED:
998
case SDL_WINDOWEVENT_HIDDEN:
999
Native_NotifyWindowHidden(true);
1000
break;
1001
case SDL_WINDOWEVENT_EXPOSED:
1002
case SDL_WINDOWEVENT_SHOWN:
1003
Native_NotifyWindowHidden(false);
1004
break;
1005
default:
1006
break;
1007
}
1008
break;
1009
#endif
1010
case SDL_KEYDOWN:
1011
{
1012
if (event.key.repeat > 0) { break;}
1013
int k = event.key.keysym.sym;
1014
KeyInput key;
1015
key.flags = KeyInputFlags::DOWN;
1016
auto mapped = KeyMapRawSDLtoNative.find(k);
1017
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
1018
break;
1019
}
1020
key.keyCode = mapped->second;
1021
key.deviceId = DEVICE_ID_KEYBOARD;
1022
NativeKey(key);
1023
1024
#ifdef _DEBUG
1025
if (k == SDLK_F7) {
1026
fprintf(stderr, "f7 pressed - rebooting emuthread\n");
1027
g_rebootEmuThread = true;
1028
}
1029
#endif
1030
// Convenience subset of what
1031
// "Enable standard shortcut keys"
1032
// does on Windows.
1033
if (g_Config.bSystemControls) {
1034
bool ctrl = bool(event.key.keysym.mod & KMOD_CTRL);
1035
if (ctrl && (k == SDLK_w))
1036
{
1037
if (Core_IsStepping())
1038
Core_Resume();
1039
Core_Stop();
1040
System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);
1041
// NOTE: Unlike Windows version, this
1042
// does not need Core_WaitInactive();
1043
// since SDL does not have a separate
1044
// UI thread.
1045
}
1046
1047
/*
1048
// TODO: Enable this?
1049
if (k == SDLK_F11) {
1050
#if !defined(MOBILE_DEVICE)
1051
g_Config.bFullScreen = !g_Config.bFullScreen;
1052
System_applyFullscreenState("");
1053
#endif
1054
}
1055
*/
1056
}
1057
break;
1058
}
1059
case SDL_KEYUP:
1060
{
1061
if (event.key.repeat > 0) { break;}
1062
int k = event.key.keysym.sym;
1063
KeyInput key;
1064
key.flags = KeyInputFlags::UP;
1065
auto mapped = KeyMapRawSDLtoNative.find(k);
1066
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
1067
break;
1068
}
1069
key.keyCode = mapped->second;
1070
key.deviceId = DEVICE_ID_KEYBOARD;
1071
NativeKey(key);
1072
break;
1073
}
1074
case SDL_TEXTINPUT:
1075
{
1076
int pos = 0;
1077
int c = u8_nextchar(event.text.text, &pos, strlen(event.text.text));
1078
KeyInput key;
1079
key.flags = KeyInputFlags::CHAR;
1080
key.unicodeChar = c;
1081
key.deviceId = DEVICE_ID_KEYBOARD;
1082
NativeKey(key);
1083
break;
1084
}
1085
// This behavior doesn't feel right on a macbook with a touchpad.
1086
#if !PPSSPP_PLATFORM(MAC)
1087
case SDL_FINGERMOTION:
1088
{
1089
int w, h;
1090
SDL_GetWindowSize(window, &w, &h);
1091
TouchInput input{};
1092
input.id = event.tfinger.fingerId;
1093
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1094
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1095
input.flags = TouchInputFlags::MOVE;
1096
input.timestamp = event.tfinger.timestamp;
1097
NativeTouch(input);
1098
break;
1099
}
1100
case SDL_FINGERDOWN:
1101
{
1102
int w, h;
1103
SDL_GetWindowSize(window, &w, &h);
1104
TouchInput input{};
1105
input.id = event.tfinger.fingerId;
1106
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1107
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1108
input.flags = TouchInputFlags::DOWN;
1109
input.timestamp = event.tfinger.timestamp;
1110
NativeTouch(input);
1111
1112
KeyInput key{};
1113
key.deviceId = DEVICE_ID_MOUSE;
1114
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
1115
key.flags = KeyInputFlags::DOWN;
1116
NativeKey(key);
1117
break;
1118
}
1119
case SDL_FINGERUP:
1120
{
1121
int w, h;
1122
SDL_GetWindowSize(window, &w, &h);
1123
TouchInput input{};
1124
input.id = event.tfinger.fingerId;
1125
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
1126
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
1127
input.flags = TouchInputFlags::UP;
1128
input.timestamp = event.tfinger.timestamp;
1129
NativeTouch(input);
1130
1131
KeyInput key;
1132
key.deviceId = DEVICE_ID_MOUSE;
1133
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
1134
key.flags = KeyInputFlags::UP;
1135
NativeKey(key);
1136
break;
1137
}
1138
#endif
1139
case SDL_MOUSEBUTTONDOWN:
1140
switch (event.button.button) {
1141
case SDL_BUTTON_LEFT:
1142
{
1143
inputTracker->mouseDown |= 1;
1144
TouchInput input{};
1145
input.x = mx;
1146
input.y = my;
1147
input.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
1148
input.buttons = 1;
1149
input.id = 0;
1150
NativeTouch(input);
1151
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KeyInputFlags::DOWN);
1152
NativeKey(key);
1153
}
1154
break;
1155
case SDL_BUTTON_RIGHT:
1156
{
1157
inputTracker->mouseDown |= 2;
1158
TouchInput input{};
1159
input.x = mx;
1160
input.y = my;
1161
input.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
1162
input.buttons = 2;
1163
input.id = 0;
1164
NativeTouch(input);
1165
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KeyInputFlags::DOWN);
1166
NativeKey(key);
1167
}
1168
break;
1169
case SDL_BUTTON_MIDDLE:
1170
{
1171
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KeyInputFlags::DOWN);
1172
NativeKey(key);
1173
}
1174
break;
1175
case SDL_BUTTON_X1:
1176
{
1177
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KeyInputFlags::DOWN);
1178
NativeKey(key);
1179
}
1180
break;
1181
case SDL_BUTTON_X2:
1182
{
1183
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KeyInputFlags::DOWN);
1184
NativeKey(key);
1185
}
1186
break;
1187
}
1188
break;
1189
case SDL_MOUSEWHEEL:
1190
{
1191
KeyInput key{};
1192
key.deviceId = DEVICE_ID_MOUSE;
1193
key.flags = KeyInputFlags::DOWN;
1194
#if SDL_VERSION_ATLEAST(2, 0, 18)
1195
if (event.wheel.preciseY != 0.0f) {
1196
// Should the scale be DPI-driven?
1197
const float scale = 30.0f;
1198
key.keyCode = event.wheel.preciseY > 0 ? NKCODE_EXT_MOUSEWHEEL_UP : NKCODE_EXT_MOUSEWHEEL_DOWN;
1199
key.flags |= KeyInputFlags::HAS_WHEEL_DELTA;
1200
int wheelDelta = event.wheel.preciseY * scale;
1201
if (event.wheel.preciseY < 0) {
1202
wheelDelta = -wheelDelta;
1203
}
1204
key.flags = (KeyInputFlags)((u32)key.flags | (wheelDelta << 16));
1205
NativeKey(key);
1206
break;
1207
}
1208
#endif
1209
if (event.wheel.y > 0) {
1210
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
1211
NativeKey(key);
1212
} else if (event.wheel.y < 0) {
1213
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
1214
NativeKey(key);
1215
}
1216
break;
1217
}
1218
case SDL_MOUSEMOTION:
1219
{
1220
TouchInput input{};
1221
input.x = mx;
1222
input.y = my;
1223
input.flags = TouchInputFlags::MOVE | TouchInputFlags::MOUSE;
1224
input.buttons = inputTracker->mouseDown;
1225
input.id = 0;
1226
NativeTouch(input);
1227
NativeMouseDelta(event.motion.xrel, event.motion.yrel);
1228
1229
UpdateCursor();
1230
break;
1231
}
1232
case SDL_MOUSEBUTTONUP:
1233
switch (event.button.button) {
1234
case SDL_BUTTON_LEFT:
1235
{
1236
inputTracker->mouseDown &= ~1;
1237
TouchInput input{};
1238
input.x = mx;
1239
input.y = my;
1240
input.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
1241
input.buttons = 1;
1242
NativeTouch(input);
1243
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KeyInputFlags::UP);
1244
NativeKey(key);
1245
}
1246
break;
1247
case SDL_BUTTON_RIGHT:
1248
{
1249
inputTracker->mouseDown &= ~2;
1250
// Right button only emits mouse move events. This is weird,
1251
// but consistent with Windows. Needs cleanup.
1252
TouchInput input{};
1253
input.x = mx;
1254
input.y = my;
1255
input.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
1256
input.buttons = 2;
1257
NativeTouch(input);
1258
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KeyInputFlags::UP);
1259
NativeKey(key);
1260
}
1261
break;
1262
case SDL_BUTTON_MIDDLE:
1263
{
1264
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KeyInputFlags::UP);
1265
NativeKey(key);
1266
}
1267
break;
1268
case SDL_BUTTON_X1:
1269
{
1270
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KeyInputFlags::UP);
1271
NativeKey(key);
1272
}
1273
break;
1274
case SDL_BUTTON_X2:
1275
{
1276
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KeyInputFlags::UP);
1277
NativeKey(key);
1278
}
1279
break;
1280
}
1281
break;
1282
1283
#if SDL_VERSION_ATLEAST(2, 0, 4)
1284
case SDL_AUDIODEVICEADDED:
1285
// Automatically switch to the new device.
1286
if (event.adevice.iscapture == 0) {
1287
const char *name = SDL_GetAudioDeviceName(event.adevice.which, 0);
1288
if (!name) {
1289
INFO_LOG(Log::Audio, "Got bogus new audio device notification");
1290
break;
1291
}
1292
// Don't start auto switching for a couple of seconds, because some devices init on start.
1293
bool doAutoSwitch = g_Config.bAutoAudioDevice;
1294
if ((time_now_d() - g_audioStartTime) < 3.0) {
1295
INFO_LOG(Log::Audio, "Ignoring new audio device: %s (current: %s)", name, g_Config.sAudioDevice.c_str());
1296
doAutoSwitch = false;
1297
}
1298
if (doAutoSwitch || g_Config.sAudioDevice == name) {
1299
StopSDLAudioDevice();
1300
1301
INFO_LOG(Log::Audio, "!!! Auto-switching to new audio device: '%s'", name);
1302
1303
InitSDLAudioDevice(name ? name : "");
1304
}
1305
}
1306
break;
1307
case SDL_AUDIODEVICEREMOVED:
1308
if (event.adevice.iscapture == 0 && event.adevice.which == audioDev) {
1309
StopSDLAudioDevice();
1310
INFO_LOG(Log::Audio, "Audio device removed, reselecting");
1311
InitSDLAudioDevice();
1312
}
1313
break;
1314
#endif
1315
1316
default:
1317
if (joystick) {
1318
joystick->ProcessInput(event);
1319
}
1320
break;
1321
}
1322
}
1323
1324
void UpdateTextFocus() {
1325
if (g_textFocusChanged) {
1326
INFO_LOG(Log::System, "Updating text focus: %d", g_textFocus);
1327
if (g_textFocus) {
1328
SDL_StartTextInput();
1329
} else {
1330
SDL_StopTextInput();
1331
}
1332
g_textFocusChanged = false;
1333
}
1334
}
1335
1336
void UpdateSDLCursor() {
1337
#if !defined(MOBILE_DEVICE)
1338
if (lastUIState != GetUIState()) {
1339
lastUIState = GetUIState();
1340
if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !g_Config.bShowTouchControls)
1341
SDL_ShowCursor(SDL_DISABLE);
1342
if (lastUIState != UISTATE_INGAME || !g_Config.bFullScreen)
1343
SDL_ShowCursor(SDL_ENABLE);
1344
}
1345
#endif
1346
}
1347
1348
static int printUsage(const char *progname)
1349
{
1350
// NOTE: by convention, --help outputs to stdout,
1351
// not to stderr, since it is intended output in this
1352
// case (usage printed under different circumstances,
1353
// say in response to error during parsing commandline,
1354
// may go to stderr).
1355
FILE *dst = stdout;
1356
1357
// NOTE: wording largely taken from
1358
// https://www.ppsspp.org/docs/reference/command-line/
1359
fprintf(dst, "PPSSPP - a PSP emulator (SDL build)\n");
1360
fprintf(dst, "Usage: %s [options] [FILE]\n\n", progname);
1361
fprintf(dst, "Launches FILE (e.g. ISO image) if present.\n");
1362
fprintf(dst, "Options (some of these are specific to SDL backend):\n");
1363
fprintf(dst, " -h, --help show this message and exit\n");
1364
fprintf(dst, " --version show version information and exit\n");
1365
1366
fprintf(dst, " -d set the log level to debug\n");
1367
fprintf(dst, " -v set the log level to verbose\n");
1368
fprintf(dst, " --loglevel=INTEGER set the log level to specified value\n");
1369
fprintf(dst, " --log=FILE output log to FILE\n");
1370
fprintf(dst, " --state=FILE load state from FILE\n");
1371
1372
fprintf(dst, " -i use the interpreter\n");
1373
fprintf(dst, " -r use IR interpreter\n");
1374
fprintf(dst, " -j use JIT\n");
1375
fprintf(dst, " -J use IR JIT\n");
1376
1377
fprintf(dst, " --fullscreen force full screen mode, ignoring saved configuration\n");
1378
fprintf(dst, " --windowed force windowed mode, ignoring saved configuration\n");
1379
fprintf(dst, " --xres PIXELS set X resolution\n");
1380
fprintf(dst, " --yres PIXELS set Y resolution\n");
1381
fprintf(dst, " --dpi FACTOR set DPI\n");
1382
fprintf(dst, " --scale FACTOR set scale\n");
1383
fprintf(dst, " --ipad set resolution to 1024x768\n");
1384
fprintf(dst, " --portrait portrait mode\n");
1385
fprintf(dst, " --graphics=BACKEND use a different gpu backend\n");
1386
fprintf(dst, " options: gles, software, etc. (also opengl3.1, etc.)\n");
1387
1388
fprintf(dst, " --pause-menu-exit change \"Exit to menu\" in pause menu to \"Exit\"\n");
1389
fprintf(dst, " --escape-exit escape key exits the application\n");
1390
fprintf(dst, " --gamesettings go directly to settings\n");
1391
fprintf(dst, " --touchscreentest go directly to the touchscreentest screen\n");
1392
fprintf(dst, " --appendconfig=FILE merge config FILE into the current configuration\n");
1393
1394
return 0;
1395
}
1396
1397
#ifdef _WIN32
1398
#undef main
1399
#endif
1400
int main(int argc, char *argv[]) {
1401
for (int i = 1; i < argc; i++) {
1402
if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
1403
return printUsage(argv[0]);
1404
else if (!strcmp(argv[i], "--version")) {
1405
printf("%s\n", PPSSPP_GIT_VERSION);
1406
return 0;
1407
}
1408
}
1409
1410
TimeInit();
1411
1412
g_logManager.EnableOutput(LogOutput::Stdio);
1413
1414
#ifdef HAVE_LIBNX
1415
socketInitializeDefault();
1416
nxlinkStdio();
1417
#else // HAVE_LIBNX
1418
// Ignore sigpipe.
1419
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
1420
perror("Unable to ignore SIGPIPE");
1421
}
1422
#endif // HAVE_LIBNX
1423
1424
PROFILE_INIT();
1425
glslang::InitializeProcess();
1426
1427
#if PPSSPP_PLATFORM(RPI)
1428
bcm_host_init();
1429
#endif
1430
putenv((char*)"SDL_VIDEO_CENTERED=1");
1431
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
1432
1433
#ifdef SDL_HINT_TOUCH_MOUSE_EVENTS
1434
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
1435
#endif
1436
1437
bool vulkanMayBeAvailable = false;
1438
if (VulkanMayBeAvailable()) {
1439
fprintf(stderr, "DEBUG: Vulkan might be available.\n");
1440
vulkanMayBeAvailable = true;
1441
} else {
1442
fprintf(stderr, "DEBUG: Vulkan is not available, not using Vulkan.\n");
1443
}
1444
1445
SDL_version compiled;
1446
SDL_version linked;
1447
int set_xres = -1;
1448
int set_yres = -1;
1449
bool portrait = false;
1450
bool set_ipad = false;
1451
float set_dpi = 0.0f;
1452
float set_scale = 1.0f;
1453
1454
// Produce a new set of arguments with the ones we skip.
1455
int remain_argc = 1;
1456
const char *remain_argv[256] = { argv[0] };
1457
1458
// Option to force a specific OpenGL version (42="4.2",
1459
// etc.; -1 means "try them all").
1460
// Implemented as a workaround for https://github.com/hrydgard/ppsspp/issues/20687
1461
// NOTE: this is currently not persistent (doesn't
1462
// go to config), even though --graphics=openglX.Y
1463
// also sets the GPU backend which does persist.
1464
int force_gl_version = -1;
1465
1466
Uint32 mode = 0;
1467
for (int i = 1; i < argc; i++) {
1468
if (!strcmp(argv[i], "--fullscreen")) {
1469
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1470
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
1471
} else if (set_xres == -2)
1472
set_xres = parseInt(argv[i]);
1473
else if (set_yres == -2)
1474
set_yres = parseInt(argv[i]);
1475
else if (set_dpi == -2)
1476
set_dpi = parseFloat(argv[i]);
1477
else if (set_scale == -2)
1478
set_scale = parseFloat(argv[i]);
1479
else if (!strcmp(argv[i], "--xres"))
1480
set_xres = -2;
1481
else if (!strcmp(argv[i], "--yres"))
1482
set_yres = -2;
1483
else if (!strcmp(argv[i], "--dpi"))
1484
set_dpi = -2;
1485
else if (!strcmp(argv[i], "--scale"))
1486
set_scale = -2;
1487
else if (!strcmp(argv[i], "--ipad"))
1488
set_ipad = true;
1489
else if (!strcmp(argv[i], "--portrait"))
1490
portrait = true;
1491
else if (!strncmp(argv[i], "--graphics=", strlen("--graphics="))) {
1492
const char *restOfOption = argv[i] + strlen("--graphics=");
1493
double val=-1.0; // Yes, floating point.
1494
if (!strcmp(restOfOption, "vulkan")) {
1495
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
1496
g_Config.bSoftwareRendering = false;
1497
} else if (!strcmp(restOfOption, "software")) {
1498
// Same as on Windows, software presently implies OpenGL.
1499
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1500
g_Config.bSoftwareRendering = true;
1501
} else if (!strcmp(restOfOption, "gles") || !strcmp(restOfOption, "opengl")) {
1502
// NOTE: OpenGL and GLES are treated the same for
1503
// the purposes of option parsing.
1504
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1505
g_Config.bSoftwareRendering = false;
1506
} else if (sscanf(restOfOption, "gles%lg", &val) == 1 || sscanf(restOfOption, "opengl%lg", &val) == 1) {
1507
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1508
g_Config.bSoftwareRendering = false;
1509
force_gl_version = int(10.0 * val + 0.5);
1510
}
1511
} else {
1512
remain_argv[remain_argc++] = argv[i];
1513
}
1514
}
1515
1516
std::string app_name;
1517
std::string app_name_nice;
1518
std::string version;
1519
bool landscape;
1520
NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version);
1521
1522
bool joystick_enabled = true;
1523
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {
1524
fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");
1525
joystick_enabled = false;
1526
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
1527
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
1528
return 1;
1529
}
1530
}
1531
1532
SDL_VERSION(&compiled);
1533
SDL_GetVersion(&linked);
1534
fprintf(stderr, "Info: We compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch);
1535
if (compiled.minor != linked.minor || compiled.patch != linked.patch) {
1536
fprintf(stderr, ", 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);
1537
} else {
1538
fprintf(stderr, " and we are linking against SDL version %d.%d.%d. :)\n", linked.major, linked.minor, linked.patch);
1539
}
1540
1541
// Get the video info before doing anything else, so we don't get skewed resolution results.
1542
// TODO: support multiple displays correctly
1543
SDL_DisplayMode displayMode;
1544
int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode);
1545
if (should_be_zero != 0) {
1546
fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError());
1547
return 1;
1548
}
1549
g_DesktopWidth = displayMode.w;
1550
g_DesktopHeight = displayMode.h;
1551
g_RefreshRate = displayMode.refresh_rate;
1552
1553
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
1554
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
1555
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
1556
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
1557
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
1558
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
1559
1560
// Force fullscreen if the resolution is too low to run windowed.
1561
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
1562
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1563
}
1564
1565
// If we're on mobile, don't try for windowed either.
1566
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
1567
mode |= SDL_WINDOW_FULLSCREEN;
1568
#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
1569
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1570
#else
1571
mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
1572
#endif
1573
1574
if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) {
1575
g_display.pixel_xres = g_DesktopWidth;
1576
g_display.pixel_yres = g_DesktopHeight;
1577
g_Config.bFullScreen = true;
1578
} else {
1579
// set a sensible default resolution (2x)
1580
g_display.pixel_xres = 480 * 2 * set_scale;
1581
g_display.pixel_yres = 272 * 2 * set_scale;
1582
if (portrait) {
1583
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1584
}
1585
g_Config.bFullScreen = false;
1586
}
1587
1588
if (set_ipad) {
1589
g_display.pixel_xres = 1024;
1590
g_display.pixel_yres = 768;
1591
}
1592
if (!landscape) {
1593
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1594
}
1595
1596
if (set_xres > 0) {
1597
g_display.pixel_xres = set_xres;
1598
}
1599
if (set_yres > 0) {
1600
g_display.pixel_yres = set_yres;
1601
}
1602
if (set_dpi > 0) {
1603
g_ForcedDPI = set_dpi;
1604
}
1605
1606
// Mac / Linux
1607
char path[2048];
1608
#if PPSSPP_PLATFORM(SWITCH)
1609
strcpy(path, "/switch/ppsspp/");
1610
#else
1611
const char *the_path = getenv("HOME");
1612
if (!the_path) {
1613
struct passwd *pwd = getpwuid(getuid());
1614
if (pwd)
1615
the_path = pwd->pw_dir;
1616
}
1617
if (the_path)
1618
strcpy(path, the_path);
1619
#endif
1620
if (strlen(path) > 0 && path[strlen(path) - 1] != '/')
1621
strcat(path, "/");
1622
1623
#if PPSSPP_PLATFORM(MAC)
1624
std::string external_dir_str;
1625
if (SDL_GetBasePath())
1626
external_dir_str = std::string(SDL_GetBasePath()) + "/assets";
1627
else
1628
external_dir_str = "/tmp";
1629
const char *external_dir = external_dir_str.c_str();
1630
#else
1631
const char *external_dir = "/tmp";
1632
#endif
1633
NativeInit(remain_argc, (const char **)remain_argv, path, external_dir, nullptr);
1634
1635
// Use the setting from the config when initing the window.
1636
if (g_Config.bFullScreen)
1637
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1638
1639
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());
1640
int y = SDL_WINDOWPOS_UNDEFINED;
1641
int w = g_display.pixel_xres;
1642
int h = g_display.pixel_yres;
1643
1644
if (!g_Config.bFullScreen) {
1645
if (g_Config.iWindowX != -1)
1646
x = g_Config.iWindowX;
1647
if (g_Config.iWindowY != -1)
1648
y = g_Config.iWindowY;
1649
if (g_Config.iWindowWidth > 0 && set_xres <= 0)
1650
w = g_Config.iWindowWidth;
1651
if (g_Config.iWindowHeight > 0 && set_yres <= 0)
1652
h = g_Config.iWindowHeight;
1653
}
1654
1655
GraphicsContext *graphicsContext = nullptr;
1656
SDL_Window *window = nullptr;
1657
1658
// Switch away from Vulkan if not available.
1659
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && !vulkanMayBeAvailable) {
1660
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1661
}
1662
1663
std::string error_message;
1664
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1665
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1666
if (glctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version) != 0) {
1667
// Let's try the fallback once per process run.
1668
fprintf(stderr, "GL init error '%s' - falling back to Vulkan\n", error_message.c_str());
1669
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
1670
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1671
delete glctx;
1672
1673
// NOTE : This should match the lines below in the Vulkan case.
1674
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1675
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1676
fprintf(stderr, "Vulkan fallback failed: %s\n", error_message.c_str());
1677
return 1;
1678
}
1679
graphicsContext = vkctx;
1680
} else {
1681
graphicsContext = glctx;
1682
}
1683
#if !PPSSPP_PLATFORM(SWITCH)
1684
} else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1685
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1686
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1687
// Let's try the fallback once per process run.
1688
1689
fprintf(stderr, "Vulkan init error '%s' - falling back to GL\n", error_message.c_str());
1690
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1691
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1692
delete vkctx;
1693
1694
// NOTE : This should match the three lines above in the OpenGL case.
1695
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1696
if (glctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version) != 0) {
1697
fprintf(stderr, "GL fallback failed: %s\n", error_message.c_str());
1698
return 1;
1699
}
1700
graphicsContext = glctx;
1701
} else {
1702
graphicsContext = vkctx;
1703
}
1704
#endif
1705
}
1706
1707
UpdateScreenDPI(window);
1708
1709
float dpi_scale = 1.0f / (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI);
1710
1711
Native_UpdateScreenScale(w * g_DesktopDPI, h * g_DesktopDPI, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
1712
1713
bool mainThreadIsRender = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1714
1715
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
1716
1717
char iconPath[PATH_MAX];
1718
#if defined(ASSETS_DIR)
1719
snprintf(iconPath, PATH_MAX, "%sui_images/icon.png", ASSETS_DIR);
1720
if (access(iconPath, F_OK) != 0)
1721
snprintf(iconPath, PATH_MAX, "%sassets/ui_images/icon.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1722
#else
1723
snprintf(iconPath, PATH_MAX, "%sassets/ui_images/icon.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1724
#endif
1725
int width = 0, height = 0;
1726
unsigned char *imageData;
1727
if (pngLoad(iconPath, &width, &height, &imageData) == 1) {
1728
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,
1729
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
1730
memcpy(surface->pixels, imageData, width*height*4);
1731
SDL_SetWindowIcon(window, surface);
1732
SDL_FreeSurface(surface);
1733
free(imageData);
1734
imageData = NULL;
1735
}
1736
1737
// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
1738
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1739
fprintf(stderr, "Init from thread error: '%s'\n", error_message.c_str());
1740
return 1;
1741
}
1742
1743
// OK, we have a valid graphics backend selected. Let's clear the failures.
1744
g_Config.sFailedGPUBackends.clear();
1745
1746
#ifdef MOBILE_DEVICE
1747
SDL_ShowCursor(SDL_DISABLE);
1748
#endif
1749
1750
// Avoid the IME popup when holding keys. This doesn't affect all versions of SDL.
1751
// Note: We re-enable it in text input fields! This is necessary otherwise we don't receive
1752
// KeyInputFlags::CHAR events.
1753
SDL_StopTextInput();
1754
1755
InitSDLAudioDevice();
1756
g_audioStartTime = time_now_d();
1757
1758
if (joystick_enabled) {
1759
joystick = new SDLJoystick();
1760
} else {
1761
joystick = nullptr;
1762
}
1763
EnableFZ();
1764
1765
EmuThreadStart(graphicsContext);
1766
1767
graphicsContext->ThreadStart();
1768
1769
InputStateTracker inputTracker{};
1770
1771
#if PPSSPP_PLATFORM(MAC)
1772
// setup menu items for macOS
1773
initializeOSXExtras();
1774
#endif
1775
1776
bool waitOnExit = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1777
1778
// Check if the path to a directory containing an unpacked ISO is passed as a command line argument
1779
for (int i = 1; i < argc; i++) {
1780
if (File::IsDirectory(Path(argv[i]))) {
1781
// Display the toast warning
1782
System_Toast("Warning: Playing unpacked games may cause issues.");
1783
break;
1784
}
1785
}
1786
1787
if (!mainThreadIsRender) {
1788
// Vulkan mode uses this.
1789
// We should only be a message pump. This allows for lower latency
1790
// input events, and so on.
1791
while (true) {
1792
SDL_Event event;
1793
while (SDL_WaitEventTimeout(&event, 100)) {
1794
if (g_QuitRequested || g_RestartRequested)
1795
break;
1796
1797
ProcessSDLEvent(window, event, &inputTracker);
1798
}
1799
1800
if (g_QuitRequested || g_RestartRequested)
1801
break;
1802
1803
UpdateTextFocus();
1804
UpdateSDLCursor();
1805
1806
inputTracker.MouseCaptureControl();
1807
1808
{
1809
std::lock_guard<std::mutex> guard(g_mutexWindow);
1810
if (g_windowState.update) {
1811
UpdateWindowState(window);
1812
}
1813
}
1814
}
1815
} else while (true) {
1816
{
1817
SDL_Event event;
1818
while (SDL_PollEvent(&event)) {
1819
ProcessSDLEvent(window, event, &inputTracker);
1820
}
1821
}
1822
if (g_QuitRequested || g_RestartRequested)
1823
break;
1824
if (emuThreadState == (int)EmuThreadState::DISABLED) {
1825
NativeFrame(graphicsContext);
1826
}
1827
if (g_QuitRequested || g_RestartRequested)
1828
break;
1829
1830
UpdateTextFocus();
1831
UpdateSDLCursor();
1832
1833
inputTracker.MouseCaptureControl();
1834
1835
bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;
1836
if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {
1837
if (!graphicsContext->ThreadFrame(true))
1838
break;
1839
}
1840
1841
{
1842
std::lock_guard<std::mutex> guard(g_mutexWindow);
1843
if (g_windowState.update) {
1844
UpdateWindowState(window);
1845
}
1846
}
1847
1848
if (g_rebootEmuThread) {
1849
fprintf(stderr, "rebooting emu thread");
1850
g_rebootEmuThread = false;
1851
EmuThreadStop("shutdown");
1852
graphicsContext->ThreadFrameUntilCondition([]() {
1853
return emuThreadState == (int)EmuThreadState::STOPPED || emuThreadState == (int)EmuThreadState::DISABLED;
1854
});
1855
EmuThreadJoin();
1856
graphicsContext->ThreadEnd();
1857
graphicsContext->ShutdownFromRenderThread();
1858
1859
fprintf(stderr, "OK, shutdown complete. starting up graphics again.\n");
1860
1861
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1862
SDLGLGraphicsContext *ctx = (SDLGLGraphicsContext *)graphicsContext;
1863
if (!ctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version)) {
1864
fprintf(stderr, "Failed to reinit graphics.\n");
1865
}
1866
}
1867
1868
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1869
System_Toast("Graphics initialization failed. Quitting.");
1870
return 1;
1871
}
1872
1873
EmuThreadStart(graphicsContext);
1874
graphicsContext->ThreadStart();
1875
}
1876
}
1877
1878
EmuThreadStop("shutdown");
1879
1880
if (waitOnExit) {
1881
graphicsContext->ThreadFrameUntilCondition([]() {
1882
return emuThreadState == (int)EmuThreadState::STOPPED || emuThreadState == (int)EmuThreadState::DISABLED;
1883
});
1884
}
1885
1886
EmuThreadJoin();
1887
1888
delete joystick;
1889
1890
graphicsContext->ThreadEnd();
1891
1892
NativeShutdown();
1893
1894
// Destroys Draw, which is used in NativeShutdown to shutdown.
1895
graphicsContext->ShutdownFromRenderThread();
1896
graphicsContext->Shutdown();
1897
delete graphicsContext;
1898
1899
if (audioDev > 0) {
1900
SDL_PauseAudioDevice(audioDev, 1);
1901
SDL_CloseAudioDevice(audioDev);
1902
}
1903
SDL_Quit();
1904
#if PPSSPP_PLATFORM(RPI)
1905
bcm_host_deinit();
1906
#endif
1907
1908
glslang::FinalizeProcess();
1909
fprintf(stderr, "Leaving main\n");
1910
#ifdef HAVE_LIBNX
1911
socketExit();
1912
#endif
1913
1914
// If a restart was requested (and supported on this platform), respawn the executable.
1915
if (g_RestartRequested) {
1916
#if PPSSPP_PLATFORM(MAC)
1917
RestartMacApp();
1918
#elif PPSSPP_PLATFORM(LINUX)
1919
// Hackery from https://unix.stackexchange.com/questions/207935/how-to-restart-or-reset-a-running-process-in-linux,
1920
char *exec_argv[] = { argv[0], nullptr };
1921
execv("/proc/self/exe", exec_argv);
1922
#endif
1923
}
1924
return 0;
1925
}
1926
1927