diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index d3196be44..a2eab153c 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -1809,6 +1809,14 @@ void QtHost::HookSignals()
{
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
+
+#ifdef __linux__
+ // Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously.
+ struct sigaction sa_chld = {};
+ sigemptyset(&sa_chld.sa_mask);
+ sa_chld.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
+ sigaction(SIGCHLD, &sa_chld, nullptr);
+#endif
}
void QtHost::InitializeEarlyConsole()
diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt
index 7476cfc34..4e87affdf 100644
--- a/src/frontend-common/CMakeLists.txt
+++ b/src/frontend-common/CMakeLists.txt
@@ -10,8 +10,6 @@ add_library(frontend-common
host_settings.cpp
icon.cpp
icon.h
- inhibit_screensaver.cpp
- inhibit_screensaver.h
input_manager.cpp
input_manager.h
input_source.cpp
@@ -22,6 +20,7 @@ add_library(frontend-common
imgui_manager.h
imgui_overlays.cpp
imgui_overlays.h
+ platform_misc.h
postprocessing_chain.cpp
postprocessing_chain.h
postprocessing_shader.cpp
@@ -81,9 +80,21 @@ if(ENABLE_VULKAN)
)
endif()
-if(APPLE)
+if(WIN32)
+ target_sources(frontend-common PRIVATE
+ platform_misc_win32.cpp
+ )
+ target_link_libraries(frontend-common PRIVATE winmm.lib)
+elseif(APPLE)
find_library(IOK_LIBRARY IOKit REQUIRED)
target_link_libraries(frontend-common PRIVATE "${IOK_LIBRARY}")
+ target_sources(frontend-common PRIVATE
+ platform_misc_mac.mm
+ )
+elseif(NOT ANDROID)
+ target_sources(frontend-common PRIVATE
+ platform_misc_unix.cpp
+ )
endif()
if(SDL2_FOUND)
diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp
index fbad4a136..8541ba393 100644
--- a/src/frontend-common/common_host.cpp
+++ b/src/frontend-common/common_host.cpp
@@ -32,7 +32,7 @@
#include "imgui_fullscreen.h"
#include "imgui_manager.h"
#include "imgui_overlays.h"
-#include "inhibit_screensaver.h"
+#include "platform_misc.h"
#include "input_manager.h"
#include "scmversion/scmversion.h"
#include "util/audio_stream.h"
diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj
index b54c008f9..8fa7f260b 100644
--- a/src/frontend-common/frontend-common.vcxproj
+++ b/src/frontend-common/frontend-common.vcxproj
@@ -23,12 +23,12 @@
-
true
+
@@ -61,7 +61,7 @@
-
+
@@ -87,4 +87,4 @@
-
+
\ No newline at end of file
diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters
index b7d37a9ff..c01c27fab 100644
--- a/src/frontend-common/frontend-common.vcxproj.filters
+++ b/src/frontend-common/frontend-common.vcxproj.filters
@@ -15,7 +15,6 @@
-
@@ -30,6 +29,7 @@
+
@@ -46,7 +46,7 @@
-
+
diff --git a/src/frontend-common/inhibit_screensaver.h b/src/frontend-common/inhibit_screensaver.h
deleted file mode 100644
index 1fef23887..000000000
--- a/src/frontend-common/inhibit_screensaver.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "common/window_info.h"
-
-namespace FrontendCommon
-{
-void SuspendScreensaver(const WindowInfo& wi);
-void ResumeScreensaver();
-}
diff --git a/src/frontend-common/platform_misc.h b/src/frontend-common/platform_misc.h
new file mode 100644
index 000000000..2c1a6ee58
--- /dev/null
+++ b/src/frontend-common/platform_misc.h
@@ -0,0 +1,10 @@
+#include "common/window_info.h"
+
+namespace FrontendCommon {
+void SuspendScreensaver(const WindowInfo& wi);
+void ResumeScreensaver();
+
+/// Abstracts platform-specific code for asynchronously playing a sound.
+/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
+bool PlaySoundAsync(const char* path);
+} // namespace FrontendCommon
diff --git a/src/frontend-common/platform_misc_mac.mm b/src/frontend-common/platform_misc_mac.mm
new file mode 100644
index 000000000..2cb34725e
--- /dev/null
+++ b/src/frontend-common/platform_misc_mac.mm
@@ -0,0 +1,77 @@
+#include "platform_misc.h"
+#include "common/log.h"
+#include "common/string.h"
+#include
+#include
+Log_SetChannel(FrontendCommon);
+
+#import
+
+static IOPMAssertionID s_prevent_idle_assertion = kIOPMNullAssertionID;
+
+static bool SetScreensaverInhibitMacOS(bool inhibit)
+{
+ 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;
+ }
+}
+
+static bool s_screensaver_suspended;
+static WindowInfo s_screensaver_suspender;
+
+void FrontendCommon::SuspendScreensaver(const WindowInfo& wi)
+{
+ if (s_screensaver_suspended &&
+ (s_screensaver_suspender.type != wi.type || s_screensaver_suspender.window_handle != wi.window_handle))
+ ResumeScreensaver();
+
+ if (!SetScreensaverInhibitMacOS(true))
+ {
+ 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 FrontendCommon::ResumeScreensaver()
+{
+ if (!s_screensaver_suspended)
+ return;
+
+ if (!SetScreensaverInhibitMacOS(false))
+ Log_ErrorPrint("Failed to resume screensaver.");
+ else
+ Log_InfoPrint("Screensaver resumed.");
+
+ s_screensaver_suspended = false;
+ s_screensaver_suspender = {};
+}
+
+bool FrontendCommon::PlaySoundAsync(const char* path)
+{
+ NSString* nspath = [[NSString alloc] initWithUTF8String:path];
+ NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES];
+ const bool result = [sound play];
+ [sound release];
+ [nspath release];
+ return result;
+}
diff --git a/src/frontend-common/inhibit_screensaver.cpp b/src/frontend-common/platform_misc_unix.cpp
similarity index 55%
rename from src/frontend-common/inhibit_screensaver.cpp
rename to src/frontend-common/platform_misc_unix.cpp
index 9a0c07b15..1f7ae9ecb 100644
--- a/src/frontend-common/inhibit_screensaver.cpp
+++ b/src/frontend-common/platform_misc_unix.cpp
@@ -1,54 +1,9 @@
-#include "inhibit_screensaver.h"
#include "common/log.h"
#include "common/string.h"
+#include "platform_misc.h"
#include
Log_SetChannel(FrontendCommon);
-#if defined(_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
@@ -76,15 +31,7 @@ static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi)
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;
+ return true;
}
#endif // USE_X11
@@ -93,16 +40,6 @@ static bool SetScreensaverInhibit(bool inhibit, const WindowInfo& wi)
{
switch (wi.type)
{
-#if defined(_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);
@@ -114,12 +51,10 @@ static bool SetScreensaverInhibit(bool inhibit, const WindowInfo& wi)
}
}
-namespace FrontendCommon {
-
static bool s_screensaver_suspended;
static WindowInfo s_screensaver_suspender;
-void SuspendScreensaver(const WindowInfo& wi)
+void FrontendCommon::SuspendScreensaver(const WindowInfo& wi)
{
if (s_screensaver_suspended &&
(s_screensaver_suspender.type != wi.type || s_screensaver_suspender.window_handle != wi.window_handle))
@@ -137,7 +72,7 @@ void SuspendScreensaver(const WindowInfo& wi)
s_screensaver_suspender = wi;
}
-void ResumeScreensaver()
+void FrontendCommon::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
@@ -151,4 +86,18 @@ void ResumeScreensaver()
s_screensaver_suspender = {};
}
-} // namespace FrontendCommon
+bool FrontendCommon::PlaySoundAsync(const char* path)
+{
+#ifdef __linux__
+ // This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer.
+ const char* cmdname = "aplay";
+ const char* argv[] = {cmdname, path, nullptr};
+ pid_t pid;
+
+ // Since we set SA_NOCLDWAIT in Qt, we don't need to wait here.
+ int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast(argv), environ);
+ return (res == 0);
+#else
+ return false;
+#endif
+}
diff --git a/src/frontend-common/platform_misc_win32.cpp b/src/frontend-common/platform_misc_win32.cpp
new file mode 100644
index 000000000..e3179b326
--- /dev/null
+++ b/src/frontend-common/platform_misc_win32.cpp
@@ -0,0 +1,61 @@
+#include "common/log.h"
+#include "common/string.h"
+#include "common/string_util.h"
+#include "platform_misc.h"
+#include
+Log_SetChannel(FrontendCommon);
+
+#include "common/windows_headers.h"
+#include
+
+static bool SetScreensaverInhibitWin32(bool inhibit)
+{
+ if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL)
+ {
+ Log_ErrorPrintf("SetThreadExecutionState() failed: %d", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+static bool s_screensaver_suspended;
+static WindowInfo s_screensaver_suspender;
+
+void FrontendCommon::SuspendScreensaver(const WindowInfo& wi)
+{
+ if (s_screensaver_suspended &&
+ (s_screensaver_suspender.type != wi.type || s_screensaver_suspender.window_handle != wi.window_handle))
+ ResumeScreensaver();
+
+ if (!SetScreensaverInhibitWin32(true))
+ {
+ 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 FrontendCommon::ResumeScreensaver()
+{
+ if (!s_screensaver_suspended)
+ return;
+
+ if (!SetScreensaverInhibitWin32(false))
+ Log_ErrorPrint("Failed to resume screensaver.");
+ else
+ Log_InfoPrint("Screensaver resumed.");
+
+ s_screensaver_suspended = false;
+ s_screensaver_suspender = {};
+}
+
+bool FrontendCommon::PlaySoundAsync(const char* path)
+{
+ const std::wstring wpath(StringUtil::UTF8StringToWideString(path));
+ return PlaySoundW(wpath.c_str(), NULL, SND_ASYNC | SND_NODEFAULT);
+}