summaryrefslogtreecommitdiff
path: root/Source/Core/DolphinQt/MainWindow.cpp
diff options
context:
space:
mode:
authorspycrab <spycrab@users.noreply.github.com>2018-07-07 00:58:37 +0200
committerGitHub <noreply@github.com>2018-07-07 00:58:37 +0200
commit3d1a7737d86f4ecdf0131f5b84198f288800da3b (patch)
tree747e394036c189c46add1b3e1451e50f48230e11 /Source/Core/DolphinQt/MainWindow.cpp
parent059880bb1640d5a3b0e1fbbf66e1a356df4b9f41 (diff)
parent13ba24c5a6795532c8893eccf2a21ae95682e1b7 (diff)
Merge pull request #7205 from spycrab/qt_rename
Move DolphinQt2 to DolphinQt
Diffstat (limited to 'Source/Core/DolphinQt/MainWindow.cpp')
-rw-r--r--Source/Core/DolphinQt/MainWindow.cpp1465
1 files changed, 1465 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp
new file mode 100644
index 0000000000..b4883368af
--- /dev/null
+++ b/Source/Core/DolphinQt/MainWindow.cpp
@@ -0,0 +1,1465 @@
+// Copyright 2015 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <QApplication>
+#include <QCloseEvent>
+#include <QDateTime>
+#include <QDir>
+#include <QDragEnterEvent>
+#include <QDropEvent>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QIcon>
+#include <QMessageBox>
+#include <QMimeData>
+#include <QProgressDialog>
+#include <QVBoxLayout>
+
+#include <future>
+#include <optional>
+
+#if defined(__unix__) || defined(__unix) || defined(__APPLE__)
+#include <signal.h>
+
+#include "QtUtils/SignalDaemon.h"
+#endif
+
+#include "Common/Version.h"
+
+#include "Core/Boot/Boot.h"
+#include "Core/BootManager.h"
+#include "Core/CommonTitles.h"
+#include "Core/Config/NetplaySettings.h"
+#include "Core/ConfigManager.h"
+#include "Core/Core.h"
+#include "Core/HW/DVD/DVDInterface.h"
+#include "Core/HW/GCKeyboard.h"
+#include "Core/HW/GCPad.h"
+#include "Core/HW/ProcessorInterface.h"
+#include "Core/HW/Wiimote.h"
+#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
+#include "Core/HotkeyManager.h"
+#include "Core/IOS/USB/Bluetooth/BTEmu.h"
+#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
+#include "Core/Movie.h"
+#include "Core/NetPlayClient.h"
+#include "Core/NetPlayProto.h"
+#include "Core/NetPlayServer.h"
+#include "Core/State.h"
+
+#include "DiscIO/NANDImporter.h"
+
+#include "DolphinQt/AboutDialog.h"
+#include "DolphinQt/CheatsManager.h"
+#include "DolphinQt/Config/ControllersWindow.h"
+#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
+#include "DolphinQt/Config/LogConfigWidget.h"
+#include "DolphinQt/Config/LogWidget.h"
+#include "DolphinQt/Config/Mapping/MappingWindow.h"
+#include "DolphinQt/Config/SettingsWindow.h"
+#include "DolphinQt/Debugger/BreakpointWidget.h"
+#include "DolphinQt/Debugger/CodeViewWidget.h"
+#include "DolphinQt/Debugger/CodeWidget.h"
+#include "DolphinQt/Debugger/JITWidget.h"
+#include "DolphinQt/Debugger/MemoryWidget.h"
+#include "DolphinQt/Debugger/RegisterWidget.h"
+#include "DolphinQt/Debugger/WatchWidget.h"
+#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
+#include "DolphinQt/GCMemcardManager.h"
+#include "DolphinQt/Host.h"
+#include "DolphinQt/HotkeyScheduler.h"
+#include "DolphinQt/MainWindow.h"
+#include "DolphinQt/NetPlay/NetPlayDialog.h"
+#include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
+#include "DolphinQt/QtUtils/QueueOnObject.h"
+#include "DolphinQt/QtUtils/RunOnObject.h"
+#include "DolphinQt/QtUtils/WindowActivationEventFilter.h"
+#include "DolphinQt/Resources.h"
+#include "DolphinQt/SearchBar.h"
+#include "DolphinQt/Settings.h"
+#include "DolphinQt/TAS/GCTASInputWindow.h"
+#include "DolphinQt/TAS/WiiTASInputWindow.h"
+#include "DolphinQt/WiiUpdate.h"
+
+#include "InputCommon/ControllerInterface/ControllerInterface.h"
+
+#include "UICommon/DiscordPresence.h"
+#include "UICommon/UICommon.h"
+
+#include "VideoCommon/VideoConfig.h"
+
+#if defined(HAVE_XRANDR) && HAVE_XRANDR
+#include <qpa/qplatformnativeinterface.h>
+#include "UICommon/X11Utils.h"
+#endif
+
+#if defined(__unix__) || defined(__unix) || defined(__APPLE__)
+void MainWindow::OnSignal()
+{
+ close();
+}
+
+static void InstallSignalHandler()
+{
+ struct sigaction sa;
+ sa.sa_handler = &SignalDaemon::HandleInterrupt;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESETHAND;
+ sigaction(SIGINT, &sa, nullptr);
+ sigaction(SIGTERM, &sa, nullptr);
+}
+#endif
+
+MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters) : QMainWindow(nullptr)
+{
+ setWindowTitle(QString::fromStdString(Common::scm_rev_str));
+ setWindowIcon(Resources::GetAppIcon());
+ setUnifiedTitleAndToolBarOnMac(true);
+ setAcceptDrops(true);
+
+ InitControllers();
+
+ CreateComponents();
+
+ ConnectGameList();
+ ConnectHost();
+ ConnectToolBar();
+ ConnectRenderWidget();
+ ConnectStack();
+ ConnectMenuBar();
+ ConnectHotkeys();
+
+ InitCoreCallbacks();
+
+ NetPlayInit();
+
+#if defined(__unix__) || defined(__unix) || defined(__APPLE__)
+ auto* daemon = new SignalDaemon(this);
+
+ connect(daemon, &SignalDaemon::InterruptReceived, this, &MainWindow::OnSignal);
+
+ InstallSignalHandler();
+#endif
+
+ if (boot_parameters)
+ m_pending_boot = std::move(boot_parameters);
+
+ QSettings& settings = Settings::GetQSettings();
+
+ restoreState(settings.value(QStringLiteral("mainwindow/state")).toByteArray());
+ restoreGeometry(settings.value(QStringLiteral("mainwindow/geometry")).toByteArray());
+
+ m_render_widget_geometry = settings.value(QStringLiteral("renderwidget/geometry")).toByteArray();
+}
+
+MainWindow::~MainWindow()
+{
+ m_render_widget->deleteLater();
+ m_netplay_dialog->deleteLater();
+ ShutdownControllers();
+
+ QSettings& settings = Settings::GetQSettings();
+
+ settings.setValue(QStringLiteral("mainwindow/state"), saveState());
+ settings.setValue(QStringLiteral("mainwindow/geometry"), saveGeometry());
+
+ settings.setValue(QStringLiteral("renderwidget/geometry"), m_render_widget_geometry);
+
+ Config::Save();
+}
+
+void MainWindow::InitControllers()
+{
+ if (g_controller_interface.IsInit())
+ return;
+
+ g_controller_interface.Initialize(reinterpret_cast<void*>(winId()));
+ Pad::Initialize();
+ Keyboard::Initialize();
+ Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
+ m_hotkey_scheduler = new HotkeyScheduler();
+ m_hotkey_scheduler->Start();
+
+ // Defaults won't work reliabily without loading and saving the config first
+
+ Wiimote::LoadConfig();
+ Wiimote::GetConfig()->SaveConfig();
+
+ Pad::LoadConfig();
+ Pad::GetConfig()->SaveConfig();
+
+ Keyboard::LoadConfig();
+ Keyboard::GetConfig()->SaveConfig();
+}
+
+void MainWindow::ShutdownControllers()
+{
+ m_hotkey_scheduler->Stop();
+
+ g_controller_interface.Shutdown();
+ Pad::Shutdown();
+ Keyboard::Shutdown();
+ Wiimote::Shutdown();
+ HotkeyManagerEmu::Shutdown();
+
+ m_hotkey_scheduler->deleteLater();
+}
+
+void MainWindow::InitCoreCallbacks()
+{
+ connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [=](Core::State state) {
+ if (state == Core::State::Uninitialized)
+ OnStopComplete();
+
+ if (state == Core::State::Running && m_fullscreen_requested)
+ {
+ FullScreen();
+ m_fullscreen_requested = false;
+ }
+ });
+ installEventFilter(this);
+ m_render_widget->installEventFilter(this);
+}
+
+static void InstallHotkeyFilter(QWidget* dialog)
+{
+ auto* filter = new WindowActivationEventFilter();
+ dialog->installEventFilter(filter);
+
+ filter->connect(filter, &WindowActivationEventFilter::windowDeactivated,
+ [] { HotkeyManagerEmu::Enable(true); });
+ filter->connect(filter, &WindowActivationEventFilter::windowActivated,
+ [] { HotkeyManagerEmu::Enable(false); });
+}
+
+void MainWindow::CreateComponents()
+{
+ m_menu_bar = new MenuBar(this);
+ m_tool_bar = new ToolBar(this);
+ m_search_bar = new SearchBar(this);
+ m_game_list = new GameList(this);
+ m_render_widget = new RenderWidget;
+ m_stack = new QStackedWidget(this);
+ m_controllers_window = new ControllersWindow(this);
+ m_settings_window = new SettingsWindow(this);
+
+ for (int i = 0; i < 4; i++)
+ {
+ m_gc_tas_input_windows[i] = new GCTASInputWindow(this, i);
+ m_wii_tas_input_windows[i] = new WiiTASInputWindow(this, i);
+ }
+
+ Movie::SetGCInputManip([this](GCPadStatus* pad_status, int controller_id) {
+ m_gc_tas_input_windows[controller_id]->GetValues(pad_status);
+ });
+
+ Movie::SetWiiInputManip([this](u8* input_data, WiimoteEmu::ReportFeatures rptf, int controller_id,
+ int ext, wiimote_key key) {
+ m_wii_tas_input_windows[controller_id]->GetValues(input_data, rptf, ext, key);
+ });
+
+ m_jit_widget = new JITWidget(this);
+ m_log_widget = new LogWidget(this);
+ m_log_config_widget = new LogConfigWidget(this);
+ m_fifo_window = new FIFOPlayerWindow(this);
+ m_memory_widget = new MemoryWidget(this);
+
+ connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this,
+ [this](const QString& path) { StartGame(path); });
+ m_register_widget = new RegisterWidget(this);
+ m_watch_widget = new WatchWidget(this);
+ m_breakpoint_widget = new BreakpointWidget(this);
+ m_code_widget = new CodeWidget(this);
+ m_cheats_manager = new CheatsManager(this);
+
+ connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint,
+ [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); });
+ connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint,
+ [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); });
+
+ connect(m_code_widget, &CodeWidget::BreakpointsChanged, m_breakpoint_widget,
+ &BreakpointWidget::Update);
+ connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare);
+ connect(m_memory_widget, &MemoryWidget::BreakpointsChanged, m_breakpoint_widget,
+ &BreakpointWidget::Update);
+
+ connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_code_widget,
+ &CodeWidget::Update);
+ connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_memory_widget,
+ &MemoryWidget::Update);
+ connect(m_breakpoint_widget, &BreakpointWidget::SelectedBreakpoint, [this](u32 address) {
+ if (Core::GetState() == Core::State::Paused)
+ m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate);
+ });
+
+#if defined(HAVE_XRANDR) && HAVE_XRANDR
+ m_xrr_config = std::make_unique<X11Utils::XRRConfiguration>(
+ static_cast<Display*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow(
+ "display", windowHandle())),
+ winId());
+ m_graphics_window = new GraphicsWindow(m_xrr_config.get(), this);
+#else
+ m_graphics_window = new GraphicsWindow(nullptr, this);
+#endif
+
+ InstallHotkeyFilter(m_controllers_window);
+ InstallHotkeyFilter(m_settings_window);
+ InstallHotkeyFilter(m_graphics_window);
+}
+
+void MainWindow::ConnectMenuBar()
+{
+ setMenuBar(m_menu_bar);
+ // File
+ connect(m_menu_bar, &MenuBar::Open, this, &MainWindow::Open);
+ connect(m_menu_bar, &MenuBar::Exit, this, &MainWindow::close);
+ connect(m_menu_bar, &MenuBar::EjectDisc, this, &MainWindow::EjectDisc);
+ connect(m_menu_bar, &MenuBar::ChangeDisc, this, &MainWindow::ChangeDisc);
+ connect(m_menu_bar, &MenuBar::BootDVDBackup, this,
+ [this](const QString& drive) { StartGame(drive); });
+
+ // Emulation
+ connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause);
+ connect(m_menu_bar, &MenuBar::Play, this, [this]() { Play(); });
+ connect(m_menu_bar, &MenuBar::Stop, this, &MainWindow::RequestStop);
+ connect(m_menu_bar, &MenuBar::Reset, this, &MainWindow::Reset);
+ connect(m_menu_bar, &MenuBar::Fullscreen, this, &MainWindow::FullScreen);
+ connect(m_menu_bar, &MenuBar::FrameAdvance, this, &MainWindow::FrameAdvance);
+ connect(m_menu_bar, &MenuBar::Screenshot, this, &MainWindow::ScreenShot);
+ connect(m_menu_bar, &MenuBar::StateLoad, this, &MainWindow::StateLoad);
+ connect(m_menu_bar, &MenuBar::StateSave, this, &MainWindow::StateSave);
+ connect(m_menu_bar, &MenuBar::StateLoadSlot, this, &MainWindow::StateLoadSlot);
+ connect(m_menu_bar, &MenuBar::StateSaveSlot, this, &MainWindow::StateSaveSlot);
+ connect(m_menu_bar, &MenuBar::StateLoadSlotAt, this, &MainWindow::StateLoadSlotAt);
+ connect(m_menu_bar, &MenuBar::StateSaveSlotAt, this, &MainWindow::StateSaveSlotAt);
+ connect(m_menu_bar, &MenuBar::StateLoadUndo, this, &MainWindow::StateLoadUndo);
+ connect(m_menu_bar, &MenuBar::StateSaveUndo, this, &MainWindow::StateSaveUndo);
+ connect(m_menu_bar, &MenuBar::StateSaveOldest, this, &MainWindow::StateSaveOldest);
+ connect(m_menu_bar, &MenuBar::SetStateSlot, this, &MainWindow::SetStateSlot);
+
+ // Options
+ connect(m_menu_bar, &MenuBar::Configure, this, &MainWindow::ShowSettingsWindow);
+ connect(m_menu_bar, &MenuBar::ConfigureGraphics, this, &MainWindow::ShowGraphicsWindow);
+ connect(m_menu_bar, &MenuBar::ConfigureAudio, this, &MainWindow::ShowAudioWindow);
+ connect(m_menu_bar, &MenuBar::ConfigureControllers, this, &MainWindow::ShowControllersWindow);
+ connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
+
+ // Tools
+ connect(m_menu_bar, &MenuBar::ShowMemcardManager, this, &MainWindow::ShowMemcardManager);
+ connect(m_menu_bar, &MenuBar::ShowCheatsManager, this, &MainWindow::ShowCheatsManager);
+ connect(m_menu_bar, &MenuBar::BootGameCubeIPL, this, &MainWindow::OnBootGameCubeIPL);
+ connect(m_menu_bar, &MenuBar::ImportNANDBackup, this, &MainWindow::OnImportNANDBackup);
+ connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
+ connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu);
+ connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
+ connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
+ connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
+
+ // Movie
+ connect(m_menu_bar, &MenuBar::PlayRecording, this, &MainWindow::OnPlayRecording);
+ connect(m_menu_bar, &MenuBar::StartRecording, this, &MainWindow::OnStartRecording);
+ connect(m_menu_bar, &MenuBar::StopRecording, this, &MainWindow::OnStopRecording);
+ connect(m_menu_bar, &MenuBar::ExportRecording, this, &MainWindow::OnExportRecording);
+ connect(m_menu_bar, &MenuBar::ShowTASInput, this, &MainWindow::ShowTASInput);
+
+ // View
+ connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
+ connect(m_menu_bar, &MenuBar::ShowGrid, m_game_list, &GameList::SetGridView);
+ connect(m_menu_bar, &MenuBar::ToggleSearch, m_search_bar, &SearchBar::Toggle);
+
+ connect(m_menu_bar, &MenuBar::ColumnVisibilityToggled, m_game_list,
+ &GameList::OnColumnVisibilityToggled);
+
+ connect(m_menu_bar, &MenuBar::GameListPlatformVisibilityToggled, m_game_list,
+ &GameList::OnGameListVisibilityChanged);
+ connect(m_menu_bar, &MenuBar::GameListRegionVisibilityToggled, m_game_list,
+ &GameList::OnGameListVisibilityChanged);
+
+ connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog);
+
+ connect(m_game_list, &GameList::SelectionChanged, m_menu_bar, &MenuBar::SelectionChanged);
+ connect(this, &MainWindow::ReadOnlyModeChanged, m_menu_bar, &MenuBar::ReadOnlyModeChanged);
+ connect(this, &MainWindow::RecordingStatusChanged, m_menu_bar, &MenuBar::RecordingStatusChanged);
+
+ // Symbols
+ connect(m_menu_bar, &MenuBar::NotifySymbolsUpdated, [this] {
+ m_code_widget->UpdateSymbols();
+ m_code_widget->Update();
+ });
+}
+
+void MainWindow::ConnectHotkeys()
+{
+ connect(m_hotkey_scheduler, &HotkeyScheduler::Open, this, &MainWindow::Open);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ChangeDisc, this, &MainWindow::ChangeDisc);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::EjectDisc, this, &MainWindow::EjectDisc);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::RefreshGameListHotkey, this,
+ &MainWindow::RefreshGameList);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ResetHotkey, this, &MainWindow::Reset);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ScreenShotHotkey, this, &MainWindow::ScreenShot);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::FullScreenHotkey, this, &MainWindow::FullScreen);
+
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadSlot, this, &MainWindow::StateLoadSlotAt);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveSlot, this, &MainWindow::StateSaveSlotAt);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadLastSaved, this,
+ &MainWindow::StateLoadLastSavedAt);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadUndo, this, &MainWindow::StateLoadUndo);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveUndo, this, &MainWindow::StateSaveUndo);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveOldest, this,
+ &MainWindow::StateSaveOldest);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveFile, this, &MainWindow::StateSave);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadFile, this, &MainWindow::StateLoad);
+
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadSlotHotkey, this,
+ &MainWindow::StateLoadSlot);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveSlotHotkey, this,
+ &MainWindow::StateSaveSlot);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::SetStateSlotHotkey, this,
+ &MainWindow::SetStateSlot);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StartRecording, this,
+ &MainWindow::OnStartRecording);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ExportRecording, this,
+ &MainWindow::OnExportRecording);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ConnectWiiRemote, this,
+ &MainWindow::OnConnectWiiRemote);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ToggleReadOnlyMode, [this] {
+ bool read_only = !Movie::IsReadOnly();
+ Movie::SetReadOnly(read_only);
+ emit ReadOnlyModeChanged(read_only);
+ });
+
+ connect(m_hotkey_scheduler, &HotkeyScheduler::Step, m_code_widget, &CodeWidget::Step);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StepOver, m_code_widget, &CodeWidget::StepOver);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::StepOut, m_code_widget, &CodeWidget::StepOut);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::Skip, m_code_widget, &CodeWidget::Skip);
+
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ShowPC, m_code_widget, &CodeWidget::ShowPC);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::SetPC, m_code_widget, &CodeWidget::SetPC);
+
+ connect(m_hotkey_scheduler, &HotkeyScheduler::ToggleBreakpoint, m_code_widget,
+ &CodeWidget::ToggleBreakpoint);
+ connect(m_hotkey_scheduler, &HotkeyScheduler::AddBreakpoint, m_code_widget,
+ &CodeWidget::AddBreakpoint);
+}
+
+void MainWindow::ConnectToolBar()
+{
+ addToolBar(m_tool_bar);
+
+ connect(m_tool_bar, &ToolBar::OpenPressed, this, &MainWindow::Open);
+ connect(m_tool_bar, &ToolBar::RefreshPressed, this, &MainWindow::RefreshGameList);
+
+ connect(m_tool_bar, &ToolBar::PlayPressed, this, [this]() { Play(); });
+ connect(m_tool_bar, &ToolBar::PausePressed, this, &MainWindow::Pause);
+ connect(m_tool_bar, &ToolBar::StopPressed, this, &MainWindow::RequestStop);
+ connect(m_tool_bar, &ToolBar::FullScreenPressed, this, &MainWindow::FullScreen);
+ connect(m_tool_bar, &ToolBar::ScreenShotPressed, this, &MainWindow::ScreenShot);
+ connect(m_tool_bar, &ToolBar::SettingsPressed, this, &MainWindow::ShowSettingsWindow);
+ connect(m_tool_bar, &ToolBar::ControllersPressed, this, &MainWindow::ShowControllersWindow);
+ connect(m_tool_bar, &ToolBar::GraphicsPressed, this, &MainWindow::ShowGraphicsWindow);
+
+ connect(m_tool_bar, &ToolBar::StepPressed, m_code_widget, &CodeWidget::Step);
+ connect(m_tool_bar, &ToolBar::StepOverPressed, m_code_widget, &CodeWidget::StepOver);
+ connect(m_tool_bar, &ToolBar::StepOutPressed, m_code_widget, &CodeWidget::StepOut);
+ connect(m_tool_bar, &ToolBar::SkipPressed, m_code_widget, &CodeWidget::Skip);
+ connect(m_tool_bar, &ToolBar::ShowPCPressed, m_code_widget, &CodeWidget::ShowPC);
+ connect(m_tool_bar, &ToolBar::SetPCPressed, m_code_widget, &CodeWidget::SetPC);
+}
+
+void MainWindow::ConnectGameList()
+{
+ connect(m_game_list, &GameList::GameSelected, this, [this]() { Play(); });
+ connect(m_game_list, &GameList::NetPlayHost, this, &MainWindow::NetPlayHost);
+
+ connect(m_game_list, &GameList::OpenGeneralSettings, this, &MainWindow::ShowGeneralWindow);
+}
+
+void MainWindow::ConnectRenderWidget()
+{
+ m_rendering_to_main = false;
+ m_render_widget->hide();
+ connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop);
+ connect(m_render_widget, &RenderWidget::FocusChanged, this, [this](bool focus) {
+ if (m_render_widget->isFullScreen())
+ SetFullScreenResolution(focus);
+ });
+}
+
+void MainWindow::ConnectHost()
+{
+ connect(Host::GetInstance(), &Host::UpdateProgressDialog, this,
+ &MainWindow::OnUpdateProgressDialog);
+}
+
+void MainWindow::ConnectStack()
+{
+ auto* widget = new QWidget;
+ auto* layout = new QVBoxLayout;
+ widget->setLayout(layout);
+
+ layout->addWidget(m_game_list);
+ layout->addWidget(m_search_bar);
+ layout->setMargin(0);
+
+ connect(m_search_bar, &SearchBar::Search, m_game_list, &GameList::SetSearchTerm);
+
+ m_stack->addWidget(widget);
+
+ setCentralWidget(m_stack);
+
+ setTabPosition(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea, QTabWidget::North);
+ addDockWidget(Qt::LeftDockWidgetArea, m_log_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_log_config_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_code_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_register_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_watch_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_breakpoint_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_memory_widget);
+ addDockWidget(Qt::LeftDockWidgetArea, m_jit_widget);
+
+ tabifyDockWidget(m_log_widget, m_log_config_widget);
+ tabifyDockWidget(m_log_widget, m_code_widget);
+ tabifyDockWidget(m_log_widget, m_register_widget);
+ tabifyDockWidget(m_log_widget, m_watch_widget);
+ tabifyDockWidget(m_log_widget, m_breakpoint_widget);
+ tabifyDockWidget(m_log_widget, m_memory_widget);
+ tabifyDockWidget(m_log_widget, m_jit_widget);
+}
+
+void MainWindow::RefreshGameList()
+{
+ Settings::Instance().ReloadTitleDB();
+ Settings::Instance().RefreshGameList();
+}
+
+QString MainWindow::PromptFileName()
+{
+ auto& settings = Settings::Instance().GetQSettings();
+ QString path = QFileDialog::getOpenFileName(
+ this, tr("Select a File"),
+ settings.value(QStringLiteral("mainwindow/lastdir"), QStringLiteral("")).toString(),
+ tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad);;"
+ "All Files (*)"));
+
+ if (!path.isEmpty())
+ {
+ settings.setValue(QStringLiteral("mainwindow/lastdir"),
+ QFileInfo(path).absoluteDir().absolutePath());
+ }
+
+ return path;
+}
+
+void MainWindow::ChangeDisc()
+{
+ QString file = PromptFileName();
+
+ if (!file.isEmpty())
+ Core::RunAsCPUThread([&file] { DVDInterface::ChangeDisc(file.toStdString()); });
+}
+
+void MainWindow::EjectDisc()
+{
+ Core::RunAsCPUThread(DVDInterface::EjectDisc);
+}
+
+void MainWindow::Open()
+{
+ QString file = PromptFileName();
+ if (!file.isEmpty())
+ StartGame(file);
+}
+
+void MainWindow::Play(const std::optional<std::string>& savestate_path)
+{
+ // If we're in a paused game, start it up again.
+ // Otherwise, play the selected game, if there is one.
+ // Otherwise, play the default game.
+ // Otherwise, play the last played game, if there is one.
+ // Otherwise, prompt for a new game.
+ if (Core::GetState() == Core::State::Paused)
+ {
+ Core::SetState(Core::State::Running);
+ }
+ else
+ {
+ std::shared_ptr<const UICommon::GameFile> selection = m_game_list->GetSelectedGame();
+ if (selection)
+ {
+ StartGame(selection->GetFilePath(), savestate_path);
+ EnableScreenSaver(false);
+ }
+ else
+ {
+ QString default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO);
+ if (!default_path.isEmpty() && QFile::exists(default_path))
+ {
+ StartGame(default_path, savestate_path);
+ EnableScreenSaver(false);
+ }
+ else
+ {
+ Open();
+ }
+ }
+ }
+}
+
+void MainWindow::Pause()
+{
+ Core::SetState(Core::State::Paused);
+}
+
+void MainWindow::TogglePause()
+{
+ if (Core::GetState() == Core::State::Paused)
+ {
+ Play();
+ }
+ else
+ {
+ Pause();
+ }
+}
+
+void MainWindow::OnStopComplete()
+{
+ m_stop_requested = false;
+ HideRenderWidget();
+ EnableScreenSaver(true);
+#ifdef USE_DISCORD_PRESENCE
+ Discord::UpdateDiscordPresence();
+#endif
+
+ SetFullScreenResolution(false);
+
+ if (m_exit_requested || Settings::Instance().IsBatchModeEnabled())
+ QGuiApplication::instance()->quit();
+
+ // If the current emulation prevented the booting of another, do that now
+ if (m_pending_boot != nullptr)
+ {
+ StartGame(std::move(m_pending_boot));
+ m_pending_boot.reset();
+ }
+}
+
+bool MainWindow::RequestStop()
+{
+ if (!Core::IsRunning())
+ {
+ Core::QueueHostJob([this] { OnStopComplete(); }, true);
+ return true;
+ }
+
+ if (!m_render_widget->isFullScreen())
+ m_render_widget_geometry = m_render_widget->saveGeometry();
+ else
+ FullScreen();
+
+ if (SConfig::GetInstance().bConfirmStop)
+ {
+ const Core::State state = Core::GetState();
+
+ // Only pause the game, if NetPlay is not running
+ bool pause = Settings::Instance().GetNetPlayClient() == nullptr;
+
+ if (pause)
+ Core::SetState(Core::State::Paused);
+
+ QMessageBox::StandardButton confirm;
+ confirm = QMessageBox::question(this, tr("Confirm"),
+ m_stop_requested ?
+ tr("A shutdown is already in progress. Unsaved data "
+ "may be lost if you stop the current emulation "
+ "before it completes. Force stop?") :
+ tr("Do you want to stop the current emulation?"));
+
+ if (pause)
+ Core::SetState(state);
+
+ if (confirm != QMessageBox::Yes)
+ return false;
+ }
+
+ // TODO: Add Movie shutdown
+ // TODO: Add Debugger shutdown
+
+ if (!m_stop_requested && UICommon::TriggerSTMPowerEvent())
+ {
+ m_stop_requested = true;
+
+ // Unpause because gracefully shutting down needs the game to actually request a shutdown.
+ // TODO: Do not unpause in debug mode to allow debugging until the complete shutdown.
+ if (Core::GetState() == Core::State::Paused)
+ Core::SetState(Core::State::Running);
+
+ return true;
+ }
+
+ ForceStop();
+#ifdef Q_OS_WIN
+ // Allow windows to idle or turn off display again
+ SetThreadExecutionState(ES_CONTINUOUS);
+#endif
+ return true;
+}
+
+void MainWindow::ForceStop()
+{
+ Core::Stop();
+}
+
+void MainWindow::Reset()
+{
+ if (Movie::IsRecordingInput())
+ Movie::SetReset(true);
+ ProcessorInterface::ResetButton_Tap();
+}
+
+void MainWindow::FrameAdvance()
+{
+ Core::DoFrameStep();
+}
+
+void MainWindow::FullScreen()
+{
+ // If the render widget is fullscreen we want to reset it to whatever is in
+ // settings. If it's set to be fullscreen then it just remakes the window,
+ // which probably isn't ideal.
+ bool was_fullscreen = m_render_widget->isFullScreen();
+
+ if (!was_fullscreen)
+ m_render_widget_geometry = m_render_widget->saveGeometry();
+
+ HideRenderWidget(false);
+ SetFullScreenResolution(!was_fullscreen);
+
+ if (was_fullscreen)
+ {
+ ShowRenderWidget();
+ }
+ else
+ {
+ m_render_widget->showFullScreen();
+ }
+}
+
+void MainWindow::ScreenShot()
+{
+ Core::SaveScreenShot();
+}
+
+void MainWindow::StartGame(const QString& path, const std::optional<std::string>& savestate_path)
+{
+ StartGame(path.toStdString(), savestate_path);
+}
+
+void MainWindow::StartGame(const std::string& path,
+ const std::optional<std::string>& savestate_path)
+{
+ StartGame(BootParameters::GenerateFromFile(path, savestate_path));
+}
+
+void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
+{
+ // If we're running, only start a new game once we've stopped the last.
+ if (Core::GetState() != Core::State::Uninitialized)
+ {
+ if (!RequestStop())
+ return;
+
+ // As long as the shutdown isn't complete, we can't boot, so let's boot later
+ m_pending_boot = std::move(parameters);
+ return;
+ }
+ // Boot up, show an error if it fails to load the game.
+ if (!BootManager::BootCore(std::move(parameters)))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Failed to init core"), QMessageBox::Ok);
+ return;
+ }
+
+ ShowRenderWidget();
+#ifdef USE_DISCORD_PRESENCE
+ Discord::UpdateDiscordPresence();
+#endif
+
+ if (SConfig::GetInstance().bFullscreen)
+ m_fullscreen_requested = true;
+
+#ifdef Q_OS_WIN
+ // Prevents Windows from sleeping, turning off the display, or idling
+ EXECUTION_STATE shouldScreenSave =
+ SConfig::GetInstance().bDisableScreenSaver ? ES_DISPLAY_REQUIRED : 0;
+ SetThreadExecutionState(ES_CONTINUOUS | shouldScreenSave | ES_SYSTEM_REQUIRED);
+#endif
+}
+
+void MainWindow::SetFullScreenResolution(bool fullscreen)
+{
+ if (SConfig::GetInstance().strFullscreenResolution == "Auto")
+ return;
+#ifdef _WIN32
+
+ if (!fullscreen)
+ {
+ ChangeDisplaySettings(nullptr, CDS_FULLSCREEN);
+ return;
+ }
+
+ DEVMODE screen_settings;
+ memset(&screen_settings, 0, sizeof(screen_settings));
+ screen_settings.dmSize = sizeof(screen_settings);
+ sscanf(SConfig::GetInstance().strFullscreenResolution.c_str(), "%dx%d",
+ &screen_settings.dmPelsWidth, &screen_settings.dmPelsHeight);
+ screen_settings.dmBitsPerPel = 32;
+ screen_settings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
+
+ // Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
+ ChangeDisplaySettings(&screen_settings, CDS_FULLSCREEN);
+#elif defined(HAVE_XRANDR) && HAVE_XRANDR
+ m_xrr_config->ToggleDisplayMode(fullscreen);
+#endif
+}
+
+void MainWindow::ShowRenderWidget()
+{
+ SetFullScreenResolution(false);
+ Host::GetInstance()->SetRenderFullscreen(false);
+
+ if (SConfig::GetInstance().bRenderToMain)
+ {
+ // If we're rendering to main, add it to the stack and update our title when necessary.
+ m_rendering_to_main = true;
+
+ m_stack->setCurrentIndex(m_stack->addWidget(m_render_widget));
+ connect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle);
+ m_stack->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
+ m_stack->repaint();
+
+ Host::GetInstance()->SetRenderFocus(isActiveWindow());
+ }
+ else
+ {
+ // Otherwise, just show it.
+ m_rendering_to_main = false;
+
+ m_render_widget->showNormal();
+ m_render_widget->restoreGeometry(m_render_widget_geometry);
+ }
+}
+
+void MainWindow::HideRenderWidget(bool reinit)
+{
+ if (m_rendering_to_main)
+ {
+ // Remove the widget from the stack and reparent it to nullptr, so that it can draw
+ // itself in a new window if it wants. Disconnect the title updates.
+ m_stack->removeWidget(m_render_widget);
+ m_render_widget->setParent(nullptr);
+ m_rendering_to_main = false;
+ m_stack->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ disconnect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle);
+ setWindowTitle(QString::fromStdString(Common::scm_rev_str));
+ }
+
+ // The following code works around a driver bug that would lead to Dolphin crashing when changing
+ // graphics backends (e.g. OpenGL to Vulkan). To avoid this the render widget is (safely)
+ // recreated
+ if (reinit)
+ {
+ m_render_widget->hide();
+ disconnect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop);
+
+ m_render_widget->removeEventFilter(this);
+ m_render_widget->deleteLater();
+
+ m_render_widget = new RenderWidget;
+
+ m_render_widget->installEventFilter(this);
+ connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop);
+ connect(m_render_widget, &RenderWidget::FocusChanged, this, [this](bool focus) {
+ if (m_render_widget->isFullScreen())
+ SetFullScreenResolution(focus);
+ });
+ }
+}
+
+void MainWindow::ShowControllersWindow()
+{
+ m_controllers_window->show();
+ m_controllers_window->raise();
+ m_controllers_window->activateWindow();
+}
+
+void MainWindow::ShowSettingsWindow()
+{
+ m_settings_window->show();
+ m_settings_window->raise();
+ m_settings_window->activateWindow();
+}
+
+void MainWindow::ShowAudioWindow()
+{
+ m_settings_window->SelectAudioPane();
+ ShowSettingsWindow();
+}
+
+void MainWindow::ShowGeneralWindow()
+{
+ m_settings_window->SelectGeneralPane();
+ ShowSettingsWindow();
+}
+
+void MainWindow::ShowAboutDialog()
+{
+ AboutDialog about{this};
+ about.exec();
+}
+
+void MainWindow::ShowHotkeyDialog()
+{
+ auto* hotkey_window = new MappingWindow(this, MappingWindow::Type::MAPPING_HOTKEYS, 0);
+
+ InstallHotkeyFilter(hotkey_window);
+
+ hotkey_window->show();
+ hotkey_window->raise();
+ hotkey_window->activateWindow();
+}
+
+void MainWindow::ShowGraphicsWindow()
+{
+ m_graphics_window->Initialize();
+ m_graphics_window->show();
+ m_graphics_window->raise();
+ m_graphics_window->activateWindow();
+}
+
+void MainWindow::ShowNetPlaySetupDialog()
+{
+ m_netplay_setup_dialog->show();
+ m_netplay_setup_dialog->raise();
+ m_netplay_setup_dialog->activateWindow();
+}
+
+void MainWindow::ShowFIFOPlayer()
+{
+ m_fifo_window->show();
+ m_fifo_window->raise();
+ m_fifo_window->activateWindow();
+}
+
+void MainWindow::StateLoad()
+{
+ QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
+ tr("All Save States (*.sav *.s##);; All Files (*)"));
+ State::LoadAs(path.toStdString());
+}
+
+void MainWindow::StateSave()
+{
+ QString path = QFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(),
+ tr("All Save States (*.sav *.s##);; All Files (*)"));
+ State::SaveAs(path.toStdString());
+}
+
+void MainWindow::StateLoadSlot()
+{
+ State::Load(m_state_slot);
+}
+
+void MainWindow::StateSaveSlot()
+{
+ State::Save(m_state_slot, true);
+ m_menu_bar->UpdateStateSlotMenu();
+}
+
+void MainWindow::StateLoadSlotAt(int slot)
+{
+ State::Load(slot);
+}
+
+void MainWindow::StateLoadLastSavedAt(int slot)
+{
+ State::LoadLastSaved(slot);
+}
+
+void MainWindow::StateSaveSlotAt(int slot)
+{
+ State::Save(slot, true);
+ m_menu_bar->UpdateStateSlotMenu();
+}
+
+void MainWindow::StateLoadUndo()
+{
+ State::UndoLoadState();
+}
+
+void MainWindow::StateSaveUndo()
+{
+ State::UndoSaveState();
+}
+
+void MainWindow::StateSaveOldest()
+{
+ State::SaveFirstSaved();
+}
+
+void MainWindow::SetStateSlot(int slot)
+{
+ Settings::Instance().SetStateSlot(slot);
+ m_state_slot = slot;
+
+ Core::DisplayMessage(StringFromFormat("Selected slot %d - %s", m_state_slot,
+ State::GetInfoStringOfSlot(m_state_slot, false).c_str()),
+ 2500);
+}
+
+void MainWindow::PerformOnlineUpdate(const std::string& region)
+{
+ WiiUpdate::PerformOnlineUpdate(region, this);
+ // Since the update may have installed a newer system menu, refresh the tools menu.
+ m_menu_bar->UpdateToolsMenu(false);
+}
+
+void MainWindow::BootWiiSystemMenu()
+{
+ StartGame(std::make_unique<BootParameters>(BootParameters::NANDTitle{Titles::SYSTEM_MENU}));
+}
+
+void MainWindow::NetPlayInit()
+{
+ m_netplay_setup_dialog = new NetPlaySetupDialog(this);
+ m_netplay_dialog = new NetPlayDialog;
+
+ connect(m_netplay_dialog, &NetPlayDialog::Boot, this,
+ [this](const QString& path) { StartGame(path); });
+ connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::ForceStop);
+ connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
+ connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
+ connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost);
+}
+
+bool MainWindow::NetPlayJoin()
+{
+ if (Core::IsRunning())
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Error"),
+ QObject::tr("Can't start a NetPlay Session while a game is still running!"));
+ return false;
+ }
+
+ if (m_netplay_dialog->isVisible())
+ {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("A NetPlay Session is already in progress!"));
+ return false;
+ }
+
+ // Settings
+ const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ const bool is_traversal = traversal_choice == "traversal";
+
+ std::string host_ip;
+ u16 host_port;
+ if (Settings::Instance().GetNetPlayServer() != nullptr)
+ {
+ host_ip = "127.0.0.1";
+ host_port = Settings::Instance().GetNetPlayServer()->GetPort();
+ }
+ else
+ {
+ host_ip = is_traversal ? Config::Get(Config::NETPLAY_HOST_CODE) :
+ Config::Get(Config::NETPLAY_ADDRESS);
+ host_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
+ }
+
+ const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
+ const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
+ const std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
+
+ // Create Client
+ Settings::Instance().ResetNetPlayClient(new NetPlayClient(
+ host_ip, host_port, m_netplay_dialog, nickname,
+ NetTraversalConfig{Settings::Instance().GetNetPlayServer() != nullptr ? false : is_traversal,
+ traversal_host, traversal_port}));
+
+ if (!Settings::Instance().GetNetPlayClient()->IsConnected())
+ {
+ NetPlayQuit();
+ return false;
+ }
+
+ m_netplay_setup_dialog->close();
+ m_netplay_dialog->show(nickname, is_traversal);
+
+ return true;
+}
+
+bool MainWindow::NetPlayHost(const QString& game_id)
+{
+ if (Core::IsRunning())
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Error"),
+ QObject::tr("Can't start a NetPlay Session while a game is still running!"));
+ return false;
+ }
+
+ if (m_netplay_dialog->isVisible())
+ {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("A NetPlay Session is already in progress!"));
+ return false;
+ }
+
+ // Settings
+ u16 host_port = Config::Get(Config::NETPLAY_HOST_PORT);
+ const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ const bool is_traversal = traversal_choice == "traversal";
+ const bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
+
+ const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
+ const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
+
+ if (is_traversal)
+ host_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
+
+ // Create Server
+ Settings::Instance().ResetNetPlayServer(new NetPlayServer(
+ host_port, use_upnp, NetTraversalConfig{is_traversal, traversal_host, traversal_port}));
+
+ if (!Settings::Instance().GetNetPlayServer()->is_connected)
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Failed to open server"),
+ QObject::tr(
+ "Failed to listen on port %1. Is another instance of the NetPlay server running?")
+ .arg(host_port));
+ NetPlayQuit();
+ return false;
+ }
+
+ Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
+
+ // Join our local server
+ return NetPlayJoin();
+}
+
+void MainWindow::NetPlayQuit()
+{
+ Settings::Instance().ResetNetPlayClient();
+ Settings::Instance().ResetNetPlayServer();
+}
+
+void MainWindow::EnableScreenSaver(bool enable)
+{
+#if defined(HAVE_XRANDR) && HAVE_XRANDR
+ UICommon::EnableScreenSaver(winId(), enable);
+#else
+ UICommon::EnableScreenSaver(enable);
+#endif
+}
+
+bool MainWindow::eventFilter(QObject* object, QEvent* event)
+{
+ if (event->type() == QEvent::Close)
+ {
+ if (RequestStop() && object == this)
+ m_exit_requested = true;
+
+ static_cast<QCloseEvent*>(event)->ignore();
+ return true;
+ }
+
+ return false;
+}
+
+void MainWindow::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1)
+ event->acceptProposedAction();
+}
+
+void MainWindow::dropEvent(QDropEvent* event)
+{
+ const auto& urls = event->mimeData()->urls();
+ if (urls.empty())
+ return;
+
+ const auto& url = urls[0];
+ QFileInfo file_info(url.toLocalFile());
+
+ auto path = file_info.filePath();
+
+ if (!file_info.exists() || !file_info.isReadable())
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path));
+ return;
+ }
+
+ if (file_info.isFile())
+ {
+ StartGame(path);
+ }
+ else
+ {
+ auto& settings = Settings::Instance();
+
+ if (settings.GetPaths().size() != 0)
+ {
+ if (QMessageBox::question(
+ this, tr("Confirm"),
+ tr("Do you want to add \"%1\" to the list of Game Paths?").arg(path)) !=
+ QMessageBox::Yes)
+ return;
+ }
+ settings.AddPath(path);
+ }
+}
+
+QSize MainWindow::sizeHint() const
+{
+ return QSize(800, 600);
+}
+
+void MainWindow::OnBootGameCubeIPL(DiscIO::Region region)
+{
+ StartGame(std::make_unique<BootParameters>(BootParameters::IPL{region}));
+}
+
+void MainWindow::OnImportNANDBackup()
+{
+ auto response = QMessageBox::question(
+ this, tr("Question"),
+ tr("Merging a new NAND over your currently selected NAND will overwrite any channels "
+ "and savegames that already exist. This process is not reversible, so it is "
+ "recommended that you keep backups of both NANDs. Are you sure you want to "
+ "continue?"));
+
+ if (response == QMessageBox::No)
+ return;
+
+ QString file = QFileDialog::getOpenFileName(this, tr("Select the save file"), QDir::currentPath(),
+ tr("BootMii NAND backup file (*.bin);;"
+ "All Files (*)"));
+
+ if (file.isEmpty())
+ return;
+
+ QProgressDialog* dialog = new QProgressDialog(this);
+ dialog->setMinimum(0);
+ dialog->setMaximum(0);
+ dialog->setLabelText(tr("Importing NAND backup"));
+ dialog->setCancelButton(nullptr);
+
+ auto beginning = QDateTime::currentDateTime().toMSecsSinceEpoch();
+
+ auto result = std::async(std::launch::async, [&] {
+ DiscIO::NANDImporter().ImportNANDBin(
+ file.toStdString(),
+ [&dialog, beginning] {
+ QueueOnObject(dialog, [&dialog, beginning] {
+ dialog->setLabelText(
+ tr("Importing NAND backup\n Time elapsed: %1s")
+ .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000));
+ });
+ },
+ [this] {
+ std::optional<std::string> keys_file = RunOnObject(this, [this] {
+ return QFileDialog::getOpenFileName(this, tr("Select the keys file (OTP/SEEPROM dump)"),
+ QDir::currentPath(),
+ tr("BootMii keys file (*.bin);;"
+ "All Files (*)"))
+ .toStdString();
+ });
+ if (keys_file)
+ return *keys_file;
+ return std::string("");
+ });
+ QueueOnObject(dialog, &QProgressDialog::close);
+ });
+
+ dialog->exec();
+
+ result.wait();
+
+ m_menu_bar->UpdateToolsMenu(Core::IsRunning());
+}
+
+void MainWindow::OnPlayRecording()
+{
+ QString dtm_file = QFileDialog::getOpenFileName(this, tr("Select the Recording File"), QString(),
+ tr("Dolphin TAS Movies (*.dtm)"));
+
+ if (dtm_file.isEmpty())
+ return;
+
+ if (!Movie::IsReadOnly())
+ {
+ // let's make the read-only flag consistent at the start of a movie.
+ Movie::SetReadOnly(true);
+ emit ReadOnlyModeChanged(true);
+ }
+
+ std::optional<std::string> savestate_path;
+ if (Movie::PlayInput(dtm_file.toStdString(), &savestate_path))
+ {
+ emit RecordingStatusChanged(true);
+
+ Play(savestate_path);
+ }
+}
+
+void MainWindow::OnStartRecording()
+{
+ if ((!Core::IsRunningAndStarted() && Core::IsRunning()) || Movie::IsRecordingInput() ||
+ Movie::IsPlayingInput())
+ return;
+
+ if (Movie::IsReadOnly())
+ {
+ // The user just chose to record a movie, so that should take precedence
+ Movie::SetReadOnly(false);
+ emit ReadOnlyModeChanged(true);
+ }
+
+ int controllers = 0;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i]))
+ controllers |= (1 << i);
+
+ if (g_wiimote_sources[i] != WIIMOTE_SRC_NONE)
+ controllers |= (1 << (i + 4));
+ }
+
+ if (Movie::BeginRecordingInput(controllers))
+ {
+ emit RecordingStatusChanged(true);
+
+ if (!Core::IsRunning())
+ Play();
+ }
+}
+
+void MainWindow::OnStopRecording()
+{
+ if (Movie::IsRecordingInput())
+ OnExportRecording();
+
+ Movie::EndPlayInput(false);
+ emit RecordingStatusChanged(true);
+}
+
+void MainWindow::OnExportRecording()
+{
+ bool was_paused = Core::GetState() == Core::State::Paused;
+
+ if (was_paused)
+ Core::SetState(Core::State::Paused);
+
+ QString dtm_file = QFileDialog::getSaveFileName(this, tr("Select the Recording File"), QString(),
+ tr("Dolphin TAS Movies (*.dtm)"));
+
+ if (was_paused)
+ Core::SetState(Core::State::Running);
+
+ if (dtm_file.isEmpty())
+ return;
+
+ Core::SetState(Core::State::Running);
+
+ Movie::SaveRecording(dtm_file.toStdString());
+}
+
+void MainWindow::ShowTASInput()
+{
+ for (int i = 0; i < num_gc_controllers; i++)
+ {
+ if (SConfig::GetInstance().m_SIDevice[i] != SerialInterface::SIDEVICE_NONE &&
+ SConfig::GetInstance().m_SIDevice[i] != SerialInterface::SIDEVICE_GC_GBA)
+ {
+ m_gc_tas_input_windows[i]->show();
+ m_gc_tas_input_windows[i]->raise();
+ m_gc_tas_input_windows[i]->activateWindow();
+ }
+ }
+
+ for (int i = 0; i < num_wii_controllers; i++)
+ {
+ if (g_wiimote_sources[i] == WIIMOTE_SRC_EMU &&
+ (!Core::IsRunning() || SConfig::GetInstance().bWii))
+ {
+ m_wii_tas_input_windows[i]->show();
+ m_wii_tas_input_windows[i]->raise();
+ m_wii_tas_input_windows[i]->activateWindow();
+ }
+ }
+}
+
+void MainWindow::OnConnectWiiRemote(int id)
+{
+ 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"));
+ const bool is_connected = bt && bt->AccessWiimoteByIndex(id)->IsConnected();
+ Wiimote::Connect(id, !is_connected);
+ });
+}
+
+void MainWindow::ShowMemcardManager()
+{
+ GCMemcardManager manager(this);
+
+ manager.exec();
+}
+
+void MainWindow::ShowCheatsManager()
+{
+ m_cheats_manager->show();
+}
+
+void MainWindow::OnUpdateProgressDialog(QString title, int progress, int total)
+{
+ if (!m_progress_dialog)
+ {
+ m_progress_dialog = new QProgressDialog(m_render_widget);
+ m_progress_dialog->show();
+ }
+
+ m_progress_dialog->setValue(progress);
+ m_progress_dialog->setLabelText(title);
+ m_progress_dialog->setWindowTitle(title);
+ m_progress_dialog->setMaximum(total);
+
+ if (total < 0 || progress >= total)
+ {
+ m_progress_dialog->hide();
+ m_progress_dialog->deleteLater();
+ m_progress_dialog = nullptr;
+ }
+}
+
+void MainWindow::Show()
+{
+ QWidget::show();
+
+ // If the booting of a game was requested on start up, do that now
+ if (m_pending_boot != nullptr)
+ {
+ StartGame(std::move(m_pending_boot));
+ m_pending_boot.reset();
+ }
+}