mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 14:50:55 +00:00
Refrigerate our threads for later reuse (#15424)
This is my proposed solution to #15384. Basically, the issue is that we cannot ever close a `DesktopWindowXamlSource` ("DWXS"). If we do, then any other thread that tries to access XAML metadata will explode, which happens frequently. A DWXS is inextricably linked to an HWND. That means we have to not only reuse DWXS's, but the HWNDs themselves. XAML also isn't agile, so we've got to keep the `thread` that the DWXS was started on alive as well. To do this, we're going to introduce the ability to "refrigerate" and "reheat" window threads. * A window thread is "**hot**" if it's actively got a window, and is pumping window messages, and generally, is a normal thing. * When a window is closed, we need to "**refrigerate**" it's `WindowThread` and `IslandWindow`. `WindowEmperor` will take care of tracking the threads that are refrigerated. * When a new window is requested, the Emperor first try to "**reheat**"/"**microwave**" a refrigerated thread. When a thread gets reheated, we'll create a new AppHost (and `TerminalWindow`/`Page`), and we'll use the _existing_ `IslandWindow` for that instance. <sub>The metaphor is obviously ridiculous, but _you get it_ so who cares.</sub> In this way, we'll keep all the windows we've ever created around in memory, for later reuse. This means that the leak goes from (~12MB x number of windows closed) to (~12MB x maximum number of simultaneously open Terminal windows). It's still not good. We won't do this on Windows 11, because the bug that is the fundamental premise of this issue is fixed already in the OS. I'm not 100% confident in this yet. * [x] There's still a d3d leak of some sort on exit in debug builds. (maybe #15306 related) * havent seen this in a while. Must have been a issue in an earlier revision. * [x] I need to validate more on Windows 11 * [x] **BAD**: Closing the last tab on Windows 11 doesn't close the window * [x] **BAD**: Closing a window on Windows 11 doesn't close the window - it just closes the one tab item and keeps on choochin' * [x] **BAD**: Close last tab, open new one, attempt to close window - ALL windows go \*poof\*. Cause of course. No break into post-mortem either. * [x] more comments * [ ] maybe a diagram * [x] Restoring windows is at the wrong place entirely? I once reopened the Terminal with two persisted windows, and it created one at 0,0 * [x] Remaining code TODO!s: 0 (?) * [ ] "warm-reloading" `useTabsInTitlebar` (change while terminal is running after closing a window, open a new one) REALLY doesn't work. Obviously restores the last kind of window. Yike. is all about #15384 closes #15410 along the way. Might fork that fix off.
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