Host: Move screensaver inhibit to host

Removes direct dependency on DBus, uses Qt DBus instead.
This commit is contained in:
Stenzek
2026-01-17 16:16:32 +10:00
parent e2266cd617
commit ffe7ca9f0a
14 changed files with 221 additions and 273 deletions

View File

@@ -76,7 +76,11 @@ if(BUILD_QT_FRONTEND)
# All our builds include Qt, so this is not a problem.
set(QT_NO_PRIVATE_MODULE_WARNING ON)
find_package(Qt6 6.10.1 COMPONENTS Core Gui GuiPrivate Widgets LinguistTools REQUIRED)
if(LINUX)
find_package(Qt6 6.10.1 COMPONENTS Core Gui GuiPrivate Widgets LinguistTools DBus REQUIRED)
else()
find_package(Qt6 6.10.1 COMPONENTS Core Gui GuiPrivate Widgets LinguistTools REQUIRED)
endif()
# Have to verify it down here, don't want users using unpatched Qt.
if(NOT Qt6_DIR MATCHES "^${CMAKE_PREFIX_PATH}")

View File

@@ -132,4 +132,7 @@ bool CanChangeFullscreenMode(bool new_fullscreen_state);
/// Called when the pause state changes, or fullscreen UI opens.
void OnGPUThreadRunIdleChanged(bool is_active);
/// Changes the screensaver inhibit state.
bool SetScreensaverInhibit(bool inhibit, Error* error);
} // namespace Host

View File

