Skip to content

Commit

Permalink
Hide the window from DWM until we're finished with initialization (#1…
Browse files Browse the repository at this point in the history
…2979)

When we start up, our window is initially just a frame with a transparent content area. We're gonna do all this startup init on the UI thread, so the UI won't actually paint till it's all done. This results in a few frames where the frame is visible, before the page paints for the first time, before any tabs appears, etc. 

To mitigate this, we're gonna wait for the UI thread to finish everything it's gotta do for the initial init, and _then_ fire our Initialized event. By waiting for everything else to finish (`CoreDispatcherPriority::Low`), we let all the tabs and panes actually get created. In the window layer, we're gonna ~cloak~ just not show the window till this event is fired, so we don't actually see this frame until we're actually all ready to go. **This will result in the window seemingly not loading as fast**, but it will actually take exactly the same amount of time before it's usable. 

I also experimented with drawing a solid BG color before the initialization is finished. However, there are still a few frames after the frame is displayed before the XAML content first draws, so that didn't actually resolve any issues.

* [x] Closes #11561
* [x] Tested manually
* [x] I work here.
* [x] Accidentally also closes #9053. By switching the initial call from `ShowWindow(SW_SHOW)` to `ShowWindow(SW_SHOWDEFAULT)`, we actually obey the startup info now.
  • Loading branch information
zadjii-msft authored May 6, 2022
1 parent 60a4837 commit 14098d7
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spelling/allow/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ DERR
dlldata
DONTADDTORECENT
DWORDLONG
DWMWA
endfor
enumset
environstrings
Expand Down Expand Up @@ -154,6 +155,7 @@ serializer
SETVERSION
SHELLEXECUTEINFOW
shobjidl
SHOWDEFAULT
SHOWHIDE
SHOWMINIMIZED
SHOWTIP
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);
FORWARDED_TYPED_EVENT(ChangeMaximizeRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, ChangeMaximizeRequested);
FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged);
FORWARDED_TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, Initialized);
FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell);
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.idl
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ChangeMaximizeRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> Initialized;
event Windows.Foundation.TypedEventHandler<Object, Object> RaiseVisualBell;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
Expand Down
49 changes: 47 additions & 2 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void TerminalPage::_CompleteInitialization()
winrt::fire_and_forget TerminalPage::_CompleteInitialization()
{
_startupState = StartupState::Initialized;

Expand All @@ -679,10 +679,55 @@ namespace winrt::TerminalApp::implementation
if (_tabs.Size() == 0 && !(_shouldStartInboundListener || _isEmbeddingInboundListener))
{
_LastTabClosedHandlers(*this, nullptr);
co_return;
}
else
{
_InitializedHandlers(*this, nullptr);
// GH#11561: When we start up, our window is initially just a frame
// with a transparent content area. We're gonna do all this startup
// init on the UI thread, so the UI won't actually paint till it's
// all done. This results in a few frames where the frame is
// visible, before the page paints for the first time, before any
// tabs appears, etc.
//
// To mitigate this, we're gonna wait for the UI thread to finish
// everything it's gotta do for the initial init, and _then_ fire
// our Initialized event. By waiting for everything else to finish
// (CoreDispatcherPriority::Low), we let all the tabs and panes
// actually get created. In the window layer, we're gonna cloak the
// window till this event is fired, so we don't actually see this
// frame until we're actually all ready to go.
//
// This will result in the window seemingly not loading as fast, but
// it will actually take exactly the same amount of time before it's
// usable.
//
// We also experimented with drawing a solid BG color before the
// initialization is finished. However, there are still a few frames
// after the frame is displayed before the XAML content first draws,
// so that didn't actually resolve any issues.

auto weak{ get_weak() };

// Switch to the BG thread -
co_await winrt::resume_background();

if (auto self{ weak.get() })
{
// Then enqueue the rest of this function for after the UI thread settles.
co_await wil::resume_foreground(self->Dispatcher(), CoreDispatcherPriority::Low);
}
else
{
// We don't exist anymore? Well, we probably don't need to fire
// off an Initialized event then...
co_return;
}

if (auto self{ weak.get() })
{
self->_InitializedHandlers(*self, nullptr);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(AlwaysOnTopChanged, IInspectable, IInspectable);
TYPED_EVENT(RaiseVisualBell, IInspectable, IInspectable);
TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(Initialized, IInspectable, IInspectable);
TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable);
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable);
Expand Down Expand Up @@ -390,7 +390,7 @@ namespace winrt::TerminalApp::implementation

void _StartInboundListener();

void _CompleteInitialization();
winrt::fire_and_forget _CompleteInitialization();

void _FocusActiveControl(IInspectable sender, IInspectable eventArgs);

Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/TerminalPage.idl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.RoutedEventArgs> Initialized;
event Windows.Foundation.TypedEventHandler<Object, Object> Initialized;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
Expand Down
52 changes: 45 additions & 7 deletions src/cascadia/WindowsTerminal/AppHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ AppHost::AppHost() noexcept :
auto pfn = std::bind(&AppHost::_HandleCreateWindow,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3);
std::placeholders::_2);
_window->SetCreateCallback(pfn);

