// Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #include #include "Common/Common.h" #include "Core/Boot/Boot.h" #include "Core/BootManager.h" #include "Core/CommonTitles.h" #include "Core/ConfigManager.h" #include "Core/Core.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/Movie.h" #include "Core/NetPlayProto.h" #include "Core/State.h" #include "DolphinQt2/AboutDialog.h" #include "DolphinQt2/Config/ControllersWindow.h" #include "DolphinQt2/Config/Graphics/GraphicsWindow.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" #include "DolphinQt2/MainWindow.h" #include "DolphinQt2/QtUtils/WindowActivationEventFilter.h" #include "DolphinQt2/Resources.h" #include "DolphinQt2/Settings.h" #include "DolphinQt2/WiiUpdate.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "UICommon/UICommon.h" #if defined(HAVE_XRANDR) && HAVE_XRANDR #include #include "UICommon/X11Utils.h" #endif MainWindow::MainWindow() : QMainWindow(nullptr) { setWindowTitle(QString::fromStdString(scm_rev_str)); setWindowIcon(QIcon(Resources::GetMisc(Resources::LOGO_SMALL))); setUnifiedTitleAndToolBarOnMac(true); setAcceptDrops(true); CreateComponents(); ConnectGameList(); ConnectToolBar(); ConnectRenderWidget(); ConnectStack(); ConnectMenuBar(); InitControllers(); InitCoreCallbacks(); } MainWindow::~MainWindow() { m_render_widget->deleteLater(); ShutdownControllers(); } void MainWindow::InitControllers() { if (g_controller_interface.IsInit()) return; g_controller_interface.Initialize(reinterpret_cast(winId())); Pad::Initialize(); Keyboard::Initialize(); Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES); m_hotkey_scheduler = new HotkeyScheduler(); m_hotkey_scheduler->Start(); ConnectHotkeys(); } 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() { Core::SetOnStoppedCallback([this] { emit EmulationStopped(); }); 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_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); m_hotkey_window = new MappingWindow(this, 0); connect(this, &MainWindow::EmulationStarted, m_settings_window, &SettingsWindow::EmulationStarted); connect(this, &MainWindow::EmulationStopped, m_settings_window, &SettingsWindow::EmulationStopped); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( new X11Utils::XRRConfiguration( static_cast(QGuiApplication::platformNativeInterface()->nativeResourceForWindow( "display", windowHandle())), winId()), this); #else m_graphics_window = new GraphicsWindow(nullptr, this); #endif InstallHotkeyFilter(m_hotkey_window); 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); // Emulation connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause); connect(m_menu_bar, &MenuBar::Play, this, &MainWindow::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::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog); // Tools connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate); connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); // View connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView); connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView); connect(m_menu_bar, &MenuBar::ColumnVisibilityToggled, m_game_list, &GameList::OnColumnVisibilityToggled); connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog); connect(this, &MainWindow::EmulationStarted, m_menu_bar, &MenuBar::EmulationStarted); connect(this, &MainWindow::EmulationPaused, m_menu_bar, &MenuBar::EmulationPaused); connect(this, &MainWindow::EmulationStopped, m_menu_bar, &MenuBar::EmulationStopped); connect(this, &MainWindow::EmulationStarted, this, [=]() { m_controllers_window->OnEmulationStateChanged(true); }); connect(this, &MainWindow::EmulationStopped, this, [=]() { m_controllers_window->OnEmulationStateChanged(false); }); } void MainWindow::ConnectHotkeys() { connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close); connect(m_hotkey_scheduler, &HotkeyScheduler::PauseHotkey, this, &MainWindow::Pause); connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop); connect(m_hotkey_scheduler, &HotkeyScheduler::ScreenShotHotkey, this, &MainWindow::ScreenShot); connect(m_hotkey_scheduler, &HotkeyScheduler::FullScreenHotkey, this, &MainWindow::FullScreen); 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); } void MainWindow::ConnectToolBar() { addToolBar(m_tool_bar); connect(m_tool_bar, &ToolBar::OpenPressed, this, &MainWindow::Open); connect(m_tool_bar, &ToolBar::PlayPressed, this, &MainWindow::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(this, &MainWindow::EmulationStarted, m_tool_bar, &ToolBar::EmulationStarted); connect(this, &MainWindow::EmulationPaused, m_tool_bar, &ToolBar::EmulationPaused); connect(this, &MainWindow::EmulationStopped, m_tool_bar, &ToolBar::EmulationStopped); connect(this, &MainWindow::EmulationStopped, this, &MainWindow::OnStopComplete); } void MainWindow::ConnectGameList() { connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play); connect(this, &MainWindow::EmulationStarted, m_game_list, &GameList::EmulationStarted); connect(this, &MainWindow::EmulationStopped, m_game_list, &GameList::EmulationStopped); } void MainWindow::ConnectRenderWidget() { m_rendering_to_main = false; m_render_widget->hide(); connect(m_render_widget, &RenderWidget::EscapePressed, this, &MainWindow::RequestStop); connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop); } void MainWindow::ConnectStack() { m_stack->addWidget(m_game_list); setCentralWidget(m_stack); } void MainWindow::Open() { QString file = QFileDialog::getOpenFileName( this, tr("Select a File"), QDir::currentPath(), tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad);;" "All Files (*)")); if (!file.isEmpty()) StartGame(file); } void MainWindow::Play() { // 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); emit EmulationStarted(); } else { QString selection = m_game_list->GetSelectedGame(); if (selection.length() > 0) { StartGame(selection); } else { auto default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO); if (!default_path.isEmpty() && QFile::exists(default_path)) { StartGame(default_path); } else { Open(); } } } } void MainWindow::Pause() { Core::SetState(Core::State::Paused); emit EmulationPaused(); } void MainWindow::OnStopComplete() { m_stop_requested = false; m_render_widget->hide(); if (m_exit_requested) QGuiApplication::instance()->quit(); // If the current emulation prevented the booting of another, do that now if (!m_pending_boot.isEmpty()) { StartGame(m_pending_boot); m_pending_boot.clear(); } } bool MainWindow::RequestStop() { if (!Core::IsRunning()) { Core::QueueHostJob([this] { OnStopComplete(); }, true); return true; } if (SConfig::GetInstance().bConfirmStop) { const Core::State state = Core::GetState(); // TODO: Set to false when Netplay is running as a CPU thread bool pause = true; if (pause) Core::SetState(Core::State::Paused); QMessageBox::StandardButton confirm; confirm = QMessageBox::question(m_render_widget, 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() { BootManager::Stop(); HideRenderWidget(); } void MainWindow::Reset() { if (Movie::IsRecordingInput()) Movie::SetReset(true); ProcessorInterface::ResetButton_Tap(); } void MainWindow::FrameAdvance() { Movie::DoFrameStep(); EmulationPaused(); } 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(); HideRenderWidget(); if (was_fullscreen) ShowRenderWidget(); else m_render_widget->showFullScreen(); } void MainWindow::ScreenShot() { Core::SaveScreenShot(); } void MainWindow::StartGame(const QString& path) { // 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 = path; return; } // Boot up, show an error if it fails to load the game. if (!BootManager::BootCore(BootParameters::GenerateFromFile(path.toStdString()))) { QMessageBox::critical(this, tr("Error"), tr("Failed to init core"), QMessageBox::Ok); return; } ShowRenderWidget(); emit EmulationStarted(); #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::ShowRenderWidget() { 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); } else { // Otherwise, just show it. m_rendering_to_main = false; if (SConfig::GetInstance().bFullscreen) { m_render_widget->showFullScreen(); } else { m_render_widget->resize(640, 480); m_render_widget->showNormal(); } } } void MainWindow::HideRenderWidget() { 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; disconnect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle); setWindowTitle(QString::fromStdString(scm_rev_str)); } m_render_widget->hide(); } 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::ShowAboutDialog() { AboutDialog* about = new AboutDialog(this); about->show(); } void MainWindow::ShowHotkeyDialog() { m_hotkey_window->ChangeMappingType(MappingWindow::Type::MAPPING_HOTKEYS); m_hotkey_window->show(); m_hotkey_window->raise(); m_hotkey_window->activateWindow(); } void MainWindow::ShowGraphicsWindow() { m_graphics_window->show(); m_graphics_window->raise(); m_graphics_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::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; } 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(QString::fromStdString( Common::GetTitleContentPath(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT))); } bool MainWindow::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Close) { if (RequestStop() && object == this) m_exit_requested = true; static_cast(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); }