@@ -53,7 +53,6 @@
#include "util/input_manager.h"
#include "util/iso_reader.h"
#include "util/media_capture.h"
#include "util/platform_misc.h"
#include "util/postprocessing.h"
#include "util/sockets.h"
#include "util/state_wrapper.h"
@@ -197,6 +196,7 @@ static void ResetThrottler();
static void Throttle(Timer::Value current_time, Timer::Value sleep_until);
static void AccumulatePreFrameSleepTime(Timer::Value current_time);
static void UpdateDisplayVSync();
static void InhibitScreensaver(bool inhibit);
static bool UpdateGameSettingsLayer();
static void UpdateInputSettingsLayer(std::string input_profile_name, std::unique_lock<std::mutex>& lock);
@@ -1688,7 +1688,7 @@ void System::PauseSystem(bool paused)
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)
PlatformMisc::ResumeScreensaver();
InhibitScreensaver(false);
#ifdef ENABLE_GDB_SERVER
GDBServer::OnSystemPaused();
@@ -1705,7 +1705,7 @@ void System::PauseSystem(bool paused)
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)
PlatformMisc::SuspendScreensaver();
InhibitScreensaver(true);
#ifdef ENABLE_GDB_SERVER
GDBServer::OnSystemResumed();
@@ -1968,7 +1968,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
InputManager::SynchronizeBindingHandlerState();
if (g_settings.inhibit_screensaver)
PlatformMisc::SuspendScreensaver();
InhibitScreensaver(true);
#ifdef ENABLE_GDB_SERVER
if (g_settings.enable_gdb_server)
@@ -2102,7 +2102,7 @@ void System::DestroySystem()
InputManager::UpdateHostMouseMode();
if (g_settings.inhibit_screensaver)
PlatformMisc::ResumeScreensaver();
InhibitScreensaver(false);
FreeMemoryStateStorage(true, true, false);
@@ -3846,6 +3846,22 @@ bool System::ShouldAllowPresentThrottle()
return !valid_vm || IsRunningAtNonStandardSpeed();
}
void System::InhibitScreensaver(bool inhibit)
{
Error error;
if (!Host::SetScreensaverInhibit(inhibit, &error))
{
ERROR_LOG("Set screensaver {} failed: {}", inhibit ? "inhibit" : "allow", error.GetDescription());
if (IsValid())
{
Host::AddIconOSDMessage(OSDMessageType::Error, "ScreensaverInhibit", ICON_EMOJI_WARNING,
inhibit ? TRANSLATE_STR("System", "Failed to inhibit screensaver") :
TRANSLATE_STR("System", "Failed to allow screensaver"),
error.TakeDescription());
}
}
}
bool System::IsFastForwardEnabled()
{
return s_state.fast_forward_enabled;
@@ -4839,12 +4855,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
}
if (g_settings.inhibit_screensaver != old_settings.inhibit_screensaver)
{
if (g_settings.inhibit_screensaver)
PlatformMisc::SuspendScreensaver();
else
PlatformMisc::ResumeScreensaver();
}
InhibitScreensaver(g_settings.inhibit_screensaver);
#ifdef ENABLE_GDB_SERVER
if (g_settings.enable_gdb_server != old_settings.enable_gdb_server ||

View File

@@ -1082,6 +1082,30 @@ void Host::OnGPUThreadRunIdleChanged(bool is_active)
{
}
bool Host::SetScreensaverInhibit(bool inhibit, Error* error)
{
if (inhibit)
{
if (SDL_DisableScreenSaver())
{
Error::SetStringFmt(error, "SDL_DisableScreenSaver() failed: {}", SDL_GetError());
return false;
}
return true;
}
else
{
if (SDL_EnableScreenSaver())
{
Error::SetStringFmt(error, "SDL_EnableScreenSaver() failed: {}", SDL_GetError());
return false;
}
return true;
}
}
void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
{
}

View File

@@ -182,6 +182,10 @@ target_precompile_headers(duckstation-qt PRIVATE "pch.h")
target_include_directories(duckstation-qt PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(duckstation-qt PRIVATE core common imgui minizip scmversion Qt6::Core Qt6::Gui Qt6::GuiPrivate Qt6::Widgets)
if(LINUX)
target_link_libraries(duckstation-qt PRIVATE Qt6::DBus)
endif()
# Our Qt builds may have exceptions on, so force them off.
target_compile_definitions(duckstation-qt PRIVATE QT_NO_EXCEPTIONS QT_NO_SIGNALS_SLOTS_KEYWORDS)

View File

@@ -1,12 +1,14 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "qtwindowinfo.h"
#include "qtutils.h"
#include "core/core.h"
#include "core/gpu_thread.h"
#include "util/gpu_device.h"
#include "util/platform_misc.h"
#include "common/error.h"
#include "common/log.h"
@@ -16,15 +18,50 @@
#include <QtWidgets/QWidget>
#if defined(_WIN32)
#include "common/windows_headers.h"
#include <dwmapi.h>
#elif defined(__APPLE__)
#include "common/cocoa_tools.h"
#else
#include <CoreFoundation/CFString.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#elif defined(__linux__)
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusReply>
#include <qpa/qplatformnativeinterface.h>
#else
#include <qpa/qplatformnativeinterface.h>
#endif
LOG_CHANNEL(Host);
namespace {
struct WindowInfoLocals
{
bool screensaver_inhibited;
#if defined(__APPLE__)
IOPMAssertionID screensaver_inhibit_assertion;
#elif defined(__linux__)
u32 screensaver_inhibit_cookie;
std::optional<QDBusInterface> screensaver_inhibit_interface;
#endif
};
} // namespace
static WindowInfoLocals s_window_info_locals;
WindowInfoType QtUtils::GetWindowInfoType()
{
// Windows and Apple are easy here since there's no display connection.
@@ -159,3 +196,117 @@ void QtUtils::UpdateSurfaceSize(QWidget* widget, WindowInfo* wi)
wi->surface_height = static_cast<u16>(scaled_size.height());
wi->surface_scale = static_cast<float>(device_pixel_ratio);
}
#ifdef __linux__
static void FormatQDBusReplyError(Error* error, const char* prefix, const QDBusError& qerror)
{
Error::SetStringFmt(error, "{}{}: {}", prefix, qerror.name().toStdString(), qerror.message().toStdString());
}
#endif
bool Host::SetScreensaverInhibit(bool inhibit, Error* error)
{
if (s_window_info_locals.screensaver_inhibited == inhibit)
return true;
#if defined(_WIN32)
if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL)
{
Error::SetWin32(error, "SetThreadExecutionState() failed: ", GetLastError());
return false;
}
s_window_info_locals.screensaver_inhibited = inhibit;
return true;
#elif defined(__APPLE__)
if (inhibit)
{
const CFStringRef reason = CFSTR("DuckStation VM is running.");
const IOReturn ret =
IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason,
&s_window_info_locals.screensaver_inhibit_assertion);
if (ret != kIOReturnSuccess)
{
Error::SetStringFmt(error, "IOPMAssertionCreateWithName() failed: {}", static_cast<s32>(ret));
return false;
}
s_window_info_locals.screensaver_inhibited = true;
return true;
}
else
{
const IOReturn ret = IOPMAssertionRelease(s_window_info_locals.screensaver_inhibit_assertion);
if (ret != kIOReturnSuccess)
{
Error::SetStringFmt(error, "IOPMAssertionRelease() failed: {}", static_cast<s32>(ret));
return false;
}
s_window_info_locals.screensaver_inhibit_assertion = kIOPMNullAssertionID;
s_window_info_locals.screensaver_inhibited = false;
return true;
}
#elif defined(__linux__)
if (!s_window_info_locals.screensaver_inhibit_interface.has_value())
{
const QDBusConnection connection = QDBusConnection::sessionBus();
if (!connection.isConnected())
{
Error::SetStringView(error, "Failed to connect to the D-Bus session bus");
return false;
}
s_window_info_locals.screensaver_inhibit_interface.emplace(
"org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", connection);
if (!s_window_info_locals.screensaver_inhibit_interface->isValid())
{
s_window_info_locals.screensaver_inhibit_interface.reset();
Error::SetStringView(error, "org.freedesktop.ScreenSaver interface is invalid");
return false;
}
}
if (inhibit)
{
const QDBusReply<quint32> msg = s_window_info_locals.screensaver_inhibit_interface->call(
"Inhibit", QStringLiteral("DuckStation"), QStringLiteral("DuckStation VM is running."));
if (!msg.isValid())
{
FormatQDBusReplyError(error, "Inhibit message call failed: ", msg.error());
return false;
}
s_window_info_locals.screensaver_inhibit_cookie = msg.value();
s_window_info_locals.screensaver_inhibited = true;
return true;
}
else
{
const QDBusReply<void> msg = s_window_info_locals.screensaver_inhibit_interface->call(
"UnInhibit", s_window_info_locals.screensaver_inhibit_cookie);
if (!msg.isValid())
{
FormatQDBusReplyError(error, "UnInhibit message call failed: ", msg.error());
return false;
}
s_window_info_locals.screensaver_inhibit_cookie = 0;
s_window_info_locals.screensaver_inhibited = false;
return true;
}
#else
Error::SetStringView(error, "Not implemented.");
return false;
#endif
}

