platform_misc_unix.cpp (5625B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors. 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "input_manager.h" 5 #include "platform_misc.h" 6 7 #include "common/error.h" 8 #include "common/log.h" 9 #include "common/path.h" 10 #include "common/scoped_guard.h" 11 #include "common/small_string.h" 12 13 #include <cinttypes> 14 #include <dbus/dbus.h> 15 #include <signal.h> 16 #include <spawn.h> 17 #include <unistd.h> 18 19 Log_SetChannel(PlatformMisc); 20 21 bool PlatformMisc::InitializeSocketSupport(Error* error) 22 { 23 // Ignore SIGPIPE, we handle errors ourselves. 24 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) 25 { 26 Error::SetErrno(error, "signal(SIGPIPE, SIG_IGN) failed: ", errno); 27 return false; 28 } 29 30 return true; 31 } 32 33 static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason) 34 { 35 static dbus_uint32_t s_cookie; 36 const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit"; 37 DBusError error; 38 DBusConnection* connection = nullptr; 39 static DBusConnection* s_comparison_connection; 40 DBusMessage* message = nullptr; 41 DBusMessage* response = nullptr; 42 DBusMessageIter message_itr; 43 44 ScopedGuard cleanup = [&]() { 45 if (dbus_error_is_set(&error)) 46 { 47 ERROR_LOG("SetScreensaverInhibitDBus error: {}", error.message); 48 dbus_error_free(&error); 49 } 50 if (message) 51 dbus_message_unref(message); 52 if (response) 53 dbus_message_unref(response); 54 }; 55 56 dbus_error_init(&error); 57 // Calling dbus_bus_get() after the first time returns a pointer to the existing connection. 58 connection = dbus_bus_get(DBUS_BUS_SESSION, &error); 59 if (!connection || (dbus_error_is_set(&error))) 60 return false; 61 if (s_comparison_connection != connection) 62 { 63 dbus_connection_set_exit_on_disconnect(connection, false); 64 s_cookie = 0; 65 s_comparison_connection = connection; 66 } 67 message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", 68 "org.freedesktop.ScreenSaver", bus_method); 69 if (!message) 70 return false; 71 // Initialize an append iterator for the message, gets freed with the message. 72 dbus_message_iter_init_append(message, &message_itr); 73 if (inhibit_requested) 74 { 75 // Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie. 76 if (s_cookie) 77 return false; 78 // Append process/window name. 79 if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name)) 80 return false; 81 // Append reason for inhibiting the screensaver. 82 if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason)) 83 return false; 84 } 85 else 86 { 87 // Only Append the cookie. 88 if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie)) 89 return false; 90 } 91 // Send message and get response. 92 response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); 93 if (!response || dbus_error_is_set(&error)) 94 return false; 95 s_cookie = 0; 96 if (inhibit_requested) 97 { 98 // Get the cookie from the response message. 99 if (!dbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) || 100 dbus_error_is_set(&error)) 101 return false; 102 } 103 return true; 104 } 105 106 static bool SetScreensaverInhibit(bool inhibit) 107 { 108 return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running."); 109 } 110 111 static bool s_screensaver_suspended; 112 113 void PlatformMisc::SuspendScreensaver() 114 { 115 if (s_screensaver_suspended) 116 return; 117 118 if (!SetScreensaverInhibit(true)) 119 { 120 ERROR_LOG("Failed to suspend screensaver."); 121 return; 122 } 123 124 s_screensaver_suspended = true; 125 } 126 127 void PlatformMisc::ResumeScreensaver() 128 { 129 if (!s_screensaver_suspended) 130 return; 131 132 if (!SetScreensaverInhibit(false)) 133 ERROR_LOG("Failed to resume screensaver."); 134 135 s_screensaver_suspended = false; 136 } 137 138 size_t PlatformMisc::GetRuntimePageSize() 139 { 140 int res = sysconf(_SC_PAGESIZE); 141 return (res > 0) ? static_cast<size_t>(res) : 0; 142 } 143 144 bool PlatformMisc::PlaySoundAsync(const char* path) 145 { 146 #ifdef __linux__ 147 // This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer. 148 const char* cmdname = "aplay"; 149 const char* argv[] = {cmdname, path, nullptr}; 150 pid_t pid; 151 152 // Since we set SA_NOCLDWAIT in Qt, we don't need to wait here. 153 int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast<char**>(argv), environ); 154 if (res == 0) 155 return true; 156 157 // Try gst-play-1.0. 158 const char* gst_play_cmdname = "gst-play-1.0"; 159 const char* gst_play_argv[] = {cmdname, path, nullptr}; 160 res = posix_spawnp(&pid, gst_play_cmdname, nullptr, nullptr, const_cast<char**>(gst_play_argv), environ); 161 if (res == 0) 162 return true; 163 164 // gst-launch? Bit messier for sure. 165 TinyString location_str = TinyString::from_format("location={}", path); 166 TinyString parse_str = TinyString::from_format("{}parse", Path::GetExtension(path)); 167 const char* gst_launch_cmdname = "gst-launch-1.0"; 168 const char* gst_launch_argv[] = {gst_launch_cmdname, "filesrc", location_str.c_str(), "!", 169 parse_str.c_str(), "!", "alsasink", nullptr}; 170 res = posix_spawnp(&pid, gst_launch_cmdname, nullptr, nullptr, const_cast<char**>(gst_launch_argv), environ); 171 if (res == 0) 172 return true; 173 174 ERROR_LOG("Failed to play sound effect {}. Make sure you have aplay, gst-play-1.0, or gst-launch-1.0 available.", 175 path); 176 return false; 177 #else 178 return false; 179 #endif 180 }