2013-04-06 14:34:46 +00:00
// SDL/EGL implementation of the framework.
// This is quite messy due to platform-specific implementations and #ifdef's.
2014-05-30 15:21:43 +00:00
// If your platform is not supported, it is suggested to use Qt instead.
2012-04-10 09:59:57 +00:00
2021-10-20 04:26:37 +00:00
# include <cstdlib>
2013-12-13 10:03:40 +00:00
# include <unistd.h>
2013-11-05 02:58:30 +00:00
# include <pwd.h>
2014-05-30 15:21:43 +00:00
2020-03-04 06:53:03 +00:00
# include "ppsspp_config.h"
2023-04-24 12:44:07 +00:00
# if PPSSPP_PLATFORM(MAC)
# include "SDL2/SDL.h"
# include "SDL2/SDL_syswm.h"
# else
2012-11-25 21:25:54 +00:00
# include "SDL.h"
2023-04-24 12:44:07 +00:00
# include "SDL_syswm.h"
# endif
2014-05-30 15:21:43 +00:00
# include "SDL/SDLJoystick.h"
2014-06-07 14:55:51 +00:00
SDLJoystick * joystick = NULL ;
2013-12-13 11:49:38 +00:00
2016-10-12 15:32:52 +00:00
# if PPSSPP_PLATFORM(RPI)
2014-02-09 22:31:31 +00:00
# include <bcm_host.h>
# endif
2018-01-21 17:03:19 +00:00
# include <atomic>
2013-12-13 11:49:38 +00:00
# include <algorithm>
2017-12-13 22:22:21 +00:00
# include <cmath>
2022-04-02 23:07:51 +00:00
# include <csignal>
2018-01-21 17:03:19 +00:00
# include <thread>
2018-08-28 19:40:58 +00:00
# include <locale>
2013-12-13 11:49:38 +00:00
2020-10-04 08:10:55 +00:00
# include "Common/System/Display.h"
2020-10-04 08:30:18 +00:00
# include "Common/System/System.h"
2023-03-22 16:57:28 +00:00
# include "Common/System/Request.h"
2020-10-04 08:30:18 +00:00
# include "Common/System/NativeApp.h"
2017-12-07 16:02:00 +00:00
# include "ext/glslang/glslang/Public/ShaderLang.h"
2020-10-04 18:48:47 +00:00
# include "Common/Data/Format/PNGLoad.h"
# include "Common/Net/Resolve.h"
2023-03-22 16:57:28 +00:00
# include "Common/File/FileUtil.h"
2019-08-06 15:12:19 +00:00
# include "NKCodeFromSDL.h"
2020-10-03 22:25:21 +00:00
# include "Common/Math/math_util.h"
2020-10-04 21:24:14 +00:00
# include "Common/GPU/OpenGL/GLRenderManager.h"
2021-02-19 06:59:56 +00:00
# include "Common/Profiler/Profiler.h"
2017-12-13 21:58:45 +00:00
2017-12-13 22:22:21 +00:00
# if defined(VK_USE_PLATFORM_XLIB_KHR)
2017-12-13 21:58:45 +00:00
# include <X11/Xlib.h>
# include <X11/Xutil.h>
2017-12-15 15:03:48 +00:00
# elif defined(VK_USE_PLATFORM_XCB_KHR)
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xlib-xcb.h>
2017-12-13 21:58:45 +00:00
# endif
2014-02-05 13:46:21 +00:00
2020-10-01 11:05:04 +00:00
# include "Common/GraphicsContext.h"
# include "Common/TimeUtil.h"
# include "Common/Input/InputState.h"
# include "Common/Input/KeyCodes.h"
# include "Common/Data/Collections/ConstMap.h"
# include "Common/Data/Encoding/Utf8.h"
# include "Common/Thread/ThreadUtil.h"
2014-06-22 09:17:03 +00:00
# include "Core/System.h"
2013-11-26 15:30:10 +00:00
# include "Core/Core.h"
# include "Core/Config.h"
2018-06-17 01:42:31 +00:00
# include "Core/ConfigValues.h"
2018-02-04 11:26:35 +00:00
# include "SDLGLGraphicsContext.h"
# include "SDLVulkanGraphicsContext.h"
2023-06-30 18:07:38 +00:00
# if PPSSPP_PLATFORM(MAC)
# include "SDL2/SDL_vulkan.h"
# else
# include "SDL_vulkan.h"
# endif
2023-02-01 16:03:12 +00:00
# if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
# include "UI/DarwinFileSystemServices.h"
# endif
2013-11-22 00:59:18 +00:00
2023-02-06 19:17:27 +00:00
# if PPSSPP_PLATFORM(MAC)
2023-02-14 16:11:32 +00:00
# include "CocoaBarItems.h"
2023-02-06 19:17:27 +00:00
# endif
2023-06-21 22:46:02 +00:00
# if PPSSPP_PLATFORM(SWITCH)
# define LIBNX_SWKBD_LIMIT 500 // enforced by HOS
extern u32 __nx_applet_type ; // Not exposed through a header?
# endif
2014-06-22 08:53:06 +00:00
GlobalUIState lastUIState = UISTATE_MENU ;
2014-06-22 08:34:22 +00:00
GlobalUIState GetUIState ( ) ;
2013-11-05 02:58:30 +00:00
2023-04-29 09:17:10 +00:00
static bool g_QuitRequested = false ;
static bool g_RestartRequested = false ;
2014-01-03 14:15:35 +00:00
2014-05-02 21:46:24 +00:00
static int g_DesktopWidth = 0 ;
static int g_DesktopHeight = 0 ;
2023-06-30 18:07:38 +00:00
static float g_DesktopDPI = 1.0f ;
2023-07-01 14:50:06 +00:00
static float g_ForcedDPI = 0.0f ; // if this is 0.0f, use g_DesktopDPI
2020-01-05 17:04:07 +00:00
static float g_RefreshRate = 60.f ;
2023-03-24 12:59:27 +00:00
static int g_sampleRate = 44100 ;
2014-05-02 21:46:24 +00:00
2023-08-08 09:21:09 +00:00
static bool g_rebootEmuThread = false ;
2020-05-16 15:46:21 +00:00
static SDL_AudioSpec g_retFmt ;
2024-10-02 14:08:12 +00:00
static bool g_textFocusChanged ;
static bool g_textFocus ;
2023-03-28 12:13:23 +00:00
// Window state to be transferred to the main SDL thread.
static std : : mutex g_mutexWindow ;
struct WindowState {
std : : string title ;
bool toggleFullScreenNextFrame ;
int toggleFullScreenType ;
2023-03-28 14:09:15 +00:00
bool clipboardDataAvailable ;
std : : string clipboardString ;
2023-03-28 12:13:23 +00:00
bool update ;
} ;
static WindowState g_windowState ;
2017-02-06 10:20:27 +00:00
int getDisplayNumber ( void ) {
int displayNumber = 0 ;
char * displayNumberStr ;
2014-10-15 20:35:03 +00:00
2017-02-06 10:20:27 +00:00
//get environment
displayNumberStr = getenv ( " SDL_VIDEO_FULLSCREEN_HEAD " ) ;
2014-10-15 20:35:03 +00:00
2018-02-04 11:26:35 +00:00
if ( displayNumberStr ) {
2017-02-06 10:20:27 +00:00
displayNumber = atoi ( displayNumberStr ) ;
}
2014-10-15 20:35:03 +00:00
2017-02-06 10:20:27 +00:00
return displayNumber ;
2014-09-22 18:08:02 +00:00
}
2020-05-16 19:15:51 +00:00
void sdl_mixaudio_callback ( void * userdata , Uint8 * stream , int len ) {
2023-03-24 12:59:27 +00:00
NativeMix ( ( short * ) stream , len / ( 2 * 2 ) , g_sampleRate ) ;
2019-09-15 20:23:48 +00:00
}
static SDL_AudioDeviceID audioDev = 0 ;
// Must be called after NativeInit().
2019-09-15 20:42:56 +00:00
static void InitSDLAudioDevice ( const std : : string & name = " " ) {
2020-05-16 15:46:21 +00:00
SDL_AudioSpec fmt ;
2019-09-15 20:23:48 +00:00
memset ( & fmt , 0 , sizeof ( fmt ) ) ;
2023-03-24 12:59:27 +00:00
fmt . freq = g_sampleRate ;
2019-09-15 20:23:48 +00:00
fmt . format = AUDIO_S16 ;
fmt . channels = 2 ;
2023-01-17 10:45:05 +00:00
fmt . samples = 256 ;
2020-05-16 19:15:51 +00:00
fmt . callback = & sdl_mixaudio_callback ;
2019-09-15 20:23:48 +00:00
fmt . userdata = nullptr ;
2019-09-15 20:42:56 +00:00
std : : string startDevice = name ;
if ( startDevice . empty ( ) ) {
startDevice = g_Config . sAudioDevice ;
}
2019-09-15 20:23:48 +00:00
audioDev = 0 ;
2019-09-15 20:42:56 +00:00
if ( ! startDevice . empty ( ) ) {
2020-05-16 15:46:21 +00:00
audioDev = SDL_OpenAudioDevice ( startDevice . c_str ( ) , 0 , & fmt , & g_retFmt , SDL_AUDIO_ALLOW_FREQUENCY_CHANGE ) ;
2019-09-15 20:23:48 +00:00
if ( audioDev < = 0 ) {
2024-07-14 12:42:59 +00:00
WARN_LOG ( Log : : Audio , " Failed to open audio device: %s " , startDevice . c_str ( ) ) ;
2019-09-15 20:23:48 +00:00
}
}
if ( audioDev < = 0 ) {
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : Audio , " SDL: Trying a different audio device " ) ;
2020-05-16 15:46:21 +00:00
audioDev = SDL_OpenAudioDevice ( nullptr , 0 , & fmt , & g_retFmt , SDL_AUDIO_ALLOW_FREQUENCY_CHANGE ) ;
2019-09-15 20:23:48 +00:00
}
if ( audioDev < = 0 ) {
2024-07-14 12:42:59 +00:00
ERROR_LOG ( Log : : Audio , " Failed to open audio device: %s " , SDL_GetError ( ) ) ;
2019-09-15 20:23:48 +00:00
} else {
2020-05-16 15:46:21 +00:00
if ( g_retFmt . samples ! = fmt . samples ) // Notify, but still use it
2024-07-14 12:42:59 +00:00
ERROR_LOG ( Log : : Audio , " Output audio samples: %d (requested: %d) " , g_retFmt . samples , fmt . samples ) ;
2020-05-16 19:15:51 +00:00
if ( g_retFmt . format ! = fmt . format | | g_retFmt . channels ! = fmt . channels ) {
2024-07-14 12:42:59 +00:00
ERROR_LOG ( Log : : Audio , " Sound buffer format does not match requested format. " ) ;
ERROR_LOG ( Log : : Audio , " Output audio freq: %d (requested: %d) " , g_retFmt . freq , fmt . freq ) ;
ERROR_LOG ( Log : : Audio , " Output audio format: %d (requested: %d) " , g_retFmt . format , fmt . format ) ;
ERROR_LOG ( Log : : Audio , " Output audio channels: %d (requested: %d) " , g_retFmt . channels , fmt . channels ) ;
ERROR_LOG ( Log : : Audio , " Provided output format does not match requirement, turning audio off " ) ;
2019-09-15 20:23:48 +00:00
SDL_CloseAudioDevice ( audioDev ) ;
}
SDL_PauseAudioDevice ( audioDev , 0 ) ;
}
}
static void StopSDLAudioDevice ( ) {
if ( audioDev > 0 ) {
SDL_PauseAudioDevice ( audioDev , 1 ) ;
SDL_CloseAudioDevice ( audioDev ) ;
}
}
2023-06-30 18:07:38 +00:00
static void UpdateScreenDPI ( SDL_Window * window ) {
int drawable_width , window_width ;
SDL_GetWindowSize ( window , & window_width , NULL ) ;
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL )
SDL_GL_GetDrawableSize ( window , & drawable_width , NULL ) ;
else if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : VULKAN )
SDL_Vulkan_GetDrawableSize ( window , & drawable_width , NULL ) ;
2024-11-06 21:09:53 +00:00
else {
// If we add SDL support for more platforms, we'll end up here.
g_DesktopDPI = 1.0f ;
return ;
}
2023-06-30 18:07:38 +00:00
// Round up a little otherwise there would be a gap sometimes
// in fractional scaling
2023-07-01 14:50:06 +00:00
g_DesktopDPI = ( ( float ) drawable_width + 1.0f ) / window_width ;
2023-08-03 08:32:20 +00:00
// Temporary hack
# if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : VULKAN ) {
g_DesktopDPI = 1.0f ;
}
# endif
2023-06-30 18:07:38 +00:00
}
2013-01-12 15:38:37 +00:00
// Simple implementations of System functions
2012-07-16 13:00:52 +00:00
2024-01-19 12:44:49 +00:00
void System_Toast ( std : : string_view text ) {
2012-07-05 21:30:35 +00:00
# ifdef _WIN32
2018-03-25 21:18:31 +00:00
std : : wstring str = ConvertUTF8ToWString ( text ) ;
MessageBox ( 0 , str . c_str ( ) , L " Toast! " , MB_ICONINFORMATION ) ;
2012-07-05 21:30:35 +00:00
# else
2024-01-19 12:44:49 +00:00
printf ( " %*.s " , ( int ) text . length ( ) , text . data ( ) ) ;
2012-07-05 21:30:35 +00:00
# endif
2012-04-10 09:59:57 +00:00
}
2023-03-21 09:42:23 +00:00
void System_ShowKeyboard ( ) {
2012-04-16 21:30:13 +00:00
// Irrelevant on PC
}
2023-03-21 09:42:23 +00:00
void System_Vibrate ( int length_ms ) {
2012-04-10 09:59:57 +00:00
// Ignore on PC
}
2024-04-06 10:04:45 +00:00
bool System_MakeRequest ( SystemRequestType type , int requestId , const std : : string & param1 , const std : : string & param2 , int64_t param3 , int64_t param4 ) {
2023-03-22 16:39:45 +00:00
switch ( type ) {
2023-04-29 09:17:10 +00:00
case SystemRequestType : : RESTART_APP :
g_RestartRequested = true ;
2023-04-29 09:47:37 +00:00
// TODO: Also save param1 and then split it into an argv.
2023-04-29 09:17:10 +00:00
return true ;
2023-03-22 21:07:29 +00:00
case SystemRequestType : : EXIT_APP :
// Do a clean exit
g_QuitRequested = true ;
2023-03-22 21:17:53 +00:00
return true ;
2023-06-21 22:46:02 +00:00
# if PPSSPP_PLATFORM(SWITCH)
2024-09-26 08:48:31 +00:00
case SystemRequestType : : INPUT_TEXT_MODAL :
{
2023-06-21 22:46:02 +00:00
// swkbd only works on "real" titles
if ( __nx_applet_type ! = AppletType_Application & & __nx_applet_type ! = AppletType_SystemApplication ) {
g_requestManager . PostSystemFailure ( requestId ) ;
return true ;
}
2023-07-15 17:19:44 +00:00
SwkbdConfig kbd ;
Result rc = swkbdCreate ( & kbd , 0 ) ;
2023-06-21 22:46:02 +00:00
if ( R_SUCCEEDED ( rc ) ) {
char buf [ LIBNX_SWKBD_LIMIT ] = { ' \0 ' } ;
swkbdConfigMakePresetDefault ( & kbd ) ;
swkbdConfigSetHeaderText ( & kbd , param1 . c_str ( ) ) ;
swkbdConfigSetInitialText ( & kbd , param2 . c_str ( ) ) ;
rc = swkbdShow ( & kbd , buf , sizeof ( buf ) ) ;
swkbdClose ( & kbd ) ;
g_requestManager . PostSystemSuccess ( requestId , buf ) ;
return true ;
}
g_requestManager . PostSystemFailure ( requestId ) ;
return true ;
}
# endif // PPSSPP_PLATFORM(SWITCH)
2023-03-22 16:39:45 +00:00
# if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
2023-03-22 16:57:28 +00:00
case SystemRequestType : : BROWSE_FOR_FILE :
{
DarwinDirectoryPanelCallback callback = [ requestId ] ( bool success , Path path ) {
2023-03-22 16:39:45 +00:00
if ( success ) {
2023-03-22 16:57:28 +00:00
g_requestManager . PostSystemSuccess ( requestId , path . c_str ( ) ) ;
2023-03-22 16:39:45 +00:00
} else {
g_requestManager . PostSystemFailure ( requestId ) ;
}
} ;
2023-07-16 13:22:04 +00:00
BrowseFileType fileType = ( BrowseFileType ) param3 ;
2024-06-01 10:17:32 +00:00
DarwinFileSystemServices : : presentDirectoryPanel ( callback , /* allowFiles = */ true , /* allowDirectories = */ false , fileType ) ;
2023-03-22 16:39:45 +00:00
return true ;
2023-03-22 16:57:28 +00:00
}
case SystemRequestType : : BROWSE_FOR_FOLDER :
{
DarwinDirectoryPanelCallback callback = [ requestId ] ( bool success , Path path ) {
if ( success ) {
g_requestManager . PostSystemSuccess ( requestId , path . c_str ( ) ) ;
} else {
g_requestManager . PostSystemFailure ( requestId ) ;
}
} ;
2024-06-01 10:17:32 +00:00
DarwinFileSystemServices : : presentDirectoryPanel ( callback , /* allowFiles = */ false , /* allowDirectories = */ true ) ;
2023-03-22 16:57:28 +00:00
return true ;
}
2023-03-22 16:39:45 +00:00
# endif
2023-03-22 21:17:53 +00:00
case SystemRequestType : : TOGGLE_FULLSCREEN_STATE :
2023-03-28 12:13:23 +00:00
{
std : : lock_guard < std : : mutex > guard ( g_mutexWindow ) ;
g_windowState . update = true ;
g_windowState . toggleFullScreenNextFrame = true ;
2023-03-22 21:17:53 +00:00
if ( param1 = = " 1 " ) {
2023-03-28 12:13:23 +00:00
g_windowState . toggleFullScreenType = 1 ;
2023-03-22 21:17:53 +00:00
} else if ( param1 = = " 0 " ) {
2023-03-28 12:13:23 +00:00
g_windowState . toggleFullScreenType = 0 ;
2017-07-30 14:51:53 +00:00
} else {
// Just toggle.
2023-03-28 12:13:23 +00:00
g_windowState . toggleFullScreenType = - 1 ;
2017-07-30 14:51:53 +00:00
}
2023-03-22 21:17:53 +00:00
return true ;
2023-03-28 12:13:23 +00:00
}
case SystemRequestType : : SET_WINDOW_TITLE :
{
std : : lock_guard < std : : mutex > guard ( g_mutexWindow ) ;
2023-04-19 12:54:58 +00:00
const char * app_name = System_GetPropertyBool ( SYSPROP_APP_GOLD ) ? " PPSSPP Gold " : " PPSSPP " ;
g_windowState . title = param1 . empty ( ) ? app_name : param1 ;
2023-03-28 14:09:15 +00:00
g_windowState . update = true ;
return true ;
}
case SystemRequestType : : COPY_TO_CLIPBOARD :
{
std : : lock_guard < std : : mutex > guard ( g_mutexWindow ) ;
g_windowState . clipboardString = param1 ;
g_windowState . clipboardDataAvailable = true ;
2023-03-28 12:13:23 +00:00
g_windowState . update = true ;
return true ;
}
2023-08-24 11:04:41 +00:00
case SystemRequestType : : SHOW_FILE_IN_FOLDER :
{
# if PPSSPP_PLATFORM(WINDOWS)
SFGAOF flags ;
PIDLIST_ABSOLUTE pidl = nullptr ;
HRESULT hr = SHParseDisplayName ( ConvertUTF8ToWString ( ReplaceAll ( path , " / " , " \\ " ) ) . c_str ( ) , nullptr , & pidl , 0 , & flags ) ;
if ( pidl ) {
if ( SUCCEEDED ( hr ) )
SHOpenFolderAndSelectItems ( pidl , 0 , NULL , 0 ) ;
CoTaskMemFree ( pidl ) ;
}
# elif PPSSPP_PLATFORM(MAC)
OSXShowInFinder ( param1 . c_str ( ) ) ;
# elif (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
pid_t pid = fork ( ) ;
if ( pid < 0 )
return true ;
if ( pid = = 0 ) {
execlp ( " xdg-open " , " xdg-open " , param1 . c_str ( ) , nullptr ) ;
exit ( 1 ) ;
}
# endif /* PPSSPP_PLATFORM(WINDOWS) */
return true ;
}
2024-10-02 14:08:12 +00:00
case SystemRequestType : : NOTIFY_UI_EVENT :
{
switch ( ( UIEventNotification ) param3 ) {
case UIEventNotification : : TEXT_GOTFOCUS :
g_textFocus = true ;
g_textFocusChanged = true ;
break ;
case UIEventNotification : : POPUP_CLOSED :
case UIEventNotification : : TEXT_LOSTFOCUS :
g_textFocus = false ;
g_textFocusChanged = true ;
break ;
default :
break ;
}
return true ;
}
2023-03-22 21:17:53 +00:00
default :
return false ;
}
}
2015-12-17 21:41:50 +00:00
void System_AskForPermission ( SystemPermission permission ) { }
PermissionStatus System_GetPermissionStatus ( SystemPermission permission ) { return PERMISSION_STATUS_GRANTED ; }
2023-03-21 09:42:23 +00:00
void System_LaunchUrl ( LaunchUrlType urlType , const char * url ) {
switch ( urlType ) {
case LaunchUrlType : : BROWSER_URL :
case LaunchUrlType : : MARKET_URL :
{
2020-03-04 06:53:03 +00:00
# if PPSSPP_PLATFORM(SWITCH)
2023-03-21 09:42:23 +00:00
Uuid uuid = { 0 } ;
WebWifiConfig conf ;
webWifiCreate ( & conf , NULL , url , uuid , 0 ) ;
webWifiShow ( & conf , NULL ) ;
2020-03-04 06:53:03 +00:00
# elif defined(MOBILE_DEVICE)
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " Would have gone to %s but LaunchBrowser is not implemented on this platform " , url ) ;
2015-05-26 14:51:53 +00:00
# elif defined(_WIN32)
2023-03-21 09:42:23 +00:00
std : : wstring wurl = ConvertUTF8ToWString ( url ) ;
ShellExecute ( NULL , L " open " , wurl . c_str ( ) , NULL , NULL , SW_SHOWNORMAL ) ;
2015-05-26 14:51:53 +00:00
# elif defined(__APPLE__)
2023-04-27 13:32:41 +00:00
OSXOpenURL ( url ) ;
2015-05-26 14:51:53 +00:00
# else
2023-03-21 09:42:23 +00:00
std : : string command = std : : string ( " xdg-open " ) + url ;
int err = system ( command . c_str ( ) ) ;
if ( err ) {
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " Would have gone to %s but xdg-utils seems not to be installed " , url ) ;
2023-03-21 09:42:23 +00:00
}
2012-04-10 09:59:57 +00:00
# endif
2023-03-21 09:42:23 +00:00
break ;
2014-01-30 16:23:04 +00:00
}
2023-03-21 09:42:23 +00:00
case LaunchUrlType : : EMAIL_ADDRESS :
{
2015-05-26 14:51:53 +00:00
# if defined(MOBILE_DEVICE)
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " Would have opened your email client for %s but LaunchEmail is not implemented on this platform " , url ) ;
2015-05-26 14:51:53 +00:00
# elif defined(_WIN32)
2023-03-21 09:42:23 +00:00
std : : wstring mailto = std : : wstring ( L " mailto: " ) + ConvertUTF8ToWString ( url ) ;
ShellExecute ( NULL , L " open " , mailto . c_str ( ) , NULL , NULL , SW_SHOWNORMAL ) ;
2015-05-26 14:51:53 +00:00
# elif defined(__APPLE__)
2023-04-27 13:32:41 +00:00
std : : string mailToURL = std : : string ( " mailto: " ) + url ;
OSXOpenURL ( mailToURL . c_str ( ) ) ;
2015-05-26 14:51:53 +00:00
# else
2023-03-21 09:42:23 +00:00
std : : string command = std : : string ( " xdg-email " ) + url ;
int err = system ( command . c_str ( ) ) ;
if ( err ) {
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " Would have gone to %s but xdg-utils seems not to be installed " , url ) ;
2023-03-21 09:42:23 +00:00
}
2012-04-10 09:59:57 +00:00
# endif
2023-03-21 09:42:23 +00:00
break ;
}
}
2012-04-10 09:59:57 +00:00
}
2013-09-04 09:28:19 +00:00
std : : string System_GetProperty ( SystemProperty prop ) {
switch ( prop ) {
case SYSPROP_NAME :
2013-11-26 06:38:00 +00:00
# ifdef _WIN32
return " SDL:Windows " ;
# elif __linux__
return " SDL:Linux " ;
# elif __APPLE__
2020-01-24 17:39:37 +00:00
return " SDL:macOS " ;
2020-03-04 06:53:03 +00:00
# elif PPSSPP_PLATFORM(SWITCH)
return " SDL:Horizon " ;
2013-11-26 06:38:00 +00:00
# else
2013-09-04 09:28:19 +00:00
return " SDL: " ;
2013-11-26 06:38:00 +00:00
# endif
2018-08-28 19:40:58 +00:00
case SYSPROP_LANGREGION : {
// Get user-preferred locale from OS
setlocale ( LC_ALL , " " ) ;
std : : string locale ( setlocale ( LC_ALL , NULL ) ) ;
// Set c and c++ strings back to POSIX
std : : locale : : global ( std : : locale ( " POSIX " ) ) ;
if ( ! locale . empty ( ) ) {
2020-09-27 19:55:02 +00:00
// Technically, this is an opaque string, but try to find the locale code.
size_t messagesPos = locale . find ( " LC_MESSAGES= " ) ;
if ( messagesPos ! = std : : string : : npos ) {
messagesPos + = strlen ( " LC_MESSAGES= " ) ;
size_t semi = locale . find ( ' ; ' , messagesPos ) ;
locale = locale . substr ( messagesPos , semi - messagesPos ) ;
}
2018-08-28 19:40:58 +00:00
if ( locale . find ( " _ " , 0 ) ! = std : : string : : npos ) {
if ( locale . find ( " . " , 0 ) ! = std : : string : : npos ) {
return locale . substr ( 0 , locale . find ( " . " , 0 ) ) ;
}
2020-09-27 19:55:02 +00:00
return locale ;
2018-08-28 19:40:58 +00:00
}
}
2013-09-04 09:28:19 +00:00
return " en_US " ;
2018-08-28 19:40:58 +00:00
}
2019-05-15 21:29:40 +00:00
case SYSPROP_CLIPBOARD_TEXT :
return SDL_HasClipboardText ( ) ? SDL_GetClipboardText ( ) : " " ;
2019-09-15 20:23:48 +00:00
case SYSPROP_AUDIO_DEVICE_LIST :
{
std : : string result ;
for ( int i = 0 ; i < SDL_GetNumAudioDevices ( 0 ) ; + + i ) {
const char * name = SDL_GetAudioDeviceName ( i , 0 ) ;
if ( ! name ) {
continue ;
}
if ( i = = 0 ) {
result = name ;
} else {
result . append ( 1 , ' \0 ' ) ;
result . append ( name ) ;
}
}
return result ;
}
2023-07-20 07:56:51 +00:00
case SYSPROP_BUILD_VERSION :
return PPSSPP_GIT_VERSION ;
2023-08-18 13:04:20 +00:00
case SYSPROP_USER_DOCUMENTS_DIR :
{
const char * home = getenv ( " HOME " ) ;
return home ? std : : string ( home ) : " / " ;
}
2013-09-04 09:28:19 +00:00
default :
return " " ;
}
2013-08-17 22:14:25 +00:00
}
2021-01-06 15:37:04 +00:00
std : : vector < std : : string > System_GetPropertyStringVec ( SystemProperty prop ) {
2021-01-09 22:45:49 +00:00
std : : vector < std : : string > result ;
2021-01-06 15:37:04 +00:00
switch ( prop ) {
2021-01-09 22:45:49 +00:00
case SYSPROP_TEMP_DIRS :
if ( getenv ( " TMPDIR " ) & & strlen ( getenv ( " TMPDIR " ) ) ! = 0 )
result . push_back ( getenv ( " TMPDIR " ) ) ;
2021-01-10 19:33:17 +00:00
if ( getenv ( " TMP " ) & & strlen ( getenv ( " TMP " ) ) ! = 0 )
2021-01-09 22:45:49 +00:00
result . push_back ( getenv ( " TMP " ) ) ;
2021-01-10 19:33:17 +00:00
if ( getenv ( " TEMP " ) & & strlen ( getenv ( " TEMP " ) ) ! = 0 )
2021-01-09 22:45:49 +00:00
result . push_back ( getenv ( " TEMP " ) ) ;
return result ;
2021-01-06 15:37:04 +00:00
default :
2021-01-09 22:45:49 +00:00
return result ;
2021-01-06 15:37:04 +00:00
}
}
2024-04-05 09:07:57 +00:00
int64_t System_GetPropertyInt ( SystemProperty prop ) {
2015-01-11 13:15:26 +00:00
switch ( prop ) {
case SYSPROP_AUDIO_SAMPLE_RATE :
2020-05-16 15:46:21 +00:00
return g_retFmt . freq ;
case SYSPROP_AUDIO_FRAMES_PER_BUFFER :
return g_retFmt . samples ;
2015-04-03 09:13:38 +00:00
case SYSPROP_DEVICE_TYPE :
2015-05-26 14:51:53 +00:00
# if defined(MOBILE_DEVICE)
2015-04-03 09:13:38 +00:00
return DEVICE_TYPE_MOBILE ;
# else
2015-05-26 14:51:53 +00:00
return DEVICE_TYPE_DESKTOP ;
2015-04-03 09:13:38 +00:00
# endif
2020-01-19 09:34:21 +00:00
case SYSPROP_DISPLAY_COUNT :
return SDL_GetNumVideoDisplays ( ) ;
2022-01-10 00:11:08 +00:00
case SYSPROP_KEYBOARD_LAYOUT :
{
char q , w , y ;
q = SDL_GetKeyFromScancode ( SDL_SCANCODE_Q ) ;
w = SDL_GetKeyFromScancode ( SDL_SCANCODE_W ) ;
y = SDL_GetKeyFromScancode ( SDL_SCANCODE_Y ) ;
if ( q = = ' a ' & & w = = ' z ' & & y = = ' y ' )
return KEYBOARD_LAYOUT_AZERTY ;
else if ( q = = ' q ' & & w = = ' w ' & & y = = ' z ' )
return KEYBOARD_LAYOUT_QWERTZ ;
return KEYBOARD_LAYOUT_QWERTY ;
}
2022-08-16 22:22:01 +00:00
case SYSPROP_DISPLAY_XRES :
return g_DesktopWidth ;
case SYSPROP_DISPLAY_YRES :
return g_DesktopHeight ;
2017-04-30 00:35:12 +00:00
default :
return - 1 ;
}
}
2020-01-05 17:04:07 +00:00
float System_GetPropertyFloat ( SystemProperty prop ) {
switch ( prop ) {
case SYSPROP_DISPLAY_REFRESH_RATE :
return g_RefreshRate ;
2023-06-30 18:07:38 +00:00
case SYSPROP_DISPLAY_DPI :
2023-07-01 14:50:06 +00:00
return ( g_ForcedDPI = = 0.0f ? g_DesktopDPI : g_ForcedDPI ) * 96.0 ;
2020-04-06 00:06:36 +00:00
case SYSPROP_DISPLAY_SAFE_INSET_LEFT :
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT :
case SYSPROP_DISPLAY_SAFE_INSET_TOP :
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM :
return 0.0f ;
2020-01-05 17:04:07 +00:00
default :
return - 1 ;
}
}
2017-04-30 00:35:12 +00:00
bool System_GetPropertyBool ( SystemProperty prop ) {
switch ( prop ) {
2024-09-02 20:48:47 +00:00
case SYSPROP_HAS_TEXT_CLIPBOARD :
2023-08-24 11:04:41 +00:00
case SYSPROP_CAN_SHOW_FILE :
2023-08-28 12:03:28 +00:00
# if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
2023-08-24 11:04:41 +00:00
return true ;
2023-08-28 12:03:28 +00:00
# else
return false ;
# endif
2023-02-02 13:54:50 +00:00
case SYSPROP_HAS_OPEN_DIRECTORY :
# if PPSSPP_PLATFORM(WINDOWS)
return true ;
# elif PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
return true ;
# endif
2017-03-07 09:33:53 +00:00
case SYSPROP_HAS_BACK_BUTTON :
2017-04-30 00:35:12 +00:00
return true ;
2023-06-21 22:24:36 +00:00
# if PPSSPP_PLATFORM(SWITCH)
case SYSPROP_HAS_TEXT_INPUT_DIALOG :
2023-07-15 17:19:44 +00:00
return __nx_applet_type = = AppletType_Application | | __nx_applet_type ! = AppletType_SystemApplication ;
2024-01-20 10:22:28 +00:00
# endif
2023-06-29 11:27:49 +00:00
case SYSPROP_HAS_KEYBOARD :
return true ;
2017-04-05 14:21:08 +00:00
case SYSPROP_APP_GOLD :
# ifdef GOLD
2017-04-30 00:35:12 +00:00
return true ;
2017-04-05 14:21:08 +00:00
# else
2017-04-30 00:35:12 +00:00
return false ;
2017-04-05 14:21:08 +00:00
# endif
2021-02-21 21:02:11 +00:00
case SYSPROP_CAN_JIT :
return true ;
2023-08-28 12:03:28 +00:00
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR :
2022-07-10 20:34:44 +00:00
return true ; // FileUtil.cpp: OpenFileInEditor
2023-07-20 14:01:51 +00:00
# ifndef HTTPS_NOT_AVAILABLE
case SYSPROP_SUPPORTS_HTTPS :
2023-08-24 19:28:30 +00:00
return ! g_Config . bDisableHTTPS ;
2023-07-20 14:01:51 +00:00
# endif
2023-03-22 16:57:28 +00:00
# if PPSSPP_PLATFORM(MAC)
case SYSPROP_HAS_FOLDER_BROWSER :
case SYSPROP_HAS_FILE_BROWSER :
return true ;
2023-11-06 23:44:43 +00:00
# endif
case SYSPROP_HAS_ACCELEROMETER :
# if defined(MOBILE_DEVICE)
return true ;
# else
return false ;
2023-03-22 16:57:28 +00:00
# endif
2015-01-11 13:15:26 +00:00
default :
2017-04-30 00:35:12 +00:00
return false ;
2015-01-11 13:15:26 +00:00
}
}
2014-07-20 10:04:22 +00:00
2023-03-21 10:10:09 +00:00
void System_Notify ( SystemNotification notification ) {
switch ( notification ) {
2023-03-22 22:15:08 +00:00
case SystemNotification : : AUDIO_RESET_DEVICE :
StopSDLAudioDevice ( ) ;
InitSDLAudioDevice ( ) ;
break ;
2023-03-21 10:10:09 +00:00
default :
break ;
}
}
2013-12-16 13:05:51 +00:00
// returns -1 on failure
2014-01-21 14:57:15 +00:00
static int parseInt ( const char * str ) {
2013-12-16 13:05:51 +00:00
int val ;
int retval = sscanf ( str , " %d " , & val ) ;
2014-01-21 14:57:15 +00:00
printf ( " %i = scanf %s \n " , retval , str ) ;
2013-12-16 13:05:51 +00:00
if ( retval ! = 1 ) {
return - 1 ;
} else {
return val ;
}
}
2014-01-21 14:57:15 +00:00
static float parseFloat ( const char * str ) {
float val ;
int retval = sscanf ( str , " %f " , & val ) ;
printf ( " %i = sscanf %s \n " , retval , str ) ;
if ( retval ! = 1 ) {
return - 1.0f ;
} else {
return val ;
}
}
2023-03-28 12:13:23 +00:00
void UpdateWindowState ( SDL_Window * window ) {
SDL_SetWindowTitle ( window , g_windowState . title . c_str ( ) ) ;
if ( g_windowState . toggleFullScreenNextFrame ) {
g_windowState . toggleFullScreenNextFrame = false ;
2014-01-03 14:15:35 +00:00
2017-12-13 22:22:21 +00:00
Uint32 window_flags = SDL_GetWindowFlags ( window ) ;
2023-03-28 12:13:23 +00:00
if ( g_windowState . toggleFullScreenType = = - 1 ) {
2017-07-30 14:51:53 +00:00
window_flags ^ = SDL_WINDOW_FULLSCREEN_DESKTOP ;
2023-03-28 12:13:23 +00:00
} else if ( g_windowState . toggleFullScreenType = = 1 ) {
2017-07-30 14:51:53 +00:00
window_flags | = SDL_WINDOW_FULLSCREEN_DESKTOP ;
} else {
window_flags & = ~ SDL_WINDOW_FULLSCREEN_DESKTOP ;
}
2017-12-13 22:22:21 +00:00
SDL_SetWindowFullscreen ( window , window_flags ) ;
2014-01-03 14:15:35 +00:00
}
2023-03-28 14:09:15 +00:00
if ( g_windowState . clipboardDataAvailable ) {
SDL_SetClipboardText ( g_windowState . clipboardString . c_str ( ) ) ;
g_windowState . clipboardDataAvailable = false ;
g_windowState . clipboardString . clear ( ) ;
}
g_windowState . update = false ;
2014-01-03 14:15:35 +00:00
}
2018-01-21 17:03:19 +00:00
enum class EmuThreadState {
DISABLED ,
START_REQUESTED ,
RUNNING ,
QUIT_REQUESTED ,
STOPPED ,
} ;
static std : : thread emuThread ;
static std : : atomic < int > emuThreadState ( ( int ) EmuThreadState : : DISABLED ) ;
2018-02-04 11:00:56 +00:00
static void EmuThreadFunc ( GraphicsContext * graphicsContext ) {
2024-11-03 17:11:55 +00:00
SetCurrentThreadName ( " EmuThread " ) ;
2018-01-21 17:47:07 +00:00
2018-01-21 17:03:19 +00:00
// There's no real requirement that NativeInit happen on this thread.
// We just call the update/render loop here.
emuThreadState = ( int ) EmuThreadState : : RUNNING ;
2018-02-04 11:00:56 +00:00
NativeInitGraphics ( graphicsContext ) ;
2018-01-21 17:03:19 +00:00
while ( emuThreadState ! = ( int ) EmuThreadState : : QUIT_REQUESTED ) {
2023-08-10 14:33:02 +00:00
UpdateRunLoop ( graphicsContext ) ;
2018-01-21 17:03:19 +00:00
}
emuThreadState = ( int ) EmuThreadState : : STOPPED ;
2021-03-11 22:13:57 +00:00
graphicsContext - > StopThread ( ) ;
2018-02-04 11:00:56 +00:00
NativeShutdownGraphics ( ) ;
2018-01-21 17:03:19 +00:00
}
2018-02-04 11:00:56 +00:00
static void EmuThreadStart ( GraphicsContext * context ) {
2018-01-21 17:03:19 +00:00
emuThreadState = ( int ) EmuThreadState : : START_REQUESTED ;
2018-02-04 11:00:56 +00:00
emuThread = std : : thread ( & EmuThreadFunc , context ) ;
2018-01-21 17:03:19 +00:00
}
2022-12-29 15:14:54 +00:00
static void EmuThreadStop ( const char * reason ) {
2018-01-21 17:03:19 +00:00
emuThreadState = ( int ) EmuThreadState : : QUIT_REQUESTED ;
2018-02-07 11:22:19 +00:00
}
static void EmuThreadJoin ( ) {
2018-01-21 17:03:19 +00:00
emuThread . join ( ) ;
emuThread = std : : thread ( ) ;
}
2023-08-08 09:21:09 +00:00
struct InputStateTracker {
void MouseCaptureControl ( ) {
bool captureMouseCondition = g_Config . bMouseControl & & ( ( GetUIState ( ) = = UISTATE_INGAME & & g_Config . bMouseConfine ) | | g_Config . bMapMouse ) ;
if ( mouseCaptured ! = captureMouseCondition ) {
mouseCaptured = captureMouseCondition ;
if ( captureMouseCondition )
SDL_SetRelativeMouseMode ( SDL_TRUE ) ;
else
SDL_SetRelativeMouseMode ( SDL_FALSE ) ;
}
}
2024-11-06 21:09:53 +00:00
int mouseDown ; // bitflags
2023-08-08 09:21:09 +00:00
bool mouseCaptured ;
} ;
static void ProcessSDLEvent ( SDL_Window * window , const SDL_Event & event , InputStateTracker * inputTracker ) {
// We have to juggle around 3 kinds of "DPI spaces" if a logical DPI is
// provided (through --dpi, it is equal to system DPI if unspecified):
// - SDL gives us motion events in "system DPI" points
// - UpdateScreenScale expects pixels, so in a way "96 DPI" points
// - The UI code expects motion events in "logical DPI" points
float mx = event . motion . x * g_DesktopDPI * g_display . dpi_scale_x ;
float my = event . motion . y * g_DesktopDPI * g_display . dpi_scale_x ;
switch ( event . type ) {
case SDL_QUIT :
g_QuitRequested = 1 ;
break ;
# if !defined(MOBILE_DEVICE)
case SDL_WINDOWEVENT :
switch ( event . window . event ) {
case SDL_WINDOWEVENT_SIZE_CHANGED : // better than RESIZED, more general
{
int new_width = event . window . data1 ;
int new_height = event . window . data2 ;
// The size given by SDL is in point-units, convert these to
// pixels before passing to UpdateScreenScale()
int new_width_px = new_width * g_DesktopDPI ;
int new_height_px = new_height * g_DesktopDPI ;
Core_NotifyWindowHidden ( false ) ;
Uint32 window_flags = SDL_GetWindowFlags ( window ) ;
bool fullscreen = ( window_flags & SDL_WINDOW_FULLSCREEN ) ;
// This one calls NativeResized if the size changed.
UpdateScreenScale ( new_width_px , new_height_px ) ;
// Set variable here in case fullscreen was toggled by hotkey
if ( g_Config . UseFullScreen ( ) ! = fullscreen ) {
g_Config . bFullScreen = fullscreen ;
g_Config . iForceFullScreen = - 1 ;
} else {
// It is possible for the monitor to change DPI, so recalculate
// DPI on each resize event.
UpdateScreenDPI ( window ) ;
}
if ( ! g_Config . bFullScreen ) {
g_Config . iWindowWidth = new_width ;
g_Config . iWindowHeight = new_height ;
}
// Hide/Show cursor correctly toggling fullscreen
if ( lastUIState = = UISTATE_INGAME & & fullscreen & & ! g_Config . bShowTouchControls ) {
SDL_ShowCursor ( SDL_DISABLE ) ;
} else if ( lastUIState ! = UISTATE_INGAME | | ! fullscreen ) {
SDL_ShowCursor ( SDL_ENABLE ) ;
}
break ;
}
case SDL_WINDOWEVENT_MOVED :
{
Uint32 window_flags = SDL_GetWindowFlags ( window ) ;
bool fullscreen = ( window_flags & SDL_WINDOW_FULLSCREEN ) ;
if ( ! fullscreen ) {
2023-09-29 17:35:16 +00:00
g_Config . iWindowX = ( int ) event . window . data1 ;
g_Config . iWindowY = ( int ) event . window . data2 ;
2023-08-08 09:21:09 +00:00
}
break ;
}
case SDL_WINDOWEVENT_MINIMIZED :
case SDL_WINDOWEVENT_HIDDEN :
Core_NotifyWindowHidden ( true ) ;
break ;
case SDL_WINDOWEVENT_EXPOSED :
case SDL_WINDOWEVENT_SHOWN :
Core_NotifyWindowHidden ( false ) ;
break ;
default :
break ;
}
break ;
# endif
case SDL_KEYDOWN :
{
if ( event . key . repeat > 0 ) { break ; }
int k = event . key . keysym . sym ;
KeyInput key ;
key . flags = KEY_DOWN ;
auto mapped = KeyMapRawSDLtoNative . find ( k ) ;
if ( mapped = = KeyMapRawSDLtoNative . end ( ) | | mapped - > second = = NKCODE_UNKNOWN ) {
break ;
}
key . keyCode = mapped - > second ;
key . deviceId = DEVICE_ID_KEYBOARD ;
NativeKey ( key ) ;
# ifdef _DEBUG
if ( k = = SDLK_F7 ) {
printf ( " f7 pressed - rebooting emuthread \n " ) ;
g_rebootEmuThread = true ;
}
# endif
2023-09-02 22:47:37 +00:00
// Convenience subset of what
// "Enable standard shortcut keys"
// does on Windows.
if ( g_Config . bSystemControls ) {
bool ctrl = bool ( event . key . keysym . mod & KMOD_CTRL ) ;
if ( ctrl & & ( k = = SDLK_w ) )
{
if ( Core_IsStepping ( ) )
2024-11-01 21:52:47 +00:00
Core_Resume ( ) ;
2023-09-02 22:47:37 +00:00
Core_Stop ( ) ;
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_GAME_STOP ) ;
2023-09-02 22:47:37 +00:00
// NOTE: Unlike Windows version, this
// does not need Core_WaitInactive();
// since SDL does not have a separate
// UI thread.
}
if ( ctrl & & ( k = = SDLK_b ) )
{
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_GAME_RESET ) ;
2024-11-01 21:52:47 +00:00
Core_Resume ( ) ;
2023-09-02 22:47:37 +00:00
}
}
2023-08-08 09:21:09 +00:00
break ;
}
case SDL_KEYUP :
{
if ( event . key . repeat > 0 ) { break ; }
int k = event . key . keysym . sym ;
KeyInput key ;
key . flags = KEY_UP ;
auto mapped = KeyMapRawSDLtoNative . find ( k ) ;
if ( mapped = = KeyMapRawSDLtoNative . end ( ) | | mapped - > second = = NKCODE_UNKNOWN ) {
break ;
}
key . keyCode = mapped - > second ;
key . deviceId = DEVICE_ID_KEYBOARD ;
NativeKey ( key ) ;
break ;
}
case SDL_TEXTINPUT :
{
int pos = 0 ;
2024-01-12 11:10:08 +00:00
int c = u8_nextchar ( event . text . text , & pos , strlen ( event . text . text ) ) ;
2023-08-08 09:21:09 +00:00
KeyInput key ;
key . flags = KEY_CHAR ;
key . unicodeChar = c ;
key . deviceId = DEVICE_ID_KEYBOARD ;
NativeKey ( key ) ;
break ;
}
// This behavior doesn't feel right on a macbook with a touchpad.
# if !PPSSPP_PLATFORM(MAC)
case SDL_FINGERMOTION :
{
int w , h ;
SDL_GetWindowSize ( window , & w , & h ) ;
TouchInput input ;
input . id = event . tfinger . fingerId ;
input . x = event . tfinger . x * w * g_DesktopDPI * g_display . dpi_scale_x ;
input . y = event . tfinger . y * h * g_DesktopDPI * g_display . dpi_scale_x ;
input . flags = TOUCH_MOVE ;
input . timestamp = event . tfinger . timestamp ;
NativeTouch ( input ) ;
break ;
}
case SDL_FINGERDOWN :
{
int w , h ;
SDL_GetWindowSize ( window , & w , & h ) ;
TouchInput input ;
input . id = event . tfinger . fingerId ;
input . x = event . tfinger . x * w * g_DesktopDPI * g_display . dpi_scale_x ;
input . y = event . tfinger . y * h * g_DesktopDPI * g_display . dpi_scale_x ;
input . flags = TOUCH_DOWN ;
input . timestamp = event . tfinger . timestamp ;
NativeTouch ( input ) ;
KeyInput key ;
key . deviceId = DEVICE_ID_MOUSE ;
key . keyCode = NKCODE_EXT_MOUSEBUTTON_1 ;
key . flags = KEY_DOWN ;
NativeKey ( key ) ;
break ;
}
case SDL_FINGERUP :
{
int w , h ;
SDL_GetWindowSize ( window , & w , & h ) ;
TouchInput input ;
input . id = event . tfinger . fingerId ;
input . x = event . tfinger . x * w * g_DesktopDPI * g_display . dpi_scale_x ;
input . y = event . tfinger . y * h * g_DesktopDPI * g_display . dpi_scale_x ;
input . flags = TOUCH_UP ;
input . timestamp = event . tfinger . timestamp ;
NativeTouch ( input ) ;
KeyInput key ;
key . deviceId = DEVICE_ID_MOUSE ;
key . keyCode = NKCODE_EXT_MOUSEBUTTON_1 ;
key . flags = KEY_UP ;
NativeKey ( key ) ;
break ;
}
# endif
case SDL_MOUSEBUTTONDOWN :
switch ( event . button . button ) {
case SDL_BUTTON_LEFT :
{
2024-11-06 21:09:53 +00:00
inputTracker - > mouseDown | = 1 ;
2023-08-08 09:21:09 +00:00
TouchInput input { } ;
input . x = mx ;
input . y = my ;
input . flags = TOUCH_DOWN | TOUCH_MOUSE ;
2024-11-06 11:04:24 +00:00
input . buttons = 1 ;
2023-08-08 09:21:09 +00:00
input . id = 0 ;
NativeTouch ( input ) ;
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_1 , KEY_DOWN ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_RIGHT :
{
2024-11-06 21:09:53 +00:00
inputTracker - > mouseDown | = 2 ;
2024-04-04 21:25:22 +00:00
TouchInput input { } ;
input . x = mx ;
input . y = my ;
2024-11-06 11:04:24 +00:00
input . flags = TOUCH_DOWN | TOUCH_MOUSE ;
input . buttons = 2 ;
2024-04-04 21:25:22 +00:00
input . id = 0 ;
NativeTouch ( input ) ;
2023-08-08 09:21:09 +00:00
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_2 , KEY_DOWN ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_MIDDLE :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_3 , KEY_DOWN ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_X1 :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_4 , KEY_DOWN ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_X2 :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_5 , KEY_DOWN ) ;
NativeKey ( key ) ;
}
break ;
}
break ;
case SDL_MOUSEWHEEL :
{
KeyInput key ;
key . deviceId = DEVICE_ID_MOUSE ;
key . flags = KEY_DOWN ;
# if SDL_VERSION_ATLEAST(2, 0, 18)
if ( event . wheel . preciseY ! = 0.0f ) {
// Should the scale be DPI-driven?
const float scale = 30.0f ;
key . keyCode = event . wheel . preciseY > 0 ? NKCODE_EXT_MOUSEWHEEL_UP : NKCODE_EXT_MOUSEWHEEL_DOWN ;
key . flags | = KEY_HASWHEELDELTA ;
int wheelDelta = event . wheel . preciseY * scale ;
if ( event . wheel . preciseY < 0 ) {
wheelDelta = - wheelDelta ;
}
key . flags | = wheelDelta < < 16 ;
NativeKey ( key ) ;
break ;
}
# endif
if ( event . wheel . y > 0 ) {
key . keyCode = NKCODE_EXT_MOUSEWHEEL_UP ;
NativeKey ( key ) ;
} else if ( event . wheel . y < 0 ) {
key . keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN ;
NativeKey ( key ) ;
}
break ;
}
case SDL_MOUSEMOTION :
2024-11-06 21:09:53 +00:00
{
2023-08-08 09:21:09 +00:00
TouchInput input { } ;
input . x = mx ;
input . y = my ;
input . flags = TOUCH_MOVE | TOUCH_MOUSE ;
2024-11-06 21:09:53 +00:00
input . buttons = inputTracker - > mouseDown ;
2023-08-08 09:21:09 +00:00
input . id = 0 ;
NativeTouch ( input ) ;
2024-11-06 21:09:53 +00:00
NativeMouseDelta ( event . motion . xrel , event . motion . yrel ) ;
break ;
2023-08-08 09:21:09 +00:00
}
case SDL_MOUSEBUTTONUP :
switch ( event . button . button ) {
case SDL_BUTTON_LEFT :
{
2024-11-06 21:09:53 +00:00
inputTracker - > mouseDown & = ~ 1 ;
2023-08-08 09:21:09 +00:00
TouchInput input { } ;
input . x = mx ;
input . y = my ;
input . flags = TOUCH_UP | TOUCH_MOUSE ;
2024-11-06 11:04:24 +00:00
input . buttons = 1 ;
2023-08-08 09:21:09 +00:00
NativeTouch ( input ) ;
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_1 , KEY_UP ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_RIGHT :
{
2024-11-06 21:09:53 +00:00
inputTracker - > mouseDown & = ~ 2 ;
2024-04-04 21:25:22 +00:00
// Right button only emits mouse move events. This is weird,
// but consistent with Windows. Needs cleanup.
TouchInput input { } ;
input . x = mx ;
input . y = my ;
2024-11-06 11:04:24 +00:00
input . flags = TOUCH_UP | TOUCH_MOUSE ;
input . buttons = 2 ;
2024-04-04 21:25:22 +00:00
NativeTouch ( input ) ;
2023-08-08 09:21:09 +00:00
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_2 , KEY_UP ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_MIDDLE :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_3 , KEY_UP ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_X1 :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_4 , KEY_UP ) ;
NativeKey ( key ) ;
}
break ;
case SDL_BUTTON_X2 :
{
KeyInput key ( DEVICE_ID_MOUSE , NKCODE_EXT_MOUSEBUTTON_5 , KEY_UP ) ;
NativeKey ( key ) ;
}
break ;
}
break ;
# if SDL_VERSION_ATLEAST(2, 0, 4)
case SDL_AUDIODEVICEADDED :
// Automatically switch to the new device.
if ( event . adevice . iscapture = = 0 ) {
const char * name = SDL_GetAudioDeviceName ( event . adevice . which , 0 ) ;
if ( ! name ) {
break ;
}
// Don't start auto switching for a second, because some devices init on start.
2023-09-29 17:01:23 +00:00
bool doAutoSwitch = g_Config . bAutoAudioDevice & & time_now_d ( ) > 1.0f ;
2023-08-08 09:21:09 +00:00
if ( doAutoSwitch | | g_Config . sAudioDevice = = name ) {
StopSDLAudioDevice ( ) ;
InitSDLAudioDevice ( name ? name : " " ) ;
}
}
break ;
case SDL_AUDIODEVICEREMOVED :
if ( event . adevice . iscapture = = 0 & & event . adevice . which = = audioDev ) {
StopSDLAudioDevice ( ) ;
InitSDLAudioDevice ( ) ;
}
break ;
# endif
default :
if ( joystick ) {
joystick - > ProcessInput ( event ) ;
}
break ;
}
2023-09-29 17:02:25 +00:00
}
2023-08-08 09:21:09 +00:00
2024-10-02 14:08:12 +00:00
void UpdateTextFocus ( ) {
if ( g_textFocusChanged ) {
INFO_LOG ( Log : : System , " Updating text focus: %d " , g_textFocus ) ;
if ( g_textFocus ) {
SDL_StartTextInput ( ) ;
} else {
SDL_StopTextInput ( ) ;
}
g_textFocusChanged = false ;
}
}
2023-09-29 17:02:25 +00:00
void UpdateSDLCursor ( ) {
# if !defined(MOBILE_DEVICE)
if ( lastUIState ! = GetUIState ( ) ) {
lastUIState = GetUIState ( ) ;
if ( lastUIState = = UISTATE_INGAME & & g_Config . UseFullScreen ( ) & & ! g_Config . bShowTouchControls )
SDL_ShowCursor ( SDL_DISABLE ) ;
if ( lastUIState ! = UISTATE_INGAME | | ! g_Config . UseFullScreen ( ) )
SDL_ShowCursor ( SDL_ENABLE ) ;
}
# endif
2023-08-08 09:21:09 +00:00
}
2012-04-10 09:59:57 +00:00
# ifdef _WIN32
# undef main
# endif
int main ( int argc , char * argv [ ] ) {
2018-05-09 19:22:47 +00:00
for ( int i = 1 ; i < argc ; i + + ) {
if ( ! strcmp ( argv [ i ] , " --version " ) ) {
printf ( " %s \n " , PPSSPP_GIT_VERSION ) ;
return 0 ;
}
}
2024-07-26 09:16:41 +00:00
TimeInit ( ) ;
2020-03-15 14:56:38 +00:00
# ifdef HAVE_LIBNX
socketInitializeDefault ( ) ;
nxlinkStdio ( ) ;
2022-04-02 23:07:51 +00:00
# else // HAVE_LIBNX
// Ignore sigpipe.
if ( signal ( SIGPIPE , SIG_IGN ) = = SIG_ERR ) {
perror ( " Unable to ignore SIGPIPE " ) ;
}
2020-03-15 14:56:38 +00:00
# endif // HAVE_LIBNX
2021-02-19 06:59:56 +00:00
PROFILE_INIT ( ) ;
2017-12-13 21:58:45 +00:00
glslang : : InitializeProcess ( ) ;
2016-10-12 15:32:52 +00:00
# if PPSSPP_PLATFORM(RPI)
2014-02-09 22:31:31 +00:00
bcm_host_init ( ) ;
# endif
2014-05-30 15:21:43 +00:00
putenv ( ( char * ) " SDL_VIDEO_CENTERED=1 " ) ;
2017-02-22 21:44:32 +00:00
SDL_SetHint ( SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS , " 0 " ) ;
2014-02-05 18:44:43 +00:00
2020-10-06 02:50:25 +00:00
# ifdef SDL_HINT_TOUCH_MOUSE_EVENTS
SDL_SetHint ( SDL_HINT_TOUCH_MOUSE_EVENTS , " 0 " ) ;
# endif
2024-09-09 19:03:27 +00:00
bool vulkanMayBeAvailable = false ;
2017-12-15 14:29:19 +00:00
if ( VulkanMayBeAvailable ( ) ) {
2019-03-23 15:32:18 +00:00
printf ( " DEBUG: Vulkan might be available. \n " ) ;
2024-09-09 19:03:27 +00:00
vulkanMayBeAvailable = true ;
2017-12-15 14:29:19 +00:00
} else {
2019-03-23 15:32:18 +00:00
printf ( " DEBUG: Vulkan is not available, not using Vulkan. \n " ) ;
2017-12-15 14:29:19 +00:00
}
2019-03-11 15:06:06 +00:00
SDL_version compiled ;
SDL_version linked ;
2017-12-13 21:58:45 +00:00
int set_xres = - 1 ;
int set_yres = - 1 ;
bool portrait = false ;
bool set_ipad = false ;
2023-07-01 14:50:06 +00:00
float set_dpi = 0.0f ;
2017-12-13 21:58:45 +00:00
float set_scale = 1.0f ;
// Produce a new set of arguments with the ones we skip.
int remain_argc = 1 ;
const char * remain_argv [ 256 ] = { argv [ 0 ] } ;
Uint32 mode = 0 ;
for ( int i = 1 ; i < argc ; i + + ) {
2022-12-09 08:28:28 +00:00
if ( ! strcmp ( argv [ i ] , " --fullscreen " ) ) {
2017-12-13 21:58:45 +00:00
mode | = SDL_WINDOW_FULLSCREEN_DESKTOP ;
2022-12-09 08:28:28 +00:00
g_Config . iForceFullScreen = 1 ;
} else if ( set_xres = = - 2 )
2017-12-13 21:58:45 +00:00
set_xres = parseInt ( argv [ i ] ) ;
else if ( set_yres = = - 2 )
set_yres = parseInt ( argv [ i ] ) ;
else if ( set_dpi = = - 2 )
set_dpi = parseFloat ( argv [ i ] ) ;
else if ( set_scale = = - 2 )
set_scale = parseFloat ( argv [ i ] ) ;
else if ( ! strcmp ( argv [ i ] , " --xres " ) )
set_xres = - 2 ;
else if ( ! strcmp ( argv [ i ] , " --yres " ) )
set_yres = - 2 ;
else if ( ! strcmp ( argv [ i ] , " --dpi " ) )
set_dpi = - 2 ;
else if ( ! strcmp ( argv [ i ] , " --scale " ) )
set_scale = - 2 ;
else if ( ! strcmp ( argv [ i ] , " --ipad " ) )
set_ipad = true ;
else if ( ! strcmp ( argv [ i ] , " --portrait " ) )
portrait = true ;
else {
remain_argv [ remain_argc + + ] = argv [ i ] ;
}
}
2012-04-10 09:59:57 +00:00
std : : string app_name ;
std : : string app_name_nice ;
2014-12-18 21:50:17 +00:00
std : : string version ;
2012-10-31 11:12:24 +00:00
bool landscape ;
2014-12-18 21:50:17 +00:00
NativeGetAppInfo ( & app_name , & app_name_nice , & landscape , & version ) ;
2013-07-14 11:51:30 +00:00
2016-07-09 07:15:11 +00:00
bool joystick_enabled = true ;
2016-09-18 22:23:36 +00:00
if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO ) < 0 ) {
2017-12-15 14:29:19 +00:00
fprintf ( stderr , " Failed to initialize SDL with joystick support. Retrying without. \n " ) ;
2016-07-09 07:15:11 +00:00
joystick_enabled = false ;
if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 ) {
fprintf ( stderr , " Unable to initialize SDL: %s \n " , SDL_GetError ( ) ) ;
return 1 ;
}
2014-09-01 13:46:14 +00:00
}
2019-03-11 15:06:06 +00:00
SDL_VERSION ( & compiled ) ;
SDL_GetVersion ( & linked ) ;
printf ( " Info: We compiled against SDL version %d.%d.%d " , compiled . major , compiled . minor , compiled . patch ) ;
if ( compiled . minor ! = linked . minor | | compiled . patch ! = linked . patch ) {
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 ) ;
} else {
printf ( " and we are linking against SDL version %d.%d.%d. :) \n " , linked . major , linked . minor , linked . patch ) ;
}
2014-05-02 21:46:24 +00:00
// Get the video info before doing anything else, so we don't get skewed resolution results.
2014-09-01 13:46:14 +00:00
// TODO: support multiple displays correctly
SDL_DisplayMode displayMode ;
int should_be_zero = SDL_GetCurrentDisplayMode ( 0 , & displayMode ) ;
if ( should_be_zero ! = 0 ) {
fprintf ( stderr , " Could not get display mode: %s \n " , SDL_GetError ( ) ) ;
return 1 ;
}
g_DesktopWidth = displayMode . w ;
g_DesktopHeight = displayMode . h ;
2020-01-05 17:04:07 +00:00
g_RefreshRate = displayMode . refresh_rate ;
2014-05-02 21:46:24 +00:00
2012-04-10 09:59:57 +00:00
SDL_GL_SetAttribute ( SDL_GL_RED_SIZE , 8 ) ;
SDL_GL_SetAttribute ( SDL_GL_GREEN_SIZE , 8 ) ;
SDL_GL_SetAttribute ( SDL_GL_BLUE_SIZE , 8 ) ;
SDL_GL_SetAttribute ( SDL_GL_DEPTH_SIZE , 24 ) ;
SDL_GL_SetAttribute ( SDL_GL_STENCIL_SIZE , 8 ) ;
SDL_GL_SetAttribute ( SDL_GL_DOUBLEBUFFER , 1 ) ;
2019-02-17 14:27:41 +00:00
// Force fullscreen if the resolution is too low to run windowed.
2014-11-13 17:13:01 +00:00
if ( g_DesktopWidth < 480 * 2 & & g_DesktopHeight < 272 * 2 ) {
2014-11-13 15:38:50 +00:00
mode | = SDL_WINDOW_FULLSCREEN_DESKTOP ;
}
2018-06-06 03:39:56 +00:00
// If we're on mobile, don't try for windowed either.
2020-03-15 14:56:38 +00:00
# if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
2019-02-18 13:00:28 +00:00
mode | = SDL_WINDOW_FULLSCREEN ;
2020-03-15 14:56:38 +00:00
# elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
2019-02-17 14:27:41 +00:00
mode | = SDL_WINDOW_FULLSCREEN_DESKTOP ;
2018-06-06 03:39:56 +00:00
# else
2023-06-30 18:07:38 +00:00
mode | = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI ;
2018-06-06 03:39:56 +00:00
# endif
2014-09-01 13:46:14 +00:00
if ( mode & SDL_WINDOW_FULLSCREEN_DESKTOP ) {
2023-02-25 12:09:44 +00:00
g_display . pixel_xres = g_DesktopWidth ;
g_display . pixel_yres = g_DesktopHeight ;
2022-12-09 08:28:28 +00:00
if ( g_Config . iForceFullScreen = = - 1 )
g_Config . bFullScreen = true ;
2013-11-04 13:49:30 +00:00
} else {
2013-10-30 11:22:22 +00:00
// set a sensible default resolution (2x)
2023-02-25 12:09:44 +00:00
g_display . pixel_xres = 480 * 2 * set_scale ;
g_display . pixel_yres = 272 * 2 * set_scale ;
2014-04-13 15:08:36 +00:00
if ( portrait ) {
2023-02-25 12:09:44 +00:00
std : : swap ( g_display . pixel_xres , g_display . pixel_yres ) ;
2014-04-13 15:08:36 +00:00
}
2022-12-09 08:28:28 +00:00
if ( g_Config . iForceFullScreen = = - 1 )
g_Config . bFullScreen = false ;
2013-10-30 11:22:22 +00:00
}
2013-12-13 11:49:38 +00:00
2014-04-08 13:27:14 +00:00
if ( set_ipad ) {
2023-02-25 12:09:44 +00:00
g_display . pixel_xres = 1024 ;
g_display . pixel_yres = 768 ;
2014-04-08 13:27:14 +00:00
}
2013-12-13 11:49:38 +00:00
if ( ! landscape ) {
2023-02-25 12:09:44 +00:00
std : : swap ( g_display . pixel_xres , g_display . pixel_yres ) ;
2013-12-13 11:49:38 +00:00
}
2013-12-16 13:05:51 +00:00
if ( set_xres > 0 ) {
2023-02-25 12:09:44 +00:00
g_display . pixel_xres = set_xres ;
2013-12-16 13:05:51 +00:00
}
if ( set_yres > 0 ) {
2023-02-25 12:09:44 +00:00
g_display . pixel_yres = set_yres ;
2013-12-16 13:05:51 +00:00
}
2014-01-21 14:57:15 +00:00
if ( set_dpi > 0 ) {
2023-07-01 14:50:06 +00:00
g_ForcedDPI = set_dpi ;
2014-01-21 14:57:15 +00:00
}
2013-12-16 13:05:51 +00:00
2017-10-01 20:59:21 +00:00
// Mac / Linux
char path [ 2048 ] ;
2020-03-15 14:56:38 +00:00
# if PPSSPP_PLATFORM(SWITCH)
strcpy ( path , " /switch/ppsspp/ " ) ;
# else
2017-10-01 20:59:21 +00:00
const char * the_path = getenv ( " HOME " ) ;
if ( ! the_path ) {
2020-03-15 14:56:38 +00:00
struct passwd * pwd = getpwuid ( getuid ( ) ) ;
2017-10-01 20:59:21 +00:00
if ( pwd )
the_path = pwd - > pw_dir ;
}
2020-03-15 14:56:38 +00:00
if ( the_path )
strcpy ( path , the_path ) ;
# endif
if ( strlen ( path ) > 0 & & path [ strlen ( path ) - 1 ] ! = ' / ' )
2017-10-01 20:59:21 +00:00
strcat ( path , " / " ) ;
2021-11-06 06:22:08 +00:00
# if PPSSPP_PLATFORM(MAC)
std : : string external_dir_str ;
if ( SDL_GetBasePath ( ) )
external_dir_str = std : : string ( SDL_GetBasePath ( ) ) + " /assets " ;
else
external_dir_str = " /tmp " ;
const char * external_dir = external_dir_str . c_str ( ) ;
# else
const char * external_dir = " /tmp " ;
# endif
NativeInit ( remain_argc , ( const char * * ) remain_argv , path , external_dir , nullptr ) ;
2017-10-01 20:59:21 +00:00
// Use the setting from the config when initing the window.
2022-05-28 22:47:12 +00:00
if ( g_Config . UseFullScreen ( ) )
2017-10-01 20:59:21 +00:00
mode | = SDL_WINDOW_FULLSCREEN_DESKTOP ;
2017-11-13 06:49:31 +00:00
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY ( getDisplayNumber ( ) ) ;
int y = SDL_WINDOWPOS_UNDEFINED ;
2023-03-28 12:51:18 +00:00
int w = g_display . pixel_xres ;
int h = g_display . pixel_yres ;
2017-11-13 06:49:31 +00:00
2023-03-28 12:59:06 +00:00
if ( ! g_Config . bFullScreen ) {
if ( g_Config . iWindowX ! = - 1 )
x = g_Config . iWindowX ;
if ( g_Config . iWindowY ! = - 1 )
y = g_Config . iWindowY ;
2024-05-10 19:23:32 +00:00
if ( g_Config . iWindowWidth > 0 & & set_xres < = 0 )
2023-03-28 12:59:06 +00:00
w = g_Config . iWindowWidth ;
2024-05-10 19:23:32 +00:00
if ( g_Config . iWindowHeight > 0 & & set_yres < = 0 )
2023-03-28 12:59:06 +00:00
h = g_Config . iWindowHeight ;
}
2014-02-10 14:12:55 +00:00
2017-12-15 14:29:19 +00:00
GraphicsContext * graphicsContext = nullptr ;
SDL_Window * window = nullptr ;
2018-02-04 11:00:56 +00:00
2024-09-09 19:03:27 +00:00
// Switch away from Vulkan if not available.
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : VULKAN & & ! vulkanMayBeAvailable ) {
g_Config . iGPUBackend = ( int ) GPUBackend : : OPENGL ;
}
2017-12-15 14:29:19 +00:00
std : : string error_message ;
2017-12-26 23:55:24 +00:00
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL ) {
2024-09-16 10:28:38 +00:00
SDLGLGraphicsContext * glctx = new SDLGLGraphicsContext ( ) ;
if ( glctx - > Init ( window , x , y , w , h , mode , & error_message ) ! = 0 ) {
// Let's try the fallback once per process run.
printf ( " GL init error '%s' - falling back to Vulkan \n " , error_message . c_str ( ) ) ;
g_Config . iGPUBackend = ( int ) GPUBackend : : VULKAN ;
SetGPUBackend ( ( GPUBackend ) g_Config . iGPUBackend ) ;
delete glctx ;
// NOTE : This should match the lines below in the Vulkan case.
SDLVulkanGraphicsContext * vkctx = new SDLVulkanGraphicsContext ( ) ;
if ( ! vkctx - > Init ( window , x , y , w , h , mode | SDL_WINDOW_VULKAN , & error_message ) ) {
printf ( " Vulkan fallback failed: %s \n " , error_message . c_str ( ) ) ;
return 1 ;
}
graphicsContext = vkctx ;
} else {
graphicsContext = glctx ;
2017-12-15 14:29:19 +00:00
}
2022-04-02 23:34:13 +00:00
# if !PPSSPP_PLATFORM(SWITCH)
2017-12-26 23:55:24 +00:00
} else if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : VULKAN ) {
2024-09-16 10:28:38 +00:00
SDLVulkanGraphicsContext * vkctx = new SDLVulkanGraphicsContext ( ) ;
if ( ! vkctx - > Init ( window , x , y , w , h , mode | SDL_WINDOW_VULKAN , & error_message ) ) {
// Let's try the fallback once per process run.
2017-12-13 21:58:45 +00:00
printf ( " Vulkan init error '%s' - falling back to GL \n " , error_message . c_str ( ) ) ;
2017-12-26 23:55:24 +00:00
g_Config . iGPUBackend = ( int ) GPUBackend : : OPENGL ;
SetGPUBackend ( ( GPUBackend ) g_Config . iGPUBackend ) ;
2024-09-16 10:28:38 +00:00
delete vkctx ;
// NOTE : This should match the three lines above in the OpenGL case.
2017-12-15 14:29:19 +00:00
SDLGLGraphicsContext * glctx = new SDLGLGraphicsContext ( ) ;
2024-09-16 10:28:38 +00:00
if ( glctx - > Init ( window , x , y , w , h , mode , & error_message ) ! = 0 ) {
printf ( " GL fallback failed: %s \n " , error_message . c_str ( ) ) ;
return 1 ;
}
2017-12-15 14:29:19 +00:00
graphicsContext = glctx ;
2017-12-13 21:58:45 +00:00
} else {
2024-09-16 10:28:38 +00:00
graphicsContext = vkctx ;
2017-12-13 21:58:45 +00:00
}
2022-04-02 23:34:13 +00:00
# endif
2017-12-13 21:58:45 +00:00
}
2023-08-03 08:32:20 +00:00
# if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : VULKAN ) {
g_ForcedDPI = 1.0f ;
}
# endif
2017-12-15 14:29:19 +00:00
2023-06-30 18:07:38 +00:00
UpdateScreenDPI ( window ) ;
2023-07-01 14:50:06 +00:00
float dpi_scale = 1.0f / ( g_ForcedDPI = = 0.0f ? g_DesktopDPI : g_ForcedDPI ) ;
2023-08-02 14:30:07 +00:00
UpdateScreenScale ( w * g_DesktopDPI , h * g_DesktopDPI ) ;
2023-07-01 14:50:06 +00:00
2023-09-29 17:10:51 +00:00
bool mainThreadIsRender = g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL ;
2018-02-10 08:09:13 +00:00
2018-02-07 14:52:19 +00:00
SDL_SetWindowTitle ( window , ( app_name_nice + " " + PPSSPP_GIT_VERSION ) . c_str ( ) ) ;
2020-01-17 13:53:56 +00:00
char iconPath [ PATH_MAX ] ;
2022-12-06 15:17:35 +00:00
# if defined(ASSETS_DIR)
snprintf ( iconPath , PATH_MAX , " %sicon_regular_72.png " , ASSETS_DIR ) ;
if ( access ( iconPath , F_OK ) ! = 0 )
snprintf ( iconPath , PATH_MAX , " %sassets/icon_regular_72.png " , SDL_GetBasePath ( ) ? SDL_GetBasePath ( ) : " " ) ;
# else
2020-01-17 13:53:56 +00:00
snprintf ( iconPath , PATH_MAX , " %sassets/icon_regular_72.png " , SDL_GetBasePath ( ) ? SDL_GetBasePath ( ) : " " ) ;
2022-12-06 15:17:35 +00:00
# endif
2020-01-17 13:53:56 +00:00
int width = 0 , height = 0 ;
unsigned char * imageData ;
2020-10-04 18:48:47 +00:00
if ( pngLoad ( iconPath , & width , & height , & imageData ) = = 1 ) {
2020-01-17 13:53:56 +00:00
SDL_Surface * surface = SDL_CreateRGBSurface ( 0 , width , height , 32 ,
0x000000ff , 0x0000ff00 , 0x00ff0000 , 0xff000000 ) ;
memcpy ( surface - > pixels , imageData , width * height * 4 ) ;
SDL_SetWindowIcon ( window , surface ) ;
SDL_FreeSurface ( surface ) ;
free ( imageData ) ;
imageData = NULL ;
}
2018-01-21 17:03:19 +00:00
// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
if ( ! graphicsContext - > InitFromRenderThread ( & error_message ) ) {
printf ( " Init from thread error: '%s' \n " , error_message . c_str ( ) ) ;
2024-09-16 10:28:38 +00:00
return 1 ;
2018-01-21 17:03:19 +00:00
}
2024-09-16 10:28:38 +00:00
// OK, we have a valid graphics backend selected. Let's clear the failures.
g_Config . sFailedGPUBackends . clear ( ) ;
2017-12-15 14:29:19 +00:00
# ifdef MOBILE_DEVICE
SDL_ShowCursor ( SDL_DISABLE ) ;
# endif
2024-09-09 19:09:26 +00:00
// Avoid the IME popup when holding keys. This doesn't affect all versions of SDL.
2024-10-02 14:08:12 +00:00
// Note: We re-enable it in text input fields! This is necessary otherwise we don't receive
// KEY_CHAR events.
2024-09-09 19:09:26 +00:00
SDL_StopTextInput ( ) ;
2019-09-15 20:23:48 +00:00
InitSDLAudioDevice ( ) ;
2012-07-06 20:32:32 +00:00
2016-07-09 07:15:11 +00:00
if ( joystick_enabled ) {
joystick = new SDLJoystick ( ) ;
} else {
joystick = nullptr ;
}
2022-10-10 16:35:29 +00:00
EnableFZ ( ) ;
2013-07-14 11:51:30 +00:00
2023-09-29 17:10:51 +00:00
EmuThreadStart ( graphicsContext ) ;
2018-01-21 17:03:19 +00:00
graphicsContext - > ThreadStart ( ) ;
2023-08-08 09:21:09 +00:00
InputStateTracker inputTracker { } ;
2023-02-06 19:17:27 +00:00
# if PPSSPP_PLATFORM(MAC)
2023-03-23 19:09:01 +00:00
// setup menu items for macOS
2022-12-29 15:14:54 +00:00
initializeOSXExtras ( ) ;
2023-02-06 19:17:27 +00:00
# endif
2023-09-29 17:16:06 +00:00
bool waitOnExit = g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL ;
2023-09-29 17:10:51 +00:00
if ( ! mainThreadIsRender ) {
2024-05-28 17:06:00 +00:00
// Vulkan mode uses this.
// We should only be a message pump. This allows for lower latency
// input events, and so on.
2023-09-29 17:10:51 +00:00
while ( true ) {
SDL_Event event ;
2024-05-28 17:06:00 +00:00
while ( SDL_WaitEventTimeout ( & event , 100 ) ) {
2023-09-29 17:10:51 +00:00
ProcessSDLEvent ( window , event , & inputTracker ) ;
}
if ( g_QuitRequested | | g_RestartRequested )
break ;
2023-09-29 17:35:16 +00:00
2024-10-02 14:08:12 +00:00
UpdateTextFocus ( ) ;
2023-09-29 17:35:16 +00:00
UpdateSDLCursor ( ) ;
inputTracker . MouseCaptureControl ( ) ;
{
std : : lock_guard < std : : mutex > guard ( g_mutexWindow ) ;
if ( g_windowState . update ) {
UpdateWindowState ( window ) ;
}
}
2023-09-29 17:10:51 +00:00
}
} else while ( true ) {
2023-09-29 17:01:23 +00:00
{
SDL_Event event ;
while ( SDL_PollEvent ( & event ) ) {
ProcessSDLEvent ( window , event , & inputTracker ) ;
}
2012-04-10 09:59:57 +00:00
}
2023-04-29 09:17:10 +00:00
if ( g_QuitRequested | | g_RestartRequested )
2012-07-06 20:32:32 +00:00
break ;
2018-01-21 17:03:19 +00:00
if ( emuThreadState = = ( int ) EmuThreadState : : DISABLED ) {
2023-08-10 14:33:02 +00:00
UpdateRunLoop ( graphicsContext ) ;
2018-01-21 17:03:19 +00:00
}
2023-04-29 09:17:10 +00:00
if ( g_QuitRequested | | g_RestartRequested )
2014-02-12 11:45:26 +00:00
break ;
2023-04-29 09:17:10 +00:00
2024-10-02 14:08:12 +00:00
UpdateTextFocus ( ) ;
2023-09-29 17:02:25 +00:00
UpdateSDLCursor ( ) ;
2012-10-26 16:42:17 +00:00
2023-08-08 09:21:09 +00:00
inputTracker . MouseCaptureControl ( ) ;
2012-10-31 11:12:24 +00:00
2023-08-08 09:21:09 +00:00
bool renderThreadPaused = Core_IsWindowHidden ( ) & & g_Config . bPauseWhenMinimized & & emuThreadState ! = ( int ) EmuThreadState : : DISABLED ;
2019-02-18 13:26:57 +00:00
if ( emuThreadState ! = ( int ) EmuThreadState : : DISABLED & & ! renderThreadPaused ) {
2018-01-21 17:03:19 +00:00
if ( ! graphicsContext - > ThreadFrame ( ) )
break ;
}
2018-03-25 13:32:51 +00:00
2023-03-28 12:13:23 +00:00
{
std : : lock_guard < std : : mutex > guard ( g_mutexWindow ) ;
if ( g_windowState . update ) {
UpdateWindowState ( window ) ;
}
}
2018-03-25 13:32:51 +00:00
2023-08-08 09:21:09 +00:00
if ( g_rebootEmuThread ) {
2022-12-29 15:14:54 +00:00
printf ( " rebooting emu thread " ) ;
2023-08-08 09:21:09 +00:00
g_rebootEmuThread = false ;
2022-12-29 15:14:54 +00:00
EmuThreadStop ( " shutdown " ) ;
// Skipping GL calls, the old context is gone.
while ( graphicsContext - > ThreadFrame ( ) ) {
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " graphicsContext->ThreadFrame executed to clear buffers " ) ;
2022-12-29 15:14:54 +00:00
}
EmuThreadJoin ( ) ;
graphicsContext - > ThreadEnd ( ) ;
graphicsContext - > ShutdownFromRenderThread ( ) ;
printf ( " OK, shutdown complete. starting up graphics again. \n " ) ;
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL ) {
SDLGLGraphicsContext * ctx = ( SDLGLGraphicsContext * ) graphicsContext ;
if ( ! ctx - > Init ( window , x , y , w , h , mode , & error_message ) ) {
printf ( " Failed to reinit graphics. \n " ) ;
}
}
if ( ! graphicsContext - > InitFromRenderThread ( & error_message ) ) {
System_Toast ( " Graphics initialization failed. Quitting. " ) ;
return 1 ;
}
EmuThreadStart ( graphicsContext ) ;
graphicsContext - > ThreadStart ( ) ;
}
2012-04-10 09:59:57 +00:00
}
2018-01-21 17:03:19 +00:00
2023-09-29 17:10:51 +00:00
EmuThreadStop ( " shutdown " ) ;
2023-09-29 17:16:06 +00:00
if ( waitOnExit ) {
2018-02-11 19:40:11 +00:00
while ( graphicsContext - > ThreadFrame ( ) ) {
2018-02-07 11:22:19 +00:00
// Need to keep eating frames to allow the EmuThread to exit correctly.
2018-02-11 19:40:11 +00:00
continue ;
2018-02-07 11:22:19 +00:00
}
}
2023-09-29 17:16:06 +00:00
2023-09-29 17:10:51 +00:00
EmuThreadJoin ( ) ;
2018-01-21 17:03:19 +00:00
2014-06-07 14:55:51 +00:00
delete joystick ;
2018-02-04 11:26:35 +00:00
2018-01-31 17:35:48 +00:00
graphicsContext - > ThreadEnd ( ) ;
2018-02-07 11:22:19 +00:00
2015-12-26 03:39:52 +00:00
NativeShutdown ( ) ;
2018-03-25 21:19:02 +00:00
// Destroys Draw, which is used in NativeShutdown to shutdown.
graphicsContext - > ShutdownFromRenderThread ( ) ;
2020-08-11 20:28:36 +00:00
graphicsContext - > Shutdown ( ) ;
2017-03-11 11:54:13 +00:00
delete graphicsContext ;
2017-12-13 21:58:45 +00:00
2019-09-15 19:42:49 +00:00
if ( audioDev > 0 ) {
SDL_PauseAudioDevice ( audioDev , 1 ) ;
SDL_CloseAudioDevice ( audioDev ) ;
}
2012-04-10 09:59:57 +00:00
SDL_Quit ( ) ;
2016-10-12 15:32:52 +00:00
# if PPSSPP_PLATFORM(RPI)
2014-02-09 22:31:31 +00:00
bcm_host_deinit ( ) ;
# endif
2017-12-13 21:58:45 +00:00
glslang : : FinalizeProcess ( ) ;
2022-08-16 22:22:01 +00:00
printf ( " Leaving main \n " ) ;
2020-03-15 14:56:38 +00:00
# ifdef HAVE_LIBNX
socketExit ( ) ;
# endif
2023-04-29 09:17:10 +00:00
// If a restart was requested (and supported on this platform), respawn the executable.
if ( g_RestartRequested ) {
# if PPSSPP_PLATFORM(MAC)
RestartMacApp ( ) ;
2023-04-29 09:47:37 +00:00
# elif PPSSPP_PLATFORM(LINUX)
// Hackery from https://unix.stackexchange.com/questions/207935/how-to-restart-or-reset-a-running-process-in-linux,
char * exec_argv [ ] = { argv [ 0 ] , nullptr } ;
execv ( " /proc/self/exe " , exec_argv ) ;
2023-04-29 09:17:10 +00:00
# endif
}
2012-04-10 09:59:57 +00:00
return 0 ;
}