Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/non-terminal-panes-2023

This commit is contained in:
Mike Griese
2023-07-20 07:02:16 -05:00
13 changed files with 493 additions and 125 deletions

View File

@@ -33,7 +33,8 @@ constexpr const auto FrameUpdateInterval = std::chrono::milliseconds(16);
AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const Remoting::WindowManager& manager,
const Remoting::Peasant& peasant) noexcept :
const Remoting::Peasant& peasant,
std::unique_ptr<IslandWindow> window) noexcept :
_appLogic{ logic },
_windowLogic{ nullptr }, // don't make one, we're going to take a ref on app's
_windowManager{ manager },
@@ -48,13 +49,22 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
// _HandleCommandlineArgs will create a _windowLogic
_useNonClientArea = _windowLogic.GetShowTabsInTitlebar();
if (_useNonClientArea)
const bool isWarmStart = window != nullptr;
if (isWarmStart)
{
_window = std::make_unique<NonClientIslandWindow>(_windowLogic.GetRequestedTheme());
_window = std::move(window);
}
else
{
_window = std::make_unique<IslandWindow>();
if (_useNonClientArea)
{
_window = std::make_unique<NonClientIslandWindow>(_windowLogic.GetRequestedTheme());
}
else
{
_window = std::make_unique<IslandWindow>();
}
}
// Update our own internal state tracking if we're in quake mode or not.
@@ -69,14 +79,10 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
std::placeholders::_2);
_window->SetCreateCallback(pfn);
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
_window->WindowMoved({ this, &AppHost::_WindowMoved });
_window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen });
_window->SetAlwaysOnTop(_windowLogic.GetInitialAlwaysOnTop());
_window->SetAutoHideWindow(_windowLogic.AutoHideWindow());
_windowCallbacks.MouseScrolled = _window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_windowCallbacks.WindowActivated = _window->WindowActivated({ this, &AppHost::_WindowActivated });
_windowCallbacks.WindowMoved = _window->WindowMoved({ this, &AppHost::_WindowMoved });
_windowCallbacks.ShouldExitFullscreen = _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen });
_window->MakeWindow();
}
@@ -286,6 +292,14 @@ void AppHost::Initialize()
_windowLogic.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent });
}
// These call APIs that are reentrant on the window message loop. If
// you call them in the ctor, we might deadlock. The ctor for AppHost isn't
// always called on the window thread - for reheated windows, it could be
// called on a random COM thread.
_window->SetAlwaysOnTop(_windowLogic.GetInitialAlwaysOnTop());
_window->SetAutoHideWindow(_windowLogic.AutoHideWindow());
// MORE EVENT HANDLERS HERE!
// MAKE SURE THEY ARE ALL:
// * winrt::auto_revoke
@@ -295,13 +309,14 @@ void AppHost::Initialize()
// tearing down, after we've nulled out the window, during the dtor. That
// can cause unexpected AV's everywhere.
//
// _window callbacks don't need to be treated this way, because:
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like this)
// * This particular bug scenario applies when we've already freed the window.
// _window callbacks are a little special:
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like
// this), so instead they go in their own special helper struct.
// * they all need to be manually revoked in _revokeWindowCallbacks.
// Register the 'X' button of the window for a warning experience of multiple
// tabs opened, this is consistent with Alt+F4 closing
_window->WindowCloseButtonClicked([this]() {
_windowCallbacks.WindowCloseButtonClicked = _window->WindowCloseButtonClicked([this]() {
_CloseRequested(nullptr, nullptr);
});
// If the user requests a close in another way handle the same as if the 'X'
@@ -310,11 +325,11 @@ void AppHost::Initialize()
// Add an event handler to plumb clicks in the titlebar area down to the
// application layer.
_window->DragRegionClicked([this]() { _windowLogic.TitlebarClicked(); });
_windowCallbacks.DragRegionClicked = _window->DragRegionClicked([this]() { _windowLogic.TitlebarClicked(); });
_window->WindowVisibilityChanged([this](bool showOrHide) { _windowLogic.WindowVisibilityChanged(showOrHide); });
_windowCallbacks.WindowVisibilityChanged = _window->WindowVisibilityChanged([this](bool showOrHide) { _windowLogic.WindowVisibilityChanged(showOrHide); });
_window->UpdateSettingsRequested({ this, &AppHost::_requestUpdateSettings });
_windowCallbacks.UpdateSettingsRequested = _window->UpdateSettingsRequested({ this, &AppHost::_requestUpdateSettings });
_revokers.Initialized = _windowLogic.Initialized(winrt::auto_revoke, { this, &AppHost::_WindowInitializedHandler });
_revokers.RequestedThemeChanged = _windowLogic.RequestedThemeChanged(winrt::auto_revoke, { this, &AppHost::_UpdateTheme });
@@ -325,14 +340,14 @@ void AppHost::Initialize()
_revokers.SystemMenuChangeRequested = _windowLogic.SystemMenuChangeRequested(winrt::auto_revoke, { this, &AppHost::_SystemMenuChangeRequested });
_revokers.ChangeMaximizeRequested = _windowLogic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested });
_window->MaximizeChanged([this](bool newMaximize) {
_windowCallbacks.MaximizeChanged = _window->MaximizeChanged([this](bool newMaximize) {
if (_windowLogic)
{
_windowLogic.Maximized(newMaximize);
}
});
_window->AutomaticShutdownRequested([this]() {
_windowCallbacks.AutomaticShutdownRequested = _window->AutomaticShutdownRequested([this]() {
// Raised when the OS is beginning an update of the app. We will quit,
// to save our state, before the OS manually kills us.
Remoting::WindowManager::RequestQuitAll(_peasant);
@@ -428,6 +443,9 @@ void AppHost::Close()
_frameTimer.Tick(_frameTimerToken);
}
_showHideWindowThrottler.reset();
_revokeWindowCallbacks();
_window->Close();
if (_windowLogic)
@@ -437,6 +455,50 @@ void AppHost::Close()
}
}
void AppHost::_revokeWindowCallbacks()
{
// You'll recall, IslandWindow isn't a WinRT type so it can't have auto-revokers.
//
// Instead, we need to manually remove our callbacks we registered on the window object.
_window->MouseScrolled(_windowCallbacks.MouseScrolled);
_window->WindowActivated(_windowCallbacks.WindowActivated);
_window->WindowMoved(_windowCallbacks.WindowMoved);
_window->ShouldExitFullscreen(_windowCallbacks.ShouldExitFullscreen);
_window->WindowCloseButtonClicked(_windowCallbacks.WindowCloseButtonClicked);
_window->DragRegionClicked(_windowCallbacks.DragRegionClicked);
_window->WindowVisibilityChanged(_windowCallbacks.WindowVisibilityChanged);
_window->UpdateSettingsRequested(_windowCallbacks.UpdateSettingsRequested);
_window->MaximizeChanged(_windowCallbacks.MaximizeChanged);
_window->AutomaticShutdownRequested(_windowCallbacks.AutomaticShutdownRequested);
}
// revoke our callbacks, discard our XAML content (TerminalWindow &
// TerminalPage), and hand back our IslandWindow. This does _not_ close the XAML
// island for this thread. We should not be re-used after this, and our caller
// can destruct us like they normally would during a close. The returned
// IslandWindow will retain ownership of the DesktopWindowXamlSource, for later
// reuse.
[[nodiscard]] std::unique_ptr<IslandWindow> AppHost::Refrigerate()
{
// After calling _window->Close() we should avoid creating more WinUI related actions.
// I suspect WinUI wouldn't like that very much. As such unregister all event handlers first.
_revokers = {};
_showHideWindowThrottler.reset();
_revokeWindowCallbacks();
// DO NOT CLOSE THE WINDOW
_window->Refrigerate();
if (_windowLogic)
{
_windowLogic.DismissDialog();
_windowLogic = nullptr;
}
return std::move(_window);
}
// Method Description:
// - Called every time when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
@@ -1049,22 +1111,7 @@ static bool _isActuallyDarkTheme(const auto requestedTheme)
// Windows 10, so that we don't even get that spew
void _frameColorHelper(const HWND h, const COLORREF color)
{
static const bool isWindows11 = []() {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
if (isWindows11)
if (Utils::IsWindows11())
{
LOG_IF_FAILED(DwmSetWindowAttribute(h, DWMWA_BORDER_COLOR, &color, sizeof(color)));
}

View File

@@ -12,12 +12,16 @@ public:
AppHost(const winrt::TerminalApp::AppLogic& logic,
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant,
std::unique_ptr<IslandWindow> window = nullptr) noexcept;
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args);
void Initialize();
void Close();
[[nodiscard]] std::unique_ptr<IslandWindow> Refrigerate();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
@@ -58,6 +62,8 @@ private:
void _preInit();
void _revokeWindowCallbacks();
void _HandleCommandlineArgs(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args);
void _HandleSessionRestore(const bool startedForContent);
@@ -202,4 +208,24 @@ private:
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::SendContentRequested_revoker SendContentRequested;
} _revokers{};
// our IslandWindow is not a WinRT type. It can't make auto_revokers like
// the above. We also need to make sure to unregister ourself from the
// window when we refrigerate the window thread so that the window can later
// be re-used.
struct WindowRevokers
{
winrt::event_token MouseScrolled;
winrt::event_token WindowActivated;
winrt::event_token WindowMoved;
winrt::event_token ShouldExitFullscreen;
winrt::event_token WindowCloseButtonClicked;
winrt::event_token DragRegionClicked;
winrt::event_token WindowVisibilityChanged;
winrt::event_token UpdateSettingsRequested;
winrt::event_token MaximizeChanged;
winrt::event_token AutomaticShutdownRequested;
// LOAD BEARING!!
//If you add events here, make sure they're revoked in AppHost::_revokeWindowCallbacks
} _windowCallbacks{};
};

View File

@@ -210,13 +210,17 @@ protected:
bool _minimized = false;
void _setupUserData()
{
SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
// Method Description:
// - This method is called when the window receives the WM_NCCREATE message.
// Return Value:
// - The value returned from the window proc.
[[nodiscard]] virtual LRESULT OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept
{
SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
_setupUserData();
EnableNonClientDpiScaling(_window.get());
_currentDpi = GetDpiForWindow(_window.get());

View File

@@ -42,46 +42,6 @@ IslandWindow::~IslandWindow()
void IslandWindow::Close()
{
static const bool isWindows11 = []() {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
if (!isWindows11)
{
// BODGY
// ____ ____ _____ _______ __
// | _ \ / __ \| __ \ / ____\ \ / /
// | |_) | | | | | | | | __ \ \_/ /
// | _ <| | | | | | | | |_ | \ /
// | |_) | |__| | |__| | |__| | | |
// |____/ \____/|_____/ \_____| |_|
//
// There's a bug in Windows 10 where closing a DesktopWindowXamlSource
// on any thread will free an internal static resource that's used by
// XAML for the entire process. This would result in closing window
// essentially causing the entire app to crash.
//
// To avoid this, leak the XAML island. We only need to leak this on
// Windows 10, since the bug is fixed in Windows 11.
//
// See GH #15384, MSFT:32109540
auto a{ _source };
winrt::detach_abi(_source);
// </BODGY>
}
// GH#15454: Unset the user data for the window. This will prevent future
// callbacks that come onto our window message loop from being sent to the
// IslandWindow (or other derived class's) implementation.
@@ -99,6 +59,27 @@ void IslandWindow::Close()
}
}
// Clear out any state that might be associated with this app instance, so that
// we can later re-use this HWND for another instance.
//
// This doesn't actually close out our HWND or DesktopWindowXamlSource, but it
// will remove all our content, and SW_HIDE the window, so it isn't accessible.
void IslandWindow::Refrigerate() noexcept
{
// Similar to in Close - unset our HWND's user data. We'll re-set this when
// we get re-heated, so that while we're refrigerated, we won't have
// unexpected callbacks into us while we don't have content.
//
// This pointer will get re-set in _warmInitialize
SetWindowLongPtr(_window.get(), GWLP_USERDATA, 0);
_pfnCreateCallback = nullptr;
_pfnSnapDimensionCallback = nullptr;
_rootGrid.Children().Clear();
ShowWindow(_window.get(), SW_HIDE);
}
HWND IslandWindow::GetInteropHandle() const
{
return _interopWindowHandle;
@@ -112,6 +93,12 @@ HWND IslandWindow::GetInteropHandle() const
// - <none>
void IslandWindow::MakeWindow() noexcept
{
if (_window)
{
// no-op if we already have a window.
return;
}
WNDCLASS wc{};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
@@ -345,7 +332,30 @@ LRESULT IslandWindow::_OnMoving(const WPARAM /*wParam*/, const LPARAM lParam)
return false;
}
void IslandWindow::Initialize()
// return true if this was a "cold" initialize, that didn't start XAML before.
bool IslandWindow::Initialize()
{
if (!_source)
{
_coldInitialize();
return true;
}
else
{
// This was a "warm" initialize - we've already got an HWND, but we need
// to move it to the new correct place, new size, and reset any leftover
// runtime state.
_warmInitialize();
return false;
}
}
// Method Description:
// - Start this window for the first time. This will instantiate our XAML
// island, set up our root grid, and initialize some other members that only
// need to be initialized once.
// - This should only be called once.
void IslandWindow::_coldInitialize()
{
_source = DesktopWindowXamlSource{};
@@ -378,9 +388,32 @@ void IslandWindow::Initialize()
// We don't really care if this failed or not.
TerminalTrySetTransparentBackground(true);
}
void IslandWindow::_warmInitialize()
{
// re-add the pointer to us to our HWND's user data, so that we can start
// getting window proc callbacks again.
_setupUserData();
// Manually ask how we want to be created.
if (_pfnCreateCallback)
{
til::rect rc{ GetWindowRect() };
_pfnCreateCallback(_window.get(), rc);
}
// Don't call IslandWindow::OnSize - that will set the Width/Height members
// of the _rootGrid. However, NonClientIslandWindow doesn't use those! If you set them, here,
// the contents of the window will never resize.
UpdateWindow(_window.get());
ForceResize();
}
void IslandWindow::OnSize(const UINT width, const UINT height)
{
// NOTE: This _isn't_ called by NonClientIslandWindow::OnSize. The
// NonClientIslandWindow has very different logic for positioning the
// DesktopWindowXamlSource inside its HWND.
// update the interop window size
SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW | SWP_NOACTIVATE);

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include "BaseWindow.h"
#include <til/winrt.h>
void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept;
@@ -21,6 +22,9 @@ public:
virtual void MakeWindow() noexcept;
virtual void Close();
virtual void Refrigerate() noexcept;
virtual void OnSize(const UINT width, const UINT height);
HWND GetInteropHandle() const;
@@ -37,7 +41,7 @@ public:
virtual til::rect GetNonClientFrame(const UINT dpi) const noexcept;
virtual til::size GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept;
virtual void Initialize();
virtual bool Initialize();
void SetCreateCallback(std::function<void(const HWND, const til::rect&)> pfn) noexcept;
@@ -114,6 +118,9 @@ protected:
RECT _rcWorkBeforeFullscreen{};
UINT _dpiBeforeFullscreen{ 96 };
void _coldInitialize();
void _warmInitialize();
virtual void _SetIsBorderless(const bool borderlessEnabled);
virtual void _SetIsFullscreen(const bool fullscreenEnabled);
void _RestoreFullscreenPosition(const RECT& rcWork);

View File

@@ -57,6 +57,12 @@ static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" };
void NonClientIslandWindow::MakeWindow() noexcept
{
if (_window)
{
// no-op if we already have a window.
return;
}
IslandWindow::MakeWindow();
static auto dragBarWindowClass{ []() {
@@ -335,9 +341,17 @@ void NonClientIslandWindow::OnAppInitialized()
IslandWindow::OnAppInitialized();
}
void NonClientIslandWindow::Initialize()
void NonClientIslandWindow::Refrigerate() noexcept
{
IslandWindow::Initialize();
IslandWindow::Refrigerate();
// Revoke all our XAML callbacks.
_callbacks = {};
}
bool NonClientIslandWindow::Initialize()
{
const bool coldInit = IslandWindow::Initialize();
_UpdateFrameMargins();
@@ -349,6 +363,7 @@ void NonClientIslandWindow::Initialize()
Controls::RowDefinition contentRow{};
titlebarRow.Height(GridLengthHelper::Auto());
_rootGrid.RowDefinitions().Clear();
_rootGrid.RowDefinitions().Append(titlebarRow);
_rootGrid.RowDefinitions().Append(contentRow);
@@ -356,8 +371,8 @@ void NonClientIslandWindow::Initialize()
_titlebar = winrt::TerminalApp::TitlebarControl{ reinterpret_cast<uint64_t>(GetHandle()) };
_dragBar = _titlebar.DragBar();
_dragBar.SizeChanged({ this, &NonClientIslandWindow::_OnDragBarSizeChanged });
_rootGrid.SizeChanged({ this, &NonClientIslandWindow::_OnDragBarSizeChanged });
_callbacks.dragBarSizeChanged = _dragBar.SizeChanged(winrt::auto_revoke, { this, &NonClientIslandWindow::_OnDragBarSizeChanged });
_callbacks.rootGridSizeChanged = _rootGrid.SizeChanged(winrt::auto_revoke, { this, &NonClientIslandWindow::_OnDragBarSizeChanged });
_rootGrid.Children().Append(_titlebar);
@@ -366,7 +381,15 @@ void NonClientIslandWindow::Initialize()
// GH#3440 - When the titlebar is loaded (officially added to our UI tree),
// then make sure to update its visual state to reflect if we're in the
// maximized state on launch.
_titlebar.Loaded([this](auto&&, auto&&) { _OnMaximizeChange(); });
_callbacks.titlebarLoaded = _titlebar.Loaded(winrt::auto_revoke, [this](auto&&, auto&&) { _OnMaximizeChange(); });
// LOAD BEARING: call _ResizeDragBarWindow to update the position of our
// XAML island to reflect our current bounds. In the case of a "warm init"
// (i.e. re-using an existing window), we need to manually update the
// island's position to fill the new window bounds.
_ResizeDragBarWindow();
return coldInit;
}
// Method Description:

View File

@@ -31,6 +31,8 @@ public:
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
void Refrigerate() noexcept override;
virtual void Close() override;
void MakeWindow() noexcept override;
virtual void OnSize(const UINT width, const UINT height) override;
@@ -40,7 +42,7 @@ public:
virtual til::rect GetNonClientFrame(UINT dpi) const noexcept override;
virtual til::size GetTotalNonClientExclusiveSize(UINT dpi) const noexcept override;
void Initialize() override;
bool Initialize() override;
void OnAppInitialized() override;
void SetContent(winrt::Windows::UI::Xaml::UIElement content) override;
@@ -95,4 +97,11 @@ private:
void _UpdateFrameMargins() const noexcept;
void _UpdateMaximizedState();
void _UpdateIslandPosition(const UINT windowWidth, const UINT windowHeight);
struct Revokers
{
winrt::Windows::UI::Xaml::Controls::Border::SizeChanged_revoker dragBarSizeChanged;
winrt::Windows::UI::Xaml::Controls::Grid::SizeChanged_revoker rootGridSizeChanged;
winrt::TerminalApp::TitlebarControl::Loaded_revoker titlebarLoaded;
} _callbacks{};
};

View File

@@ -111,7 +111,8 @@ bool WindowEmperor::HandleCommandlineArgs()
const auto result = _manager.ProposeCommandline(eventArgs, isolatedMode);
if (result.ShouldCreateWindow())
const bool makeWindow = result.ShouldCreateWindow();
if (makeWindow)
{
_createNewWindowThread(Remoting::WindowRequestedArgs{ result, eventArgs });
@@ -127,7 +128,7 @@ bool WindowEmperor::HandleCommandlineArgs()
}
}
return result.ShouldCreateWindow();
return makeWindow;
}
void WindowEmperor::WaitForWindows()
@@ -143,7 +144,38 @@ void WindowEmperor::WaitForWindows()
void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs& args)
{
Remoting::Peasant peasant{ _manager.CreatePeasant(args) };
auto window{ std::make_shared<WindowThread>(_app.Logic(), args, _manager, peasant) };
std::shared_ptr<WindowThread> window{ nullptr };
// FIRST: Attempt to reheat an existing window that we refrigerated for
// later. If we have an existing unused window, then we don't need to create
// a new WindowThread & HWND for this request.
{ // Add a scope to minimize lock duration.
auto fridge{ _oldThreads.lock() };
if (!fridge->empty())
{
// Look at that, a refrigerated thread ready to be used. Let's use that!
window = std::move(fridge->back());
fridge->pop_back();
}
}
// Did we find one?
if (window)
{
// Cool! Let's increment the number of active windows, and re-heat it.
_windowThreadInstances.fetch_add(1, std::memory_order_relaxed);
window->Microwave(args, peasant);
// This will unblock the event we're waiting on in KeepWarm, and the
// window thread (started below) will continue through it's loop
return;
}
// At this point, there weren't any pending refrigerated threads we could
// just use. That's fine. Let's just go create a new one.
window = std::make_shared<WindowThread>(_app.Logic(), args, _manager, peasant);
std::weak_ptr<WindowEmperor> weakThis{ weak_from_this() };
// Increment our count of window instances _now_, immediately. We're
@@ -164,33 +196,66 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
std::thread t([weakThis, window]() {
try
{
const auto decrementWindowCount = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_decrementWindowCount();
}
});
auto removeWindow = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_removeWindow(window->PeasantID());
}
});
window->CreateHost();
if (auto self{ weakThis.lock() })
{
self->_windowStartedHandlerPostXAML(window);
}
while (window->KeepWarm())
{
// Now that the window is ready to go, we can add it to our list of windows,
// because we know it will be well behaved.
//
// Be sure to only modify the list of windows under lock.
window->RunMessagePump();
if (auto self{ weakThis.lock() })
{
auto lockedWindows{ self->_windows.lock() };
lockedWindows->push_back(window);
}
auto removeWindow = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_removeWindow(window->PeasantID());
}
});
// Manually trigger the cleanup callback. This will ensure that we
// remove the window from our list of windows, before we release the
// AppHost (and subsequently, the host's Logic() member that we use
// elsewhere).
removeWindow.reset();
auto decrementWindowCount = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_decrementWindowCount();
}
});
window->RunMessagePump();
// Manually trigger the cleanup callback. This will ensure that we
// remove the window from our list of windows, before we release the
// AppHost (and subsequently, the host's Logic() member that we use
// elsewhere).
removeWindow.reset();
// On Windows 11, we DONT want to refrigerate the window. There,
// we can just close it like normal. Break out of the loop, so
// we don't try to put this window in the fridge.
if (Utils::IsWindows11())
{
decrementWindowCount.reset();
break;
}
else
{
window->Refrigerate();
decrementWindowCount.reset();
if (auto self{ weakThis.lock() })
{
auto fridge{ self->_oldThreads.lock() };
fridge->push_back(window);
}
}
}
// Now that we no longer care about this thread's window, let it
// release it's app host and flush the rest of the XAML queue.
@@ -224,15 +289,6 @@ void WindowEmperor::_windowStartedHandlerPostXAML(const std::shared_ptr<WindowTh
// the Terminal window, making it visible BEFORE the XAML island is actually
// ready to be drawn. We want to wait till the app's Initialized event
// before we make the window visible.
// Now that the window is ready to go, we can add it to our list of windows,
// because we know it will be well behaved.
//
// Be sure to only modify the list of windows under lock.
{
auto lockedWindows{ _windows.lock() };
lockedWindows->push_back(sender);
}
}
void WindowEmperor::_removeWindow(uint64_t senderID)
@@ -518,8 +574,22 @@ LRESULT WindowEmperor::_messageHandler(UINT const message, WPARAM const wParam,
return DefWindowProc(_window.get(), message, wParam, lParam);
}
// Close the Terminal application. This will exit the main thread for the
// emperor itself. We should probably only ever be called when we have no
// windows left, and we don't want to keep running anymore. This will discard
// all our refrigerated windows. If we try to use XAML on Windows 10 after this,
// we'll undoubtedly crash.
winrt::fire_and_forget WindowEmperor::_close()
{
{
auto fridge{ _oldThreads.lock() };
for (auto& window : *fridge)
{
window->ThrowAway();
}
fridge->clear();
}
// Important! Switch back to the main thread for the emperor. That way, the
// quit will go to the emperor's message pump.
co_await wil::resume_foreground(_dispatcher);

View File

@@ -43,6 +43,8 @@ private:
til::shared_mutex<std::vector<std::shared_ptr<WindowThread>>> _windows;
std::atomic<uint32_t> _windowThreadInstances;
til::shared_mutex<std::vector<std::shared_ptr<WindowThread>>> _oldThreads;
std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
winrt::event_token _WindowCreatedToken;

View File

@@ -18,12 +18,18 @@ WindowThread::WindowThread(winrt::TerminalApp::AppLogic logic,
void WindowThread::CreateHost()
{
// Calling this while refrigerated won't work.
// * We can't re-initialize our winrt apartment.
// * AppHost::Initialize has to be done on the "UI" thread.
assert(_warmWindow == nullptr);
// Start the AppHost HERE, on the actual thread we want XAML to run on
_host = std::make_unique<::AppHost>(_appLogic,
_args,
_manager,
_peasant);
_host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); });
_UpdateSettingsRequestedToken = _host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); });
winrt::init_apartment(winrt::apartment_type::single_threaded);
@@ -40,9 +46,29 @@ int WindowThread::RunMessagePump()
return exitCode;
}
void WindowThread::_pumpRemainingXamlMessages()
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
void WindowThread::RundownForExit()
{
_host->Close();
if (_host)
{
_host->UpdateSettingsRequested(_UpdateSettingsRequestedToken);
_host->Close();
}
if (_warmWindow)
{
// If we have a _warmWindow, we're a refrigerated thread without a
// AppHost in control of the window. Manually close the window
// ourselves, to free the DesktopWindowXamlSource.
_warmWindow->Close();
}
// !! LOAD BEARING !!
//
@@ -52,13 +78,100 @@ void WindowThread::RundownForExit()
// exiting. So do that now. If you don't, then the last tab to close
// will never actually destruct the last tab / TermControl / ControlCore
// / renderer.
_pumpRemainingXamlMessages();
}
void WindowThread::ThrowAway()
{
// raise the signal to unblock KeepWarm. We won't have a host, so we'll drop
// out of the message loop to eventually RundownForExit.
//
// This should only be called when the app is fully quitting. After this is
// called on any thread, on win10, we won't be able to call into XAML
// anymore.
_microwaveBuzzer.notify_one();
}
// Method Description:
// - Check if we should keep this window alive, to try it's message loop again.
// If we were refrigerated for later, then this will block the thread on the
// _microwaveBuzzer. We'll sit there like that till the emperor decides if
// they want to re-use this window thread for a new window.
// Return Value:
// - true IFF we should enter this thread's message loop
// INVARIANT: This must be called on our "ui thread", our window thread.
bool WindowThread::KeepWarm()
{
if (_host != nullptr)
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
// We're currently hot
return true;
}
// If we're refrigerated, then wait on the microwave signal, which will be
// raised when we get re-heated by another thread to reactivate us.
if (_warmWindow != nullptr)
{
std::unique_lock lock(_microwave);
_microwaveBuzzer.wait(lock);
// If ThrowAway() was called, then the buzzer will be signalled without
// setting a new _host. In that case, the app is quitting, for real. We
// just want to exit with false.
const bool reheated = _host != nullptr;
if (reheated)
{
_UpdateSettingsRequestedToken = _host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); });
// Re-initialize the host here, on the window thread
_host->Initialize();
}
return reheated;
}
else
{
return false;
}
}
// Method Description:
// - "Refrigerate" this thread for later reuse. This will refrigerate the window
// itself, and tear down our current app host. We'll save our window for
// later. We'll also pump out the existing message from XAML, before
// returning. After we return, the emperor will add us to the list of threads
// that can be re-used.
void WindowThread::Refrigerate()
{
_host->UpdateSettingsRequested(_UpdateSettingsRequestedToken);
// keep a reference to the HWND and DesktopWindowXamlSource alive.
_warmWindow = std::move(_host->Refrigerate());
// rundown remaining messages before destructing the app host
_pumpRemainingXamlMessages();
_host = nullptr;
}
// Method Description:
// - "Reheat" this thread for reuse. We'll build a new AppHost, and pass in the
// existing window to it. We'll then trigger the _microwaveBuzzer, so KeepWarm
// (which is on the UI thread) will get unblocked, and we can initialize this
// window.
void WindowThread::Microwave(
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
winrt::Microsoft::Terminal::Remoting::Peasant peasant)
{
_peasant = std::move(peasant);
_args = std::move(args);
_host = std::make_unique<::AppHost>(_appLogic,
_args,
_manager,
_peasant,
std::move(_warmWindow));
// raise the signal to unblock KeepWarm and start the window message loop again.
_microwaveBuzzer.notify_one();
}
winrt::TerminalApp::TerminalWindow WindowThread::Logic()

