Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions runtime/include/aurora_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "settings_overlay.h"
#include "runtime_config.h"
#include "platform/host_platform.h"

#include <aurora/aurora.h>
#include <aurora/event.h>
Expand Down Expand Up @@ -59,16 +60,27 @@ inline void Flush(bool force = false) {
// fiber re-enters runtime teardown and can fault while the fiber machinery is
// still active. A window close is an intentional successful exit, so end the
// process directly and do not run the crash/atexit paths.
[[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
settings_overlay::ReleaseControllers();
WindowPlacementPersistence::Flush(true);
[[noreturn]] inline void ExitAuroraProcess() noexcept {
#if defined(_WIN32)
::ExitProcess(0);
#else
std::_Exit(EXIT_SUCCESS);
#endif
}

[[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
settings_overlay::ReleaseControllers();
WindowPlacementPersistence::Flush(true);
ExitAuroraProcess();
}

// Returns only if the new instance could not be started; the LED is left for the new instance to set.
inline void RestartForAuroraWindowClose() noexcept {
WindowPlacementPersistence::Flush(true);
settings_overlay::ReleaseControllers(false);
if (RuntimePlatform::RelaunchSelf()) ExitAuroraProcess();
}

// Update cached Aurora window/framebuffer dimensions based on pending events.
inline void ProcessAuroraEvents(const AuroraEvent* events) {
if (!events) {
Expand Down
3 changes: 3 additions & 0 deletions runtime/include/platform/host_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ std::filesystem::path LogDirectory(std::string_view applicationName);

uint64_t CurrentProcessId() noexcept;

// Starts a new instance of this executable. False if it could not be started.
bool RelaunchSelf() noexcept;

} // namespace RuntimePlatform
2 changes: 1 addition & 1 deletion runtime/include/settings_overlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ bool StartupScreenVisible() noexcept;
void NotifyStrapInputAccepted() noexcept;
void AdvancePresentedFrame() noexcept;
// Put host controllers back to a neutral state before the process ends.
void ReleaseControllers() noexcept;
void ReleaseControllers(bool resetLeds = true) noexcept;
} // namespace settings_overlay
66 changes: 66 additions & 0 deletions runtime/src/platform/host_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@
#include <windows.h>
#include <shlobj.h>
#else
#include <climits>
#include <fcntl.h>
#include <spawn.h>
#include <unistd.h>
#endif

#if !defined(_WIN32) && !defined(__APPLE__)
#include <dirent.h>
#endif

#if defined(__APPLE__)
#include <crt_externs.h>
#include <mach-o/dyld.h>
#include <pwd.h>
#endif
Expand Down Expand Up @@ -82,4 +90,62 @@ uint64_t CurrentProcessId() noexcept {
#endif
}

bool RelaunchSelf() noexcept {
#if defined(_WIN32)
std::wstring path(32768, L'\0');
const DWORD length = GetModuleFileNameW(nullptr, path.data(), static_cast<DWORD>(path.size()));
if (length == 0 || length >= path.size()) {
return false;
}
path.resize(length);
std::wstring commandLine = GetCommandLineW();
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
PROCESS_INFORMATION process{};
if (!CreateProcessW(path.c_str(), commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
&startup, &process)) {
return false;
}
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
return true;
#else
std::string path;
// Mark inherited descriptors close-on-exec, or the new instance keeps this one's sockets and devices open.
#if defined(__APPLE__)
uint32_t size = 0;
if (_NSGetExecutablePath(nullptr, &size) != -1 || size == 0) {
return false;
}
path.resize(size);
if (_NSGetExecutablePath(path.data(), &size) != 0) {
return false;
}
char** const env = *_NSGetEnviron();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
for (int fd = 3, max = getdtablesize(); fd < max; ++fd) {
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
#else
path.resize(PATH_MAX);
const ssize_t length = readlink("/proc/self/exe", path.data(), path.size() - 1);
if (length <= 0) {
return false;
}
path.resize(static_cast<size_t>(length));
char** const env = environ;
if (DIR* dir = opendir("/proc/self/fd")) {
while (const dirent* entry = readdir(dir)) {
if (const int fd = std::atoi(entry->d_name); fd > 2 && fd != dirfd(dir)) {
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
}
closedir(dir);
}
#endif
char* const argv[] = {path.data(), nullptr};
pid_t pid = 0;
return posix_spawn(&pid, path.c_str(), nullptr, nullptr, argv, env) == 0;
#endif
}

} // namespace RuntimePlatform
16 changes: 13 additions & 3 deletions runtime/src/settings_overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1202,10 +1202,20 @@ void DrawStartupScreen() {
void DrawExitPrompt() {
constexpr const char* kTitle = "Exit";
if (g_exitPromptOpen && !ImGui::IsPopupOpen(kTitle)) ImGui::OpenPopup(kTitle);
if (!ImGui::BeginPopupModal(kTitle, &g_exitPromptOpen, ImGuiWindowFlags_AlwaysAutoResize)) return;
static bool restartFailed = false;
if (!ImGui::BeginPopupModal(kTitle, &g_exitPromptOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
restartFailed = false;
return;
}
ImGui::TextUnformatted("Quit the game?");
if (restartFailed) ImGui::TextDisabled("Could not restart.");
if (ImGui::Button("Exit", ImVec2(120.0f, 0.0f))) ExitForAuroraWindowClose();
ImGui::SameLine();
if (ImGui::Button("Restart", ImVec2(120.0f, 0.0f))) {
RestartForAuroraWindowClose();
restartFailed = true;
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120.0f, 0.0f))) g_exitPromptOpen = false;
if (!g_exitPromptOpen) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
Expand Down Expand Up @@ -1423,15 +1433,15 @@ void HandleEvents(const AuroraEvent* events) noexcept {
}
}

void ReleaseControllers() noexcept {
void ReleaseControllers(bool resetLeds) noexcept {
// Aurora drives the LED white on first PADRead and never clears it, and the
// exit paths terminate the process outright, so do it here.
bool queued = false;
for (uint32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
const s32 index = PADGetIndexForPort(port);
if (index < 0) continue;
if (SDL_Gamepad* pad = PADGetSDLGamepadForIndex(static_cast<u32>(index))) {
SDL_SetGamepadLED(pad, 0, 0, 0);
if (resetLeds) SDL_SetGamepadLED(pad, 0, 0, 0);
queued = true;
}
}
Expand Down
Loading