diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index c0847b3612..a237fa66b5 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -16,6 +16,7 @@ #include "PrecompiledHeader.h" #include +#include #include "common/Assertions.h" #include "common/Console.h" @@ -137,12 +138,16 @@ void EmuThread::setVMPaused(bool paused) VMManager::SetPaused(paused); } -void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking /* = false */) +bool EmuThread::shutdownVM(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool blocking /* = false */) { if (!isOnEmuThread()) { - QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, allow_save_to_state), - Q_ARG(bool, blocking)); + // only confirm on UI thread because we need to display a msgbox + if (allow_confirm && g_main_window && !g_main_window->confirmShutdown()) + return false; + + QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, false), + Q_ARG(bool, allow_save_to_state), Q_ARG(bool, blocking)); if (blocking) { @@ -151,16 +156,17 @@ void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); } - return; + return true; } const VMState state = VMManager::GetState(); if (state == VMState::Paused) m_event_loop->quit(); else if (state != VMState::Running) - return; + return true; VMManager::SetState(VMState::Stopping); + return true; } void EmuThread::loadState(const QString& filename) @@ -741,7 +747,7 @@ ScopedVMPause::ScopedVMPause(bool was_paused) ScopedVMPause::~ScopedVMPause() { - if (m_was_paused) + if (!m_was_paused) g_emu_thread->setVMPaused(false); } diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h index 3ab714a662..28ecbf6e17 100644 --- a/pcsx2-qt/EmuThread.h +++ b/pcsx2-qt/EmuThread.h @@ -57,7 +57,7 @@ public Q_SLOTS: void startVM(std::shared_ptr boot_params); void resetVM(); void setVMPaused(bool paused); - void shutdownVM(bool allow_save_to_state = true, bool blocking = false); + bool shutdownVM(bool allow_confirm = true, bool allow_save_to_state = true, bool blocking = false); void loadState(const QString& filename); void loadStateFromSlot(qint32 slot); void saveState(const QString& filename); diff --git a/pcsx2-qt/Main.cpp b/pcsx2-qt/Main.cpp index 7ad9a0c048..b26daa564a 100644 --- a/pcsx2-qt/Main.cpp +++ b/pcsx2-qt/Main.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "MainWindow.h" #include "EmuThread.h" @@ -212,7 +213,6 @@ int main(int argc, char* argv[]) const int result = app.exec(); - EmuThread::stop(); QtHost::Shutdown(); return result; } diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 70f249e323..c6b786bc60 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -16,6 +16,7 @@ #include "PrecompiledHeader.h" #include +#include #include #include #include @@ -229,7 +230,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread) void MainWindow::recreate() { if (m_vm_valid) - g_emu_thread->shutdownVM(true, true); + g_emu_thread->shutdownVM(false, true, true); close(); g_main_window = nullptr; @@ -258,7 +259,7 @@ void MainWindow::setStyleFromSettings() qApp->setStyle(QStyleFactory::create("Fusion")); const QColor black(25, 25, 25); - const QColor teal(0, 128, 128); + const QColor teal(0, 128, 128); const QColor tameTeal(160, 190, 185); const QColor grayBlue(160, 180, 190); @@ -640,6 +641,25 @@ void MainWindow::invalidateSaveStateCache() { m_save_states_invalidated = true; void MainWindow::reportError(const QString& title, const QString& message) { QMessageBox::critical(this, title, message); } +bool MainWindow::confirmShutdown() +{ + if (!m_vm_valid || !QtHost::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true)) + return true; + + ScopedVMPause pauser(m_vm_paused); + + return (QMessageBox::question(g_main_window, tr("Confirm Shutdown"), + tr("Are you sure you want to shut down the virtual machine?\n\nAll unsaved progress will be lost.")) == QMessageBox::Yes); +} + +void MainWindow::requestExit() +{ + if (!g_emu_thread->shutdownVM(true, true, false)) + return; + + close(); +} + void Host::InvalidateSaveStateCache() { QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection); } void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total) @@ -966,7 +986,12 @@ void MainWindow::onGameChanged(const QString& path, const QString& serial, const void MainWindow::closeEvent(QCloseEvent* event) { - g_emu_thread->shutdownVM(true, true); + if (!g_emu_thread->shutdownVM(true, true, true)) + { + event->ignore(); + return; + } + saveStateToConfig(); QMainWindow::closeEvent(event); } diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 25a8299415..5ad1cb9181 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -54,6 +54,8 @@ public Q_SLOTS: void refreshGameList(bool invalidate_cache); void invalidateSaveStateCache(); void reportError(const QString& title, const QString& message); + bool confirmShutdown(); + void requestExit(); private Q_SLOTS: DisplayWidget* createDisplay(bool fullscreen, bool render_to_main); diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 1aa79d6dea..e093325334 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -15,6 +15,15 @@ #include "PrecompiledHeader.h" +#include + +#include +#include + +#ifdef _WIN32 +#include "common/RedtapeWindows.h" +#endif + #include "common/Assertions.h" #include "common/Console.h" #include "common/FileSystem.h" @@ -26,9 +35,6 @@ #include "pcsx2/HostSettings.h" #include "pcsx2/PAD/Host/PAD.h" -#include -#include - #include "EmuThread.h" #include "GameList/GameListWidget.h" #include "MainWindow.h" @@ -42,11 +48,15 @@ static constexpr u32 SETTINGS_SAVE_DELAY = 1000; ////////////////////////////////////////////////////////////////////////// // Local function declarations ////////////////////////////////////////////////////////////////////////// +namespace QtHost { static void InitializeWxRubbish(); static bool InitializeConfig(); +static void HookSignals(); +static bool SetCriticalFolders(); static void SetDefaultConfig(); static void QueueSettingsSave(); static void SaveSettings(); +} ////////////////////////////////////////////////////////////////////////// // Local variable declarations @@ -72,12 +82,21 @@ bool QtHost::Initialize() return false; } + HookSignals(); return true; } -void QtHost::Shutdown() {} +void QtHost::Shutdown() +{ + EmuThread::stop(); + if (g_main_window) + { + g_main_window->close(); + delete g_main_window; + } +} -static bool SetCriticalFolders() +bool QtHost::SetCriticalFolders() { std::string program_path(FileSystem::GetProgramPath()); EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path))); @@ -110,7 +129,7 @@ void QtHost::UpdateFolders() EmuFolders::EnsureFoldersExist(); } -bool InitializeConfig() +bool QtHost::InitializeConfig() { if (!SetCriticalFolders()) return false; @@ -138,7 +157,7 @@ bool InitializeConfig() return true; } -void SetDefaultConfig() +void QtHost::SetDefaultConfig() { EmuConfig = Pcsx2Config(); EmuFolders::SetDefaults(); @@ -247,7 +266,7 @@ void QtHost::RemoveBaseSettingValue(const char* section, const char* key) QueueSettingsSave(); } -void SaveSettings() +void QtHost::SaveSettings() { pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread."); @@ -261,7 +280,7 @@ void SaveSettings() s_settings_save_timer.release(); } -void QueueSettingsSave() +void QtHost::QueueSettingsSave() { if (s_settings_save_timer) return; @@ -338,9 +357,38 @@ void PatchesVerboseReset() // FIXME } +static void SignalHandler(int signal) +{ + // First try the normal (graceful) shutdown/exit. + static bool graceful_shutdown_attempted = false; + if (!graceful_shutdown_attempted && g_main_window) + { + std::fprintf(stderr, "Received CTRL+C, attempting graceful shutdown. Press CTRL+C again to force.\n"); + graceful_shutdown_attempted = true; + + // This could be a bit risky invoking from a signal handler... hopefully it's okay. + QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit, Qt::QueuedConnection); + return; + } + + std::signal(signal, SIG_DFL); + + // MacOS is missing std::quick_exit() despite it being C++11... +#ifndef __APPLE__ + std::quick_exit(1); +#else + _Exit(1); +#endif +} + +void QtHost::HookSignals() +{ + std::signal(SIGINT, SignalHandler); + std::signal(SIGTERM, SignalHandler); +} + // Replacement for Console so we actually get output to our console window on Windows. #ifdef _WIN32 -#include "common/RedtapeWindows.h" static bool s_debugger_attached = false; static bool s_console_handle_set = false; @@ -433,6 +481,17 @@ static const IConsoleWriter ConsoleWriter_WinQt = ConsoleWinQt_Newline, ConsoleWinQt_SetTitle, }; + +static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) +{ + Console.WriteLn("Handler %u", dwCtrlType); + if (dwCtrlType != CTRL_C_EVENT) + return FALSE; + + SignalHandler(SIGTERM); + return TRUE; +} + #endif void QtHost::UpdateLogging() @@ -445,9 +504,9 @@ void QtHost::UpdateLogging() SysConsole.eeConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableEEConsole", true); SysConsole.iopConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableIOPConsole", true); +#ifdef _WIN32 if (system_console_enabled) { -#ifdef _WIN32 s_debugger_attached = IsDebuggerPresent(); if (!s_console_handle_set) { @@ -468,17 +527,31 @@ void QtHost::UpdateLogging() } if (!s_console_handle && !s_debugger_attached) + { Console_SetActiveHandler(ConsoleWriter_Null); + SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE); + } else + { Console_SetActiveHandler(ConsoleWriter_WinQt); + SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); + } + } + else + { + Console_SetActiveHandler(ConsoleWriter_Null); + SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE); + } #else + if (system_console_enabled) + { Console_SetActiveHandler(ConsoleWriter_Stdout); -#endif } else { Console_SetActiveHandler(ConsoleWriter_Null); } +#endif } #include @@ -488,7 +561,7 @@ extern "C" HINSTANCE wxGetInstance(); extern void wxSetInstance(HINSTANCE hInst); #endif -void InitializeWxRubbish() +void QtHost::InitializeWxRubbish() { wxLog::DoCreateOnDemand(); wxLog::GetActiveTarget(); diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp index 11c8d6469b..5cb45b3d8d 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp @@ -41,7 +41,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.inhibitScreensaver, "UI", "InhibitScreensaver", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "UI", "DiscordPresence", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmPowerOff, "UI", "ConfirmPowerOff", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.saveStateOnExit, "EmuCore", "AutoStateLoadSave", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnStart, "UI", "StartPaused", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false); @@ -84,8 +84,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget } dialog->registerWidgetHelp( - m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"), - tr("Determines whether a prompt will be displayed to confirm shutting down the emulator/game " + m_ui.confirmShutdown, tr("Confirm Shutdown"), tr("Checked"), + tr("Determines whether a prompt will be displayed to confirm shutting down the virtual machine " "when the hotkey is pressed.")); dialog->registerWidgetHelp(m_ui.saveStateOnExit, tr("Save State On Exit"), tr("Checked"), tr("Automatically saves the emulator state when powering down or exiting. You can then " diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.ui b/pcsx2-qt/Settings/InterfaceSettingsWidget.ui index 2e79617188..7cf727328c 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.ui +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.ui @@ -61,9 +61,9 @@ - + - Confirm Power Off + Confirm Shutdown