diff --git a/hook/src/audio_hook.cpp b/hook/src/audio_hook.cpp index 286d530..1664df2 100644 --- a/hook/src/audio_hook.cpp +++ b/hook/src/audio_hook.cpp @@ -600,9 +600,14 @@ void set_audio_ring(unsigned index, AudioRingHeader* ring) { return; } - g_rings[index].store(ring, std::memory_order_release); - logf("set_audio_ring: index=%u ring=%p capture_enabled=%u", index, ring, - ring ? ring->capture_enabled.load(std::memory_order_relaxed) : 0u); + // The worker thread re-attaches every tick (idempotent); only log when the ring + // pointer actually changes so the log isn't flooded with identical lines. + AudioRingHeader* const prev = g_rings[index].exchange(ring, std::memory_order_acq_rel); + if (prev != ring) + { + logf("set_audio_ring: index=%u ring=%p capture_enabled=%u", index, ring, + ring ? ring->capture_enabled.load(std::memory_order_relaxed) : 0u); + } // The stream may already be registered (game was playing before we injected and // before the host created the ring); publish its format so the host stops waiting // and consumes the ring instead of falling back to loopback. diff --git a/host/src/imgui_layer.cpp b/host/src/imgui_layer.cpp index 4206962..d5d551c 100644 --- a/host/src/imgui_layer.cpp +++ b/host/src/imgui_layer.cpp @@ -1,12 +1,34 @@ #include "imgui_layer.hpp" +#include + #include #include #include +#include "coop/tool_paths.hpp" +#include "ui/app_chrome.hpp" + namespace coop { +namespace +{ +// ImGui's file IO treats IniFilename as UTF-8 (it converts to wide for _wfopen), so +// the path must be UTF-8 rather than the system ANSI code page. +std::string to_utf8(const std::wstring& w) +{ + if (w.empty()) + { + return {}; + } + const int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); + std::string s(static_cast(n), '\0'); + WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), s.data(), n, nullptr, nullptr); + return s; +} +} // namespace + ImGuiLayer::~ImGuiLayer() { if (initialized_) @@ -23,7 +45,15 @@ bool ImGuiLayer::init(HWND hwnd, ID3D11Device* device, ID3D11DeviceContext* cont IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - io.IniFilename = nullptr; // don't litter the cwd with imgui.ini during the spike + // Persist the panel layout next to the executable so window positions/sizes survive + // restarts. CWD is unreliable when Steam launches us under the donor appid, so anchor + // to the exe directory. If a layout already exists, tell the chrome not to force the + // computed default on startup (which would clobber the restored positions). + const std::wstring ini_w = exe_directory() + L"coop_layout.ini"; + const bool had_layout = GetFileAttributesW(ini_w.c_str()) != INVALID_FILE_ATTRIBUTES; + ini_path_ = to_utf8(ini_w); + io.IniFilename = ini_path_.c_str(); + set_layout_persisted(had_layout); ImGui::StyleColorsDark(); if (!ImGui_ImplWin32_Init(hwnd)) diff --git a/host/src/imgui_layer.hpp b/host/src/imgui_layer.hpp index 7378fc6..9634e09 100644 --- a/host/src/imgui_layer.hpp +++ b/host/src/imgui_layer.hpp @@ -1,6 +1,8 @@ // Owns ImGui setup/teardown for the Win32 + DX11 backends. #pragma once +#include + #include #include @@ -22,6 +24,9 @@ public: private: bool initialized_ = false; + // Backing storage for io.IniFilename (ImGui keeps the pointer, not a copy), so the + // layout .ini path must outlive the context. Empty until init() sets it. + std::string ini_path_; }; } // namespace coop diff --git a/host/src/ui/app_chrome.cpp b/host/src/ui/app_chrome.cpp index a43cf89..c3f57af 100644 --- a/host/src/ui/app_chrome.cpp +++ b/host/src/ui/app_chrome.cpp @@ -17,6 +17,9 @@ bool g_layout_reset = false; // WorkSize isn't trustworthy on frame 0, and FirstUseEver would otherwise lock in // those wrong positions (overlapping) until the user pressed Reset layout. int g_startup_force = 4; +// True when ImGui loaded a saved layout (.ini) at startup; then the startup force is +// suppressed so the restored window positions survive (Reset layout still re-applies). +bool g_had_persisted_layout = false; } // namespace void request_layout_reset() @@ -24,6 +27,11 @@ void request_layout_reset() g_layout_reset = true; } +void set_layout_persisted(bool had_persisted_layout) +{ + g_had_persisted_layout = had_persisted_layout; +} + void apply_layout_end_frame() { g_layout_reset = false; @@ -87,7 +95,11 @@ void apply_panel_layout(Panel panel) break; } - const ImGuiCond cond = (g_layout_reset || g_startup_force > 0) ? ImGuiCond_Always : ImGuiCond_FirstUseEver; + // Force the computed layout only when the user asks (Reset layout) or on a fresh + // install with no saved layout to restore. Otherwise FirstUseEver lets ImGui's + // restored .ini positions stand (and still seeds any brand-new panel). + const bool force = g_layout_reset || (!g_had_persisted_layout && g_startup_force > 0); + const ImGuiCond cond = force ? ImGuiCond_Always : ImGuiCond_FirstUseEver; ImGui::SetNextWindowPos(pos, cond); ImGui::SetNextWindowSize(size, cond); } diff --git a/host/src/ui/app_chrome.hpp b/host/src/ui/app_chrome.hpp index 588de8c..a98df49 100644 --- a/host/src/ui/app_chrome.hpp +++ b/host/src/ui/app_chrome.hpp @@ -43,6 +43,11 @@ void apply_panel_layout(Panel panel); void request_layout_reset(); void apply_layout_end_frame(); +// Tell the layout whether ImGui restored a saved layout (.ini) at startup. When it +// did, the computed default layout must NOT be force-applied on launch (that would +// clobber the user's saved window positions); only an explicit Reset layout re-applies. +void set_layout_persisted(bool had_persisted_layout); + // Rolling frame-timing over a ~1 s window, recomputed each window so the status // bar can show a stable FPS plus the min/max frame time (jitter) underneath it. class FrameStats