dolphin/Source/Core/DolphinNoGUI/MainNoGUI.cpp
Dentomologist c3bdd05d2a TAS Input: Enable hotkeys and controller input when Input has focus
Enable emulator hotkeys and controller input (when that option is
enabled) when a TAS Input window has focus, as if it was the render
window instead.  This allows TASers to use frame advance and the like
without having to switch the focused window or disabling Hotkeys Require
Window Focus which also picks up keypresses while other apps are active.

Cursor updates are disabled when the TAS Input window has focus, as
otherwise the Wii IR widget (and anything else controlled by the mouse)
becomes unusable. The cursor continues to work normally when the render
window has focus.
2024-05-31 15:14:44 -07:00

346 lines
8.0 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinNoGUI/Platform.h"
#include <OptionParser.h>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <signal.h>
#include <string>
#include <vector>
#ifndef _WIN32
#include <unistd.h>
#else
#include <Windows.h>
#endif
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/Core.h"
#include "Core/DolphinAnalytics.h"
#include "Core/Host.h"
#include "Core/System.h"
#include "UICommon/CommandLineParse.h"
#ifdef USE_DISCORD_PRESENCE
#include "UICommon/DiscordPresence.h"
#endif
#include "UICommon/UICommon.h"
#include "InputCommon/GCAdapter.h"
#include "VideoCommon/VideoBackendBase.h"
static std::unique_ptr<Platform> s_platform;
static void signal_handler(int)
{
const char message[] = "A signal was received. A second signal will force Dolphin to stop.\n";
#ifdef _WIN32
puts(message);
#else
if (write(STDERR_FILENO, message, sizeof(message)) < 0)
{
}
#endif
s_platform->RequestShutdown();
}
std::vector<std::string> Host_GetPreferredLocales()
{
return {};
}
void Host_PPCSymbolsChanged()
{
}
void Host_RefreshDSPDebuggerWindow()
{
}
bool Host_UIBlocksControllerState()
{
return false;
}
static Common::Event s_update_main_frame_event;
void Host_Message(HostMessageID id)
{
if (id == HostMessageID::WMUserStop)
s_platform->Stop();
}
void Host_UpdateTitle(const std::string& title)
{
s_platform->SetTitle(title);
}
void Host_UpdateDisasmDialog()
{
}
void Host_UpdateMainFrame()
{
s_update_main_frame_event.Set();
}
void Host_RequestRenderWindowSize(int width, int height)
{
}
bool Host_RendererHasFocus()
{
return s_platform->IsWindowFocused();
}
bool Host_RendererHasFullFocus()
{
// Mouse capturing isn't implemented
return Host_RendererHasFocus();
}
bool Host_RendererIsFullscreen()
{
return s_platform->IsWindowFullscreen();
}
bool Host_TASInputHasFocus()
{
return false;
}
void Host_YieldToUI()
{
}
void Host_TitleChanged()
{
#ifdef USE_DISCORD_PRESENCE
Discord::UpdateDiscordPresence();
#endif
}
void Host_UpdateDiscordClientID(const std::string& client_id)
{
#ifdef USE_DISCORD_PRESENCE
Discord::UpdateClientID(client_id);
#endif
}
bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string& state,
const std::string& large_image_key,
const std::string& large_image_text,
const std::string& small_image_key,
const std::string& small_image_text,
const int64_t start_timestamp, const int64_t end_timestamp,
const int party_size, const int party_max)
{
#ifdef USE_DISCORD_PRESENCE
return Discord::UpdateDiscordPresenceRaw(details, state, large_image_key, large_image_text,
small_image_key, small_image_text, start_timestamp,
end_timestamp, party_size, party_max);
#else
return false;
#endif
}
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}
static std::unique_ptr<Platform> GetPlatform(const optparse::Values& options)
{
std::string platform_name = static_cast<const char*>(options.get("platform"));
#if HAVE_X11
if (platform_name == "x11" || platform_name.empty())
return Platform::CreateX11Platform();
#endif
#ifdef __linux__
if (platform_name == "fbdev" || platform_name.empty())
return Platform::CreateFBDevPlatform();
#endif
#ifdef _WIN32
if (platform_name == "win32" || platform_name.empty())
return Platform::CreateWin32Platform();
#endif
#ifdef __APPLE__
if (platform_name == "macos" || platform_name.empty())
return Platform::CreateMacOSPlatform();
#endif
if (platform_name == "headless" || platform_name.empty())
return Platform::CreateHeadlessPlatform();
return nullptr;
}
#ifdef _WIN32
#define main app_main
#endif
int main(int argc, char* argv[])
{
Core::DeclareAsHostThread();
auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::OmitGUIOptions);
parser->add_option("-p", "--platform")
.action("store")
.help("Window platform to use [%choices]")
.choices({
"headless"
#ifdef __linux__
,
"fbdev"
#endif
#if HAVE_X11
,
"x11"
#endif
#ifdef _WIN32
,
"win32"
#endif
#ifdef __APPLE__
,
"macos"
#endif
});
optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
std::vector<std::string> args = parser->args();
std::optional<std::string> save_state_path;
if (options.is_set("save_state"))
{
save_state_path = static_cast<const char*>(options.get("save_state"));
}
std::unique_ptr<BootParameters> boot;
bool game_specified = false;
if (options.is_set("exec"))
{
const std::list<std::string> paths_list = options.all("exec");
const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
std::make_move_iterator(std::end(paths_list))};
boot = BootParameters::GenerateFromFile(
paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
}
else if (options.is_set("nand_title"))
{
const std::string hex_string = static_cast<const char*>(options.get("nand_title"));
if (hex_string.length() != 16)
{
fprintf(stderr, "Invalid title ID\n");
parser->print_help();
return 1;
}
const u64 title_id = std::stoull(hex_string, nullptr, 16);
boot = std::make_unique<BootParameters>(BootParameters::NANDTitle{title_id});
}
else if (args.size())
{
boot = BootParameters::GenerateFromFile(
args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
args.erase(args.begin());
game_specified = true;
}
else
{
parser->print_help();
return 0;
}
std::string user_directory;
if (options.is_set("user"))
user_directory = static_cast<const char*>(options.get("user"));
s_platform = GetPlatform(options);
if (!s_platform || !s_platform->Init())
{
fprintf(stderr, "No platform found, or failed to initialize.\n");
return 1;
}
const WindowSystemInfo wsi = s_platform->GetWindowSystemInfo();
UICommon::SetUserDirectory(user_directory);
UICommon::Init();
UICommon::InitControllers(wsi);
Common::ScopeGuard ui_common_guard([] {
UICommon::ShutdownControllers();
UICommon::Shutdown();
});
if (save_state_path && !game_specified)
{
fprintf(stderr, "A save state cannot be loaded without specifying a game to launch.\n");
return 1;
}
Core::AddOnStateChangedCallback([](Core::State state) {
if (state == Core::State::Uninitialized)
s_platform->Stop();
});
#ifdef _WIN32
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
#else
// Shut down cleanly on SIGINT and SIGTERM
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
#endif
DolphinAnalytics::Instance().ReportDolphinStart("nogui");
if (!BootManager::BootCore(Core::System::GetInstance(), std::move(boot), wsi))
{
fprintf(stderr, "Could not boot the specified file\n");
return 1;
}
#ifdef USE_DISCORD_PRESENCE
Discord::UpdateDiscordPresence();
#endif
s_platform->MainLoop();
Core::Stop(Core::System::GetInstance());
Core::Shutdown(Core::System::GetInstance());
s_platform.reset();
return 0;
}
#ifdef _WIN32
int wmain(int, wchar_t*[], wchar_t*[])
{
std::vector<std::string> args = Common::CommandLineToUtf8Argv(GetCommandLineW());
const int argc = static_cast<int>(args.size());
std::vector<char*> argv(args.size());
for (size_t i = 0; i < args.size(); ++i)
argv[i] = args[i].data();
return main(argc, argv.data());
}
#undef main
#endif