diff --git a/new_editor/app/Bootstrap/Application.cpp b/new_editor/app/Bootstrap/Application.cpp index dd2d78df..d82a4ef8 100644 --- a/new_editor/app/Bootstrap/Application.cpp +++ b/new_editor/app/Bootstrap/Application.cpp @@ -193,7 +193,8 @@ LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionI exceptionInfo->ExceptionRecord != nullptr) { AppendUIEditorCrashTrace( exceptionInfo->ExceptionRecord->ExceptionCode, - exceptionInfo->ExceptionRecord->ExceptionAddress); + exceptionInfo->ExceptionRecord->ExceptionAddress, + exceptionInfo); } else { AppendUIEditorCrashTrace(0u, nullptr); } diff --git a/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp b/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp index 03175f54..22152b18 100644 --- a/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp +++ b/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp @@ -1,6 +1,9 @@ #include "Composition/EditorWindowWorkspaceStore.h" +#include + #include +#include #include namespace XCEngine::UI::Editor::App { @@ -39,6 +42,24 @@ void UpsertWindowState( windowSet.windows.push_back(std::move(windowState)); } +std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) { + std::ostringstream stream = {}; + stream << "primary='" << windowSet.primaryWindowId + << "' active='" << windowSet.activeWindowId + << "' count=" << windowSet.windows.size() + << " windows=["; + bool first = true; + for (const UIEditorWindowWorkspaceState& state : windowSet.windows) { + if (!first) { + stream << ','; + } + first = false; + stream << state.windowId; + } + stream << ']'; + return stream.str(); +} + } // namespace EditorWindowWorkspaceStore::EditorWindowWorkspaceStore(UIEditorPanelRegistry panelRegistry) @@ -133,12 +154,24 @@ void EditorWindowWorkspaceStore::ReplaceWindowSet(UIEditorWindowWorkspaceSet win } void EditorWindowWorkspaceStore::RemoveWindow(std::string_view windowId, bool primary) { + const std::string beforeState = DescribeWindowSetState(m_windowSet); if (m_windowSet.windows.empty()) { + AppendUIEditorRuntimeTrace( + "window-close", + "store remove skipped windowId='" + std::string(windowId) + + "' primaryArg=" + (primary ? "1" : "0") + + " stateBefore=" + beforeState); return; } if (primary || m_windowSet.primaryWindowId == windowId) { m_windowSet = {}; + AppendUIEditorRuntimeTrace( + "window-close", + "store remove cleared all windows windowId='" + std::string(windowId) + + "' primaryArg=" + (primary ? "1" : "0") + + " stateBefore=" + beforeState + + " stateAfter=" + DescribeWindowSetState(m_windowSet)); return; } @@ -164,6 +197,13 @@ void EditorWindowWorkspaceStore::RemoveWindow(std::string_view windowId, bool pr FindUIEditorWindowWorkspaceState(m_windowSet, m_windowSet.activeWindowId) == nullptr) { m_windowSet.activeWindowId = ResolveFallbackWindowId(m_windowSet); } + + AppendUIEditorRuntimeTrace( + "window-close", + "store remove completed windowId='" + std::string(windowId) + + "' primaryArg=" + (primary ? "1" : "0") + + " stateBefore=" + beforeState + + " stateAfter=" + DescribeWindowSetState(m_windowSet)); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindow.cpp b/new_editor/app/Platform/Win32/EditorWindow.cpp index 2bae9230..cfd338f9 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.cpp +++ b/new_editor/app/Platform/Win32/EditorWindow.cpp @@ -183,6 +183,10 @@ void EditorWindow::ClearClosing() { m_state->window.closing = false; } +void EditorWindow::SetPrimary(bool primary) { + m_state->window.primary = primary; +} + void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { m_inputController->SetTrackingMouseLeave(trackingMouseLeave); } @@ -231,6 +235,15 @@ bool EditorWindow::Initialize( } void EditorWindow::Shutdown() { + std::ostringstream trace = {}; + trace << "EditorWindow::Shutdown begin windowId='" << GetWindowId() + << "' hwnd=0x" << std::hex << std::uppercase + << reinterpret_cast(GetHwnd()) + << std::dec + << " primary=" << (IsPrimary() ? 1 : 0) + << " closing=" << (IsClosing() ? 1 : 0) + << " runtimeReady=" << (m_runtime->IsReady() ? 1 : 0); + LogRuntimeTrace("window-close", trace.str()); ForceReleasePointerCapture(); if (m_runtime->IsReady()) { @@ -238,6 +251,9 @@ void EditorWindow::Shutdown() { } m_inputController->ClearPendingEvents(); m_chromeController->Reset(); + LogRuntimeTrace( + "window-close", + "EditorWindow::Shutdown end windowId='" + std::string(GetWindowId()) + "'"); } void EditorWindow::ResetInteractionState() { diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index dfad4ae9..bb4c760f 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -122,6 +122,7 @@ private: void MarkDestroyed(); void MarkClosing(); void ClearClosing(); + void SetPrimary(bool primary); void SetTrackingMouseLeave(bool trackingMouseLeave); void SetTitle(std::wstring title); void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController); diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp index d167f5c4..bd2f3613 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp @@ -181,16 +181,28 @@ bool EditorWindowRuntimeController::Initialize( void EditorWindowRuntimeController::Shutdown() { m_ready = false; ResetFrameTiming(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WaitForGpuIdle"); + m_windowRenderer.WaitForGpuIdle(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=AutoScreenshot"); m_autoScreenshot.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=ShellRuntime"); m_shellRuntime.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderLoopDetach"); m_windowRenderLoop.Detach(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=UiRenderer"); m_uiRenderer.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextSystem"); m_textSystem.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TitleBarLogo"); m_textureHost.ReleaseTexture(m_titleBarLogoIcon); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextureHost"); m_textureHost.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowRenderer"); m_windowRenderer.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=NativeRenderer"); m_renderer.Shutdown(); m_dpiScale = 1.0f; + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown end"); } void EditorWindowRuntimeController::ResetInteractionState() { diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp index 764f70a6..9a17bb5b 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp @@ -8,9 +8,47 @@ #include #include +#include +#include namespace XCEngine::UI::Editor::App { +namespace { + +std::string DescribeHwnd(HWND hwnd) { + std::ostringstream stream = {}; + stream << "0x" << std::hex << std::uppercase + << reinterpret_cast(hwnd); + return stream.str(); +} + +std::string DescribeHostWindows( + const std::vector>& windows) { + std::ostringstream stream = {}; + stream << "count=" << windows.size() << " ["; + bool first = true; + for (const std::unique_ptr& window : windows) { + if (!first) { + stream << ", "; + } + first = false; + if (window == nullptr) { + stream << ""; + continue; + } + + stream << window->GetWindowId() + << "{hwnd=" << DescribeHwnd(window->GetHwnd()) + << ",primary=" << (window->IsPrimary() ? '1' : '0') + << ",closing=" << (window->IsClosing() ? '1' : '0') + << '}'; + } + stream << ']'; + return stream.str(); +} + +} // namespace + EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, @@ -133,6 +171,13 @@ bool EditorWindowHostRuntime::HasWindows() const { void EditorWindowHostRuntime::DestroyEditorWindow(EditorWindow& window) { const HWND hwnd = window.GetHwnd(); + LogRuntimeTrace( + "window-close", + "DestroyEditorWindow begin windowId='" + std::string(window.GetWindowId()) + + "' hwnd=" + DescribeHwnd(hwnd) + + " primary=" + (window.IsPrimary() ? "1" : "0") + + " closing=" + (window.IsClosing() ? "1" : "0") + + " host=" + DescribeHostWindows(m_windows)); window.ForceReleasePointerCapture(); window.Shutdown(); @@ -140,6 +185,10 @@ void EditorWindowHostRuntime::DestroyEditorWindow(EditorWindow& window) { DestroyWindow(hwnd); } window.MarkDestroyed(); + LogRuntimeTrace( + "window-close", + "DestroyEditorWindow end windowId='" + std::string(window.GetWindowId()) + + "' host=" + DescribeHostWindows(m_windows)); } void EditorWindowHostRuntime::DestroyClosedWindows() { @@ -154,8 +203,15 @@ void EditorWindowHostRuntime::DestroyClosedWindows() { m_pendingCreateWindow = nullptr; } + LogRuntimeTrace( + "window-close", + "DestroyClosedWindows reap begin windowId='" + std::string(window->GetWindowId()) + + "' hostBefore=" + DescribeHostWindows(m_windows)); window->Shutdown(); it = m_windows.erase(it); + LogRuntimeTrace( + "window-close", + "DestroyClosedWindows reap end hostAfter=" + DescribeHostWindows(m_windows)); } } @@ -203,21 +259,38 @@ void EditorWindowHostRuntime::RenderAllWindows( } } -void EditorWindowHostRuntime::HandleDestroyedWindow(HWND hwnd) { - if (EditorWindow* window = FindWindow(hwnd); window != nullptr) { - window->MarkDestroyed(); - if (window->IsPrimary()) { - for (const std::unique_ptr& otherWindow : m_windows) { - if (otherWindow != nullptr && - otherWindow.get() != window && - otherWindow->GetHwnd() != nullptr && - !otherWindow->IsClosing()) { - otherWindow->MarkClosing(); - PostMessageW(otherWindow->GetHwnd(), WM_CLOSE, 0, 0); - } - } +void EditorWindowHostRuntime::HandleDestroyedWindow(EditorWindow& window, bool destroyedPrimary) { + LogRuntimeTrace( + "window-close", + "HandleDestroyedWindow begin windowId='" + std::string(window.GetWindowId()) + + "' destroyedPrimary=" + (destroyedPrimary ? "1" : "0") + + " hostBefore=" + DescribeHostWindows(m_windows)); + window.MarkDestroyed(); + if (!destroyedPrimary) { + LogRuntimeTrace( + "window-close", + "HandleDestroyedWindow end windowId='" + std::string(window.GetWindowId()) + + "' no primary cascade hostAfter=" + DescribeHostWindows(m_windows)); + return; + } + + for (const std::unique_ptr& otherWindow : m_windows) { + if (otherWindow != nullptr && + otherWindow.get() != &window && + otherWindow->GetHwnd() != nullptr && + !otherWindow->IsClosing()) { + otherWindow->MarkClosing(); + LogRuntimeTrace( + "window-close", + "HandleDestroyedWindow posting WM_CLOSE to windowId='" + + std::string(otherWindow->GetWindowId()) + "'"); + PostMessageW(otherWindow->GetHwnd(), WM_CLOSE, 0, 0); } } + LogRuntimeTrace( + "window-close", + "HandleDestroyedWindow end windowId='" + std::string(window.GetWindowId()) + + "' hostAfter=" + DescribeHostWindows(m_windows)); } EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) { diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h index 12e9ae77..373938ff 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h @@ -41,7 +41,7 @@ public: void RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator); - void HandleDestroyedWindow(HWND hwnd); + void HandleDestroyedWindow(EditorWindow& window, bool destroyedPrimary); EditorContext& GetEditorContext() { return m_editorContext; diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp index d00c33d6..3437f354 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp @@ -7,6 +7,8 @@ #include "Platform/Win32/WindowManager/EditorWindowHostRuntime.h" #include "Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h" +#include +#include #include namespace XCEngine::UI::Editor::App { @@ -16,6 +18,38 @@ namespace { constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; +std::string DescribeHwnd(HWND hwnd) { + std::ostringstream stream = {}; + stream << "0x" << std::hex << std::uppercase + << reinterpret_cast(hwnd); + return stream.str(); +} + +std::string DescribeHostWindows(const EditorWindowHostRuntime& hostRuntime) { + std::ostringstream stream = {}; + const auto& windows = hostRuntime.GetWindows(); + stream << "count=" << windows.size() << " ["; + bool first = true; + for (const std::unique_ptr& window : windows) { + if (!first) { + stream << ", "; + } + first = false; + if (window == nullptr) { + stream << ""; + continue; + } + + stream << window->GetWindowId() + << "{hwnd=" << DescribeHwnd(window->GetHwnd()) + << ",primary=" << (window->IsPrimary() ? '1' : '0') + << ",closing=" << (window->IsClosing() ? '1' : '0') + << '}'; + } + stream << ']'; + return stream.str(); +} + } // namespace struct EditorWindowMessageDispatcher::DispatchContext { @@ -387,6 +421,14 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 0; return true; case WM_CLOSE: + context.hostRuntime.LogRuntimeTrace( + "window-close", + "WM_CLOSE windowId='" + std::string(context.window.GetWindowId()) + + "' hwnd=" + DescribeHwnd(context.hwnd) + + " localPrimary=" + (context.window.IsPrimary() ? "1" : "0") + + " closingBefore=" + (context.window.IsClosing() ? "1" : "0") + + " workspace=" + context.workspaceCoordinator.DescribeWindowSet() + + " host=" + DescribeHostWindows(context.hostRuntime)); if (!context.window.IsClosing()) { context.window.MarkClosing(); } @@ -411,8 +453,27 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { context.workspaceCoordinator.EndGlobalTabDragSession(); } - context.workspaceCoordinator.HandleWindowDestroyed(context.window); - context.hostRuntime.HandleDestroyedWindow(context.hwnd); + { + const bool destroyedPrimary = + context.workspaceCoordinator.IsPrimaryWindowId(context.window.GetWindowId()); + context.hostRuntime.LogRuntimeTrace( + "window-close", + "WM_DESTROY begin windowId='" + std::string(context.window.GetWindowId()) + + "' hwnd=" + DescribeHwnd(context.hwnd) + + " destroyedPrimary=" + (destroyedPrimary ? "1" : "0") + + " localPrimary=" + (context.window.IsPrimary() ? "1" : "0") + + " workspaceBefore=" + context.workspaceCoordinator.DescribeWindowSet() + + " hostBefore=" + DescribeHostWindows(context.hostRuntime)); + context.workspaceCoordinator.HandleWindowDestroyed( + context.window.GetWindowId(), + destroyedPrimary); + context.hostRuntime.HandleDestroyedWindow(context.window, destroyedPrimary); + context.hostRuntime.LogRuntimeTrace( + "window-close", + "WM_DESTROY end windowId='" + std::string(context.window.GetWindowId()) + + "' workspaceAfter=" + context.workspaceCoordinator.DescribeWindowSet() + + " hostAfter=" + DescribeHostWindows(context.hostRuntime)); + } outResult = 0; return true; default: diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp index 1f5c6049..5618e88e 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace XCEngine::UI::Editor::App { @@ -21,6 +22,7 @@ struct ExistingWindowSnapshot { EditorWindow* window = nullptr; UIEditorWorkspaceController workspaceController = {}; std::wstring title = {}; + bool primary = false; }; using ::XCEngine::UI::UIPoint; @@ -58,6 +60,24 @@ bool IsLiveInteractiveWindow(const EditorWindow* window) { !window->IsClosing(); } +std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) { + std::ostringstream stream = {}; + stream << "primary='" << windowSet.primaryWindowId + << "' active='" << windowSet.activeWindowId + << "' count=" << windowSet.windows.size() + << " windows=["; + bool first = true; + for (const UIEditorWindowWorkspaceState& state : windowSet.windows) { + if (!first) { + stream << ','; + } + first = false; + stream << state.windowId; + } + stream << ']'; + return stream.str(); +} + } // namespace EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( @@ -109,8 +129,27 @@ void EditorWindowWorkspaceCoordinator::CommitWindowProjection(EditorWindow& wind RefreshWindowTitle(window); } -void EditorWindowWorkspaceCoordinator::HandleWindowDestroyed(const EditorWindow& window) { - m_workspaceStore.RemoveWindow(window.GetWindowId(), window.IsPrimary()); +bool EditorWindowWorkspaceCoordinator::IsPrimaryWindowId(std::string_view windowId) const { + return m_workspaceStore.GetWindowSet().primaryWindowId == windowId; +} + +std::string EditorWindowWorkspaceCoordinator::DescribeWindowSet() const { + return DescribeWindowSetState(m_workspaceStore.GetWindowSet()); +} + +void EditorWindowWorkspaceCoordinator::HandleWindowDestroyed( + std::string_view windowId, + bool primary) { + LogRuntimeTrace( + "window-close", + "workspace remove begin windowId='" + std::string(windowId) + + "' primaryArg=" + (primary ? "1" : "0") + + " stateBefore=" + DescribeWindowSet()); + m_workspaceStore.RemoveWindow(windowId, primary); + LogRuntimeTrace( + "window-close", + "workspace remove end windowId='" + std::string(windowId) + + "' stateAfter=" + DescribeWindowSet()); } UIEditorWindowWorkspaceController @@ -201,6 +240,7 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( } snapshot.window->ReplaceWorkspaceController(snapshot.workspaceController); + snapshot.window->SetPrimary(snapshot.primary); snapshot.window->SetTitle(snapshot.title); snapshot.window->ResetInteractionState(); if (snapshot.window->GetHwnd() != nullptr) { @@ -241,6 +281,7 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) { windowIdsInSet.push_back(entry.windowId); + const bool isPrimaryWindow = entry.windowId == windowSet.primaryWindowId; if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(entry.windowId); existingWindow != nullptr) { existingWindow->ClearClosing(); @@ -248,22 +289,28 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( existingWindow, existingWindow->GetWorkspaceController(), existingWindow->GetTitle(), + existingWindow->IsPrimary(), }); + existingWindow->SetPrimary(isPrimaryWindow); existingWindow->ReplaceWorkspaceController(BuildWorkspaceControllerForWindow(entry)); existingWindow->ResetInteractionState(); - if (!existingWindow->IsPrimary()) { - existingWindow->SetTitle( - BuildWindowTitle(existingWindow->GetWorkspaceController())); - if (existingWindow->GetHwnd() != nullptr) { - SetWindowTextW(existingWindow->GetHwnd(), existingWindow->GetTitle().c_str()); - } + existingWindow->SetTitle( + isPrimaryWindow + ? std::wstring( + m_hostRuntime.GetHostConfig().primaryWindowTitle != nullptr && + m_hostRuntime.GetHostConfig().primaryWindowTitle[0] != L'\0' + ? m_hostRuntime.GetHostConfig().primaryWindowTitle + : L"XCEngine Editor") + : BuildWindowTitle(existingWindow->GetWorkspaceController())); + if (existingWindow->GetHwnd() != nullptr) { + SetWindowTextW(existingWindow->GetHwnd(), existingWindow->GetTitle().c_str()); } continue; } EditorWindowHostRuntime::CreateParams createParams = {}; createParams.windowId = entry.windowId; - createParams.primary = entry.windowId == windowSet.primaryWindowId; + createParams.primary = isPrimaryWindow; createParams.title = createParams.primary ? std::wstring( diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h index 0286fc2e..412ec264 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h @@ -24,7 +24,9 @@ public: void RegisterExistingWindow(EditorWindow& window); void CommitWindowProjection(EditorWindow& window); - void HandleWindowDestroyed(const EditorWindow& window); + bool IsPrimaryWindowId(std::string_view windowId) const; + std::string DescribeWindowSet() const; + void HandleWindowDestroyed(std::string_view windowId, bool primary); bool IsGlobalTabDragActive() const; bool OwnsActiveGlobalTabDrag(std::string_view windowId) const;