Add Exit button + controller led fix (#221)

This commit is contained in:
Nicholas Bly authored and GitHub committed 2026-09-13 14:07:54 +02:00
1 parent b555ede2d3
commit 4bdaff01fc
3 files changed
+51 -2

No files matched your search

+1
View File
@@ -58,6 +58,7 @@ inline void Flush(bool force = false) {
// still active. A window close is an intentional successful exit, so end the // still active. A window close is an intentional successful exit, so end the
// process directly and do not run the crash/atexit paths. // process directly and do not run the crash/atexit paths.
[[noreturn]] inline void ExitForAuroraWindowClose() noexcept { [[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
settings_overlay::ReleaseControllers();
WindowPlacementPersistence::Flush(true); WindowPlacementPersistence::Flush(true);
#if defined(_WIN32) #if defined(_WIN32)
::ExitProcess(0); ::ExitProcess(0);
+2
View File
@@ -12,4 +12,6 @@ void Draw() noexcept;
bool StartupScreenVisible() noexcept; bool StartupScreenVisible() noexcept;
void NotifyStrapInputAccepted() noexcept; void NotifyStrapInputAccepted() noexcept;
void AdvancePresentedFrame() noexcept; void AdvancePresentedFrame() noexcept;
// Put host controllers back to a neutral state before the process ends.
void ReleaseControllers() noexcept;
} // namespace settings_overlay } // namespace settings_overlay
+48 -2
View File
@@ -1,5 +1,6 @@
#include "settings_overlay.h" #include "settings_overlay.h"
#include "audio_backend.h" #include "audio_backend.h"
#include "aurora_events.h"
#include "controller_button_names.h" #include "controller_button_names.h"
#include "controller_mapping_wizard.h" #include "controller_mapping_wizard.h"
#include "input_bindings.h" #include "input_bindings.h"
@@ -15,6 +16,7 @@
#include <SDL3/SDL_keyboard.h> #include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h> #include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_scancode.h> #include <SDL3/SDL_scancode.h>
#include <SDL3/SDL_timer.h>
#include <array> #include <array>
#include <algorithm> #include <algorithm>
@@ -70,6 +72,7 @@ const char* GraphicsApiDisplayName() {
} }
bool g_topBarVisible = false; bool g_topBarVisible = false;
bool g_exitPromptOpen = false;
bool g_rumbleEnabled = RuntimeConfigFile::RumbleEnabled(true); bool g_rumbleEnabled = RuntimeConfigFile::RumbleEnabled(true);
int g_controllerPort = 0; int g_controllerPort = 0;
float g_resolutionScale = RuntimeConfigFile::ResolutionMultiplier(1.0f); float g_resolutionScale = RuntimeConfigFile::ResolutionMultiplier(1.0f);
@@ -1175,6 +1178,18 @@ void DrawStartupScreen() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
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;
ImGui::TextUnformatted("Quit the game?");
if (ImGui::Button("Exit", ImVec2(120.0f, 0.0f))) ExitForAuroraWindowClose();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120.0f, 0.0f))) g_exitPromptOpen = false;
if (!g_exitPromptOpen) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
void DrawTopBar() { void DrawTopBar() {
if (!g_topBarVisible) { if (!g_topBarVisible) {
return; return;
@@ -1243,11 +1258,17 @@ void DrawTopBar() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
const float hideWidth = ImGui::CalcTextSize("Hide (F10)").x + ImGui::GetStyle().FramePadding.x * 2.0f; const ImGuiStyle& style = ImGui::GetStyle();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() - hideWidth - 8.0f)); const float hideWidth = ImGui::CalcTextSize("Hide (F10)").x + style.FramePadding.x * 2.0f;
const float exitWidth = ImGui::CalcTextSize("X").x + style.FramePadding.x * 2.0f;
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(),
ImGui::GetWindowWidth() - hideWidth - exitWidth - style.ItemSpacing.x - 8.0f));
if (ImGui::MenuItem("Hide (F10)")) { if (ImGui::MenuItem("Hide (F10)")) {
SetTopBarVisible(false); SetTopBarVisible(false);
} }
if (ImGui::MenuItem("X")) {
g_exitPromptOpen = true;
}
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
@@ -1342,12 +1363,36 @@ void HandleEvents(const AuroraEvent* events) noexcept {
if (!g_rebind.active && IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) { if (!g_rebind.active && IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) {
SetTopBarVisible(!g_topBarVisible); SetTopBarVisible(!g_topBarVisible);
} }
if (!g_rebind.active && !g_topBarVisible && IsToggleKey(ev->sdl, SDL_SCANCODE_ESCAPE)) {
g_exitPromptOpen = true;
}
if (IsMouseActivity(ev->sdl)) { if (IsMouseActivity(ev->sdl)) {
g_lastMouseActivity = Clock::now(); g_lastMouseActivity = Clock::now();
} }
} }
} }
void ReleaseControllers() 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);
queued = true;
}
}
constexpr std::array<uint32_t, PAD_MAX_CONTROLLERS> stopAll{
PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD};
PADControlAllMotors(stopAll.data());
// SDL hands LED and rumble reports to its own HIDAPI sender thread rather
// than writing them here, so without this the process dies before the
// controller ever receives them.
if (queued) SDL_Delay(120);
}
void Draw() noexcept { void Draw() noexcept {
// Wait for the frame worker's DONE phase: it has replayed the previous frame's ImGui draw lists // Wait for the frame worker's DONE phase: it has replayed the previous frame's ImGui draw lists
// and started the next ImGui frame, so all overlay callers can now safely issue ImGui commands. // and started the next ImGui frame, so all overlay callers can now safely issue ImGui commands.
@@ -1365,6 +1410,7 @@ void Draw() noexcept {
} }
DrawFpsOverlay(); DrawFpsOverlay();
DrawTopBar(); DrawTopBar();
DrawExitPrompt();
controller_mapping_wizard::Draw(); controller_mapping_wizard::Draw();
// The wizard captures raw presses; keep them out of the game. // The wizard captures raw presses; keep them out of the game.
const bool inputBlocked = controller_mapping_wizard::IsActive() || g_rebind.active; const bool inputBlocked = controller_mapping_wizard::IsActive() || g_rebind.active;