View File

@@ -17,6 +17,13 @@ public:
int RunMessagePump();
void RundownForExit();
bool KeepWarm();
void Refrigerate();
void Microwave(
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
winrt::Microsoft::Terminal::Remoting::Peasant peasant);
void ThrowAway();
uint64_t PeasantID();
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
@@ -29,6 +36,12 @@ private:
winrt::Microsoft::Terminal::Remoting::WindowManager _manager{ nullptr };
std::unique_ptr<::AppHost> _host{ nullptr };
winrt::event_token _UpdateSettingsRequestedToken;
std::unique_ptr<::IslandWindow> _warmWindow{ nullptr };
std::mutex _microwave;
std::condition_variable _microwaveBuzzer;
int _messagePump();
void _pumpRemainingXamlMessages();
};

View File

@@ -115,4 +115,6 @@ namespace Microsoft::Console::Utils
// Same deal, but in TerminalPage::_evaluatePathForCwd
std::wstring EvaluateStartingDirectory(std::wstring_view cwd, std::wstring_view startingDirectory);
bool IsWindows11() noexcept;
}

View File

@@ -852,3 +852,22 @@ std::wstring Utils::EvaluateStartingDirectory(
}
return resultPath;
}
bool Utils::IsWindows11() noexcept
{
static const bool isWindows11 = []() noexcept {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
return isWindows11;
}