2020-12-05 13:08:24 +01:00
#include "headless_application.h"
2017-06-04 09:48:33 -05:00
2025-02-11 03:00:37 +01:00
#include "Emu/System.h"
2020-07-30 13:23:55 +02:00
#include "Emu/RSX/Null/NullGSRender.h"
2020-02-25 22:42:29 +01:00
#include "Emu/Cell/Modules/cellMsgDialog.h"
#include "Emu/Cell/Modules/cellOskDialog.h"
#include "Emu/Cell/Modules/cellSaveData.h"
#include "Emu/Cell/Modules/sceNpTrophy.h"
2021-11-14 16:49:32 +01:00
#include "Emu/Io/Null/null_camera_handler.h"
2022-02-20 19:01:21 +01:00
#include "Emu/Io/Null/null_music_handler.h"
2025-03-31 01:02:05 +02:00
#include "util/video_source.h"
2019-06-21 13:33:32 +02:00
2019-09-20 00:14:09 +02:00
#include <clocale>
2025-05-07 21:20:09 +02:00
LOG_CHANNEL ( sys_log , "SYS" );
2023-06-16 21:05:35 +03:00
[[noreturn]] void report_fatal_error ( std :: string_view text , bool is_html = false , bool include_help_text = true );
2019-07-11 23:11:52 +02:00
// For now, a trivial constructor/destructor. May add command line usage later.
2019-08-14 20:01:55 +02:00
headless_application :: headless_application ( int & argc , char ** argv ) : QCoreApplication ( argc , argv )
2017-06-04 09:48:33 -05:00
{
}
2026-08-11 02:32:53 +02:00
void headless_application :: Init ()
2017-06-04 09:48:33 -05:00
{
// Create callbacks from the emulator, which reference the handlers.
InitializeCallbacks ();
2026-08-09 11:42:38 +02:00
// Force init the emulator
InitializeEmulator ( m_active_user . empty () ? "00000001" : m_active_user , false , true );
2017-06-04 09:48:33 -05:00
// Create connects to propagate events throughout Gui.
InitializeConnects ();
2019-09-20 00:14:09 +02:00
2021-04-02 00:54:32 +02:00
// As per Qt recommendations to avoid conflicts for POSIX functions
2019-09-20 00:14:09 +02:00
std :: setlocale ( LC_NUMERIC , "C" );
2017-06-04 09:48:33 -05:00
}
2021-04-07 23:05:18 +02:00
void headless_application :: InitializeConnects () const
2018-07-23 23:55:27 +02:00
{
2019-06-18 04:02:06 +02:00
qRegisterMetaType < std :: function < void () >> ( "std::function<void()>" );
2022-01-20 18:44:49 +01:00
connect ( this , & headless_application :: RequestCallFromMainThread , this , & headless_application :: CallFromMainThread );
2018-07-23 23:55:27 +02:00
}
2019-06-18 04:02:06 +02:00
/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */
2019-08-14 20:01:55 +02:00
void headless_application :: InitializeCallbacks ()
2017-06-04 09:48:33 -05:00
{
2019-06-18 04:02:06 +02:00
EmuCallbacks callbacks = CreateCallbacks ();
2017-06-04 09:48:33 -05:00
2026-03-27 17:21:41 +01:00
callbacks . try_to_quit = []( bool force_quit , std :: function < void () > on_exit ) -> bool
2017-06-04 09:48:33 -05:00
{
2019-08-25 11:51:13 +02:00
if ( force_quit )
{
2020-12-18 12:40:25 +01:00
if ( on_exit )
{
on_exit ();
}
2026-04-18 17:37:13 +02:00
sys_log . notice ( "Quitting headless application (force_quit=%d)" , force_quit );
2019-08-25 11:51:13 +02:00
quit ();
2020-07-03 17:11:28 +02:00
return true ;
2019-08-25 11:51:13 +02:00
}
2020-07-03 17:11:28 +02:00
return false ;
2017-06-04 09:48:33 -05:00
};
2023-07-31 23:57:26 +03:00
callbacks . call_from_main_thread = [ this ]( std :: function < void () > func , atomic_t < u32 >* wake_up )
2017-09-13 00:49:02 +01:00
{
2022-06-24 19:58:26 +02:00
RequestCallFromMainThread ( std :: move ( func ), wake_up );
2017-06-04 09:48:33 -05:00
};
2022-07-04 16:02:17 +03:00
callbacks . init_gs_render = []( utils :: serial * ar )
2020-07-30 13:23:55 +02:00
{
2021-04-07 23:05:18 +02:00
switch ( const video_renderer type = g_cfg . video . renderer )
2020-07-30 13:23:55 +02:00
{
case video_renderer :: null :
{
2022-07-04 16:02:17 +03:00
g_fxo -> init < rsx :: thread , named_thread < NullGSRender >> ( ar );
2020-07-30 13:23:55 +02:00
break ;
}
case video_renderer :: opengl :
case video_renderer :: vulkan :
{
fmt :: throw_exception ( "Headless mode can only be used with the %s video renderer. Current renderer: %s" , video_renderer :: null , type );
2021-04-07 23:05:18 +02:00
[[fallthrough]] ;
2020-07-30 13:23:55 +02:00
}
default :
{
2020-12-09 18:04:52 +03:00
fmt :: throw_exception ( "Invalid video renderer: %s" , type );
2020-07-30 13:23:55 +02:00
}
}
};
2021-11-14 16:49:32 +01:00
callbacks . get_camera_handler = []() -> std :: shared_ptr < camera_handler_base >
{
switch ( g_cfg . io . camera . get ())
{
case camera_handler :: null :
case camera_handler :: fake :
{
return std :: make_shared < null_camera_handler > ();
}
case camera_handler :: qt :
2025-01-27 20:32:29 +01:00
#ifdef HAVE_SDL3
case camera_handler :: sdl :
#endif
2021-11-14 16:49:32 +01:00
{
fmt :: throw_exception ( "Headless mode can not be used with this camera handler. Current handler: %s" , g_cfg . io . camera . get ());
}
}
return nullptr ;
};
2022-02-20 19:01:21 +01:00
callbacks . get_music_handler = []() -> std :: shared_ptr < music_handler_base >
{
switch ( g_cfg . audio . music . get ())
{
case music_handler :: null :
{
return std :: make_shared < null_music_handler > ();
}
case music_handler :: qt :
{
fmt :: throw_exception ( "Headless mode can not be used with this music handler. Current handler: %s" , g_cfg . audio . music . get ());
}
}
return nullptr ;
};
2025-01-04 14:23:10 +01:00
callbacks . close_gs_frame = [](){};
2020-07-30 13:23:55 +02:00
callbacks . get_gs_frame = []() -> std :: unique_ptr < GSFrameBase >
{
if ( g_cfg . video . renderer != video_renderer :: null )
{
fmt :: throw_exception ( "Headless mode can only be used with the %s video renderer. Current renderer: %s" , video_renderer :: null , g_cfg . video . renderer . get ());
}
return std :: unique_ptr < GSFrameBase > ();
};
2019-06-18 04:02:06 +02:00
callbacks . get_msg_dialog = []() -> std :: shared_ptr < MsgDialogBase > { return std :: shared_ptr < MsgDialogBase > (); };
callbacks . get_osk_dialog = []() -> std :: shared_ptr < OskDialogBase > { return std :: shared_ptr < OskDialogBase > (); };
callbacks . get_save_dialog = []() -> std :: unique_ptr < SaveDialogBase > { return std :: unique_ptr < SaveDialogBase > (); };
callbacks . get_trophy_notification_dialog = []() -> std :: unique_ptr < TrophyNotificationBase > { return std :: unique_ptr < TrophyNotificationBase > (); };
2018-12-17 19:13:35 +01:00
2020-02-07 21:55:29 +01:00
callbacks . on_run = []( bool /*start_playtime*/ ) {};
2019-06-18 04:02:06 +02:00
callbacks . on_pause = []() {};
callbacks . on_resume = []() {};
callbacks . on_stop = []() {};
callbacks . on_ready = []() {};
2023-06-16 21:05:35 +03:00
callbacks . on_emulation_stop_no_response = []( std :: shared_ptr < atomic_t < bool >> closed_successfully , int /*seconds_waiting_already*/ )
{
if ( ! closed_successfully || !* closed_successfully )
{
report_fatal_error ( tr ( "Stopping emulator took too long."
" \n Some thread has probably deadlocked. Aborting." ). toStdString ());
}
};
2018-06-08 15:20:38 +02:00
2024-03-06 17:28:07 +02:00
callbacks . on_save_state_progress = []( std :: shared_ptr < atomic_t < bool >> , stx :: shared_ptr < utils :: serial > , stx :: atomic_ptr < std :: string >* , std :: shared_ptr < void > )
2024-03-24 12:08:42 +02:00
{
};
2022-06-21 22:13:22 +02:00
callbacks . enable_disc_eject = []( bool ) {};
callbacks . enable_disc_insert = []( bool ) {};
2024-11-24 09:23:32 +02:00
callbacks . on_missing_fw = []() {};
2021-11-14 16:49:32 +01:00
callbacks . handle_taskbar_progress = []( s32 , s32 ) {};
2020-09-03 18:30:46 +02:00
callbacks . get_localized_string = []( localized_string_id , const char * ) -> std :: string { return {}; };
callbacks . get_localized_u32string = []( localized_string_id , const char * ) -> std :: u32string { return {}; };
2024-11-14 23:48:51 +01:00
callbacks . get_localized_setting = []( const cfg :: _base * , u32 ) -> std :: string { return {}; };
2020-09-02 16:20:29 +02:00
2025-11-18 19:52:58 +01:00
callbacks . play_sound = []( const std :: string & , std :: optional < f32 > ){};
2023-09-25 18:32:50 +03:00
callbacks . add_breakpoint = []( u32 /*addr*/ ){};
2021-10-29 22:04:49 +02:00
2025-02-24 21:41:14 +01:00
callbacks . display_sleep_control_supported = [](){ return false ; };
callbacks . enable_display_sleep = []( bool /*enabled*/ ){};
2025-02-24 21:38:54 +01:00
callbacks . check_microphone_permissions = [](){};
2025-03-31 01:02:05 +02:00
callbacks . make_video_source = [](){ return nullptr ; };
2017-06-04 09:48:33 -05:00
Emu . SetCallbacks ( std :: move ( callbacks ));
}
/**
2019-06-18 04:02:06 +02:00
* Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct.
*/
2023-07-31 23:57:26 +03:00
void headless_application :: CallFromMainThread ( const std :: function < void () >& func , atomic_t < u32 >* wake_up )
2017-06-04 09:48:33 -05:00
{
func ();
2022-06-24 19:58:26 +02:00
if ( wake_up )
{
* wake_up = true ;
wake_up -> notify_one ();
}
2017-06-04 09:48:33 -05:00
}