diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 9b0abe8904a..d2545d0b3ab 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -623,7 +623,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::_CompleteInitialization() + winrt::fire_and_forget TerminalPage::_CompleteInitialization() { _startupState = StartupState::Initialized; @@ -643,10 +643,39 @@ namespace winrt::TerminalApp::implementation if (_tabs.Size() == 0 && !(_shouldStartInboundListener || _isEmbeddingInboundListener)) { _LastTabClosedHandlers(*this, winrt::make(false)); + 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. + Dispatcher().RunAsync(CoreDispatcherPriority::Low, [weak = get_weak()]() { + if (auto self{ weak.get() }) + { + self->_InitializedHandlers(*self, nullptr); + } + }); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index b178389ec2b..423ffea1b36 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -180,7 +180,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(SummonWindowRequested, IInspectable, IInspectable); @@ -447,7 +447,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 5c8a151b909..92052ed1677 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -89,7 +89,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/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index adb2c9b6638..5c923e25df6 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -203,6 +203,8 @@ namespace winrt::TerminalApp::implementation // These are events that are handled by the TerminalPage, but are // exposed through the AppLogic. This macro is used to forward the event // directly to them. + FORWARDED_TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, Initialized); + FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent); FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged); FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 825afa44c4a..912663e4a0b 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -114,6 +114,8 @@ namespace TerminalApp // information. void DismissDialog(); + event Windows.Foundation.TypedEventHandler Initialized; + event Windows.Foundation.TypedEventHandler SetTitleBarContent; event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 2c5ba7ab153..633b3519e50 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -60,8 +60,7 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic, auto pfn = std::bind(&AppHost::_HandleCreateWindow, this, std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3); + std::placeholders::_2); _window->SetCreateCallback(pfn); // Event handlers: @@ -339,6 +338,7 @@ void AppHost::Initialize() _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 }); _revokers.FullscreenChanged = _windowLogic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged }); _revokers.FocusModeChanged = _windowLogic.FocusModeChanged(winrt::auto_revoke, { this, &AppHost::_FocusModeChanged }); @@ -510,12 +510,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 */, const til::rect& proposedRect) +{ + // GH#11561: Hide the window until we're totally done being initialized. + // More commentary in TerminalPage::_CompleteInitialization + _initialResizeAndRepositionWindow(_window->GetHandle(), proposedRect, _launchMode); +} + // 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 @@ -524,7 +543,7 @@ LaunchPosition AppHost::_GetWindowLaunchPosition() // - launchMode: A LaunchMode enum reference that indicates the launch mode // Return Value: // - None -void AppHost::_HandleCreateWindow(const HWND hwnd, til::rect proposedRect, LaunchMode& launchMode) +void AppHost::_initialResizeAndRepositionWindow(const HWND hwnd, til::rect proposedRect, LaunchMode& launchMode) { launchMode = _windowLogic.GetLaunchMode(); @@ -1211,6 +1230,42 @@ void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspect } } +winrt::fire_and_forget AppHost::_WindowInitializedHandler(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. + + auto nCmdShow = SW_SHOW; + if (WI_IsFlagSet(_launchMode, LaunchMode::MaximizedMode)) + { + nCmdShow = SW_MAXIMIZE; + } + + // For inexplicable reasons, again, hop to the BG thread, then back to the + // UI thread. This is shockingly load bearing - without this, then + // sometimes, we'll _still_ show the HWND before the XAML island actually + // paints. + co_await winrt::resume_background(); + co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher(), winrt::Windows::UI::Core::CoreDispatcherPriority::Low); + ShowWindow(_window->GetHandle(), nCmdShow); + + // If we didn't start the window hidden (in one way or another), then try to + // pull ourselves to the foreground. Don't necessarily do a whole "summon", + // we don't really want to STEAL foreground if someone rightfully took it + + const bool noForeground = nCmdShow == SW_SHOWMINIMIZED || + nCmdShow == SW_SHOWNOACTIVATE || + nCmdShow == SW_SHOWMINNOACTIVE || + nCmdShow == SW_SHOWNA || + nCmdShow == SW_FORCEMINIMIZE; + if (!noForeground) + { + SetForegroundWindow(_window->GetHandle()); + } +} + winrt::TerminalApp::TerminalWindow AppHost::Logic() { return _windowLogic; diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index dc2c3da91cc..6fa0c5c2191 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -39,6 +39,7 @@ class AppHost winrt::com_ptr _desktopManager{ nullptr }; bool _useNonClientArea{ false }; + winrt::Microsoft::Terminal::Settings::Model::LaunchMode _launchMode{}; std::shared_ptr> _showHideWindowThrottler; @@ -47,7 +48,8 @@ class AppHost void _HandleCommandlineArgs(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args); winrt::Microsoft::Terminal::Settings::Model::LaunchPosition _GetWindowLaunchPosition(); - void _HandleCreateWindow(const HWND hwnd, til::rect proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode); + void _HandleCreateWindow(const HWND hwnd, const til::rect& proposedRect); + void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::UIElement& arg); void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&, @@ -60,6 +62,9 @@ class AppHost const winrt::Windows::Foundation::IInspectable& arg); void _AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& arg); + winrt::fire_and_forget _WindowInitializedHandler(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); @@ -116,7 +121,7 @@ class AppHost void _PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); - void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode); + void _initialResizeAndRepositionWindow(const HWND hwnd, til::rect proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode); void _handleMoveContent(const winrt::Windows::Foundation::IInspectable& sender, winrt::TerminalApp::RequestMoveContentArgs args); @@ -146,8 +151,10 @@ class AppHost winrt::Microsoft::Terminal::Remoting::Peasant::SummonRequested_revoker peasantSummonRequested; winrt::Microsoft::Terminal::Remoting::Peasant::DisplayWindowIdRequested_revoker peasantDisplayWindowIdRequested; winrt::Microsoft::Terminal::Remoting::Peasant::QuitRequested_revoker peasantQuitRequested; + winrt::Microsoft::Terminal::Remoting::Peasant::AttachRequested_revoker AttachRequested; + winrt::TerminalApp::TerminalWindow::Initialized_revoker Initialized; winrt::TerminalApp::TerminalWindow::CloseRequested_revoker CloseRequested; winrt::TerminalApp::TerminalWindow::RequestedThemeChanged_revoker RequestedThemeChanged; winrt::TerminalApp::TerminalWindow::FullscreenChanged_revoker FullscreenChanged; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 978159b2803..7e4c5ff8aee 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -111,7 +111,7 @@ void IslandWindow::Close() // window. // Return Value: // - -void IslandWindow::SetCreateCallback(std::function pfn) noexcept +void IslandWindow::SetCreateCallback(std::function pfn) noexcept { _pfnCreateCallback = pfn; } @@ -153,19 +153,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 eb412a0f445..3673ce8f5b0 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -39,7 +39,8 @@ 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); @@ -97,7 +98,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; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 3343004aa0b..04df0b88818 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -169,18 +169,13 @@ void WindowEmperor::_windowStartedHandlerPostXAML(const std::shared_ptrLogic().IsQuakeWindowChanged({ this, &WindowEmperor::_windowIsQuakeWindowChanged }); sender->UpdateSettingsRequested({ this, &WindowEmperor::_windowRequestUpdateSettings }); - // Summon the window to the foreground, since we might not _currently_ be in + // DON'T Summon the window to the foreground, since we might not _currently_ be in // the foreground, but we should act like the new window is. // - // TODO: GH#14957 - use AllowSetForeground from the original wt.exe instead - Remoting::SummonWindowSelectionArgs args{}; - args.OnCurrentDesktop(false); - args.WindowID(sender->Peasant().GetID()); - args.SummonBehavior().MoveToCurrentDesktop(false); - args.SummonBehavior().ToggleVisibility(false); - args.SummonBehavior().DropdownDuration(0); - args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace); - _manager.SummonWindow(args); + // If you summon here, the resulting code will call ShowWindow(SW_SHOW) on + // 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.