_window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::AppLogic::CalcSnappedDimension,
Expand Down Expand Up @@ -379,6 +378,7 @@ void AppHost::Initialize()
_revokers.FullscreenChanged = _logic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged });
_revokers.FocusModeChanged = _logic.FocusModeChanged(winrt::auto_revoke, { this, &AppHost::_FocusModeChanged });
_revokers.AlwaysOnTopChanged = _logic.AlwaysOnTopChanged(winrt::auto_revoke, { this, &AppHost::_AlwaysOnTopChanged });
_revokers.Initialized = _logic.Initialized(winrt::auto_revoke, { this, &AppHost::_AppInitializedHandler });
_revokers.RaiseVisualBell = _logic.RaiseVisualBell(winrt::auto_revoke, { this, &AppHost::_RaiseVisualBell });
_revokers.SystemMenuChangeRequested = _logic.SystemMenuChangeRequested(winrt::auto_revoke, { this, &AppHost::_SystemMenuChangeRequested });
_revokers.ChangeMaximizeRequested = _logic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested });
Expand Down Expand Up @@ -520,12 +520,31 @@ LaunchPosition AppHost::_GetWindowLaunchPosition()
return pos;
}

// Method Description:
// - Callback for when the window is first being created (during WM_CREATE).
// Stash the proposed size for later. We'll need that once we're totally
// initialized, so that we can show the window in the right position *when we
// want to show it*. If we did the _initialResizeAndRepositionWindow work now,
// it would have no effect, because the window is not yet visible.
// Arguments:
// - hwnd: The HWND of the window we're about to create.
// - proposedRect: The location and size of the window that we're about to
// create. We'll use this rect to determine which monitor the window is about
// to appear on.
void AppHost::_HandleCreateWindow(const HWND /* hwnd */, RECT proposedRect)
{
// GH#11561: Hide the window until we're totally done being initialized.
// More commentary in TerminalPage::_CompleteInitialization
_proposedRect = proposedRect;
}

// Method Description:
// - Resize the window we're about to create to the appropriate dimensions, as
// specified in the settings. This will be called during the handling of
// WM_CREATE. We'll load the settings for the app, then get the proposed size
// of the terminal from the app. Using that proposed size, we'll resize the
// window we're creating, so that it'll match the values in the settings.
// specified in the settings. This is called once the app has finished it's
// initial setup, once we have created all the tabs, panes, etc. We'll load
// the settings for the app, then get the proposed size of the terminal from
// the app. Using that proposed size, we'll resize the window we're creating,
// so that it'll match the values in the settings.
// Arguments:
// - hwnd: The HWND of the window we're about to create.
// - proposedRect: The location and size of the window that we're about to
Expand All @@ -534,7 +553,7 @@ LaunchPosition AppHost::_GetWindowLaunchPosition()
// - launchMode: A LaunchMode enum reference that indicates the launch mode
// Return Value:
// - None
void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, LaunchMode& launchMode)
void AppHost::_initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, LaunchMode& launchMode)
{
launchMode = _logic.GetLaunchMode();

Expand Down Expand Up @@ -1556,3 +1575,22 @@ void AppHost::_CloseRequested(const winrt::Windows::Foundation::IInspectable& /*
const auto pos = _GetWindowLaunchPosition();
_logic.CloseWindow(pos);
}

void AppHost::_AppInitializedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*arg*/)
{
// GH#11561: We're totally done being initialized. Resize the window to
// match the initial settings, and then call ShowWindow to finally make us
// visible.

LaunchMode launchMode{};
_initialResizeAndRepositionWindow(_window->GetHandle(), _proposedRect, launchMode);

auto nCmdShow = SW_SHOWDEFAULT;
if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode))
{
nCmdShow = SW_MAXIMIZE;
}

