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 <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
#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);
}

View File

@ -57,7 +57,7 @@ public Q_SLOTS:
void startVM(std::shared_ptr<VMBootParameters> 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);

View File

@ -17,6 +17,7 @@
#include <QtWidgets/QApplication>
#include <cstdlib>
#include <csignal>
#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;
}

View File

@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include <QtCore/QDateTime>
#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar>
@ -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);
}

View File

@ -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);

View File

@ -15,6 +15,15 @@
#include "PrecompiledHeader.h"
#include <csignal>
#include <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#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 <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#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 <wx/module.h>
@ -488,7 +561,7 @@ extern "C" HINSTANCE wxGetInstance();
extern void wxSetInstance(HINSTANCE hInst);
#endif
void InitializeWxRubbish()
void QtHost::InitializeWxRubbish()
{
wxLog::DoCreateOnDemand();
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.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 "

View File

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