From 70988aa50c1f0be57c53442f03ada33deaa22919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 24 Oct 2026 13:30:40 +0200 Subject: [PATCH] ImGui: Keep the context and debugger state when Android switches away NativeShutdownGraphics, which Android runs whenever the app loses its surface, destroyed the imgui context and the ImDebugger along with the device objects, so window layout and debugger state reset on return. Now only the device objects go there (the backend already recreates them), and the context and debugger are torn down in NativeShutdown. Both configs are still saved on device loss, in case the app is killed in the background. imgui.ini is now read and written through our File API rather than imgui's fopen, which can't reach a memstick behind a content URI. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/ImDebugger/ImDebugger.cpp | 4 ++++ UI/ImDebugger/ImDebugger.h | 1 + UI/NativeApp.cpp | 30 +++++++++++++++++++----------- ext/imgui/imgui_impl_platform.cpp | 26 +++++++++++++++++++++++--- ext/imgui/imgui_impl_platform.h | 2 ++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 435915ff62..861395d6f7 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -2656,6 +2656,10 @@ ImDebugger::ImDebugger() { } ImDebugger::~ImDebugger() { + SaveConfig(); +} + +void ImDebugger::SaveConfig() { cfg_.SaveConfig(ConfigPath()); } diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index b79209b509..caf982a6b9 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -190,6 +190,7 @@ public: externalCommand_ = cmd; } void DeviceLost(); + void SaveConfig(); private: Path ConfigPath(); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index bbbf6cf386..6c3f2a73cb 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -436,6 +436,7 @@ void runImDebugger(Draw::DrawContext *draw) { // Convert to drawlists. ImGui::Render(); + ImGui_ImplPlatform_SaveIni(false); wantTextInput = io.WantTextInput; } else { uiContext->SetOverrideScreenFrame(nullptr); @@ -1128,19 +1129,13 @@ void NativeShutdownGraphics(GraphicsContext *graphicsContext) { gpu->DeviceLost(); } + // The imgui context and the debugger outlive the graphics, so that their state survives Android + // switching away from the app. Save now anyway, since the app may be killed in the background. if (imguiInited_) { - if (imDebugger_) { - imDebugger_->DeviceLost(); - } + imDebugger_->DeviceLost(); + imDebugger_->SaveConfig(); + ImGui_ImplPlatform_SaveIni(true); ImGui_ImplThin3d_DestroyDeviceObjects(); - ImGui_ImplThin3d_Shutdown(); - // Destroy the debugger here, while the things it refers to are still alive. If left to - // static destruction, ~ImDisasmView runs after Core's globals are gone and its - // g_disassemblyManager.clear() walks a destroyed map (and locks a destroyed mutex). - imDebugger_.reset(); - ImGui::DestroyContext(ctx_); - ctx_ = nullptr; - imguiInited_ = false; } #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) @@ -1979,6 +1974,19 @@ void NativeShutdown() { g_screenManager = nullptr; } + if (imguiInited_) { + ImGui_ImplPlatform_SaveIni(true); + // The device objects went with NativeShutdownGraphics. + ImGui_ImplThin3d_Shutdown(); + // Destroy the debugger here, while the things it refers to are still alive. If left to + // static destruction, ~ImDisasmView runs after Core's globals are gone and its + // g_disassemblyManager.clear() walks a destroyed map (and locks a destroyed mutex). + imDebugger_.reset(); + ImGui::DestroyContext(ctx_); + ctx_ = nullptr; + imguiInited_ = false; + } + System_Notify(SystemNotification::BEFORE_CONFIG_SAVE_ON_EXIT); g_Config.Save("NativeShutdown"); diff --git a/ext/imgui/imgui_impl_platform.cpp b/ext/imgui/imgui_impl_platform.cpp index eda6490851..dfaff32157 100644 --- a/ext/imgui/imgui_impl_platform.cpp +++ b/ext/imgui/imgui_impl_platform.cpp @@ -1,5 +1,6 @@ #include "ext/imgui/imgui.h" #include "Common/File/Path.h" +#include "Common/File/FileUtil.h" #include "Common/Input/KeyCodes.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/System/Display.h" @@ -10,6 +11,7 @@ #include "imgui_impl_platform.h" static ImGuiMouseCursor g_cursor = ImGuiMouseCursor_Arrow; +static Path g_iniPath; void ImGui_ImplPlatform_KeyEvent(const KeyInput &key) { ImGuiIO &io = ImGui::GetIO(); @@ -86,10 +88,28 @@ void ImGui_ImplPlatform_TouchEvent(const TouchInput &touch) { } void ImGui_ImplPlatform_Init(const Path &configPath) { - static char path[1024]; - truncate_cpy(path, configPath.ToString()); - ImGui::GetIO().IniFilename = path; + // imgui's own ini handling uses fopen, which can't reach a memstick behind an Android content URI. + g_iniPath = configPath; + ImGui::GetIO().IniFilename = nullptr; ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + std::string data; + if (File::ReadTextFileToString(g_iniPath, &data)) { + ImGui::LoadIniSettingsFromMemory(data.data(), data.size()); + } +} + +void ImGui_ImplPlatform_SaveIni(bool force) { + ImGuiIO &io = ImGui::GetIO(); + if (!force && !io.WantSaveIniSettings) { + return; + } + size_t size = 0; + const char *data = ImGui::SaveIniSettingsToMemory(&size); + if (!File::WriteDataToFile(true, data, size, g_iniPath)) { + WARN_LOG(Log::System, "Failed to save imgui settings to '%s'", g_iniPath.c_str()); + } + io.WantSaveIniSettings = false; } void ImGui_ImplPlatform_AxisEvent(const AxisInput &axis) { diff --git a/ext/imgui/imgui_impl_platform.h b/ext/imgui/imgui_impl_platform.h index c6bec60101..4e6f0f8c3f 100644 --- a/ext/imgui/imgui_impl_platform.h +++ b/ext/imgui/imgui_impl_platform.h @@ -11,6 +11,8 @@ ImGuiKey KeyCodeToImGui(InputKeyCode keyCode); void ImGui_ImplPlatform_Init(const Path &configPath); void ImGui_ImplPlatform_NewFrame(); +// Writes the ini if imgui asked for it, or always if force is set. Call after ImGui::Render. +void ImGui_ImplPlatform_SaveIni(bool force); void ImGui_ImplPlatform_KeyEvent(const KeyInput &key); void ImGui_ImplPlatform_TouchEvent(const TouchInput &touch);