diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 1874f2d315..4714a8e472 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -106,7 +106,7 @@ class Emulator final atomic_t m_pause_start_time{0}; // set when paused atomic_t m_pause_amend_time{0}; // increased when resumed - atomic_t m_stop_ctr{0}; // Increments when emulation is stopped + atomic_t m_stop_ctr{1}; // Increments when emulation is stopped games_config m_games_config; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index bbc7cec335..7c88a4593f 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -326,6 +326,7 @@ struct cfg_root : cfg::node cfg::_bool autostart{ this, "Automatically start games after boot", true, true }; cfg::_bool autoexit{ this, "Exit RPCS3 when process finishes", false, true }; + cfg::_bool autopause{ this, "Pause emulation on RPCS3 focus loss", false, true }; cfg::_bool start_fullscreen{ this, "Start games in fullscreen mode", false, true }; cfg::_bool prevent_display_sleep{ this, "Prevent display sleep while running games", true, true }; cfg::_bool show_trophy_popups{ this, "Show trophy popups", true, true }; diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 064332dbe6..f4600d5b87 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -162,6 +162,7 @@ enum class emu_settings_type // Misc ExitRPCS3OnFinish, StartOnBoot, + PauseOnFocusLoss, StartGameFullscreen, PreventDisplaySleep, ShowTrophyPopups, @@ -346,6 +347,7 @@ inline static const QMap settings_location = // Misc { emu_settings_type::ExitRPCS3OnFinish, { "Miscellaneous", "Exit RPCS3 when process finishes" }}, { emu_settings_type::StartOnBoot, { "Miscellaneous", "Automatically start games after boot" }}, + { emu_settings_type::PauseOnFocusLoss, { "Miscellaneous", "Pause emulation on RPCS3 focus loss" }}, { emu_settings_type::StartGameFullscreen, { "Miscellaneous", "Start games in fullscreen mode"}}, { emu_settings_type::PreventDisplaySleep, { "Miscellaneous", "Prevent display sleep while running games"}}, { emu_settings_type::ShowTrophyPopups, { "Miscellaneous", "Show trophy popups"}}, diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 685ed4d7fa..a36efa071a 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -270,6 +270,7 @@ void gui_application::InitializeConnects() connect(this, &gui_application::OnEmulatorStop, this, &gui_application::StopPlaytime); connect(this, &gui_application::OnEmulatorPause, this, &gui_application::StopPlaytime); connect(this, &gui_application::OnEmulatorResume, this, &gui_application::StartPlaytime); + connect(this, &QGuiApplication::applicationStateChanged, this, &gui_application::OnAppStateChanged); if (m_main_window) { @@ -798,3 +799,76 @@ void gui_application::CallFromMainThread(const std::function& func, atom wake_up->notify_one(); } } + +void gui_application::OnAppStateChanged(Qt::ApplicationState state) +{ + // Invalidate previous delayed pause call (even when the setting is off because it is dynamic) + m_pause_delayed_tag++; + + if (!g_cfg.misc.autopause) + { + return; + } + + const auto emu_state = Emu.GetStatus(); + const bool is_active = state == Qt::ApplicationActive; + + if (emu_state != system_state::paused && emu_state != system_state::running) + { + return; + } + + const bool is_paused = emu_state == system_state::paused; + + if (is_active != is_paused) + { + // Nothing to do (either paused and this is focus-out event or running and this is a focus-in event) + // Invalidate data + m_is_pause_on_focus_loss_active = false; + m_emu_focus_out_emulation_id = Emulator::stop_counter_t{}; + return; + } + + if (is_paused) + { + // Check if Emu.Resume() or Emu.Kill() has not been called since + if (m_is_pause_on_focus_loss_active && m_pause_amend_time_on_focus_loss == Emu.GetPauseTime() && m_emu_focus_out_emulation_id == Emu.GetEmulationIdentifier()) + { + m_is_pause_on_focus_loss_active = false; + Emu.Resume(); + } + + return; + } + + // Gather validation data + m_emu_focus_out_emulation_id = Emu.GetEmulationIdentifier(); + + auto pause_callback = [this, delayed_tag = m_pause_delayed_tag]() + { + // Check if Emu.Kill() has not been called since + if (applicationState() != Qt::ApplicationActive && Emu.IsRunning() && + m_emu_focus_out_emulation_id == Emu.GetEmulationIdentifier() && + delayed_tag == m_pause_delayed_tag && + !m_is_pause_on_focus_loss_active) + { + if (Emu.Pause()) + { + // Gather validation data + m_pause_amend_time_on_focus_loss = Emu.GetPauseTime(); + m_emu_focus_out_emulation_id = Emu.GetEmulationIdentifier(); + m_is_pause_on_focus_loss_active = true; + } + } + }; + + if (state == Qt::ApplicationSuspended) + { + // Must be invoked now (otherwise it may not happen later) + pause_callback(); + return; + } + + // Delay pause so it won't immediately pause the emulated application + QTimer::singleShot(1000, this, pause_callback); +} diff --git a/rpcs3/rpcs3qt/gui_application.h b/rpcs3/rpcs3qt/gui_application.h index 762a8a045a..ff86ea56d3 100644 --- a/rpcs3/rpcs3qt/gui_application.h +++ b/rpcs3/rpcs3qt/gui_application.h @@ -10,6 +10,8 @@ #include "main_application.h" +#include "Emu/System.h" + #include #include @@ -95,8 +97,14 @@ private: bool m_start_games_fullscreen = false; int m_game_screen_index = -1; + u64 m_pause_amend_time_on_focus_loss = umax; + u64 m_pause_delayed_tag = 0; + typename Emulator::stop_counter_t m_emu_focus_out_emulation_id{}; + bool m_is_pause_on_focus_loss_active = false; + private Q_SLOTS: void OnChangeStyleSheetRequest(); + void OnAppStateChanged(Qt::ApplicationState state); Q_SIGNALS: void OnEmulatorRun(bool start_playtime); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 433b830d32..eae45ebba9 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1734,6 +1734,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->exitOnStop, emu_settings_type::ExitRPCS3OnFinish); SubscribeTooltip(ui->exitOnStop, tooltips.settings.exit_on_stop); + m_emu_settings->EnhanceCheckBox(ui->pauseOnFocusLoss, emu_settings_type::PauseOnFocusLoss); + SubscribeTooltip(ui->pauseOnFocusLoss, tooltips.settings.pause_on_focus_loss); + m_emu_settings->EnhanceCheckBox(ui->startGameFullscreen, emu_settings_type::StartGameFullscreen); SubscribeTooltip(ui->startGameFullscreen, tooltips.settings.start_game_fullscreen); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 91edc0f5b5..e96cac9ac6 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2878,6 +2878,13 @@ + + + + Pause emulation on RPCS3 focus loss + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 8a165e3789..638fc6a289 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -122,6 +122,7 @@ public: // emulator const QString exit_on_stop = tr("Automatically close RPCS3 when closing a game, or when a game closes itself."); + const QString pause_on_focus_loss = tr("Automatically pause emulation when RPCS3 loses its focus or the application is inactive in order to save power and reduce CPU usage.\nDo note that emulation pausing in general is not perfect and may not be compatible with all games.\nAlthough it currently also pauses gameplay, it is not recommended to rely on it as this behavior may be changed in the future and it is not the purpose of this setting."); const QString start_game_fullscreen = tr("Automatically puts the game window in fullscreen.\nDouble click on the game window or press Alt+Enter to toggle fullscreen and windowed mode."); const QString prevent_display_sleep = tr("Prevent the display from sleeping while a game is running.\nThis requires the org.freedesktop.ScreenSaver D-Bus service on Linux.\nThis option will be disabled if the current platform does not support display sleep control."); const QString game_window_title_format = tr("Configure the game window title.\nChanging this and/or adding the framerate may cause buggy or outdated recording software to not notice RPCS3.");