View File

@@ -23,4 +23,7 @@ std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, RenderAPI rend
/// Also sets the "real" DPR scale for the widget, ignoring any operating-system level downsampling.
void UpdateSurfaceSize(QWidget* widget, WindowInfo* wi);
/// Changes the screensaver inhibit state.
bool SetScreensaverInhibit(bool inhibit, Error* error);
} // namespace QtUtils

View File

@@ -323,6 +323,12 @@ void Host::OnGPUThreadRunIdleChanged(bool is_active)
//
}
bool Host::SetScreensaverInhibit(bool inhibit, Error* error)
{
Error::SetStringView(error, "Not implemented");
return false;
}
void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
{
//

View File

@@ -283,8 +283,6 @@ elseif(NOT ANDROID)
target_sources(util PRIVATE
platform_misc_unix.cpp
)
pkg_check_modules(DBUS REQUIRED dbus-1)
target_include_directories(util PRIVATE ${DBUS_INCLUDE_DIRS})
if(LINUX)
target_link_libraries(util PRIVATE UDEV::UDEV)

View File

@@ -11,9 +11,6 @@ namespace PlatformMisc {
bool InitializeSocketSupport(Error* error);
void SuspendScreensaver();
void ResumeScreensaver();
/// Sets the rounded corner state for a window.
/// Currently only supported on Windows.
bool SetWindowRoundedCornerState(void* window_handle, bool enabled, Error* error = nullptr);

View File

@@ -3,7 +3,6 @@
// Normally, system includes come last. But apparently some of our macro names are redefined...
#include <Cocoa/Cocoa.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <QuartzCore/QuartzCore.h>
#include <cinttypes>
#include <optional>
@@ -23,62 +22,11 @@ LOG_CHANNEL(PlatformMisc);
#error ARC should not be enabled.
#endif
static IOPMAssertionID s_prevent_idle_assertion = kIOPMNullAssertionID;
bool PlatformMisc::InitializeSocketSupport(Error* error)
{
return true;
}
static bool SetScreensaverInhibitMacOS(bool inhibit)
{
if (inhibit)
{
const CFStringRef reason = CFSTR("DuckStation System Running");
if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason,
&s_prevent_idle_assertion) != kIOReturnSuccess)
{
ERROR_LOG("IOPMAssertionCreateWithName() failed");
return false;
}
return true;
}
else
{
IOPMAssertionRelease(s_prevent_idle_assertion);
s_prevent_idle_assertion = kIOPMNullAssertionID;
return true;
}
}
static bool s_screensaver_suspended = false;
void PlatformMisc::SuspendScreensaver()
{
if (s_screensaver_suspended)
return;
if (!SetScreensaverInhibitMacOS(true))
{
ERROR_LOG("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void PlatformMisc::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibitMacOS(false))
ERROR_LOG("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool PlatformMisc::SetWindowRoundedCornerState(void* window_handle, bool enabled, Error* error /* = nullptr */)
{
Error::SetStringView(error, "Unsupported on this platform.");

View File

@@ -12,7 +12,6 @@
#include "common/small_string.h"
#include <cinttypes>
#include <dbus/dbus.h>
#include <mutex>
#include <signal.h>
#include <unistd.h>
@@ -31,171 +30,6 @@ bool PlatformMisc::InitializeSocketSupport(Error* error)
return true;
}
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
{
#define DBUS_FUNCS(X) \
X(dbus_error_is_set) \
X(dbus_error_free) \
X(dbus_message_unref) \
X(dbus_error_init) \
X(dbus_bus_get) \
X(dbus_connection_set_exit_on_disconnect) \
X(dbus_message_new_method_call) \
X(dbus_message_iter_init_append) \
X(dbus_message_iter_append_basic) \
X(dbus_connection_send_with_reply_and_block) \
X(dbus_message_get_args)
static std::mutex s_screensaver_inhibit_dbus_mutex;
static DynamicLibrary s_dbus_library;
static bool s_dbus_library_loaded;
static dbus_uint32_t s_cookie;
static DBusConnection* s_comparison_connection;
#define DEFINE_FUNC(F) static decltype(&::F) x##F;
DBUS_FUNCS(DEFINE_FUNC)
#undef DEFINE_FUNC
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
DBusError error;
DBusConnection* connection = nullptr;
DBusMessage* message = nullptr;
DBusMessage* response = nullptr;
DBusMessageIter message_itr;
std::unique_lock lock(s_screensaver_inhibit_dbus_mutex);
if (!s_dbus_library_loaded)
{
Error lerror;
s_dbus_library_loaded = true;
if (!s_dbus_library.Open(DynamicLibrary::GetVersionedFilename("dbus-1", 3).c_str(), &lerror))
{
ERROR_LOG("Failed to open libdbus: {}", lerror.GetDescription());
return false;
}
#define LOAD_FUNC(F) \
if (!s_dbus_library.GetSymbol(#F, &x##F)) \
{ \
ERROR_LOG("Failed to find function {}", #F); \
s_dbus_library.Close(); \
return false; \
}
DBUS_FUNCS(LOAD_FUNC)
#undef LOAD_FUNC
}
if (!s_dbus_library.IsOpen())
return false;
ScopedGuard cleanup = [&]() {
if (xdbus_error_is_set(&error))
{
ERROR_LOG("SetScreensaverInhibitDBus error: {}", error.message);
xdbus_error_free(&error);
}
if (message)
xdbus_message_unref(message);
if (response)
xdbus_message_unref(response);
};
xdbus_error_init(&error);
// Calling dbus_bus_get() after the first time returns a pointer to the existing connection.
connection = xdbus_bus_get(DBUS_BUS_SESSION, &error);
if (!connection || (xdbus_error_is_set(&error)))
return false;
if (s_comparison_connection != connection)
{
xdbus_connection_set_exit_on_disconnect(connection, false);
s_cookie = 0;
s_comparison_connection = connection;
}
message = xdbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver", bus_method);
if (!message)
return false;
// Initialize an append iterator for the message, gets freed with the message.
xdbus_message_iter_init_append(message, &message_itr);
if (inhibit_requested)
{
// Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie.
if (s_cookie)
return false;
// Append process/window name.
if (!xdbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
return false;
// Append reason for inhibiting the screensaver.
if (!xdbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
return false;
}
else
{
// Only Append the cookie.
if (!xdbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
return false;
}
// Send message and get response.
response = xdbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
if (!response || xdbus_error_is_set(&error))
return false;
s_cookie = 0;
if (inhibit_requested)
{
// Get the cookie from the response message.
if (!xdbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) ||
xdbus_error_is_set(&error))
{
return false;
}
}
return true;
#undef DBUS_FUNCS
}
static bool SetScreensaverInhibit(bool inhibit)
{
return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running.");
}
static bool s_screensaver_suspended = false;
void PlatformMisc::SuspendScreensaver()
{
if (s_screensaver_suspended)
return;
if (!SetScreensaverInhibit(true))
{
ERROR_LOG("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void PlatformMisc::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibit(false))
ERROR_LOG("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool PlatformMisc::SetWindowRoundedCornerState(void* window_handle, bool enabled, Error* error /* = nullptr */)
{
Error::SetStringView(error, "Unsupported on this platform.");

View File

@@ -45,42 +45,6 @@ bool PlatformMisc::InitializeSocketSupport(Error* error)
return s_winsock_initialized;
}
static bool SetScreensaverInhibitWin32(bool inhibit)
{
if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL)
{
ERROR_LOG("SetThreadExecutionState() failed: {}", GetLastError());
return false;
}
return true;
}
void PlatformMisc::SuspendScreensaver()
{
if (s_screensaver_suspended)
return;
if (!SetScreensaverInhibitWin32(true))
{
ERROR_LOG("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void PlatformMisc::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibitWin32(false))
ERROR_LOG("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool PlatformMisc::SetWindowRoundedCornerState(void* window_handle, bool enabled, Error* error)
{
const DWM_WINDOW_CORNER_PREFERENCE value = enabled ? DWMWCP_DEFAULT : DWMWCP_DONOTROUND;

View File

@@ -35,6 +35,7 @@ using nfds_t = ULONG;
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>