ShowWindow(_window->GetHandle(), nCmdShow);
}
10 changes: 9 additions & 1 deletion src/cascadia/WindowsTerminal/AppHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AppHost

bool _shouldCreateWindow{ false };
bool _useNonClientArea{ false };
RECT _proposedRect{};

std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
winrt::Windows::Foundation::IAsyncAction _SaveWindowLayouts();
Expand All @@ -39,7 +40,7 @@ class AppHost
void _HandleCommandlineArgs();
winrt::Microsoft::Terminal::Settings::Model::LaunchPosition _GetWindowLaunchPosition();

void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
void _HandleCreateWindow(const HWND hwnd, RECT proposedRect);
void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::UIElement& arg);
void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&,
Expand All @@ -52,6 +53,9 @@ class AppHost
const winrt::Windows::Foundation::IInspectable& arg);
void _AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _AppInitializedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);

void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const til::point coord, const int32_t delta);
Expand Down Expand Up @@ -121,6 +125,9 @@ class AppHost
const winrt::Windows::Foundation::IInspectable& args);
void _HideNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);

void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);

std::unique_ptr<NotificationIcon> _notificationIcon;
winrt::event_token _ReAddNotificationIconToken;
winrt::event_token _NotificationIconPressedToken;
Expand All @@ -147,6 +154,7 @@ class AppHost
winrt::TerminalApp::AppLogic::FullscreenChanged_revoker FullscreenChanged;
winrt::TerminalApp::AppLogic::FocusModeChanged_revoker FocusModeChanged;
winrt::TerminalApp::AppLogic::AlwaysOnTopChanged_revoker AlwaysOnTopChanged;
winrt::TerminalApp::AppLogic::Initialized_revoker Initialized;
winrt::TerminalApp::AppLogic::RaiseVisualBell_revoker RaiseVisualBell;
winrt::TerminalApp::AppLogic::SystemMenuChangeRequested_revoker SystemMenuChangeRequested;
winrt::TerminalApp::AppLogic::ChangeMaximizeRequested_revoker ChangeMaximizeRequested;
Expand Down
14 changes: 4 additions & 10 deletions src/cascadia/WindowsTerminal/IslandWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void IslandWindow::Close()
// window.
// Return Value:
// - <none>
void IslandWindow::SetCreateCallback(std::function<void(const HWND, const RECT, LaunchMode& launchMode)> pfn) noexcept
void IslandWindow::SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept
{
_pfnCreateCallback = pfn;
}
Expand Down Expand Up @@ -154,19 +154,13 @@ void IslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexce
rc.right = rc.left + pcs->cx;
rc.bottom = rc.top + pcs->cy;

auto launchMode = LaunchMode::DefaultMode;
if (_pfnCreateCallback)
{
_pfnCreateCallback(_window.get(), rc, launchMode);
_pfnCreateCallback(_window.get(), rc);
}

auto nCmdShow = SW_SHOW;
if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode))
{
nCmdShow = SW_MAXIMIZE;
}

ShowWindow(_window.get(), nCmdShow);
// GH#11561: DO NOT call ShowWindow here. The AppHost will call ShowWindow
// once the app has completed its initialization.

UpdateWindow(_window.get());

Expand Down
4 changes: 2 additions & 2 deletions src/cascadia/WindowsTerminal/IslandWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class IslandWindow :

virtual void Initialize();

void SetCreateCallback(std::function<void(const HWND, const RECT, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode)> pfn) noexcept;
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
void SetSnapDimensionCallback(std::function<float(bool widthOrHeight, float dimension)> pfn) noexcept;

void FocusModeChanged(const bool focusMode);
Expand Down Expand Up @@ -94,7 +94,7 @@ class IslandWindow :
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
wil::com_ptr<ITaskbarList3> _taskbar;

std::function<void(const HWND, const RECT, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode)> _pfnCreateCallback;
std::function<void(const HWND, const RECT)> _pfnCreateCallback;
std::function<float(bool, float)> _pfnSnapDimensionCallback;

void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
Expand Down

0 comments on commit 14098d7

Please sign in to comment.