#include "headless_application.h" #include "Emu/System.h" #include "Emu/RSX/Null/NullGSRender.h" #include "rpcsx/fw/ps3/cellMsgDialog.h" #include "rpcsx/fw/ps3/cellOskDialog.h" #include "rpcsx/fw/ps3/cellSaveData.h" #include "rpcsx/fw/ps3/sceNpTrophy.h" #include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Io/Null/null_music_handler.h" #include "Emu/Io/Null/NullPadHandler.h" #include "Input/ds3_pad_handler.h" #include "Input/ds4_pad_handler.h" #include "Input/dualsense_pad_handler.h" #include "Input/skateboard_pad_handler.h" #include "Input/ps_move_handler.h" #include "Input/virtual_pad_handler.h" #ifdef _WIN32 #include "Input/xinput_pad_handler.h" #include "Input/mm_joystick_handler.h" #endif #ifdef HAVE_SDL3 #include "Input/sdl_pad_handler.h" #endif #ifdef HAVE_LIBEVDEV #include "Input/evdev_joystick_handler.h" #endif #include "util/video_source.h" #include [[noreturn]] void report_fatal_error(std::string_view text, bool is_html = false, bool include_help_text = true); // For now, a trivial constructor/destructor. May add command line usage later. headless_application::headless_application(int& argc, char** argv) : QCoreApplication(argc, argv) { } bool headless_application::Init() { // Force init the emulator InitializeEmulator(m_active_user.empty() ? "00000001" : m_active_user, false); // Create callbacks from the emulator, which reference the handlers. InitializeCallbacks(); // Create connects to propagate events throughout Gui. InitializeConnects(); // As per Qt recommendations to avoid conflicts for POSIX functions std::setlocale(LC_NUMERIC, "C"); return true; } void headless_application::InitializeConnects() const { qRegisterMetaType>("std::function"); connect(this, &headless_application::RequestCallFromMainThread, this, &headless_application::CallFromMainThread); } /** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */ void headless_application::InitializeCallbacks() { EmuCallbacks callbacks = CreateCallbacks(); callbacks.try_to_quit = [this](bool force_quit, std::function on_exit) -> bool { if (force_quit) { if (on_exit) { on_exit(); } quit(); return true; } return false; }; callbacks.call_from_main_thread = [this](std::function func, atomic_t* wake_up) { RequestCallFromMainThread(std::move(func), wake_up); }; callbacks.init_gs_render = [](utils::serial* ar) { switch (const video_renderer type = g_cfg.video.renderer) { case video_renderer::null: { g_fxo->init>(ar); 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); [[fallthrough]]; } default: { fmt::throw_exception("Invalid video renderer: %s", type); } } }; callbacks.get_camera_handler = []() -> std::shared_ptr { switch (g_cfg.io.camera.get()) { case camera_handler::null: case camera_handler::fake: { return std::make_shared(); } case camera_handler::qt: { fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get()); } } return nullptr; }; callbacks.get_music_handler = []() -> std::shared_ptr { switch (g_cfg.audio.music.get()) { case music_handler::null: { return std::make_shared(); } 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; }; callbacks.create_pad_handler = [](pad_handler type, void* thread, void* window) -> std::shared_ptr { switch (type) { case pad_handler::null: case pad_handler::keyboard: break; case pad_handler::ds3: return std::make_shared(); case pad_handler::ds4: return std::make_shared(); case pad_handler::dualsense: return std::make_shared(); case pad_handler::skateboard: return std::make_shared(); case pad_handler::move: return std::make_shared(); #ifdef _WIN32 case pad_handler::xinput: return std::make_shared(); case pad_handler::mm: return std::make_shared(); #endif #ifdef HAVE_SDL3 case pad_handler::sdl: return std::make_shared(); #endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: return std::make_shared(); #endif case pad_handler::virtual_pad: return std::make_shared(); } return std::make_shared(); }; callbacks.close_gs_frame = []() {}; callbacks.get_gs_frame = []() -> std::unique_ptr { 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(); }; callbacks.get_msg_dialog = []() -> std::shared_ptr { return std::shared_ptr(); }; callbacks.get_osk_dialog = []() -> std::shared_ptr { return std::shared_ptr(); }; callbacks.get_save_dialog = []() -> std::unique_ptr { return std::unique_ptr(); }; callbacks.get_trophy_notification_dialog = []() -> std::unique_ptr { return std::unique_ptr(); }; callbacks.on_run = [](bool /*start_playtime*/) {}; callbacks.on_pause = []() {}; callbacks.on_resume = []() {}; callbacks.on_stop = []() {}; callbacks.on_ready = []() {}; callbacks.on_emulation_stop_no_response = [](std::shared_ptr> closed_successfully, int /*seconds_waiting_already*/) { if (!closed_successfully || !*closed_successfully) { report_fatal_error(tr("Stopping emulator took too long." "\nSome thread has probably deadlocked. Aborting.") .toStdString()); } }; callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr, stx::atomic_ptr*, std::shared_ptr) {}; callbacks.enable_disc_eject = [](bool) {}; callbacks.enable_disc_insert = [](bool) {}; callbacks.on_missing_fw = []() {}; callbacks.handle_taskbar_progress = [](s32, s32) {}; callbacks.get_localized_string = [](localized_string_id, const char*) -> std::string { return {}; }; callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { return {}; }; callbacks.get_localized_setting = [](const cfg::_base*, u32) -> std::string { return {}; }; callbacks.play_sound = [](const std::string&) {}; callbacks.add_breakpoint = [](u32 /*addr*/) {}; callbacks.display_sleep_control_supported = []() { return false; }; callbacks.enable_display_sleep = [](bool /*enabled*/) {}; callbacks.check_microphone_permissions = []() {}; callbacks.make_video_source = []() { return nullptr; }; Emu.SetCallbacks(std::move(callbacks)); } /** * 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. */ void headless_application::CallFromMainThread(const std::function& func, atomic_t* wake_up) { func(); if (wake_up) { *wake_up = true; wake_up->notify_one(); } }