Frontends: Add shared command line interface
Also provides batch mode and automatic fullscreen switching. -help: Displays this information and exits. -version: Displays version information and exits. -batch: Enables batch mode (exits after powering off) -fastboot: Force fast boot for provided filename -slowboot: Force slow boot for provided filename -resume: Load resume save state. If a boot filename is provided, that game's resume state will be loaded, otherwise the most recent resume save state will be loaded. -state <index>: Loads specified save state by index. If a boot filename is provided, a per-game state will be loaded, otherwise a global state will be loaded. -statefile <filename>: Loads state from the specified filename. No boot filename is required with this option. -fullscreen: Enters fullscreen mode immediately after starting. -nofullscreen: Prevents fullscreen mode from triggering if enabled. -portable: Forces "portable mode", data in same directory. --: Signals that no more arguments will follow and the remaining parameters make up the filename. Use when the filename contains spaces or starts with a dash.
This commit is contained in:
parent
6a03bb2d15
commit
81cf4b469f
|
@ -61,6 +61,14 @@ void HostInterface::CreateAudioStream()
|
||||||
|
|
||||||
bool HostInterface::BootSystem(const SystemBootParameters& parameters)
|
bool HostInterface::BootSystem(const SystemBootParameters& parameters)
|
||||||
{
|
{
|
||||||
|
if (parameters.filename.empty())
|
||||||
|
Log_InfoPrintf("Boot Filename: <BIOS/Shell>");
|
||||||
|
else
|
||||||
|
Log_InfoPrintf("Boot Filename: %s", parameters.filename.c_str());
|
||||||
|
|
||||||
|
if (!parameters.state_filename.empty())
|
||||||
|
Log_InfoPrintf("Save State Filename: %s", parameters.filename.c_str());
|
||||||
|
|
||||||
if (!AcquireHostDisplay())
|
if (!AcquireHostDisplay())
|
||||||
{
|
{
|
||||||
ReportFormattedError("Failed to acquire host display");
|
ReportFormattedError("Failed to acquire host display");
|
||||||
|
@ -81,6 +89,9 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parameters.state_filename.empty())
|
||||||
|
LoadState(parameters.state_filename.c_str());
|
||||||
|
|
||||||
OnSystemCreated();
|
OnSystemCreated();
|
||||||
|
|
||||||
m_paused = m_settings.start_paused;
|
m_paused = m_settings.start_paused;
|
||||||
|
@ -611,6 +622,9 @@ void HostInterface::OnControllerTypeChanged(u32 slot) {}
|
||||||
|
|
||||||
void HostInterface::SetUserDirectory()
|
void HostInterface::SetUserDirectory()
|
||||||
{
|
{
|
||||||
|
if (!m_user_directory.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
const std::string program_path = FileSystem::GetProgramPath();
|
const std::string program_path = FileSystem::GetProgramPath();
|
||||||
const std::string program_directory = FileSystem::GetPathDirectory(program_path.c_str());
|
const std::string program_directory = FileSystem::GetPathDirectory(program_path.c_str());
|
||||||
Log_InfoPrintf("Program path: \"%s\" (directory \"%s\")", program_path.c_str(), program_directory.c_str());
|
Log_InfoPrintf("Program path: \"%s\" (directory \"%s\")", program_path.c_str(), program_directory.c_str());
|
||||||
|
@ -653,6 +667,13 @@ void HostInterface::SetUserDirectory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HostInterface::SetUserDirectoryToProgramDirectory()
|
||||||
|
{
|
||||||
|
const std::string program_path = FileSystem::GetProgramPath();
|
||||||
|
const std::string program_directory = FileSystem::GetPathDirectory(program_path.c_str());
|
||||||
|
m_user_directory = program_directory;
|
||||||
|
}
|
||||||
|
|
||||||
void HostInterface::InitializeUserDirectory()
|
void HostInterface::InitializeUserDirectory()
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("User directory: \"%s\"", m_user_directory.c_str());
|
Log_InfoPrintf("User directory: \"%s\"", m_user_directory.c_str());
|
||||||
|
|
|
@ -50,10 +50,11 @@ public:
|
||||||
/// Shuts down the emulator frontend.
|
/// Shuts down the emulator frontend.
|
||||||
virtual void Shutdown();
|
virtual void Shutdown();
|
||||||
|
|
||||||
bool BootSystem(const SystemBootParameters& parameters);
|
virtual bool BootSystem(const SystemBootParameters& parameters);
|
||||||
|
virtual void PowerOffSystem();
|
||||||
|
|
||||||
void PauseSystem(bool paused);
|
void PauseSystem(bool paused);
|
||||||
void ResetSystem();
|
void ResetSystem();
|
||||||
void PowerOffSystem();
|
|
||||||
void DestroySystem();
|
void DestroySystem();
|
||||||
|
|
||||||
/// Loads state from the specified filename.
|
/// Loads state from the specified filename.
|
||||||
|
@ -158,6 +159,9 @@ protected:
|
||||||
/// Sets the base path for the user directory. Can be overridden by platform/frontend/command line.
|
/// Sets the base path for the user directory. Can be overridden by platform/frontend/command line.
|
||||||
virtual void SetUserDirectory();
|
virtual void SetUserDirectory();
|
||||||
|
|
||||||
|
/// Sets the user directory to the program directory, i.e. "portable mode".
|
||||||
|
void SetUserDirectoryToProgramDirectory();
|
||||||
|
|
||||||
/// Performs the initial load of settings. Should call CheckSettings() and m_settings.Load().
|
/// Performs the initial load of settings. Should call CheckSettings() and m_settings.Load().
|
||||||
virtual void LoadSettings() = 0;
|
virtual void LoadSettings() = 0;
|
||||||
|
|
||||||
|
@ -229,14 +233,14 @@ protected:
|
||||||
Settings m_settings;
|
Settings m_settings;
|
||||||
std::string m_user_directory;
|
std::string m_user_directory;
|
||||||
|
|
||||||
|
std::deque<OSDMessage> m_osd_messages;
|
||||||
|
std::mutex m_osd_messages_lock;
|
||||||
|
|
||||||
bool m_paused = false;
|
bool m_paused = false;
|
||||||
bool m_speed_limiter_temp_disabled = false;
|
bool m_speed_limiter_temp_disabled = false;
|
||||||
bool m_speed_limiter_enabled = false;
|
bool m_speed_limiter_enabled = false;
|
||||||
bool m_timer_resolution_increased = false;
|
bool m_timer_resolution_increased = false;
|
||||||
|
|
||||||
std::deque<OSDMessage> m_osd_messages;
|
|
||||||
std::mutex m_osd_messages_lock;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitializeUserDirectory();
|
void InitializeUserDirectory();
|
||||||
void CreateAudioStream();
|
void CreateAudioStream();
|
||||||
|
|
|
@ -35,7 +35,9 @@ struct SystemBootParameters
|
||||||
~SystemBootParameters();
|
~SystemBootParameters();
|
||||||
|
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
std::string state_filename;
|
||||||
std::optional<bool> override_fast_boot;
|
std::optional<bool> override_fast_boot;
|
||||||
|
std::optional<bool> override_fullscreen;
|
||||||
};
|
};
|
||||||
|
|
||||||
class System
|
class System
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
#include "qthostinterface.h"
|
#include "qthostinterface.h"
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
|
#include <cstdlib>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
static void InitLogging()
|
static void InitLogging()
|
||||||
{
|
{
|
||||||
// set log flags
|
// set log flags
|
||||||
#ifdef Y_BUILD_CONFIG_DEBUG
|
#ifdef _DEBUG
|
||||||
Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||||
Log::SetFilterLevel(LOGLEVEL_DEBUG);
|
Log::SetFilterLevel(LOGLEVEL_DEBUG);
|
||||||
#else
|
#else
|
||||||
|
@ -35,18 +36,30 @@ int main(int argc, char* argv[])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::unique_ptr<QtHostInterface> host_interface = std::make_unique<QtHostInterface>();
|
std::unique_ptr<QtHostInterface> host_interface = std::make_unique<QtHostInterface>();
|
||||||
|
std::unique_ptr<SystemBootParameters> boot_params;
|
||||||
|
if (!host_interface->parseCommandLineParameters(argc, argv, &boot_params))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
if (!host_interface->Initialize())
|
if (!host_interface->Initialize())
|
||||||
{
|
{
|
||||||
host_interface->Shutdown();
|
host_interface->Shutdown();
|
||||||
QMessageBox::critical(nullptr, QObject::tr("DuckStation Error"),
|
QMessageBox::critical(nullptr, QObject::tr("DuckStation Error"),
|
||||||
QObject::tr("Failed to initialize host interface. Cannot continue."), QMessageBox::Ok);
|
QObject::tr("Failed to initialize host interface. Cannot continue."), QMessageBox::Ok);
|
||||||
return -1;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<MainWindow> window = std::make_unique<MainWindow>(host_interface.get());
|
std::unique_ptr<MainWindow> window = std::make_unique<MainWindow>(host_interface.get());
|
||||||
window->show();
|
window->show();
|
||||||
|
|
||||||
host_interface->refreshGameList();
|
// if we're in batch mode, don't bother refreshing the game list as it won't be used
|
||||||
|
if (!host_interface->inBatchMode())
|
||||||
|
host_interface->refreshGameList();
|
||||||
|
|
||||||
|
if (boot_params)
|
||||||
|
{
|
||||||
|
host_interface->bootSystem(*boot_params);
|
||||||
|
boot_params.reset();
|
||||||
|
}
|
||||||
|
|
||||||
int result = app.exec();
|
int result = app.exec();
|
||||||
|
|
||||||
|
|
|
@ -528,6 +528,7 @@ void MainWindow::connectSignals()
|
||||||
connect(m_host_interface, &QtHostInterface::systemPerformanceCountersUpdated, this,
|
connect(m_host_interface, &QtHostInterface::systemPerformanceCountersUpdated, this,
|
||||||
&MainWindow::onSystemPerformanceCountersUpdated);
|
&MainWindow::onSystemPerformanceCountersUpdated);
|
||||||
connect(m_host_interface, &QtHostInterface::runningGameChanged, this, &MainWindow::onRunningGameChanged);
|
connect(m_host_interface, &QtHostInterface::runningGameChanged, this, &MainWindow::onRunningGameChanged);
|
||||||
|
connect(m_host_interface, &QtHostInterface::exitRequested, this, &MainWindow::close);
|
||||||
|
|
||||||
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
|
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
|
||||||
connect(m_game_list_widget, &GameListWidget::entrySelected, this, &MainWindow::onGameListEntrySelected,
|
connect(m_game_list_widget, &GameListWidget::entrySelected, this, &MainWindow::onGameListEntrySelected,
|
||||||
|
|
|
@ -39,6 +39,11 @@ QtHostInterface::~QtHostInterface()
|
||||||
Assert(!m_display_widget);
|
Assert(!m_display_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* QtHostInterface::GetFrontendName() const
|
||||||
|
{
|
||||||
|
return "DuckStation Qt Frontend";
|
||||||
|
}
|
||||||
|
|
||||||
bool QtHostInterface::Initialize()
|
bool QtHostInterface::Initialize()
|
||||||
{
|
{
|
||||||
createThread();
|
createThread();
|
||||||
|
@ -104,6 +109,12 @@ bool QtHostInterface::ConfirmMessage(const char* message)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QtHostInterface::parseCommandLineParameters(int argc, char* argv[],
|
||||||
|
std::unique_ptr<SystemBootParameters>* out_boot_params)
|
||||||
|
{
|
||||||
|
return CommonHostInterface::ParseCommandLineParameters(argc, argv, out_boot_params);
|
||||||
|
}
|
||||||
|
|
||||||
QVariant QtHostInterface::getSettingValue(const QString& name, const QVariant& default_value)
|
QVariant QtHostInterface::getSettingValue(const QString& name, const QVariant& default_value)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> guard(m_qsettings_mutex);
|
std::lock_guard<std::recursive_mutex> guard(m_qsettings_mutex);
|
||||||
|
@ -320,6 +331,11 @@ bool QtHostInterface::SetFullscreen(bool enabled)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::RequestExit()
|
||||||
|
{
|
||||||
|
emit exitRequested();
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<CommonHostInterface::HostKeyCode> QtHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
std::optional<CommonHostInterface::HostKeyCode> QtHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
||||||
{
|
{
|
||||||
const std::optional<int> code =
|
const std::optional<int> code =
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "common/event.h"
|
||||||
#include "core/host_interface.h"
|
#include "core/host_interface.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
#include "common/event.h"
|
|
||||||
#include "frontend-common/common_host_interface.h"
|
#include "frontend-common/common_host_interface.h"
|
||||||
#include <QtCore/QByteArray>
|
#include <QtCore/QByteArray>
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
@ -37,6 +37,8 @@ public:
|
||||||
explicit QtHostInterface(QObject* parent = nullptr);
|
explicit QtHostInterface(QObject* parent = nullptr);
|
||||||
~QtHostInterface();
|
~QtHostInterface();
|
||||||
|
|
||||||
|
const char* GetFrontendName() const override;
|
||||||
|
|
||||||
bool Initialize() override;
|
bool Initialize() override;
|
||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
|
||||||
|
@ -44,6 +46,8 @@ public:
|
||||||
void ReportMessage(const char* message) override;
|
void ReportMessage(const char* message) override;
|
||||||
bool ConfirmMessage(const char* message) override;
|
bool ConfirmMessage(const char* message) override;
|
||||||
|
|
||||||
|
bool parseCommandLineParameters(int argc, char* argv[], std::unique_ptr<SystemBootParameters>* out_boot_params);
|
||||||
|
|
||||||
/// Thread-safe QSettings access.
|
/// Thread-safe QSettings access.
|
||||||
QVariant getSettingValue(const QString& name, const QVariant& default_value = QVariant());
|
QVariant getSettingValue(const QString& name, const QVariant& default_value = QVariant());
|
||||||
void putSettingValue(const QString& name, const QVariant& value);
|
void putSettingValue(const QString& name, const QVariant& value);
|
||||||
|
@ -55,6 +59,7 @@ public:
|
||||||
|
|
||||||
ALWAYS_INLINE const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); }
|
ALWAYS_INLINE const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); }
|
||||||
ALWAYS_INLINE ControllerInterface* getControllerInterface() const { return GetControllerInterface(); }
|
ALWAYS_INLINE ControllerInterface* getControllerInterface() const { return GetControllerInterface(); }
|
||||||
|
ALWAYS_INLINE bool inBatchMode() const { return InBatchMode(); }
|
||||||
|
|
||||||
ALWAYS_INLINE bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
ALWAYS_INLINE bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
||||||
|
|
||||||
|
@ -84,6 +89,7 @@ Q_SIGNALS:
|
||||||
void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time,
|
void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time,
|
||||||
float worst_frame_time);
|
float worst_frame_time);
|
||||||
void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title);
|
void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title);
|
||||||
|
void exitRequested();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void setDefaultSettings();
|
void setDefaultSettings();
|
||||||
|
@ -124,6 +130,7 @@ protected:
|
||||||
bool IsFullscreen() const override;
|
bool IsFullscreen() const override;
|
||||||
bool SetFullscreen(bool enabled) override;
|
bool SetFullscreen(bool enabled) override;
|
||||||
|
|
||||||
|
void RequestExit() override;
|
||||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
||||||
|
|
||||||
void OnSystemCreated() override;
|
void OnSystemCreated() override;
|
||||||
|
@ -161,7 +168,7 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QtHostInterface* m_parent;
|
QtHostInterface* m_parent;
|
||||||
std::atomic_bool m_init_result{ false };
|
std::atomic_bool m_init_result{false};
|
||||||
Common::Event m_init_event;
|
Common::Event m_init_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,70 +5,8 @@
|
||||||
#include "sdl_host_interface.h"
|
#include "sdl_host_interface.h"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
static int Run(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
// parameters
|
|
||||||
std::optional<s32> state_index;
|
|
||||||
bool state_is_global = false;
|
|
||||||
const char* boot_filename = nullptr;
|
|
||||||
for (int i = 1; i < argc; i++)
|
|
||||||
{
|
|
||||||
#define CHECK_ARG(str) !std::strcmp(argv[i], str)
|
|
||||||
#define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc))
|
|
||||||
|
|
||||||
if (CHECK_ARG_PARAM("-state") || CHECK_ARG_PARAM("-gstate"))
|
|
||||||
{
|
|
||||||
state_is_global = argv[i][1] == 'g';
|
|
||||||
state_index = std::atoi(argv[++i]);
|
|
||||||
}
|
|
||||||
else if (CHECK_ARG_PARAM("-resume"))
|
|
||||||
{
|
|
||||||
state_index = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
boot_filename = argv[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CHECK_ARG
|
|
||||||
#undef CHECK_ARG_PARAM
|
|
||||||
}
|
|
||||||
|
|
||||||
// create display and host interface
|
|
||||||
std::unique_ptr<SDLHostInterface> host_interface = SDLHostInterface::Create();
|
|
||||||
if (!host_interface->Initialize())
|
|
||||||
{
|
|
||||||
host_interface->Shutdown();
|
|
||||||
Panic("Failed to initialize host interface");
|
|
||||||
SDL_Quit();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// boot/load state
|
|
||||||
if (boot_filename)
|
|
||||||
{
|
|
||||||
SystemBootParameters boot_params;
|
|
||||||
boot_params.filename = boot_filename;
|
|
||||||
if (host_interface->BootSystem(boot_params) && state_index.has_value())
|
|
||||||
host_interface->LoadState(state_is_global, state_index.value());
|
|
||||||
}
|
|
||||||
else if (state_index.has_value())
|
|
||||||
{
|
|
||||||
host_interface->LoadState(true, state_index.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
// run
|
|
||||||
host_interface->Run();
|
|
||||||
|
|
||||||
// done
|
|
||||||
host_interface->Shutdown();
|
|
||||||
host_interface.reset();
|
|
||||||
SDL_Quit();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDL requires the entry point declared without c++ decoration
|
|
||||||
#undef main
|
#undef main
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
|
@ -91,6 +29,38 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
FrontendCommon::EnsureSDLInitialized();
|
FrontendCommon::EnsureSDLInitialized();
|
||||||
|
|
||||||
// return NoGUITest();
|
std::unique_ptr<SDLHostInterface> host_interface = SDLHostInterface::Create();
|
||||||
return Run(argc, argv);
|
std::unique_ptr<SystemBootParameters> boot_params;
|
||||||
|
if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
|
||||||
|
{
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!host_interface->Initialize())
|
||||||
|
{
|
||||||
|
host_interface->Shutdown();
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boot_params)
|
||||||
|
{
|
||||||
|
if (!host_interface->BootSystem(*boot_params) && host_interface->InBatchMode())
|
||||||
|
{
|
||||||
|
host_interface->Shutdown();
|
||||||
|
host_interface.reset();
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
boot_params.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
host_interface->Run();
|
||||||
|
host_interface->Shutdown();
|
||||||
|
host_interface.reset();
|
||||||
|
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ SDLHostInterface::SDLHostInterface()
|
||||||
|
|
||||||
SDLHostInterface::~SDLHostInterface() = default;
|
SDLHostInterface::~SDLHostInterface() = default;
|
||||||
|
|
||||||
|
const char* SDLHostInterface::GetFrontendName() const
|
||||||
|
{
|
||||||
|
return "DuckStation SDL/ImGui Frontend";
|
||||||
|
}
|
||||||
|
|
||||||
float SDLHostInterface::GetDPIScaleFactor(SDL_Window* window)
|
float SDLHostInterface::GetDPIScaleFactor(SDL_Window* window)
|
||||||
{
|
{
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
@ -191,10 +196,6 @@ bool SDLHostInterface::AcquireHostDisplay()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Switch to fullscreen if requested.
|
|
||||||
if (m_settings.start_fullscreen)
|
|
||||||
SetFullscreen(true);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +275,11 @@ void SDLHostInterface::OnRunningGameChanged()
|
||||||
SDL_SetWindowTitle(m_window, "DuckStation");
|
SDL_SetWindowTitle(m_window, "DuckStation");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDLHostInterface::RequestExit()
|
||||||
|
{
|
||||||
|
m_quit_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
void SDLHostInterface::RunLater(std::function<void()> callback)
|
void SDLHostInterface::RunLater(std::function<void()> callback)
|
||||||
{
|
{
|
||||||
SDL_Event ev = {};
|
SDL_Event ev = {};
|
||||||
|
|
|
@ -25,6 +25,8 @@ public:
|
||||||
|
|
||||||
static std::unique_ptr<SDLHostInterface> Create();
|
static std::unique_ptr<SDLHostInterface> Create();
|
||||||
|
|
||||||
|
const char* GetFrontendName() const override;
|
||||||
|
|
||||||
void ReportError(const char* message) override;
|
void ReportError(const char* message) override;
|
||||||
void ReportMessage(const char* message) override;
|
void ReportMessage(const char* message) override;
|
||||||
bool ConfirmMessage(const char* message) override;
|
bool ConfirmMessage(const char* message) override;
|
||||||
|
@ -47,6 +49,8 @@ protected:
|
||||||
void OnSystemDestroyed() override;
|
void OnSystemDestroyed() override;
|
||||||
void OnRunningGameChanged() override;
|
void OnRunningGameChanged() override;
|
||||||
|
|
||||||
|
void RequestExit() override;
|
||||||
|
|
||||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
||||||
void UpdateInputMap() override;
|
void UpdateInputMap() override;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "sdl_audio_stream.h"
|
#include "sdl_audio_stream.h"
|
||||||
#include "sdl_controller_interface.h"
|
#include "sdl_controller_interface.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
Log_SetChannel(CommonHostInterface);
|
Log_SetChannel(CommonHostInterface);
|
||||||
|
|
||||||
|
@ -63,6 +64,228 @@ void CommonHostInterface::Shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters)
|
||||||
|
{
|
||||||
|
if (!HostInterface::BootSystem(parameters))
|
||||||
|
{
|
||||||
|
// if in batch mode, exit immediately if booting failed
|
||||||
|
if (m_batch_mode)
|
||||||
|
RequestExit();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enter fullscreen if requested in the parameters
|
||||||
|
if ((parameters.override_fullscreen.has_value() && *parameters.override_fullscreen) ||
|
||||||
|
(!parameters.override_fullscreen.has_value() && m_settings.start_fullscreen))
|
||||||
|
{
|
||||||
|
SetFullscreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonHostInterface::PowerOffSystem()
|
||||||
|
{
|
||||||
|
HostInterface::PowerOffSystem();
|
||||||
|
|
||||||
|
// TODO: Do we want to move the resume state saving here?
|
||||||
|
|
||||||
|
if (m_batch_mode)
|
||||||
|
RequestExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintCommandLineVersion(const char* frontend_name)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "%s version <TODO>\n", frontend_name);
|
||||||
|
std::fprintf(stderr, "https://github.com/stenzek/duckstation\n");
|
||||||
|
std::fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintCommandLineHelp(const char* progname, const char* frontend_name)
|
||||||
|
{
|
||||||
|
PrintCommandLineVersion(frontend_name);
|
||||||
|
std::fprintf(stderr, "Usage: %s [parameters] [--] [boot filename]\n", progname);
|
||||||
|
std::fprintf(stderr, "\n");
|
||||||
|
std::fprintf(stderr, " -help: Displays this information and exits.\n");
|
||||||
|
std::fprintf(stderr, " -version: Displays version information and exits.\n");
|
||||||
|
std::fprintf(stderr, " -batch: Enables batch mode (exits after powering off)\n");
|
||||||
|
std::fprintf(stderr, " -fastboot: Force fast boot for provided filename\n");
|
||||||
|
std::fprintf(stderr, " -slowboot: Force slow boot for provided filename\n");
|
||||||
|
std::fprintf(stderr, " -resume: Load resume save state. If a boot filename is provided,\n"
|
||||||
|
" that game's resume state will be loaded, otherwise the most\n"
|
||||||
|
" recent resume save state will be loaded.\n");
|
||||||
|
std::fprintf(stderr, " -state <index>: Loads specified save state by index. If a boot\n"
|
||||||
|
" filename is provided, a per-game state will be loaded, otherwise\n"
|
||||||
|
" a global state will be loaded.\n");
|
||||||
|
std::fprintf(stderr, " -statefile <filename>: Loads state from the specified filename.\n"
|
||||||
|
" No boot filename is required with this option.\n");
|
||||||
|
std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n");
|
||||||
|
std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
|
||||||
|
std::fprintf(stderr, " -portable: Forces \"portable mode\", data in same directory.\n");
|
||||||
|
std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
|
||||||
|
" parameters make up the filename. Use when the filename contains\n"
|
||||||
|
" spaces or starts with a dash.\n");
|
||||||
|
std::fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[],
|
||||||
|
std::unique_ptr<SystemBootParameters>* out_boot_params)
|
||||||
|
{
|
||||||
|
std::optional<bool> force_fast_boot;
|
||||||
|
std::optional<bool> force_fullscreen;
|
||||||
|
std::optional<s32> state_index;
|
||||||
|
std::string state_filename;
|
||||||
|
std::string boot_filename;
|
||||||
|
bool no_more_args = false;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
if (!no_more_args)
|
||||||
|
{
|
||||||
|
#define CHECK_ARG(str) !std::strcmp(argv[i], str)
|
||||||
|
#define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc))
|
||||||
|
|
||||||
|
if (CHECK_ARG("-help"))
|
||||||
|
{
|
||||||
|
PrintCommandLineHelp(argv[0], GetFrontendName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-version"))
|
||||||
|
{
|
||||||
|
PrintCommandLineVersion(GetFrontendName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-batch"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Enabling batch mode.\n");
|
||||||
|
m_batch_mode = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-fastboot"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Forcing fast boot.\n");
|
||||||
|
force_fast_boot = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-slowboot"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Forcing slow boot.\n");
|
||||||
|
force_fast_boot = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-resume"))
|
||||||
|
{
|
||||||
|
state_index = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG_PARAM("-state"))
|
||||||
|
{
|
||||||
|
state_index = std::atoi(argv[++i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG_PARAM("-statefile"))
|
||||||
|
{
|
||||||
|
state_filename = argv[++i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-fullscreen"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Going fullscreen after booting.\n");
|
||||||
|
force_fullscreen = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-nofullscreen"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Preventing fullscreen after booting.\n");
|
||||||
|
force_fullscreen = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("-portable"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Using portable mode.\n");
|
||||||
|
SetUserDirectoryToProgramDirectory();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG_PARAM("-resume"))
|
||||||
|
{
|
||||||
|
state_index = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CHECK_ARG("--"))
|
||||||
|
{
|
||||||
|
no_more_args = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (argv[i][0] == '-')
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Unknown parameter: '%s'", argv[i]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECK_ARG
|
||||||
|
#undef CHECK_ARG_PARAM
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boot_filename.empty())
|
||||||
|
boot_filename += ' ';
|
||||||
|
boot_filename += argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_index.has_value() || !boot_filename.empty() || !state_filename.empty())
|
||||||
|
{
|
||||||
|
// init user directory early since we need it for save states
|
||||||
|
SetUserDirectory();
|
||||||
|
|
||||||
|
if (state_index.has_value() && !state_filename.empty())
|
||||||
|
{
|
||||||
|
// if a save state is provided, whether a boot filename was provided determines per-game/local
|
||||||
|
if (boot_filename.empty())
|
||||||
|
{
|
||||||
|
// loading a global state. if this is -1, we're loading the most recent resume state
|
||||||
|
if (*state_index < 0)
|
||||||
|
state_filename = GetMostRecentResumeSaveStatePath();
|
||||||
|
else
|
||||||
|
state_filename = GetGlobalSaveStateFileName(*state_index);
|
||||||
|
|
||||||
|
if (state_filename.empty() || !FileSystem::FileExists(state_filename.c_str()))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Could not find file for global save state %d", *state_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// find the game id, and get its save state path
|
||||||
|
std::string game_code = m_game_list->GetGameCodeForPath(boot_filename.c_str());
|
||||||
|
if (game_code.empty())
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("Could not identify game code for '%s', cannot load save state %d.", boot_filename.c_str(),
|
||||||
|
*state_index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state_filename = GetGameSaveStateFileName(game_code.c_str(), *state_index);
|
||||||
|
if (state_filename.empty() || !FileSystem::FileExists(state_filename.c_str()))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Could not find file for game '%s' save state %d", game_code.c_str(), *state_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SystemBootParameters> boot_params = std::make_unique<SystemBootParameters>();
|
||||||
|
boot_params->filename = std::move(boot_filename);
|
||||||
|
boot_params->state_filename = std::move(state_filename);
|
||||||
|
boot_params->override_fast_boot = std::move(force_fast_boot);
|
||||||
|
boot_params->override_fullscreen = std::move(force_fullscreen);
|
||||||
|
*out_boot_params = std::move(boot_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool CommonHostInterface::IsFullscreen() const
|
bool CommonHostInterface::IsFullscreen() const
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -401,7 +624,7 @@ void CommonHostInterface::RegisterGeneralHotkeys()
|
||||||
[this](bool pressed) {
|
[this](bool pressed) {
|
||||||
if (!pressed && m_system)
|
if (!pressed && m_system)
|
||||||
{
|
{
|
||||||
if (m_settings.confim_power_off)
|
if (m_settings.confim_power_off && !m_batch_mode)
|
||||||
{
|
{
|
||||||
SmallString confirmation_message("Are you sure you want to stop emulation?");
|
SmallString confirmation_message("Are you sure you want to stop emulation?");
|
||||||
if (m_settings.save_state_on_exit)
|
if (m_settings.save_state_on_exit)
|
||||||
|
|
|
@ -33,19 +33,34 @@ public:
|
||||||
|
|
||||||
using HotkeyInfoList = std::vector<HotkeyInfo>;
|
using HotkeyInfoList = std::vector<HotkeyInfo>;
|
||||||
|
|
||||||
|
/// Returns the name of the frontend.
|
||||||
|
virtual const char* GetFrontendName() const = 0;
|
||||||
|
|
||||||
virtual bool Initialize() override;
|
virtual bool Initialize() override;
|
||||||
virtual void Shutdown() override;
|
virtual void Shutdown() override;
|
||||||
|
|
||||||
|
virtual bool BootSystem(const SystemBootParameters& parameters) override;
|
||||||
|
virtual void PowerOffSystem() override;
|
||||||
|
|
||||||
/// Returns a list of all available hotkeys.
|
/// Returns a list of all available hotkeys.
|
||||||
ALWAYS_INLINE const HotkeyInfoList& GetHotkeyInfoList() const { return m_hotkeys; }
|
ALWAYS_INLINE const HotkeyInfoList& GetHotkeyInfoList() const { return m_hotkeys; }
|
||||||
|
|
||||||
/// Access to current controller interface.
|
/// Access to current controller interface.
|
||||||
ALWAYS_INLINE ControllerInterface* GetControllerInterface() const { return m_controller_interface.get(); }
|
ALWAYS_INLINE ControllerInterface* GetControllerInterface() const { return m_controller_interface.get(); }
|
||||||
|
|
||||||
|
/// Returns true if running in batch mode, i.e. exit after emulation.
|
||||||
|
ALWAYS_INLINE bool InBatchMode() const { return m_batch_mode; }
|
||||||
|
|
||||||
|
/// Parses command line parameters for all frontends.
|
||||||
|
bool ParseCommandLineParameters(int argc, char* argv[], std::unique_ptr<SystemBootParameters>* out_boot_params);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CommonHostInterface();
|
CommonHostInterface();
|
||||||
~CommonHostInterface();
|
~CommonHostInterface();
|
||||||
|
|
||||||
|
/// Request the frontend to exit.
|
||||||
|
virtual void RequestExit() = 0;
|
||||||
|
|
||||||
virtual bool IsFullscreen() const;
|
virtual bool IsFullscreen() const;
|
||||||
virtual bool SetFullscreen(bool enabled);
|
virtual bool SetFullscreen(bool enabled);
|
||||||
|
|
||||||
|
@ -85,4 +100,7 @@ private:
|
||||||
|
|
||||||
// input key maps
|
// input key maps
|
||||||
std::map<HostKeyCode, InputButtonHandler> m_keyboard_input_handlers;
|
std::map<HostKeyCode, InputButtonHandler> m_keyboard_input_handlers;
|
||||||
|
|
||||||
|
// running in batch mode? i.e. exit after stopping emulation
|
||||||
|
bool m_batch_mode = false;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue