Qt: Implement CTRL+C and confirm shutdown

This commit is contained in:
Connor McLaughlin 2022-03-04 20:40:03 +10:00 committed by refractionpcsx2
parent 56c8843406
commit 2f028c5f40
8 changed files with 135 additions and 29 deletions

View File

@ -16,6 +16,7 @@
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/Console.h" #include "common/Console.h"
@ -137,12 +138,16 @@ void EmuThread::setVMPaused(bool paused)
VMManager::SetPaused(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()) if (!isOnEmuThread())
{ {
QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, allow_save_to_state), // only confirm on UI thread because we need to display a msgbox
Q_ARG(bool, blocking)); 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) if (blocking)
{ {
@ -151,16 +156,17 @@ void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
} }
return; return true;
} }
const VMState state = VMManager::GetState(); const VMState state = VMManager::GetState();
if (state == VMState::Paused) if (state == VMState::Paused)
m_event_loop->quit(); m_event_loop->quit();
else if (state != VMState::Running) else if (state != VMState::Running)
return; return true;
VMManager::SetState(VMState::Stopping); VMManager::SetState(VMState::Stopping);
return true;
} }
void EmuThread::loadState(const QString& filename) void EmuThread::loadState(const QString& filename)
@ -741,7 +747,7 @@ ScopedVMPause::ScopedVMPause(bool was_paused)
ScopedVMPause::~ScopedVMPause() ScopedVMPause::~ScopedVMPause()
{ {
if (m_was_paused) if (!m_was_paused)
g_emu_thread->setVMPaused(false); g_emu_thread->setVMPaused(false);
} }

View File

@ -57,7 +57,7 @@ public Q_SLOTS:
void startVM(std::shared_ptr<VMBootParameters> boot_params); void startVM(std::shared_ptr<VMBootParameters> boot_params);
void resetVM(); void resetVM();
void setVMPaused(bool paused); 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 loadState(const QString& filename);
void loadStateFromSlot(qint32 slot); void loadStateFromSlot(qint32 slot);
void saveState(const QString& filename); void saveState(const QString& filename);

View File

@ -17,6 +17,7 @@
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <cstdlib> #include <cstdlib>
#include <csignal>
#include "MainWindow.h" #include "MainWindow.h"
#include "EmuThread.h" #include "EmuThread.h"
@ -212,7 +213,6 @@ int main(int argc, char* argv[])
const int result = app.exec(); const int result = app.exec();
EmuThread::stop();
QtHost::Shutdown(); QtHost::Shutdown();
return result; return result;
} }

View File

@ -16,6 +16,7 @@
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar> #include <QtWidgets/QProgressBar>
@ -229,7 +230,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
void MainWindow::recreate() void MainWindow::recreate()
{ {
if (m_vm_valid) if (m_vm_valid)
g_emu_thread->shutdownVM(true, true); g_emu_thread->shutdownVM(false, true, true);
close(); close();
g_main_window = nullptr; g_main_window = nullptr;
@ -258,7 +259,7 @@ void MainWindow::setStyleFromSettings()
qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor black(25, 25, 25); 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 tameTeal(160, 190, 185);
const QColor grayBlue(160, 180, 190); 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); } 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 Host::InvalidateSaveStateCache() { QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection); }
void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total) 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) void MainWindow::closeEvent(QCloseEvent* event)
{ {
g_emu_thread->shutdownVM(true, true); if (!g_emu_thread->shutdownVM(true, true, true))
{
event->ignore();
return;
}
saveStateToConfig(); saveStateToConfig();
QMainWindow::closeEvent(event); QMainWindow::closeEvent(event);
} }

View File

@ -54,6 +54,8 @@ public Q_SLOTS:
void refreshGameList(bool invalidate_cache); void refreshGameList(bool invalidate_cache);
void invalidateSaveStateCache(); void invalidateSaveStateCache();
void reportError(const QString& title, const QString& message); void reportError(const QString& title, const QString& message);
bool confirmShutdown();
void requestExit();
private Q_SLOTS: private Q_SLOTS:
DisplayWidget* createDisplay(bool fullscreen, bool render_to_main); DisplayWidget* createDisplay(bool fullscreen, bool render_to_main);

View File

@ -15,6 +15,15 @@
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include <csignal>
#include <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#endif
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/FileSystem.h" #include "common/FileSystem.h"
@ -26,9 +35,6 @@
#include "pcsx2/HostSettings.h" #include "pcsx2/HostSettings.h"
#include "pcsx2/PAD/Host/PAD.h" #include "pcsx2/PAD/Host/PAD.h"
#include <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#include "EmuThread.h" #include "EmuThread.h"
#include "GameList/GameListWidget.h" #include "GameList/GameListWidget.h"
#include "MainWindow.h" #include "MainWindow.h"
@ -42,11 +48,15 @@ static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Local function declarations // Local function declarations
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
namespace QtHost {
static void InitializeWxRubbish(); static void InitializeWxRubbish();
static bool InitializeConfig(); static bool InitializeConfig();
static void HookSignals();
static bool SetCriticalFolders();
static void SetDefaultConfig(); static void SetDefaultConfig();
static void QueueSettingsSave(); static void QueueSettingsSave();
static void SaveSettings(); static void SaveSettings();
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Local variable declarations // Local variable declarations
@ -72,12 +82,21 @@ bool QtHost::Initialize()
return false; return false;
} }
HookSignals();
return true; 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()); std::string program_path(FileSystem::GetProgramPath());
EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path))); EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path)));
@ -110,7 +129,7 @@ void QtHost::UpdateFolders()
EmuFolders::EnsureFoldersExist(); EmuFolders::EnsureFoldersExist();
} }
bool InitializeConfig() bool QtHost::InitializeConfig()
{ {
if (!SetCriticalFolders()) if (!SetCriticalFolders())
return false; return false;
@ -138,7 +157,7 @@ bool InitializeConfig()
return true; return true;
} }
void SetDefaultConfig() void QtHost::SetDefaultConfig()
{ {
EmuConfig = Pcsx2Config(); EmuConfig = Pcsx2Config();
EmuFolders::SetDefaults(); EmuFolders::SetDefaults();
@ -247,7 +266,7 @@ void QtHost::RemoveBaseSettingValue(const char* section, const char* key)
QueueSettingsSave(); QueueSettingsSave();
} }
void SaveSettings() void QtHost::SaveSettings()
{ {
pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread."); pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread.");
@ -261,7 +280,7 @@ void SaveSettings()
s_settings_save_timer.release(); s_settings_save_timer.release();
} }
void QueueSettingsSave() void QtHost::QueueSettingsSave()
{ {
if (s_settings_save_timer) if (s_settings_save_timer)
return; return;
@ -338,9 +357,38 @@ void PatchesVerboseReset()
// FIXME // 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. // Replacement for Console so we actually get output to our console window on Windows.
#ifdef _WIN32 #ifdef _WIN32
#include "common/RedtapeWindows.h"
static bool s_debugger_attached = false; static bool s_debugger_attached = false;
static bool s_console_handle_set = false; static bool s_console_handle_set = false;
@ -433,6 +481,17 @@ static const IConsoleWriter ConsoleWriter_WinQt =
ConsoleWinQt_Newline, ConsoleWinQt_Newline,
ConsoleWinQt_SetTitle, 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 #endif
void QtHost::UpdateLogging() void QtHost::UpdateLogging()
@ -445,9 +504,9 @@ void QtHost::UpdateLogging()
SysConsole.eeConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableEEConsole", true); SysConsole.eeConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableEEConsole", true);
SysConsole.iopConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableIOPConsole", true); SysConsole.iopConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableIOPConsole", true);
#ifdef _WIN32
if (system_console_enabled) if (system_console_enabled)
{ {
#ifdef _WIN32
s_debugger_attached = IsDebuggerPresent(); s_debugger_attached = IsDebuggerPresent();
if (!s_console_handle_set) if (!s_console_handle_set)
{ {
@ -468,17 +527,31 @@ void QtHost::UpdateLogging()
} }
if (!s_console_handle && !s_debugger_attached) if (!s_console_handle && !s_debugger_attached)
{
Console_SetActiveHandler(ConsoleWriter_Null); Console_SetActiveHandler(ConsoleWriter_Null);
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
}
else else
{
Console_SetActiveHandler(ConsoleWriter_WinQt); Console_SetActiveHandler(ConsoleWriter_WinQt);
SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
}
}
else
{
Console_SetActiveHandler(ConsoleWriter_Null);
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
}
#else #else
if (system_console_enabled)
{
Console_SetActiveHandler(ConsoleWriter_Stdout); Console_SetActiveHandler(ConsoleWriter_Stdout);
#endif
} }
else else
{ {
Console_SetActiveHandler(ConsoleWriter_Null); Console_SetActiveHandler(ConsoleWriter_Null);
} }
#endif
} }
#include <wx/module.h> #include <wx/module.h>
@ -488,7 +561,7 @@ extern "C" HINSTANCE wxGetInstance();
extern void wxSetInstance(HINSTANCE hInst); extern void wxSetInstance(HINSTANCE hInst);
#endif #endif
void InitializeWxRubbish() void QtHost::InitializeWxRubbish()
{ {
wxLog::DoCreateOnDemand(); wxLog::DoCreateOnDemand();
wxLog::GetActiveTarget(); wxLog::GetActiveTarget();

View File

@ -41,7 +41,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.inhibitScreensaver, "UI", "InhibitScreensaver", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.inhibitScreensaver, "UI", "InhibitScreensaver", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "UI", "DiscordPresence", false); 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.saveStateOnExit, "EmuCore", "AutoStateLoadSave", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnStart, "UI", "StartPaused", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnStart, "UI", "StartPaused", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
@ -84,8 +84,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
} }
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"), m_ui.confirmShutdown, tr("Confirm Shutdown"), tr("Checked"),
tr("Determines whether a prompt will be displayed to confirm shutting down the emulator/game " tr("Determines whether a prompt will be displayed to confirm shutting down the virtual machine "
"when the hotkey is pressed.")); "when the hotkey is pressed."));
dialog->registerWidgetHelp(m_ui.saveStateOnExit, tr("Save State On Exit"), tr("Checked"), 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 " tr("Automatically saves the emulator state when powering down or exiting. You can then "

View File

@ -61,9 +61,9 @@
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QCheckBox" name="confirmPowerOff"> <widget class="QCheckBox" name="confirmShutdown">
<property name="text"> <property name="text">
<string>Confirm Power Off</string> <string>Confirm Shutdown</string>
</property> </property>
</widget> </widget>
</item> </item>