mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-02 19:01:06 +01:00
f106a9637d
Core::PauseAndLock requires all calls to it to be balanced, like this: const bool was_unpaused = Core::PauseAndLock(true); // do stuff on the CPU thread Core::PauseAndLock(false, was_unpaused); Aside from being a bit cumbersome, it turns out all callers really don't need to know about was_unpaused at all. They just need to do something on the CPU thread safely, including locking/unlocking. So this commit replaces Core::PauseAndLock with a function that makes both the purpose and the scope of what is being run on the CPU thread visually clear. This makes it harder to accidentally run something on the wrong thread, or forget the second call to PauseAndLock to unpause, or forget that it needs to be passed was_unpaused at the end. We also don't need comments to indicate code X is being run on the CPU thread anymore, as the function name makes it obvious.
451 lines
11 KiB
C++
451 lines
11 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <OptionParser.h>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <signal.h>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Event.h"
|
|
#include "Common/Flag.h"
|
|
#include "Common/Logging/LogManager.h"
|
|
#include "Common/MsgHandler.h"
|
|
|
|
#include "Core/Analytics.h"
|
|
#include "Core/Boot/Boot.h"
|
|
#include "Core/BootManager.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/HW/Wiimote.h"
|
|
#include "Core/Host.h"
|
|
#include "Core/IOS/IOS.h"
|
|
#include "Core/IOS/STM/STM.h"
|
|
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
|
|
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
|
|
#include "Core/State.h"
|
|
|
|
#include "UICommon/CommandLineParse.h"
|
|
#include "UICommon/UICommon.h"
|
|
|
|
#include "VideoCommon/RenderBase.h"
|
|
#include "VideoCommon/VideoBackendBase.h"
|
|
|
|
static bool rendererHasFocus = true;
|
|
static bool rendererIsFullscreen = false;
|
|
static Common::Flag s_running{true};
|
|
static Common::Flag s_shutdown_requested{false};
|
|
static Common::Flag s_tried_graceful_shutdown{false};
|
|
|
|
static void signal_handler(int)
|
|
{
|
|
const char message[] = "A signal was received. A second signal will force Dolphin to stop.\n";
|
|
if (write(STDERR_FILENO, message, sizeof(message)) < 0)
|
|
{
|
|
}
|
|
s_shutdown_requested.Set();
|
|
}
|
|
|
|
namespace ProcessorInterface
|
|
{
|
|
void PowerButton_Tap();
|
|
}
|
|
|
|
class Platform
|
|
{
|
|
public:
|
|
virtual void Init() {}
|
|
virtual void SetTitle(const std::string& title) {}
|
|
virtual void MainLoop()
|
|
{
|
|
while (s_running.IsSet())
|
|
{
|
|
Core::HostDispatchJobs();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
}
|
|
virtual void Shutdown() {}
|
|
virtual ~Platform() {}
|
|
};
|
|
|
|
static Platform* platform;
|
|
|
|
void Host_NotifyMapLoaded()
|
|
{
|
|
}
|
|
void Host_RefreshDSPDebuggerWindow()
|
|
{
|
|
}
|
|
|
|
static Common::Event updateMainFrameEvent;
|
|
void Host_Message(int Id)
|
|
{
|
|
if (Id == WM_USER_STOP)
|
|
{
|
|
s_running.Clear();
|
|
updateMainFrameEvent.Set();
|
|
}
|
|
}
|
|
|
|
static void* s_window_handle = nullptr;
|
|
void* Host_GetRenderHandle()
|
|
{
|
|
return s_window_handle;
|
|
}
|
|
|
|
void Host_UpdateTitle(const std::string& title)
|
|
{
|
|
platform->SetTitle(title);
|
|
}
|
|
|
|
void Host_UpdateDisasmDialog()
|
|
{
|
|
}
|
|
|
|
void Host_UpdateMainFrame()
|
|
{
|
|
updateMainFrameEvent.Set();
|
|
}
|
|
|
|
void Host_RequestRenderWindowSize(int width, int height)
|
|
{
|
|
}
|
|
|
|
bool Host_UINeedsControllerState()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool Host_RendererHasFocus()
|
|
{
|
|
return rendererHasFocus;
|
|
}
|
|
|
|
bool Host_RendererIsFullscreen()
|
|
{
|
|
return rendererIsFullscreen;
|
|
}
|
|
|
|
void Host_ConnectWiimote(int wm_idx, bool connect)
|
|
{
|
|
Core::QueueHostJob([=] {
|
|
const auto ios = IOS::HLE::GetIOS();
|
|
if (!ios || SConfig::GetInstance().m_bt_passthrough_enabled)
|
|
return;
|
|
Core::RunAsCPUThread([&] {
|
|
const auto bt = std::static_pointer_cast<IOS::HLE::Device::BluetoothEmu>(
|
|
ios->GetDeviceByName("/dev/usb/oh1/57e/305"));
|
|
if (bt)
|
|
bt->AccessWiiMote(wm_idx | 0x100)->Activate(connect);
|
|
Host_UpdateMainFrame();
|
|
});
|
|
});
|
|
}
|
|
|
|
void Host_SetWiiMoteConnectionState(int _State)
|
|
{
|
|
}
|
|
|
|
void Host_ShowVideoConfig(void*, const std::string&)
|
|
{
|
|
}
|
|
|
|
void Host_YieldToUI()
|
|
{
|
|
}
|
|
|
|
#if HAVE_X11
|
|
#include <X11/Xlib.h>
|
|
#include <X11/keysym.h>
|
|
#include "UICommon/X11Utils.h"
|
|
|
|
class PlatformX11 : public Platform
|
|
{
|
|
Display* dpy;
|
|
Window win;
|
|
Cursor blankCursor = None;
|
|
#if defined(HAVE_XRANDR) && HAVE_XRANDR
|
|
X11Utils::XRRConfiguration* XRRConfig;
|
|
#endif
|
|
|
|
void Init() override
|
|
{
|
|
XInitThreads();
|
|
dpy = XOpenDisplay(nullptr);
|
|
if (!dpy)
|
|
{
|
|
PanicAlert("No X11 display found");
|
|
exit(1);
|
|
}
|
|
|
|
win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), SConfig::GetInstance().iRenderWindowXPos,
|
|
SConfig::GetInstance().iRenderWindowYPos,
|
|
SConfig::GetInstance().iRenderWindowWidth,
|
|
SConfig::GetInstance().iRenderWindowHeight, 0, 0, BlackPixel(dpy, 0));
|
|
XSelectInput(dpy, win, StructureNotifyMask | KeyPressMask | FocusChangeMask);
|
|
Atom wmProtocols[1];
|
|
wmProtocols[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", True);
|
|
XSetWMProtocols(dpy, win, wmProtocols, 1);
|
|
XMapRaised(dpy, win);
|
|
XFlush(dpy);
|
|
s_window_handle = (void*)win;
|
|
|
|
if (SConfig::GetInstance().bDisableScreenSaver)
|
|
X11Utils::InhibitScreensaver(dpy, win, true);
|
|
|
|
#if defined(HAVE_XRANDR) && HAVE_XRANDR
|
|
XRRConfig = new X11Utils::XRRConfiguration(dpy, win);
|
|
#endif
|
|
|
|
if (SConfig::GetInstance().bHideCursor)
|
|
{
|
|
// make a blank cursor
|
|
Pixmap Blank;
|
|
XColor DummyColor;
|
|
char ZeroData[1] = {0};
|
|
Blank = XCreateBitmapFromData(dpy, win, ZeroData, 1, 1);
|
|
blankCursor = XCreatePixmapCursor(dpy, Blank, Blank, &DummyColor, &DummyColor, 0, 0);
|
|
XFreePixmap(dpy, Blank);
|
|
XDefineCursor(dpy, win, blankCursor);
|
|
}
|
|
}
|
|
|
|
void SetTitle(const std::string& string) override { XStoreName(dpy, win, string.c_str()); }
|
|
void MainLoop() override
|
|
{
|
|
bool fullscreen = SConfig::GetInstance().bFullscreen;
|
|
int last_window_width = SConfig::GetInstance().iRenderWindowWidth;
|
|
int last_window_height = SConfig::GetInstance().iRenderWindowHeight;
|
|
|
|
if (fullscreen)
|
|
{
|
|
rendererIsFullscreen = X11Utils::ToggleFullscreen(dpy, win);
|
|
#if defined(HAVE_XRANDR) && HAVE_XRANDR
|
|
XRRConfig->ToggleDisplayMode(True);
|
|
#endif
|
|
}
|
|
|
|
// The actual loop
|
|
while (s_running.IsSet())
|
|
{
|
|
if (s_shutdown_requested.TestAndClear())
|
|
{
|
|
const auto ios = IOS::HLE::GetIOS();
|
|
const auto stm = ios ? ios->GetDeviceByName("/dev/stm/eventhook") : nullptr;
|
|
if (!s_tried_graceful_shutdown.IsSet() && stm &&
|
|
std::static_pointer_cast<IOS::HLE::Device::STMEventHook>(stm)->HasHookInstalled())
|
|
{
|
|
ProcessorInterface::PowerButton_Tap();
|
|
s_tried_graceful_shutdown.Set();
|
|
}
|
|
else
|
|
{
|
|
s_running.Clear();
|
|
}
|
|
}
|
|
|
|
XEvent event;
|
|
KeySym key;
|
|
for (int num_events = XPending(dpy); num_events > 0; num_events--)
|
|
{
|
|
XNextEvent(dpy, &event);
|
|
switch (event.type)
|
|
{
|
|
case KeyPress:
|
|
key = XLookupKeysym((XKeyEvent*)&event, 0);
|
|
if (key == XK_Escape)
|
|
{
|
|
if (Core::GetState() == Core::State::Running)
|
|
{
|
|
if (SConfig::GetInstance().bHideCursor)
|
|
XUndefineCursor(dpy, win);
|
|
Core::SetState(Core::State::Paused);
|
|
}
|
|
else
|
|
{
|
|
if (SConfig::GetInstance().bHideCursor)
|
|
XDefineCursor(dpy, win, blankCursor);
|
|
Core::SetState(Core::State::Running);
|
|
}
|
|
}
|
|
else if ((key == XK_Return) && (event.xkey.state & Mod1Mask))
|
|
{
|
|
fullscreen = !fullscreen;
|
|
X11Utils::ToggleFullscreen(dpy, win);
|
|
#if defined(HAVE_XRANDR) && HAVE_XRANDR
|
|
XRRConfig->ToggleDisplayMode(fullscreen);
|
|
#endif
|
|
}
|
|
else if (key >= XK_F1 && key <= XK_F8)
|
|
{
|
|
int slot_number = key - XK_F1 + 1;
|
|
if (event.xkey.state & ShiftMask)
|
|
State::Save(slot_number);
|
|
else
|
|
State::Load(slot_number);
|
|
}
|
|
else if (key == XK_F9)
|
|
Core::SaveScreenShot();
|
|
else if (key == XK_F11)
|
|
State::LoadLastSaved();
|
|
else if (key == XK_F12)
|
|
{
|
|
if (event.xkey.state & ShiftMask)
|
|
State::UndoLoadState();
|
|
else
|
|
State::UndoSaveState();
|
|
}
|
|
break;
|
|
case FocusIn:
|
|
rendererHasFocus = true;
|
|
if (SConfig::GetInstance().bHideCursor && Core::GetState() != Core::State::Paused)
|
|
XDefineCursor(dpy, win, blankCursor);
|
|
break;
|
|
case FocusOut:
|
|
rendererHasFocus = false;
|
|
if (SConfig::GetInstance().bHideCursor)
|
|
XUndefineCursor(dpy, win);
|
|
break;
|
|
case ClientMessage:
|
|
if ((unsigned long)event.xclient.data.l[0] == XInternAtom(dpy, "WM_DELETE_WINDOW", False))
|
|
s_shutdown_requested.Set();
|
|
break;
|
|
case ConfigureNotify:
|
|
{
|
|
if (last_window_width != event.xconfigure.width ||
|
|
last_window_height != event.xconfigure.height)
|
|
{
|
|
last_window_width = event.xconfigure.width;
|
|
last_window_height = event.xconfigure.height;
|
|
|
|
// We call Renderer::ChangeSurface here to indicate the size has changed,
|
|
// but pass the same window handle. This is needed for the Vulkan backend,
|
|
// otherwise it cannot tell that the window has been resized on some drivers.
|
|
if (g_renderer)
|
|
g_renderer->ChangeSurface(s_window_handle);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!fullscreen)
|
|
{
|
|
Window winDummy;
|
|
unsigned int borderDummy, depthDummy;
|
|
XGetGeometry(dpy, win, &winDummy, &SConfig::GetInstance().iRenderWindowXPos,
|
|
&SConfig::GetInstance().iRenderWindowYPos,
|
|
(unsigned int*)&SConfig::GetInstance().iRenderWindowWidth,
|
|
(unsigned int*)&SConfig::GetInstance().iRenderWindowHeight, &borderDummy,
|
|
&depthDummy);
|
|
rendererIsFullscreen = false;
|
|
}
|
|
Core::HostDispatchJobs();
|
|
usleep(100000);
|
|
}
|
|
}
|
|
|
|
void Shutdown() override
|
|
{
|
|
#if defined(HAVE_XRANDR) && HAVE_XRANDR
|
|
delete XRRConfig;
|
|
#endif
|
|
|
|
if (SConfig::GetInstance().bHideCursor)
|
|
XFreeCursor(dpy, blankCursor);
|
|
|
|
XCloseDisplay(dpy);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
static Platform* GetPlatform()
|
|
{
|
|
#if defined(USE_HEADLESS)
|
|
return new Platform();
|
|
#elif HAVE_X11
|
|
return new PlatformX11();
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::OmitGUIOptions);
|
|
optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
|
|
std::vector<std::string> args = parser->args();
|
|
|
|
std::string boot_filename;
|
|
if (options.is_set("exec"))
|
|
{
|
|
boot_filename = static_cast<const char*>(options.get("exec"));
|
|
}
|
|
else if (args.size())
|
|
{
|
|
boot_filename = args.front();
|
|
args.erase(args.begin());
|
|
}
|
|
else
|
|
{
|
|
parser->print_help();
|
|
return 0;
|
|
}
|
|
|
|
std::string user_directory;
|
|
if (options.is_set("user"))
|
|
{
|
|
user_directory = static_cast<const char*>(options.get("user"));
|
|
}
|
|
|
|
platform = GetPlatform();
|
|
if (!platform)
|
|
{
|
|
fprintf(stderr, "No platform found\n");
|
|
return 1;
|
|
}
|
|
|
|
UICommon::SetUserDirectory(user_directory);
|
|
UICommon::Init();
|
|
|
|
Core::SetOnStoppedCallback([]() { s_running.Clear(); });
|
|
platform->Init();
|
|
|
|
// 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);
|
|
|
|
DolphinAnalytics::Instance()->ReportDolphinStart("nogui");
|
|
|
|
if (!BootManager::BootCore(BootParameters::GenerateFromFile(boot_filename)))
|
|
{
|
|
fprintf(stderr, "Could not boot %s\n", boot_filename.c_str());
|
|
return 1;
|
|
}
|
|
|
|
while (!Core::IsRunning() && s_running.IsSet())
|
|
{
|
|
Core::HostDispatchJobs();
|
|
updateMainFrameEvent.Wait();
|
|
}
|
|
|
|
if (s_running.IsSet())
|
|
platform->MainLoop();
|
|
Core::Stop();
|
|
|
|
Core::Shutdown();
|
|
platform->Shutdown();
|
|
UICommon::Shutdown();
|
|
|
|
delete platform;
|
|
|
|
return 0;
|
|
}
|