diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 682023fd96c..8084acd4f2c 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -30,6 +30,7 @@ DERR dlldata DONTADDTORECENT DWORDLONG +DWMWA endfor enumset environstrings @@ -154,6 +155,7 @@ serializer SETVERSION SHELLEXECUTEINFOW shobjidl +SHOWDEFAULT SHOWHIDE SHOWMINIMIZED SHOWTIP diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 63d1c4e784d..8cbd0ff51b6 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -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); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index a5c0eb05a7b..7d659b02b09 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -122,6 +122,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler FullscreenChanged; event Windows.Foundation.TypedEventHandler ChangeMaximizeRequested; event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; + event Windows.Foundation.TypedEventHandler Initialized; event Windows.Foundation.TypedEventHandler RaiseVisualBell; event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 2d0078c3747..9d6e8369b45 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -659,7 +659,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::_CompleteInitialization() + winrt::fire_and_forget TerminalPage::_CompleteInitialization() { _startupState = StartupState::Initialized; @@ -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); + } } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 55021a1e896..f3172daf411 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -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); @@ -390,7 +390,7 @@ namespace winrt::TerminalApp::implementation void _StartInboundListener(); - void _CompleteInitialization(); + winrt::fire_and_forget _CompleteInitialization(); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 90d317c1d98..90d182b8d0f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -51,7 +51,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler FocusModeChanged; event Windows.Foundation.TypedEventHandler FullscreenChanged; event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; - event Windows.Foundation.TypedEventHandler Initialized; + event Windows.Foundation.TypedEventHandler Initialized; event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler RenameWindowRequested; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 8174f898011..c5847b91a23 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -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, @@ -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 }); @@ -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 @@ -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(); @@ -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); +} diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index fd95d7a406b..737c62c6cd9 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -31,6 +31,7 @@ class AppHost bool _shouldCreateWindow{ false }; bool _useNonClientArea{ false }; + RECT _proposedRect{}; std::optional> _getWindowLayoutThrottler; winrt::Windows::Foundation::IAsyncAction _SaveWindowLayouts(); @@ -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&, @@ -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); @@ -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; winrt::event_token _ReAddNotificationIconToken; winrt::event_token _NotificationIconPressedToken; @@ -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; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index a1a79a30d7c..a995ba437c7 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -112,7 +112,7 @@ void IslandWindow::Close() // window. // Return Value: // - -void IslandWindow::SetCreateCallback(std::function pfn) noexcept +void IslandWindow::SetCreateCallback(std::function pfn) noexcept { _pfnCreateCallback = pfn; } @@ -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()); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index dcd805dc52f..35c1b4dc90c 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -37,7 +37,7 @@ class IslandWindow : virtual void Initialize(); - void SetCreateCallback(std::function pfn) noexcept; + void SetCreateCallback(std::function pfn) noexcept; void SetSnapDimensionCallback(std::function pfn) noexcept; void FocusModeChanged(const bool focusMode); @@ -94,7 +94,7 @@ class IslandWindow : winrt::Windows::UI::Xaml::Controls::Grid _rootGrid; wil::com_ptr _taskbar; - std::function _pfnCreateCallback; + std::function _pfnCreateCallback; std::function _pfnSnapDimensionCallback; void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;