From d9412f9fcdda6db00cc65174c8ea92fd930dfb88 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 20 Jun 2021 15:41:01 +1000 Subject: [PATCH] FrontendCommon: Add option to inhibit screensaver --- src/core/host_display.h | 1 + src/core/host_interface.cpp | 1 + src/core/settings.cpp | 2 + src/core/settings.h | 1 + src/duckstation-qt/generalsettingswidget.cpp | 7 +- src/duckstation-qt/generalsettingswidget.ui | 17 +- src/frontend-common/CMakeLists.txt | 7 + src/frontend-common/common_host_interface.cpp | 19 +++ src/frontend-common/frontend-common.vcxproj | 2 + .../frontend-common.vcxproj.filters | 2 + src/frontend-common/fullscreen_ui.cpp | 4 + src/frontend-common/inhibit_screensaver.cpp | 154 ++++++++++++++++++ src/frontend-common/inhibit_screensaver.h | 7 + 13 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 src/frontend-common/inhibit_screensaver.cpp create mode 100644 src/frontend-common/inhibit_screensaver.h diff --git a/src/core/host_display.h b/src/core/host_display.h index 445c64494..e7c895b75 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -61,6 +61,7 @@ public: virtual ~HostDisplay(); + ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; } diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 45019e139..12e692b24 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -487,6 +487,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetFloatValue("Main", "TurboSpeed", 0.0f); si.SetBoolValue("Main", "SyncToHostRefreshRate", false); si.SetBoolValue("Main", "IncreaseTimerResolution", true); + si.SetBoolValue("Main", "InhibitScreensaver", true); si.SetBoolValue("Main", "StartPaused", false); si.SetBoolValue("Main", "StartFullscreen", false); si.SetBoolValue("Main", "PauseOnFocusLoss", false); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 90c9620fd..1f8d3fd12 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -147,6 +147,7 @@ void Settings::Load(SettingsInterface& si) turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f); sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false); increase_timer_resolution = si.GetBoolValue("Main", "IncreaseTimerResolution", true); + inhibit_screensaver = si.GetBoolValue("Main", "InhibitScreensaver", true); start_paused = si.GetBoolValue("Main", "StartPaused", false); start_fullscreen = si.GetBoolValue("Main", "StartFullscreen", false); pause_on_focus_loss = si.GetBoolValue("Main", "PauseOnFocusLoss", false); @@ -341,6 +342,7 @@ void Settings::Save(SettingsInterface& si) const si.SetFloatValue("Main", "TurboSpeed", turbo_speed); si.SetBoolValue("Main", "SyncToHostRefreshRate", sync_to_host_refresh_rate); si.SetBoolValue("Main", "IncreaseTimerResolution", increase_timer_resolution); + si.SetBoolValue("Main", "InhibitScreensaver", inhibit_screensaver); si.SetBoolValue("Main", "StartPaused", start_paused); si.SetBoolValue("Main", "StartFullscreen", start_fullscreen); si.SetBoolValue("Main", "PauseOnFocusLoss", pause_on_focus_loss); diff --git a/src/core/settings.h b/src/core/settings.h index 2ecbe51fd..26c8d9d75 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -86,6 +86,7 @@ struct Settings float turbo_speed = 0.0f; bool sync_to_host_refresh_rate = true; bool increase_timer_resolution = true; + bool inhibit_screensaver = false; bool start_paused = false; bool start_fullscreen = false; bool pause_on_focus_loss = false; diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/generalsettingswidget.cpp index bc30b0adc..3a6917afa 100644 --- a/src/duckstation-qt/generalsettingswidget.cpp +++ b/src/duckstation-qt/generalsettingswidget.cpp @@ -25,6 +25,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.hideCursorInFullscreen, "Main", "HideCursorInFullscreen", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.inhibitScreensaver, "Main", "InhibitScreensaver", + true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.renderToMain, "Main", "RenderToMainWindow", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "Main", "SaveStateOnExit", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true); @@ -51,6 +53,9 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW tr("Automatically switches to fullscreen mode when a game is started.")); dialog->registerWidgetHelp(m_ui.hideCursorInFullscreen, tr("Hide Cursor In Fullscreen"), tr("Checked"), tr("Hides the mouse pointer/cursor when the emulator is in fullscreen mode.")); + dialog->registerWidgetHelp( + m_ui.inhibitScreensaver, tr("Inhibit Screensaver"), tr("Checked"), + tr("Prevents the screen saver from activating and the host from sleeping while emulation is running.")); dialog->registerWidgetHelp( m_ui.renderToMain, tr("Render To Main Window"), tr("Checked"), tr("Renders the display of the simulated console to the main window of the application, over " @@ -81,7 +86,7 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW tr("Enables the fullscreen UI mode, suitable for controller operation which is used in the NoGUI frontend.")); // Since this one is compile-time selected, we don't put it in the .ui file. - int current_col = 1; + int current_col = 0; int current_row = m_ui.formLayout_4->rowCount() - current_col; #ifdef WITH_DISCORD_PRESENCE { diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/generalsettingswidget.ui index 7e9eadb24..678ab6bbf 100644 --- a/src/duckstation-qt/generalsettingswidget.ui +++ b/src/duckstation-qt/generalsettingswidget.ui @@ -39,7 +39,14 @@ - + + + + Inhibit Screensaver + + + + Render To Main Window @@ -74,21 +81,21 @@ - + Apply Per-Game Settings - + Automatically Load Cheats - + Load Devices From Save States @@ -102,7 +109,7 @@ - + Enable Fullscreen UI diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 2037aa28e..d24f09953 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -17,6 +17,8 @@ add_library(frontend-common game_settings.h icon.cpp icon.h + inhibit_screensaver.cpp + inhibit_screensaver.h ini_settings_interface.cpp ini_settings_interface.h input_overlay_ui.cpp @@ -89,6 +91,11 @@ if(USE_EVDEV) ) endif() +if(USE_X11) + target_compile_definitions(frontend-common PRIVATE "-DUSE_X11=1") + target_include_directories(frontend-common PRIVATE "${X11_INCLUDE_DIR}") +endif() + if(ENABLE_DISCORD_PRESENCE) target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1) target_link_libraries(frontend-common PRIVATE discord-rpc) diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 2ab47ccac..14ed65de1 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -29,6 +29,7 @@ #include "imgui.h" #include "imgui_fullscreen.h" #include "imgui_styles.h" +#include "inhibit_screensaver.h" #include "ini_settings_interface.h" #include "input_overlay_ui.h" #include "save_state_selector_ui.h" @@ -975,6 +976,9 @@ void CommonHostInterface::OnSystemCreated() if (g_settings.display_post_processing && !m_display->SetPostProcessingChain(g_settings.display_post_process_chain)) AddOSDMessage(TranslateStdString("OSDMessage", "Failed to load post processing shader chain."), 20.0f); + + if (g_settings.inhibit_screensaver) + FrontendCommon::SuspendScreensaver(m_display->GetWindowInfo()); } void CommonHostInterface::OnSystemPaused(bool paused) @@ -990,6 +994,12 @@ void CommonHostInterface::OnSystemPaused(bool paused) SetFullscreen(false); StopControllerRumble(); + FrontendCommon::ResumeScreensaver(); + } + else + { + if (g_settings.inhibit_screensaver) + FrontendCommon::SuspendScreensaver(m_display->GetWindowInfo()); } UpdateSpeedLimiterState(); @@ -1007,6 +1017,7 @@ void CommonHostInterface::OnSystemDestroyed() FullscreenUI::SystemDestroyed(); StopControllerRumble(); + FrontendCommon::ResumeScreensaver(); } void CommonHostInterface::OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, @@ -3021,6 +3032,14 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings) m_display->SetPostProcessingChain({}); } } + + if (g_settings.inhibit_screensaver != old_settings.inhibit_screensaver) + { + if (g_settings.inhibit_screensaver) + FrontendCommon::SuspendScreensaver(m_display->GetWindowInfo()); + else + FrontendCommon::ResumeScreensaver(); + } } if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter || diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj index 9523a558f..99ea93931 100644 --- a/src/frontend-common/frontend-common.vcxproj +++ b/src/frontend-common/frontend-common.vcxproj @@ -101,6 +101,7 @@ + @@ -134,6 +135,7 @@ + diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters index 4c52e58fb..59afb17c7 100644 --- a/src/frontend-common/frontend-common.vcxproj.filters +++ b/src/frontend-common/frontend-common.vcxproj.filters @@ -32,6 +32,7 @@ + @@ -65,6 +66,7 @@ + diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 1a807969c..9480b67ed 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -1325,6 +1325,10 @@ void DrawSettingsWindow() settings_changed |= ToggleButtonForNonSetting( "Hide Cursor In Fullscreen", "Hides the mouse pointer/cursor when the emulator is in fullscreen mode.", "Main", "HideCursorInFullscreen", true); + settings_changed |= ToggleButton( + "Inhibit Screensaver", + "Prevents the screen saver from activating and the host from sleeping while emulation is running.", + &s_settings_copy.inhibit_screensaver); settings_changed |= ToggleButton("Load Devices From Save States", "When enabled, memory cards and controllers will be overwritten when save states are loaded.", diff --git a/src/frontend-common/inhibit_screensaver.cpp b/src/frontend-common/inhibit_screensaver.cpp new file mode 100644 index 000000000..e0650d53d --- /dev/null +++ b/src/frontend-common/inhibit_screensaver.cpp @@ -0,0 +1,154 @@ +#include "inhibit_screensaver.h" +#include "common/log.h" +#include "common/string.h" +#include +Log_SetChannel(FrontendCommon); + +#ifdef _WIN32 +#include "common/windows_headers.h" + +static bool SetScreensaverInhibitWin32(bool inhibit, const WindowInfo& wi) +{ + if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL) + { + Log_ErrorPrintf("SetThreadExecutionState() failed: %d", GetLastError()); + return false; + } + + return true; +} + +#endif // _WIN32 + +#ifdef __APPLE__ +#include + +static IOPMAssertionID s_prevent_idle_assertion = kIOPMNullAssertionID; + +static bool SetScreensaverInhibitMacOS(bool inhibit, const WindowInfo& wi) +{ + if (inhibit) + { + const CFStringRef reason = CFSTR("System Running"); + if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason, + &s_prevent_idle_assertion) != kIOReturnSuccess) + { + Log_ErrorPrintf("IOPMAssertionCreateWithName() failed"); + return false; + } + + return true; + } + else + { + IOPMAssertionRelease(s_prevent_idle_assertion); + s_prevent_idle_assertion = kIOPMNullAssertionID; + return true; + } +} + +#endif // __APPLE__ + +#ifdef USE_X11 +#include +#include +#include +#include + +static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi) +{ + TinyString command; + command.AppendString("xdg-screensaver"); + + TinyString operation; + operation.AppendString(inhibit ? "suspend" : "resume"); + + TinyString id; + id.Format("0x%" PRIx64, static_cast(reinterpret_cast(wi.window_handle))); + + char* argv[4] = {command.GetWriteableCharArray(), operation.GetWriteableCharArray(), id.GetWriteableCharArray(), + nullptr}; + pid_t pid; + int res = posix_spawnp(&pid, "xdg-screensaver", nullptr, nullptr, argv, environ); + if (res != 0) + { + Log_ErrorPrintf("posix_spawnp() failed: %d", res); + return false; + } + + int status = 0; + while (waitpid(pid, &status, 0) == -1) + ; + + if (WEXITSTATUS(status) == 0) + return true; + + Log_ErrorPrintf("xdg-screensaver returned error %d", WEXITSTATUS(status)); + return false; +} + +#endif // USE_X11 + +static bool SetScreensaverInhibit(bool inhibit, const WindowInfo& wi) +{ + switch (wi.type) + { +#ifdef _WIN32 + case WindowInfo::Type::Win32: + return SetScreensaverInhibitWin32(inhibit, wi); +#endif + +#ifdef __APPLE__ + case WindowInfo::Type::MacOS: + return SetScreensaverInhibitMacOS(inhibit, wi); +#endif + +#ifdef USE_X11 + case WindowInfo::Type::X11: + return SetScreensaverInhibitX11(inhibit, wi); +#endif + + default: + Log_ErrorPrintf("Unknown type: %u", static_cast(wi.type)); + return false; + } +} + +namespace FrontendCommon { + +static bool s_screensaver_suspended; +static WindowInfo s_screensaver_suspender; + +void SuspendScreensaver(const WindowInfo& wi) +{ + if (s_screensaver_suspended && + (s_screensaver_suspender.type != wi.type || s_screensaver_suspender.window_handle != wi.window_handle)) + ResumeScreensaver(); + + if (!SetScreensaverInhibit(true, wi)) + { + Log_ErrorPrintf("Failed to suspend screensaver."); + return; + } + + Log_InfoPrintf("Screensaver suspended by 0x%" PRIx64 ".", + static_cast(reinterpret_cast(wi.window_handle))); + s_screensaver_suspended = true; + s_screensaver_suspender = wi; +} + +void ResumeScreensaver() +{ + if (!s_screensaver_suspended) + return; + + if (!SetScreensaverInhibit(false, s_screensaver_suspender)) + Log_ErrorPrint("Failed to resume screensaver."); + else + Log_InfoPrint("Screensaver resumed."); + + s_screensaver_suspended = false; + s_screensaver_suspender = {}; +} + +} // namespace FrontendCommon diff --git a/src/frontend-common/inhibit_screensaver.h b/src/frontend-common/inhibit_screensaver.h new file mode 100644 index 000000000..1fef23887 --- /dev/null +++ b/src/frontend-common/inhibit_screensaver.h @@ -0,0 +1,7 @@ +#include "common/window_info.h" + +namespace FrontendCommon +{ +void SuspendScreensaver(const WindowInfo& wi); +void ResumeScreensaver(); +}