mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-10 16:21:06 +00:00
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/non-terminal-panes-2023
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user